use bevy_ecs::prelude::*;
use wintf::ecs::layout::{
mark_dirty_arrangement_trees, propagate_global_arrangements, sync_simple_arrangements,
sync_window_arrangement_from_window_pos, window_pos_sync_system,
};
use wintf::ecs::window::{DPI, Window, WindowPos};
use wintf::ecs::world::FrameCount;
use wintf::ecs::{Arrangement, GlobalArrangement, LayoutScale, Offset, Size};
use wintf::ecs::{Point, SizeI};
#[test]
fn test_sync_window_arrangement_only_processes_changed_window_pos() {
let mut world = World::new();
world.insert_resource(FrameCount(0));
let entity = world
.spawn((
Window::default(),
WindowPos {
position: Some(Point { x: 100, y: 200 }),
size: Some(SizeI {
width: 800,
height: 600,
}),
..Default::default()
},
DPI::default(), Arrangement {
offset: Offset { x: 0.0, y: 0.0 },
scale: LayoutScale { x: 1.0, y: 1.0 },
size: Size {
width: 800.0,
height: 600.0,
},
},
GlobalArrangement::default(),
))
.id();
let mut schedule = Schedule::default();
schedule.add_systems((
sync_window_arrangement_from_window_pos,
sync_simple_arrangements.after(sync_window_arrangement_from_window_pos),
mark_dirty_arrangement_trees.after(sync_simple_arrangements),
propagate_global_arrangements.after(mark_dirty_arrangement_trees),
window_pos_sync_system.after(propagate_global_arrangements),
));
schedule.run(&mut world);
let arrangement = world.get::<Arrangement>(entity).unwrap();
assert_eq!(
arrangement.offset.x, 100.0,
"初回: offset.x should be 100.0"
);
assert_eq!(
arrangement.offset.y, 200.0,
"初回: offset.y should be 200.0"
);
let arrangement_before = *world.get::<Arrangement>(entity).unwrap();
schedule.run(&mut world);
let arrangement_after = *world.get::<Arrangement>(entity).unwrap();
assert_eq!(
arrangement_before, arrangement_after,
"2回目: WindowPos未変更のためArrangementは変化しないこと"
);
{
let mut wp = world.get_mut::<WindowPos>(entity).unwrap();
wp.position = Some(Point { x: 300, y: 400 });
}
schedule.run(&mut world);
let arrangement = world.get::<Arrangement>(entity).unwrap();
assert_eq!(
arrangement.offset.x, 300.0,
"3回目: offset.x should be 300.0"
);
assert_eq!(
arrangement.offset.y, 400.0,
"3回目: offset.y should be 400.0"
);
}
#[test]
fn test_sync_window_arrangement_dpi_192_no_division() {
let mut world = World::new();
world.insert_resource(FrameCount(0));
let entity = world
.spawn((
Window::default(),
WindowPos {
position: Some(Point { x: 200, y: 400 }),
size: Some(SizeI {
width: 1600,
height: 1200,
}),
..Default::default()
},
DPI {
dpi_x: 192,
dpi_y: 192,
}, Arrangement {
offset: Offset { x: 0.0, y: 0.0 },
scale: LayoutScale { x: 2.0, y: 2.0 },
size: Size {
width: 800.0,
height: 600.0,
},
},
GlobalArrangement::default(),
))
.id();
let mut schedule = Schedule::default();
schedule.add_systems(sync_window_arrangement_from_window_pos);
schedule.run(&mut world);
let arrangement = world.get::<Arrangement>(entity).unwrap();
assert_eq!(
arrangement.offset.x, 200.0,
"DPI 192: offset.x = 200.0 (物理px、レイアウト入力と同じ)"
);
assert_eq!(
arrangement.offset.y, 400.0,
"DPI 192: offset.y = 400.0 (物理px、レイアウト入力と同じ)"
);
}
#[test]
fn test_sync_window_arrangement_dpi_96_identity() {
let mut world = World::new();
world.insert_resource(FrameCount(0));
let entity = world
.spawn((
Window::default(),
WindowPos {
position: Some(Point { x: 150, y: 250 }),
size: Some(SizeI {
width: 800,
height: 600,
}),
..Default::default()
},
DPI::default(), Arrangement {
offset: Offset { x: 0.0, y: 0.0 },
scale: LayoutScale { x: 1.0, y: 1.0 },
size: Size {
width: 800.0,
height: 600.0,
},
},
GlobalArrangement::default(),
))
.id();
let mut schedule = Schedule::default();
schedule.add_systems(sync_window_arrangement_from_window_pos);
schedule.run(&mut world);
let arrangement = world.get::<Arrangement>(entity).unwrap();
assert_eq!(
arrangement.offset.x, 150.0,
"DPI 96: offset.x = 150.0 (物理px)"
);
assert_eq!(
arrangement.offset.y, 250.0,
"DPI 96: offset.y = 250.0 (物理px)"
);
}
#[test]
fn test_sync_window_arrangement_skips_none_position() {
let mut world = World::new();
world.insert_resource(FrameCount(0));
let entity = world
.spawn((
Window::default(),
WindowPos {
position: None,
size: Some(SizeI {
width: 800,
height: 600,
}),
..Default::default()
},
DPI::default(),
Arrangement {
offset: Offset { x: 50.0, y: 60.0 },
scale: LayoutScale { x: 1.0, y: 1.0 },
size: Size {
width: 800.0,
height: 600.0,
},
},
GlobalArrangement::default(),
))
.id();
let mut schedule = Schedule::default();
schedule.add_systems(sync_window_arrangement_from_window_pos);
schedule.run(&mut world);
let arrangement = world.get::<Arrangement>(entity).unwrap();
assert_eq!(
arrangement.offset.x, 50.0,
"None position: offset unchanged"
);
assert_eq!(
arrangement.offset.y, 60.0,
"None position: offset unchanged"
);
}
#[test]
fn test_sync_window_arrangement_skips_cw_usedefault() {
use windows::Win32::UI::WindowsAndMessaging::CW_USEDEFAULT;
let mut world = World::new();
world.insert_resource(FrameCount(0));
let entity = world
.spawn((
Window::default(),
WindowPos {
position: Some(Point {
x: CW_USEDEFAULT,
y: CW_USEDEFAULT,
}),
size: Some(SizeI {
width: 800,
height: 600,
}),
..Default::default()
},
DPI::default(),
Arrangement {
offset: Offset { x: 50.0, y: 60.0 },
scale: LayoutScale { x: 1.0, y: 1.0 },
size: Size {
width: 800.0,
height: 600.0,
},
},
GlobalArrangement::default(),
))
.id();
let mut schedule = Schedule::default();
schedule.add_systems(sync_window_arrangement_from_window_pos);
schedule.run(&mut world);
let arrangement = world.get::<Arrangement>(entity).unwrap();
assert_eq!(
arrangement.offset.x, 50.0,
"CW_USEDEFAULT: offset unchanged"
);
assert_eq!(
arrangement.offset.y, 60.0,
"CW_USEDEFAULT: offset unchanged"
);
}
#[test]
fn test_sync_window_arrangement_skips_equal_offset() {
let mut world = World::new();
world.insert_resource(FrameCount(0));
let entity = world
.spawn((
Window::default(),
WindowPos {
position: Some(Point { x: 100, y: 200 }),
size: Some(SizeI {
width: 800,
height: 600,
}),
..Default::default()
},
DPI::default(), Arrangement {
offset: Offset { x: 100.0, y: 200.0 }, scale: LayoutScale { x: 1.0, y: 1.0 },
size: Size {
width: 800.0,
height: 600.0,
},
},
GlobalArrangement::default(),
))
.id();
let mut schedule = Schedule::default();
schedule.add_systems(sync_window_arrangement_from_window_pos);
schedule.run(&mut world);
let arrangement = world.get::<Arrangement>(entity).unwrap();
assert_eq!(arrangement.offset.x, 100.0);
assert_eq!(arrangement.offset.y, 200.0);
}
#[test]
fn test_feedback_loop_converges_in_one_frame_dpi_96() {
let mut world = World::new();
world.insert_resource(FrameCount(0));
let entity = world
.spawn((
Window::default(),
WindowPos {
position: Some(Point { x: 0, y: 0 }),
size: Some(SizeI {
width: 800,
height: 600,
}),
..Default::default()
},
DPI::default(), Arrangement {
offset: Offset { x: 0.0, y: 0.0 },
scale: LayoutScale { x: 1.0, y: 1.0 },
size: Size {
width: 800.0,
height: 600.0,
},
},
GlobalArrangement::default(),
))
.id();
let mut schedule = Schedule::default();
schedule.add_systems((
sync_window_arrangement_from_window_pos,
sync_simple_arrangements.after(sync_window_arrangement_from_window_pos),
mark_dirty_arrangement_trees.after(sync_simple_arrangements),
propagate_global_arrangements.after(mark_dirty_arrangement_trees),
window_pos_sync_system.after(propagate_global_arrangements),
));
schedule.run(&mut world);
{
let mut wp = world.get_mut::<WindowPos>(entity).unwrap();
wp.position = Some(Point { x: 500, y: 300 });
}
schedule.run(&mut world);
let arr1 = *world.get::<Arrangement>(entity).unwrap();
let ga1 = *world.get::<GlobalArrangement>(entity).unwrap();
let wp1 = *world.get::<WindowPos>(entity).unwrap();
assert_eq!(arr1.offset.x, 500.0, "Frame1: Arrangement.offset.x = 500.0");
assert_eq!(arr1.offset.y, 300.0, "Frame1: Arrangement.offset.y = 300.0");
schedule.run(&mut world);
let arr2 = *world.get::<Arrangement>(entity).unwrap();
let ga2 = *world.get::<GlobalArrangement>(entity).unwrap();
let wp2 = *world.get::<WindowPos>(entity).unwrap();
assert_eq!(
arr1, arr2,
"Frame2: Arrangement should not change (converged)"
);
assert_eq!(
ga1, ga2,
"Frame2: GlobalArrangement should not change (converged)"
);
assert_eq!(wp1, wp2, "Frame2: WindowPos should not change (converged)");
}
#[test]
fn test_feedback_loop_converges_dpi_192() {
use bevy_ecs::hierarchy::ChildOf;
use wintf::ecs::layout::LayoutRoot;
let mut world = World::new();
world.insert_resource(FrameCount(0));
let layout_root = world
.spawn((
LayoutRoot,
Arrangement {
offset: Offset { x: 0.0, y: 0.0 },
scale: LayoutScale { x: 1.0, y: 1.0 },
size: Size {
width: 3840.0,
height: 2160.0,
},
},
GlobalArrangement::default(),
))
.id();
let entity = world
.spawn((
ChildOf(layout_root),
Window::default(),
WindowPos {
position: Some(Point { x: 0, y: 0 }),
size: Some(SizeI {
width: 1600,
height: 1200,
}),
..Default::default()
},
DPI {
dpi_x: 192,
dpi_y: 192,
}, Arrangement {
offset: Offset { x: 0.0, y: 0.0 },
scale: LayoutScale { x: 2.0, y: 2.0 },
size: Size {
width: 800.0,
height: 600.0,
},
},
GlobalArrangement::default(),
))
.id();
let mut schedule = Schedule::default();
schedule.add_systems((
sync_window_arrangement_from_window_pos,
sync_simple_arrangements.after(sync_window_arrangement_from_window_pos),
mark_dirty_arrangement_trees.after(sync_simple_arrangements),
propagate_global_arrangements.after(mark_dirty_arrangement_trees),
window_pos_sync_system.after(propagate_global_arrangements),
));
schedule.run(&mut world);
{
let mut wp = world.get_mut::<WindowPos>(entity).unwrap();
wp.position = Some(Point { x: 200, y: 400 });
}
schedule.run(&mut world);
let arr1 = *world.get::<Arrangement>(entity).unwrap();
assert_eq!(
arr1.offset.x, 200.0,
"DPI 192: offset.x = 200.0 (物理px、LayoutRoot子なのでDPI除算なし)"
);
assert_eq!(
arr1.offset.y, 400.0,
"DPI 192: offset.y = 400.0 (物理px、LayoutRoot子なのでDPI除算なし)"
);
schedule.run(&mut world);
let arr2 = *world.get::<Arrangement>(entity).unwrap();
assert_eq!(
arr1, arr2,
"DPI 192: Arrangement should converge in one frame"
);
}