use std::sync::{Arc, OnceLock, RwLock};
use std::time::Duration;
use super::*;
use bevy::prelude::*;
use bevy::tasks::{AsyncComputeTaskPool, TaskPoolBuilder};
use bevy::time::TimeUpdateStrategy;
use objc2_core_foundation::{CFRetained, CGPoint};
use objc2_core_graphics::CGDirectDisplayID;
use stdext::function_name;
use stdext::prelude::RwLockExt;
use tracing::{Level, debug, instrument};
use crate::commands::{Command, Direction, Operation, ResizeDirection, register_commands};
use crate::config::{Config, MainOptions, WindowParams};
use crate::ecs::{
BProcess, ExistingMarker, FocusFollowsMouse, FocusedMarker, Initializing, MissionControlActive,
PollForNotifications, SkipReshuffle, SpawnWindowTrigger, register_systems, register_triggers,
};
use crate::errors::{Error, Result};
use crate::events::Event;
use crate::manager::{
Application, ApplicationApi, Display, Origin, ProcessApi, Size, Window, WindowApi,
WindowManager, WindowManagerApi,
};
use crate::platform::{ConnID, Pid, WinID, WorkspaceId};
use crate::{platform::ProcessSerialNumber, util::AXUIWrapper};
const TEST_PROCESS_ID: i32 = 1;
const TEST_DISPLAY_ID: u32 = 1;
const TEST_WORKSPACE_ID: u64 = 2;
const TEST_DISPLAY_WIDTH: i32 = 1024;
const TEST_DISPLAY_HEIGHT: i32 = 768;
const TEST_MENUBAR_HEIGHT: i32 = 20;
const TEST_WINDOW_WIDTH: i32 = 400;
const TEST_WINDOW_HEIGHT: i32 = 1000;
#[derive(Debug)]
struct MockProcess {
psn: ProcessSerialNumber,
}
impl ProcessApi for MockProcess {
#[instrument(level = Level::DEBUG, ret)]
fn is_observable(&mut self) -> bool {
debug!("{}:", function_name!());
true
}
#[instrument(level = Level::DEBUG, ret)]
fn name(&self) -> &'static str {
"test"
}
#[instrument(level = Level::DEBUG, ret)]
fn pid(&self) -> Pid {
debug!("{}:", function_name!());
TEST_PROCESS_ID
}
#[instrument(level = Level::TRACE, ret)]
fn psn(&self) -> ProcessSerialNumber {
debug!("{}: {:?}", function_name!(), self.psn);
self.psn
}
#[instrument(level = Level::DEBUG, ret)]
fn application(&self) -> Option<objc2::rc::Retained<objc2_app_kit::NSRunningApplication>> {
debug!("{}:", function_name!());
None
}
#[instrument(level = Level::DEBUG, ret)]
fn ready(&mut self) -> bool {
debug!("{}:", function_name!());
true
}
}
#[derive(Clone, Debug)]
struct MockApplication {
inner: Arc<RwLock<InnerMockApplication>>,
}
#[derive(Debug)]
struct InnerMockApplication {
psn: ProcessSerialNumber,
pid: Pid,
focused_id: Option<WinID>,
}
impl MockApplication {
#[instrument(level = Level::DEBUG, ret)]
fn new(psn: ProcessSerialNumber, pid: Pid) -> Self {
MockApplication {
inner: Arc::new(RwLock::new(InnerMockApplication {
psn,
pid,
focused_id: None,
})),
}
}
}
impl ApplicationApi for MockApplication {
#[instrument(level = Level::TRACE, skip(self), ret)]
fn pid(&self) -> Pid {
self.inner.force_read().pid
}
#[instrument(level = Level::TRACE, skip(self), ret)]
fn psn(&self) -> ProcessSerialNumber {
debug!("{}:", function_name!());
self.inner.force_read().psn
}
#[instrument(level = Level::DEBUG, skip(self), ret)]
fn connection(&self) -> Option<ConnID> {
debug!("{}:", function_name!());
Some(0)
}
#[instrument(level = Level::DEBUG, skip(self), ret)]
fn focused_window_id(&self) -> Result<WinID> {
let id = self
.inner
.force_read()
.focused_id
.ok_or(Error::InvalidWindow);
debug!("{}: {id:?}", function_name!());
id
}
fn window_list(&self) -> Vec<Window> {
debug!("{}:", function_name!());
vec![]
}
#[instrument(level = Level::DEBUG, skip(self), ret)]
fn observe(&mut self) -> Result<bool> {
debug!("{}:", function_name!());
Ok(true)
}
#[instrument(level = Level::DEBUG, skip_all, ret)]
fn observe_window(&mut self, _window: &Window) -> Result<bool> {
debug!("{}:", function_name!());
Ok(true)
}
#[instrument(level = Level::DEBUG, skip_all, ret)]
fn unobserve_window(&mut self, _window: &Window) {
debug!("{}:", function_name!());
}
#[instrument(level = Level::DEBUG, skip(self), ret)]
fn is_frontmost(&self) -> bool {
debug!("{}:", function_name!());
true
}
#[instrument(level = Level::DEBUG, skip(self), ret)]
fn bundle_id(&self) -> Option<&str> {
debug!("{}:", function_name!());
Some("test")
}
}
struct MockWindowManager {
windows: Box<dyn Fn(WorkspaceId) -> Vec<Window> + Send + Sync + 'static>,
}
impl std::fmt::Debug for MockWindowManager {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MockWindowManager")
.field("windows", &"<closure>") .finish()
}
}
impl WindowManagerApi for MockWindowManager {
fn new_application(&self, process: &dyn ProcessApi) -> Result<Application> {
debug!("{}: from process {}", function_name!(), process.name());
Ok(Application::new(Box::new(MockApplication {
inner: Arc::new(RwLock::new(InnerMockApplication {
psn: process.psn(),
pid: process.pid(),
focused_id: None,
})),
})))
}
#[instrument(level = Level::DEBUG, skip(self), ret)]
fn get_associated_windows(&self, window_id: WinID) -> Vec<WinID> {
debug!("{}:", function_name!());
vec![]
}
#[instrument(level = Level::DEBUG, skip(self), ret)]
fn present_displays(&self) -> Vec<(Display, Vec<WorkspaceId>)> {
let display = Display::new(
TEST_DISPLAY_ID,
IRect::new(0, 0, TEST_DISPLAY_WIDTH, TEST_DISPLAY_HEIGHT),
TEST_MENUBAR_HEIGHT,
);
vec![(display, vec![TEST_WORKSPACE_ID])]
}
#[instrument(level = Level::DEBUG, skip(self), ret)]
fn active_display_id(&self) -> Result<u32> {
Ok(TEST_DISPLAY_ID)
}
#[instrument(level = Level::DEBUG, skip(self), ret)]
fn active_display_space(&self, display_id: CGDirectDisplayID) -> Result<WorkspaceId> {
Ok(TEST_WORKSPACE_ID)
}
fn is_fullscreen_space(&self, _display_id: CGDirectDisplayID) -> bool {
false
}
#[instrument(level = Level::DEBUG, skip_all, ret)]
fn center_mouse(&self, _window: Option<&Window>, _display_bounds: &IRect) {
debug!("{}:", function_name!());
}
#[instrument(level = Level::DEBUG, skip_all)]
fn find_existing_application_windows(
&self,
app: &mut Application,
spaces: &[WorkspaceId],
) -> Result<(Vec<Window>, Vec<WinID>)> {
debug!(
"{}: app {} spaces {:?}",
function_name!(),
app.pid(),
spaces
);
let windows = spaces
.iter()
.flat_map(|workspace_id| (self.windows)(*workspace_id))
.collect::<Vec<_>>();
Ok((windows, vec![]))
}
#[instrument(level = Level::DEBUG, skip(self), ret)]
fn find_window_at_point(&self, point: &CGPoint) -> Result<WinID> {
debug!("{}:", function_name!());
Ok(0)
}
#[instrument(level = Level::DEBUG, skip(self), ret)]
fn windows_in_workspace(&self, workspace_id: WorkspaceId) -> Result<Vec<WinID>> {
debug!("{}:", function_name!());
let ids = (self.windows)(workspace_id)
.iter()
.map(|window| window.id())
.collect();
Ok(ids)
}
#[instrument(level = Level::DEBUG, skip(self), ret)]
fn quit(&self) -> Result<()> {
debug!("{}:", function_name!());
Ok(())
}
#[instrument(level = Level::DEBUG, skip(self))]
fn setup_config_watcher(&self, path: &std::path::Path) -> Result<Box<dyn notify::Watcher>> {
todo!()
}
fn cursor_position(&self) -> Option<CGPoint> {
None
}
#[instrument(level = Level::DEBUG, skip(self))]
fn dim_windows(&self, windows: &[WinID], level: f32) {}
}
#[derive(Debug)]
struct MockWindow {
id: WinID,
frame: IRect,
app: MockApplication,
event_queue: EventQueue,
pub minimized: bool,
}
impl WindowApi for MockWindow {
#[instrument(level = Level::TRACE, skip(self), ret)]
fn id(&self) -> WinID {
self.id
}
#[instrument(level = Level::TRACE, skip(self), ret)]
fn frame(&self) -> IRect {
self.frame
}
#[instrument(level = Level::DEBUG, skip(self), ret)]
fn element(&self) -> Option<CFRetained<AXUIWrapper>> {
debug!("{}:", function_name!());
None
}
#[instrument(level = Level::TRACE, skip(self), ret)]
fn title(&self) -> Result<String> {
Ok(String::new())
}
#[instrument(level = Level::DEBUG, skip(self), ret)]
fn child_role(&self) -> Result<bool> {
debug!("{}:", function_name!());
Ok(true)
}
#[instrument(level = Level::TRACE, skip(self), ret)]
fn role(&self) -> Result<String> {
Ok(String::new())
}
#[instrument(level = Level::TRACE, skip(self), ret)]
fn subrole(&self) -> Result<String> {
Ok(String::new())
}
#[instrument(level = Level::DEBUG, skip(self), ret)]
fn is_root(&self) -> bool {
debug!("{}:", function_name!());
true
}
#[instrument(level = Level::DEBUG, skip(self))]
fn reposition(&mut self, origin: Origin) {
debug!("{}: id {} to {origin}", function_name!(), self.id);
let size = self.frame.size();
self.frame.min = origin;
self.frame.max = origin + size;
}
#[instrument(level = Level::DEBUG, skip(self))]
fn resize(&mut self, size: Size, display_width: i32) {
debug!("{}: id {} to {size}", function_name!(), self.id);
self.frame.max = self.frame.min + size;
}
#[instrument(level = Level::DEBUG, skip(self), ret)]
fn update_frame(&mut self, bounds: &IRect) -> Result<IRect> {
debug!("{}:", function_name!());
Ok(IRect::default())
}
#[instrument(level = Level::DEBUG, skip_all)]
fn focus_without_raise(
&self,
_psn: ProcessSerialNumber,
currently_focused: &Window,
_ocused_psn: ProcessSerialNumber,
) {
debug!(
"{}: id {} {}",
function_name!(),
self.id,
currently_focused.id()
);
}
#[instrument(level = Level::DEBUG, skip(self), ret)]
fn focus_with_raise(&self, psn: ProcessSerialNumber) {
debug!("{}: id {}", function_name!(), self.id);
self.event_queue
.write()
.unwrap()
.push(Event::ApplicationFrontSwitched { psn });
self.app.inner.force_write().focused_id = Some(self.id);
}
#[instrument(level = Level::DEBUG, skip(self), ret)]
fn width_ratio(&self) -> f64 {
debug!("{}:", function_name!());
0.5
}
#[instrument(level = Level::TRACE, skip(self), ret)]
fn pid(&self) -> Result<Pid> {
Ok(TEST_PROCESS_ID)
}
#[instrument(level = Level::DEBUG, skip(self), ret)]
fn set_padding(&mut self, padding: manager::WindowPadding) {
debug!("{}:", function_name!());
}
fn horizontal_padding(&self) -> i32 {
0
}
fn vertical_padding(&self) -> i32 {
0
}
#[instrument(level = Level::TRACE, skip(self), ret)]
fn is_minimized(&self) -> bool {
self.minimized
}
fn is_full_screen(&self) -> bool {
false
}
}
impl MockWindow {
fn new(id: WinID, frame: IRect, event_queue: EventQueue, app: MockApplication) -> Self {
MockWindow {
id,
frame,
app,
event_queue,
minimized: false,
}
}
}
fn setup_world() -> App {
static DONE: OnceLock<()> = OnceLock::new();
DONE.get_or_init(|| {
tracing_subscriber::registry()
.with(EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")))
.with(
fmt::layer()
.with_level(true)
.with_line_number(true)
.with_file(true)
.with_target(true)
.with_thread_ids(false)
.with_writer(std::io::stderr)
.compact(),
)
.init();
let _pool = AsyncComputeTaskPool::get_or_init(|| {
TaskPoolBuilder::new()
.num_threads(1) .build()
});
assert!(AsyncComputeTaskPool::try_get().is_some());
});
let mut bevy_app = App::new();
bevy_app
.add_plugins(MinimalPlugins)
.init_resource::<Messages<Event>>()
.insert_resource(PollForNotifications)
.insert_resource(SkipReshuffle(false))
.insert_resource(MissionControlActive(false))
.insert_resource(FocusFollowsMouse(None))
.insert_resource(Config::default())
.insert_resource(Initializing)
.add_plugins((register_triggers, register_systems, register_commands));
bevy_app.insert_resource(TimeUpdateStrategy::ManualDuration(Duration::from_millis(
100,
)));
bevy_app
}
fn setup_process(world: &mut World) -> MockApplication {
let psn = ProcessSerialNumber { high: 1, low: 2 };
let mock_process = MockProcess { psn };
let process = world.spawn(BProcess(Box::new(mock_process))).id();
let application = MockApplication::new(psn, TEST_PROCESS_ID);
world.spawn((
ExistingMarker,
ChildOf(process),
Application::new(Box::new(application.clone())),
));
application
}
type EventQueue = Arc<RwLock<Vec<Event>>>;
fn run_main_loop(
bevy_app: &mut App,
event_queue: &EventQueue,
commands: &[Event],
mut verifier: impl FnMut(usize, &mut World),
) {
for (iteration, command) in commands.iter().enumerate() {
bevy_app.world_mut().write_message::<Event>(command.clone());
for _ in 0..5 {
bevy_app.update();
while let Some(event) = event_queue.write().unwrap().pop() {
bevy_app.world_mut().write_message::<Event>(event);
}
}
verifier(iteration, bevy_app.world_mut());
}
}
fn verify_window_positions(expected_positions: &[(WinID, (i32, i32))], world: &mut World) {
let mut query = world.query::<&Window>();
for window in query.iter(world) {
if let Some((window_id, (x, y))) = expected_positions.iter().find(|id| id.0 == window.id())
{
debug!("WinID: {window_id}");
assert_eq!(*x, window.frame().min.x);
assert_eq!(*y, window.frame().min.y);
}
}
}
fn verify_window_sizes(expected_sizes: &[(WinID, (i32, i32))], world: &mut World) {
let mut query = world.query::<&Window>();
for window in query.iter(world) {
if let Some((window_id, (w, h))) = expected_sizes.iter().find(|id| id.0 == window.id()) {
let frame = window.frame();
assert_eq!(
*w,
frame.width(),
"WinID {window_id}: expected width {w}, got {}",
frame.width()
);
assert_eq!(
*h,
frame.height(),
"WinID {window_id}: expected height {h}, got {}",
frame.height()
);
}
}
}
#[test]
#[allow(clippy::too_many_lines)]
fn test_window_shuffle() {
const PADDING_LEFT: u16 = 3;
const PADDING_RIGHT: u16 = 5;
const PADDING_TOP: u16 = 7;
const PADDING_BOTTOM: u16 = 9;
const SLIVER_WIDTH: u16 = 5;
let commands = vec![
Event::MenuOpened { window_id: 0 }, Event::Command {
command: Command::Window(Operation::Focus(Direction::Last)),
},
Event::Command {
command: Command::Window(Operation::Focus(Direction::First)),
},
Event::Command {
command: Command::Window(Operation::Focus(Direction::East)),
},
Event::Command {
command: Command::Window(Operation::Stack(true)),
},
Event::Command {
command: Command::Window(Operation::Center),
},
Event::Command {
command: Command::Window(Operation::Focus(Direction::East)),
},
Event::Command {
command: Command::Window(Operation::Stack(true)),
},
Event::Command {
command: Command::Window(Operation::Center),
},
Event::Command {
command: Command::PrintState,
},
];
let top_edge = TEST_MENUBAR_HEIGHT + i32::from(PADDING_TOP);
let left_edge = i32::from(PADDING_LEFT);
let right_edge = TEST_DISPLAY_WIDTH - i32::from(PADDING_RIGHT);
let offscreen_right = TEST_DISPLAY_WIDTH - i32::from(PADDING_RIGHT.max(SLIVER_WIDTH));
let offscreen_left = i32::from(PADDING_RIGHT.max(SLIVER_WIDTH)) - TEST_WINDOW_WIDTH;
let expected_positions_last = [
(4, (offscreen_left, top_edge)),
(3, (offscreen_left, top_edge)),
(2, (right_edge - 3 * TEST_WINDOW_WIDTH, top_edge)),
(1, (right_edge - 2 * TEST_WINDOW_WIDTH, top_edge)),
(0, (right_edge - TEST_WINDOW_WIDTH, top_edge)),
];
let expected_positions_first = [
(4, (left_edge, top_edge)),
(3, (left_edge + TEST_WINDOW_WIDTH, top_edge)),
(2, (left_edge + 2 * TEST_WINDOW_WIDTH, top_edge)),
(1, (offscreen_right, top_edge)),
(0, (offscreen_right, top_edge)),
];
let centered = (TEST_DISPLAY_WIDTH - TEST_WINDOW_WIDTH) / 2;
let expected_positions_stacked = [
(4, (centered, top_edge)),
(3, (centered, 393)),
(2, (centered + TEST_WINDOW_WIDTH, top_edge)),
(1, (offscreen_right, top_edge)),
(0, (offscreen_right, top_edge)),
];
let expected_positions_stacked2 = [
(4, (centered, top_edge)),
(3, (centered, 271)),
(2, (centered, 515)),
(1, (712, top_edge)),
(0, (offscreen_right, top_edge)),
];
let check = |iteration, world: &mut World| {
let iterations = [
None,
Some(expected_positions_last.as_slice()),
Some(expected_positions_first.as_slice()),
None,
None,
Some(expected_positions_stacked.as_slice()),
None,
None,
None,
Some(expected_positions_stacked2.as_slice()),
];
if let Some(positions) = iterations[iteration] {
debug!("Iteration: {iteration}");
verify_window_positions(positions, world);
}
};
let mut bevy = setup_world();
let mock_app = setup_process(bevy.world_mut());
let internal_queue = Arc::new(RwLock::new(Vec::<Event>::new()));
let event_queue = internal_queue.clone();
let windows = Box::new(move |_| {
(0..5)
.map(|i| {
let origin = Origin::new(100 * i, 0);
let size = Size::new(TEST_WINDOW_WIDTH, TEST_WINDOW_HEIGHT);
let window = MockWindow::new(
i,
IRect {
min: origin,
max: origin + size,
},
event_queue.clone(),
mock_app.clone(),
);
Window::new(Box::new(window))
})
.collect::<Vec<_>>()
});
let window_manager = MockWindowManager { windows };
bevy.world_mut()
.insert_resource(WindowManager(Box::new(window_manager)));
let mut params = WindowParams::new(".*", None);
params.vertical_padding = Some(3);
params.horizontal_padding = Some(2);
let config: Config = (
MainOptions {
padding_left: Some(PADDING_LEFT),
padding_right: Some(PADDING_RIGHT),
padding_top: Some(PADDING_TOP),
padding_bottom: Some(PADDING_BOTTOM),
..Default::default()
},
vec![params],
)
.into();
bevy.insert_resource(config);
run_main_loop(&mut bevy, &internal_queue, &commands, check);
}
#[test]
fn test_startup_windows() {
let commands = vec![
Event::MenuOpened { window_id: 0 }, Event::Command {
command: Command::Window(Operation::Focus(Direction::East)),
},
Event::Command {
command: Command::Window(Operation::Focus(Direction::East)),
},
Event::Command {
command: Command::Window(Operation::Focus(Direction::First)),
},
Event::Command {
command: Command::PrintState,
},
];
let expected_positions = [
(4, (0, TEST_MENUBAR_HEIGHT)),
(3, (400, TEST_MENUBAR_HEIGHT)),
(2, (800, TEST_MENUBAR_HEIGHT)),
];
let check = |iteration, world: &mut World| {
let iterations = [None, None, None, None, Some(expected_positions.as_slice())];
if let Some(positions) = iterations[iteration] {
debug!("Iteration: {iteration}");
verify_window_positions(positions, world);
}
};
let mut bevy = setup_world();
let mock_app = setup_process(bevy.world_mut());
let internal_queue = Arc::new(RwLock::new(Vec::<Event>::new()));
let event_queue = internal_queue.clone();
let windows = Box::new(move |_| {
(0..5)
.map(|i| {
let origin = Origin::new(100 * i, 0);
let size = Size::new(TEST_WINDOW_WIDTH, TEST_WINDOW_HEIGHT);
let mut window = MockWindow::new(
i,
IRect {
min: origin,
max: origin + size,
},
event_queue.clone(),
mock_app.clone(),
);
if i < 2 {
window.minimized = true;
}
Window::new(Box::new(window))
})
.collect::<Vec<_>>()
});
let window_manager = MockWindowManager { windows };
bevy.world_mut()
.insert_resource(WindowManager(Box::new(window_manager)));
run_main_loop(&mut bevy, &internal_queue, &commands, check);
}
#[test]
fn test_dont_focus() {
let commands = vec![
Event::MenuOpened { window_id: 0 }, Event::Command {
command: Command::Window(Operation::Focus(Direction::Last)),
},
Event::Command {
command: Command::Window(Operation::Focus(Direction::First)),
},
Event::Command {
command: Command::PrintState,
},
];
let offscreen_right = TEST_DISPLAY_WIDTH - 5;
let expected_positions = [
(2, (0, TEST_MENUBAR_HEIGHT)),
(1, (400, TEST_MENUBAR_HEIGHT)),
(0, (800, TEST_MENUBAR_HEIGHT)),
(3, (offscreen_right, TEST_MENUBAR_HEIGHT)),
];
let mut bevy = setup_world();
let app = setup_process(bevy.world_mut());
let mock_app = app.clone();
let internal_queue = Arc::new(RwLock::new(Vec::<Event>::new()));
let event_queue = internal_queue.clone();
let windows = Box::new(move |_| {
(0..3)
.map(|i| {
let origin = Origin::new(100 * i, 0);
let size = Size::new(TEST_WINDOW_WIDTH, TEST_WINDOW_HEIGHT);
let window = MockWindow::new(
i,
IRect {
min: origin,
max: origin + size,
},
event_queue.clone(),
mock_app.clone(),
);
Window::new(Box::new(window))
})
.collect::<Vec<_>>()
});
let window_manager = MockWindowManager { windows };
bevy.world_mut()
.insert_resource(WindowManager(Box::new(window_manager)));
let check_queue = internal_queue.clone();
let check = |iteration, world: &mut World| {
let iterations = [None, None, None, Some(expected_positions.as_slice())];
if let Some(positions) = iterations[iteration] {
debug!("Iteration: {iteration}");
verify_window_positions(positions, world);
let mut query = world.query::<(&Window, Has<FocusedMarker>)>();
for (window, focused) in query.iter(world) {
if focused {
assert_eq!(window.id(), 2);
}
}
}
if iteration == 1 {
let origin = Origin::new(0, 0);
let size = Size::new(TEST_WINDOW_WIDTH, TEST_WINDOW_HEIGHT);
let window = MockWindow::new(
3,
IRect {
min: origin,
max: origin + size,
},
check_queue.clone(),
app.clone(),
);
let window = Window::new(Box::new(window));
world.trigger(SpawnWindowTrigger(vec![window]));
}
};
let mut params = WindowParams::new(".*", None);
params.dont_focus = Some(true);
params.index = Some(100);
let config: Config = (MainOptions::default(), vec![params]).into();
bevy.insert_resource(config);
run_main_loop(&mut bevy, &internal_queue, &commands, check);
}
#[test]
fn test_offscreen_windows_preserve_height() {
let expected_height = TEST_DISPLAY_HEIGHT - TEST_MENUBAR_HEIGHT;
let commands = vec![
Event::MenuOpened { window_id: 0 }, Event::Command {
command: Command::Window(Operation::Focus(Direction::First)),
},
];
let expected_sizes = [
(4, (TEST_WINDOW_WIDTH, expected_height)),
(3, (TEST_WINDOW_WIDTH, expected_height)),
(2, (TEST_WINDOW_WIDTH, expected_height)),
(1, (TEST_WINDOW_WIDTH, expected_height)),
(0, (TEST_WINDOW_WIDTH, expected_height)),
];
let check = |iteration, world: &mut World| {
if iteration == 1 {
verify_window_sizes(&expected_sizes, world);
}
};
let mut bevy = setup_world();
let mock_app = setup_process(bevy.world_mut());
let internal_queue = Arc::new(RwLock::new(Vec::<Event>::new()));
let event_queue = internal_queue.clone();
let windows = Box::new(move |_| {
(0..5)
.map(|i| {
let origin = Origin::new(100 * i, 0);
let size = Size::new(TEST_WINDOW_WIDTH, TEST_WINDOW_HEIGHT);
let window = MockWindow::new(
i,
IRect {
min: origin,
max: origin + size,
},
event_queue.clone(),
mock_app.clone(),
);
Window::new(Box::new(window))
})
.collect::<Vec<_>>()
});
let window_manager = MockWindowManager { windows };
bevy.world_mut()
.insert_resource(WindowManager(Box::new(window_manager)));
run_main_loop(&mut bevy, &internal_queue, &commands, check);
}
#[test]
fn test_window_resize_grow_and_shrink_cycle() {
let commands = vec![
Event::MenuOpened { window_id: 0 }, Event::Command {
command: Command::Window(Operation::Resize(ResizeDirection::Grow)),
},
Event::Command {
command: Command::Window(Operation::Resize(ResizeDirection::Grow)),
},
Event::Command {
command: Command::Window(Operation::Resize(ResizeDirection::Grow)),
},
Event::Command {
command: Command::Window(Operation::Resize(ResizeDirection::Shrink)),
},
];
let expected_widths = [None, Some(512), Some(768), Some(256), Some(768)];
let check = |iteration, world: &mut World| {
let Some(expected_width) = expected_widths[iteration] else {
return;
};
let mut query = world.query::<&Window>();
let window = query
.iter(world)
.find(|window| window.id() == 0)
.expect("expected window with id 0");
assert_eq!(
window.frame().width(),
expected_width,
"iteration {iteration}: expected width {expected_width}, got {}",
window.frame().width()
);
};
let mut bevy = setup_world();
let mock_app = setup_process(bevy.world_mut());
let internal_queue = Arc::new(RwLock::new(Vec::<Event>::new()));
let event_queue = internal_queue.clone();
let windows = Box::new(move |_| {
let origin = Origin::new(0, 0);
let size = Size::new(TEST_WINDOW_WIDTH, TEST_WINDOW_HEIGHT);
let window = MockWindow::new(
0,
IRect {
min: origin,
max: origin + size,
},
event_queue.clone(),
mock_app.clone(),
);
vec![Window::new(Box::new(window))]
});
let window_manager = MockWindowManager { windows };
bevy.world_mut()
.insert_resource(WindowManager(Box::new(window_manager)));
let config: Config = (
MainOptions {
preset_column_widths: vec![0.25, 0.5, 0.75],
..Default::default()
},
vec![],
)
.into();
bevy.insert_resource(config);
run_main_loop(&mut bevy, &internal_queue, &commands, check);
}
#[test]
fn test_scrolling() {
let mut bevy = setup_world();
let mock_app = setup_process(bevy.world_mut());
let internal_queue = Arc::new(RwLock::new(Vec::<Event>::new()));
let event_queue = internal_queue.clone();
let windows = Box::new(move |_| {
(0..3)
.map(|i| {
let origin = Origin::new(400 * i, 0);
let size = Size::new(TEST_WINDOW_WIDTH, TEST_WINDOW_HEIGHT);
let window = MockWindow::new(
i,
IRect {
min: origin,
max: origin + size,
},
event_queue.clone(),
mock_app.clone(),
);
Window::new(Box::new(window))
})
.collect::<Vec<_>>()
});
let window_manager = MockWindowManager { windows };
bevy.world_mut()
.insert_resource(WindowManager(Box::new(window_manager)));
let commands = vec![
Event::MenuOpened { window_id: 0 }, Event::Command {
command: Command::Window(Operation::Focus(Direction::Last)),
},
Event::Command {
command: Command::Window(Operation::Focus(Direction::First)),
},
Event::Command {
command: Command::PrintState,
},
Event::Swipe {
deltas: vec![0.1, 0.1, 0.1],
},
Event::Command {
command: Command::PrintState,
},
];
let expected_initial = [
(2, (0, TEST_MENUBAR_HEIGHT)),
(1, (400, TEST_MENUBAR_HEIGHT)),
(0, (800, TEST_MENUBAR_HEIGHT)),
];
let expected = [
(2, (-320, TEST_MENUBAR_HEIGHT)),
(1, (80, TEST_MENUBAR_HEIGHT)),
(0, (480, TEST_MENUBAR_HEIGHT)),
];
let check = |iteration, world: &mut World| {
let iterations = [
None,
None,
None,
Some(expected_initial.as_slice()),
None,
Some(expected.as_slice()),
];
if let Some(positions) = iterations[iteration] {
debug!("Iteration: {iteration}");
verify_window_positions(positions, world);
}
};
let config: Config = (
MainOptions {
swipe_gesture_fingers: Some(3),
..Default::default()
},
vec![],
)
.into();
bevy.insert_resource(config);
run_main_loop(&mut bevy, &internal_queue, &commands, check);
}