use std::time::Duration;
use bevy::{
ecs::{
entity::Entity,
hierarchy::ChildOf,
query::{With, Without},
system::{Local, Query, Res, ResMut, Single, SystemParam},
world::Mut,
},
time::Time,
};
use log::warn;
use objc2_core_foundation::CGRect;
use objc2_core_graphics::CGDirectDisplayID;
use stdext::function_name;
use super::{ActiveDisplayMarker, FocusFollowsMouse, MissionControlActive, SkipReshuffle};
use crate::{
config::{Config, WindowParams},
ecs::{ActiveWorkspaceMarker, FocusedMarker, FullWidthMarker, Unmanaged},
manager::{Display, LayoutStrip, Window},
platform::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 continuous_swipe(&self) -> bool {
self.config.options().continuous_swipe.is_none_or(|cs| cs)
}
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 mission_control_active(&self) -> bool {
self.mission_control_active.0
}
}
#[derive(SystemParam)]
pub struct DebouncedSystem<'w, 's> {
time: Res<'w, Time>,
elapsed: Local<'s, Duration>,
}
impl DebouncedSystem<'_, '_> {
pub fn bounce(&mut self, duration: Duration) -> bool {
if self.time.elapsed().saturating_sub(*self.elapsed) > duration {
*self.elapsed = self.time.elapsed();
return false;
}
true
}
}
#[derive(SystemParam)]
pub struct ActiveDisplay<'w, 's> {
strip: Single<'w, 's, &'static LayoutStrip, With<ActiveWorkspaceMarker>>,
display: Single<'w, 's, &'static Display, With<ActiveDisplayMarker>>,
other_displays: Query<'w, 's, &'static Display, Without<ActiveDisplayMarker>>,
}
impl ActiveDisplay<'_, '_> {
pub fn display(&self) -> &Display {
&self.display
}
pub fn id(&self) -> CGDirectDisplayID {
self.display.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) -> CGRect {
self.display.bounds
}
}
#[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) -> CGRect {
self.display.bounds
}
}
#[derive(SystemParam)]
pub struct Windows<'w, 's> {
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<'_, '_> {
pub 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}", function_name!()))
.ok()
}
pub fn get(&self, entity: Entity) -> Option<&Window> {
self.get_all(entity).map(|(window, _, _, _)| window)
}
pub fn find_all(
&self,
window_id: WinID,
) -> Option<(&Window, Entity, &ChildOf, Option<&Unmanaged>)> {
self.all
.iter()
.find(|(window, _, _, _)| window.id() == window_id)
}
pub fn find(&self, window_id: WinID) -> Option<(&Window, Entity)> {
self.find_all(window_id)
.map(|(window, entity, _, _)| (window, entity))
}
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,
) -> bevy::ecs::query::QueryIter<
'_,
'_,
(
&'static Window,
Entity,
&'static ChildOf,
Option<&'static Unmanaged>,
),
(),
> {
self.all.iter()
}
pub fn full_width(&self, entity: Entity) -> Option<f64> {
self.previous_size
.get(entity)
.map(|(_, _, width)| width.0)
.ok()
}
}