use std::cell::Cell;
use std::rc::Rc;
use crate::{DesktopEvent, EventRegistry, WindowId};
use gtk4::{gdk, prelude::*, ApplicationWindow};
use webkit6::WebView;
use super::backend::dispatch_event;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub(crate) struct ToplevelFlags {
pub(crate) minimized: bool,
pub(crate) maximized: bool,
pub(crate) fullscreen: bool,
}
pub(crate) fn diff_toplevel_state(
previous: ToplevelFlags,
current: ToplevelFlags,
window_id: WindowId,
) -> Vec<DesktopEvent> {
let mut events = Vec::with_capacity(3);
if previous.minimized != current.minimized {
events.push(if current.minimized {
DesktopEvent::WindowMinimized { window_id }
} else {
DesktopEvent::WindowRestored { window_id }
});
}
if previous.maximized != current.maximized {
events.push(if current.maximized {
DesktopEvent::WindowMaximized { window_id }
} else {
DesktopEvent::WindowUnmaximized { window_id }
});
}
if previous.fullscreen != current.fullscreen {
events.push(if current.fullscreen {
DesktopEvent::WindowEnteredFullscreen { window_id }
} else {
DesktopEvent::WindowLeftFullscreen { window_id }
});
}
events
}
pub(crate) fn connect_toplevel_state_events(
window: &ApplicationWindow,
webview: &WebView,
events: &EventRegistry,
) {
let webview = webview.clone();
let events = events.clone();
let previous = Rc::new(Cell::new(ToplevelFlags::default()));
let connected = Rc::new(Cell::new(false));
window.connect_realize(move |window| {
if connected.replace(true) {
return;
}
let Some(surface) = window.surface() else {
connected.set(false);
return;
};
let Some(toplevel) = surface.downcast_ref::<gdk::Toplevel>() else {
connected.set(false);
return;
};
previous.set(toplevel_flags(toplevel.state()));
let webview = webview.clone();
let events = events.clone();
let previous = Rc::clone(&previous);
toplevel.connect_state_notify(move |toplevel| {
let current = toplevel_flags(toplevel.state());
let last = previous.replace(current);
for event in diff_toplevel_state(last, current, WindowId::PRIMARY) {
dispatch_event(&events, &webview, &event);
}
});
});
}
fn toplevel_flags(state: gdk::ToplevelState) -> ToplevelFlags {
ToplevelFlags {
minimized: state.contains(gdk::ToplevelState::MINIMIZED),
maximized: state.contains(gdk::ToplevelState::MAXIMIZED),
fullscreen: state.contains(gdk::ToplevelState::FULLSCREEN),
}
}
#[cfg(test)]
mod tests {
use super::*;
const WINDOW_ID: WindowId = WindowId::PRIMARY;
#[test]
fn no_change_emits_nothing() {
let flags = ToplevelFlags {
minimized: true,
maximized: false,
fullscreen: false,
};
assert!(diff_toplevel_state(flags, flags, WINDOW_ID).is_empty());
}
#[test]
fn minimize_then_restore() {
let normal = ToplevelFlags::default();
let minimized = ToplevelFlags {
minimized: true,
..normal
};
assert_eq!(
diff_toplevel_state(normal, minimized, WINDOW_ID),
vec![DesktopEvent::WindowMinimized {
window_id: WINDOW_ID
}]
);
assert_eq!(
diff_toplevel_state(minimized, normal, WINDOW_ID),
vec![DesktopEvent::WindowRestored {
window_id: WINDOW_ID
}]
);
}
#[test]
fn maximize_then_unmaximize() {
let normal = ToplevelFlags::default();
let maximized = ToplevelFlags {
maximized: true,
..normal
};
assert_eq!(
diff_toplevel_state(normal, maximized, WINDOW_ID),
vec![DesktopEvent::WindowMaximized {
window_id: WINDOW_ID
}]
);
assert_eq!(
diff_toplevel_state(maximized, normal, WINDOW_ID),
vec![DesktopEvent::WindowUnmaximized {
window_id: WINDOW_ID
}]
);
}
#[test]
fn fullscreen_enter_and_leave() {
let normal = ToplevelFlags::default();
let fullscreen = ToplevelFlags {
fullscreen: true,
..normal
};
assert_eq!(
diff_toplevel_state(normal, fullscreen, WINDOW_ID),
vec![DesktopEvent::WindowEnteredFullscreen {
window_id: WINDOW_ID
}]
);
assert_eq!(
diff_toplevel_state(fullscreen, normal, WINDOW_ID),
vec![DesktopEvent::WindowLeftFullscreen {
window_id: WINDOW_ID
}]
);
}
#[test]
fn simultaneous_transitions_emit_in_fixed_order() {
let normal = ToplevelFlags::default();
let all = ToplevelFlags {
minimized: true,
maximized: true,
fullscreen: true,
};
assert_eq!(
diff_toplevel_state(normal, all, WINDOW_ID),
vec![
DesktopEvent::WindowMinimized {
window_id: WINDOW_ID
},
DesktopEvent::WindowMaximized {
window_id: WINDOW_ID
},
DesktopEvent::WindowEnteredFullscreen {
window_id: WINDOW_ID
},
]
);
}
#[test]
fn unrelated_bits_do_not_trigger_events() {
let normal = ToplevelFlags::default();
assert!(diff_toplevel_state(normal, normal, WINDOW_ID).is_empty());
}
}