use bevy::{
ecs::{
entity::Entity,
hierarchy::ChildOf,
query::{With, Without},
system::{Query, Res, ResMut, Single, SystemParam},
world::Mut,
},
math::IRect,
};
use objc2_core_graphics::CGDirectDisplayID;
use tracing::warn;
use super::{ActiveDisplayMarker, FocusFollowsMouse, MissionControlActive, SkipReshuffle};
use crate::{
config::{Config, WindowParams},
ecs::{ActiveWorkspaceMarker, DockPosition, FocusedMarker, FullWidthMarker, Unmanaged},
manager::{Application, Display, LayoutStrip, Window},
platform::{ProcessSerialNumber, WinID},
};
#[derive(SystemParam)]
pub struct Configuration<'w> {
config: Res<'w, Config>,
focus_follows_mouse_id: ResMut<'w, FocusFollowsMouse>,
skip_reshuffle: ResMut<'w, SkipReshuffle>,
mission_control_active: Res<'w, MissionControlActive>,
}
impl Configuration<'_> {
pub fn focus_follows_mouse(&self) -> bool {
self.config
.options()
.focus_follows_mouse
.is_none_or(|ffm| ffm)
}
pub fn mouse_follows_focus(&self) -> bool {
self.config
.options()
.mouse_follows_focus
.is_none_or(|mff| mff)
}
pub fn auto_center(&self) -> bool {
self.config
.options()
.auto_center
.is_some_and(|centered| centered)
}
pub fn swipe_gesture_fingers(&self) -> Option<usize> {
self.config.options().swipe_gesture_fingers
}
pub fn find_window_properties(&self, title: &str, bundle_id: &str) -> Vec<WindowParams> {
self.config.find_window_properties(title, bundle_id)
}
pub fn ffm_flag(&self) -> Option<WinID> {
self.focus_follows_mouse_id.0
}
pub fn set_ffm_flag(&mut self, flag: Option<WinID>) {
self.focus_follows_mouse_id.as_mut().0 = flag;
}
pub fn set_skip_reshuffle(&mut self, to: bool) {
self.skip_reshuffle.as_mut().0 = to;
}
pub fn skip_reshuffle(&self) -> bool {
self.skip_reshuffle.0
}
pub fn edge_padding(&self) -> (i32, i32, i32, i32) {
self.config.edge_padding()
}
pub fn mission_control_active(&self) -> bool {
self.mission_control_active.0
}
}
#[derive(SystemParam)]
pub struct ActiveDisplay<'w, 's> {
strip: Single<'w, 's, &'static LayoutStrip, With<ActiveWorkspaceMarker>>,
display: Single<
'w,
's,
(&'static Display, Option<&'static DockPosition>),
With<ActiveDisplayMarker>,
>,
other_displays: Query<'w, 's, &'static Display, Without<ActiveDisplayMarker>>,
}
impl ActiveDisplay<'_, '_> {
pub fn display(&self) -> &Display {
self.display.0
}
pub fn id(&self) -> CGDirectDisplayID {
self.display.0.id()
}
pub fn other(&self) -> impl Iterator<Item = &Display> {
self.other_displays.iter()
}
pub fn active_strip(&self) -> &LayoutStrip {
*self.strip
}
pub fn bounds(&self) -> IRect {
self.display.0.bounds()
}
pub fn dock(&self) -> Option<&DockPosition> {
self.display.1
}
}
#[derive(SystemParam)]
pub struct ActiveDisplayMut<'w, 's> {
strip: Single<'w, 's, &'static mut LayoutStrip, With<ActiveWorkspaceMarker>>,
display: Single<'w, 's, &'static mut Display, With<ActiveDisplayMarker>>,
other_displays: Query<'w, 's, &'static mut Display, Without<ActiveDisplayMarker>>,
}
impl ActiveDisplayMut<'_, '_> {
pub fn display(&self) -> &Display {
&self.display
}
pub fn id(&self) -> CGDirectDisplayID {
self.display.id()
}
pub fn other(&mut self) -> impl Iterator<Item = Mut<'_, Display>> {
self.other_displays.iter_mut()
}
pub fn active_strip(&mut self) -> &mut LayoutStrip {
&mut self.strip
}
pub fn bounds(&self) -> IRect {
self.display.bounds()
}
}
#[derive(SystemParam)]
pub struct Windows<'w, 's> {
#[allow(clippy::type_complexity)]
all: Query<
'w,
's,
(
&'static Window,
Entity,
&'static ChildOf,
Option<&'static Unmanaged>,
),
>,
focus: Query<'w, 's, (&'static Window, Entity), With<FocusedMarker>>,
previous_size: Query<'w, 's, (&'static Window, Entity, &'static FullWidthMarker)>,
}
impl Windows<'_, '_> {
#[allow(clippy::type_complexity)]
fn get_all(&self, entity: Entity) -> Option<(&Window, Entity, &ChildOf, Option<&Unmanaged>)> {
self.all
.get(entity)
.inspect_err(|err| warn!("unable to find window: {err}"))
.ok()
}
pub fn get_managed(&self, entity: Entity) -> Option<(&Window, Entity, Option<&Unmanaged>)> {
self.get_all(entity)
.map(|(window, entity, _, unmanaged)| (window, entity, unmanaged))
}
pub fn get(&self, entity: Entity) -> Option<&Window> {
self.get_all(entity).map(|(window, _, _, _)| window)
}
pub fn find(&self, window_id: WinID) -> Option<(&Window, Entity)> {
self.all
.into_iter()
.find(|(window, _, _, _)| window.id() == window_id)
.map(|(window, entity, _, _)| (window, entity))
}
pub fn find_parent(&self, window_id: WinID) -> Option<(&Window, Entity, Entity)> {
self.all.iter().find_map(|(window, entity, childof, _)| {
(window.id() == window_id).then_some((window, entity, childof.parent()))
})
}
pub fn find_managed(&self, window_id: WinID) -> Option<(&Window, Entity)> {
self.all.iter().find_map(|(window, entity, _, unmanaged)| {
(unmanaged.is_none() && window.id() == window_id).then_some((window, entity))
})
}
pub fn focused(&self) -> Option<(&Window, Entity)> {
self.focus.single().ok()
}
pub fn iter(&self) -> impl Iterator<Item = (&Window, Entity)> {
self.all
.iter()
.map(|(window, entity, _, _)| (window, entity))
}
pub fn full_width(&self, entity: Entity) -> Option<&FullWidthMarker> {
self.previous_size
.get(entity)
.map(|(_, _, marker)| marker)
.ok()
}
pub fn psn(&self, window_id: WinID, apps: &Query<&Application>) -> Option<ProcessSerialNumber> {
self.find_parent(window_id)
.and_then(|(_, _, parent)| apps.get(parent).ok())
.map(|app| app.psn())
}
}