From e194682785709ccf3f57b9e14a025aab24bb00e4 Mon Sep 17 00:00:00 2001 From: drendog Date: Mon, 17 Nov 2025 00:46:29 +0100 Subject: [PATCH] refactor: make outputs a first-class concept --- .../wayland/event_handling/app_dispatcher.rs | 8 +- crates/adapters/src/wayland/outputs.rs | 36 ++++- .../src/wayland/surfaces/app_state.rs | 129 +++++++++++++----- crates/composition/src/lib.rs | 1 + crates/composition/src/system.rs | 25 +++- crates/domain/src/value_objects/mod.rs | 1 + .../domain/src/value_objects/output_handle.rs | 26 ++++ src/lib.rs | 6 +- src/prelude.rs | 8 +- 9 files changed, 187 insertions(+), 53 deletions(-) create mode 100644 crates/domain/src/value_objects/output_handle.rs diff --git a/crates/adapters/src/wayland/event_handling/app_dispatcher.rs b/crates/adapters/src/wayland/event_handling/app_dispatcher.rs index a9d495a..9d37978 100644 --- a/crates/adapters/src/wayland/event_handling/app_dispatcher.rs +++ b/crates/adapters/src/wayland/event_handling/app_dispatcher.rs @@ -138,7 +138,7 @@ impl Dispatch for AppState { if let Some(window) = state.get_output_by_surface_mut(&surface_id) { window.handle_pointer_enter(serial, &surface, surface_x, surface_y); - if let Some(key) = state.get_key_by_surface(&surface_id).cloned() { + if let Some(key) = state.get_key_by_surface(&surface_id) { state.set_active_output(Some(key)); } } else { @@ -158,7 +158,7 @@ impl Dispatch for AppState { surface_y, .. } => { - if let Some(output_key) = state.active_output().cloned() { + if let Some(output_key) = state.active_output() { if let Some(window) = state.get_output_by_key_mut(&output_key) { window.handle_pointer_motion(surface_x, surface_y); } @@ -166,7 +166,7 @@ impl Dispatch for AppState { } wl_pointer::Event::Leave { .. } => { - if let Some(output_key) = state.active_output().cloned() { + if let Some(output_key) = state.active_output() { if let Some(window) = state.get_output_by_key_mut(&output_key) { window.handle_pointer_leave(); } @@ -179,7 +179,7 @@ impl Dispatch for AppState { state: button_state, .. } => { - if let Some(output_key) = state.active_output().cloned() { + if let Some(output_key) = state.active_output() { if let Some(window) = state.get_output_by_key_mut(&output_key) { window.handle_pointer_button(serial, button_state); } diff --git a/crates/adapters/src/wayland/outputs.rs b/crates/adapters/src/wayland/outputs.rs index 8a6a165..9f828a3 100644 --- a/crates/adapters/src/wayland/outputs.rs +++ b/crates/adapters/src/wayland/outputs.rs @@ -1,20 +1,42 @@ +use layer_shika_domain::value_objects::output_handle::OutputHandle; +use std::collections::hash_map::DefaultHasher; +use std::hash::{Hash, Hasher}; use wayland_client::backend::ObjectId; -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct OutputKey(ObjectId); +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct OutputKey { + handle: OutputHandle, + _object_id_hash: u64, +} impl OutputKey { - pub const fn new(id: ObjectId) -> Self { - Self(id) + pub fn new(object_id: &ObjectId) -> Self { + let mut hasher = DefaultHasher::new(); + object_id.hash(&mut hasher); + let object_id_hash = hasher.finish(); + + Self { + handle: OutputHandle::new(), + _object_id_hash: object_id_hash, + } } - pub const fn id(&self) -> &ObjectId { - &self.0 + pub const fn handle(&self) -> OutputHandle { + self.handle } } impl From for OutputKey { fn from(id: ObjectId) -> Self { - Self::new(id) + Self::new(&id) + } +} + +impl From for OutputKey { + fn from(handle: OutputHandle) -> Self { + Self { + handle, + _object_id_hash: 0, + } } } diff --git a/crates/adapters/src/wayland/surfaces/app_state.rs b/crates/adapters/src/wayland/surfaces/app_state.rs index a6e4eb3..26cc457 100644 --- a/crates/adapters/src/wayland/surfaces/app_state.rs +++ b/crates/adapters/src/wayland/surfaces/app_state.rs @@ -1,21 +1,23 @@ -use super::surface_state::WindowState; use super::event_context::SharedPointerSerial; +use super::surface_state::WindowState; use crate::wayland::managed_proxies::ManagedWlPointer; use crate::wayland::outputs::OutputKey; +use layer_shika_domain::value_objects::output_handle::OutputHandle; use std::collections::HashMap; use std::rc::Rc; -use wayland_client::backend::ObjectId; use wayland_client::Proxy; +use wayland_client::backend::ObjectId; pub type PerOutputWindow = WindowState; pub struct AppState { - outputs: HashMap, - surface_to_output: HashMap, - output_to_key: HashMap, + outputs: HashMap, + surface_to_output: HashMap, + output_to_handle: HashMap, _pointer: ManagedWlPointer, shared_pointer_serial: Rc, - active_output: Option, + active_output: Option, + primary_output: Option, } impl AppState { @@ -23,10 +25,11 @@ impl AppState { Self { outputs: HashMap::new(), surface_to_output: HashMap::new(), - output_to_key: HashMap::new(), + output_to_handle: HashMap::new(), _pointer: pointer, shared_pointer_serial: shared_serial, active_output: None, + primary_output: None, } } @@ -36,40 +39,56 @@ impl AppState { main_surface_id: ObjectId, window: PerOutputWindow, ) { - let key = OutputKey::new(output_id.clone()); - self.output_to_key.insert(output_id, key.clone()); - self.surface_to_output - .insert(main_surface_id, key.clone()); - self.outputs.insert(key, window); + let key = OutputKey::new(&output_id); + let handle = key.handle(); + self.output_to_handle.insert(output_id, handle); + self.surface_to_output.insert(main_surface_id, handle); + + if self.primary_output.is_none() { + self.primary_output = Some(handle); + } + + self.outputs.insert(handle, window); } pub fn get_output_by_key(&self, key: &OutputKey) -> Option<&PerOutputWindow> { - self.outputs.get(key) + self.outputs.get(&key.handle()) } pub fn get_output_by_key_mut(&mut self, key: &OutputKey) -> Option<&mut PerOutputWindow> { - self.outputs.get_mut(key) + self.outputs.get_mut(&key.handle()) + } + + pub fn get_output_by_handle(&self, handle: OutputHandle) -> Option<&PerOutputWindow> { + self.outputs.get(&handle) + } + + pub fn get_output_by_handle_mut( + &mut self, + handle: OutputHandle, + ) -> Option<&mut PerOutputWindow> { + self.outputs.get_mut(&handle) } pub fn get_output_by_output_id(&self, output_id: &ObjectId) -> Option<&PerOutputWindow> { - self.output_to_key + self.output_to_handle .get(output_id) - .and_then(|key| self.outputs.get(key)) + .and_then(|handle| self.outputs.get(handle)) } pub fn get_output_by_output_id_mut( &mut self, output_id: &ObjectId, ) -> Option<&mut PerOutputWindow> { - self.output_to_key + self.output_to_handle .get(output_id) - .and_then(|key| self.outputs.get_mut(key)) + .and_then(|handle| self.outputs.get_mut(handle)) } pub fn get_output_by_surface(&self, surface_id: &ObjectId) -> Option<&PerOutputWindow> { self.surface_to_output .get(surface_id) - .and_then(|key| self.outputs.get(key)) + .and_then(|handle| self.outputs.get(handle)) } pub fn get_output_by_surface_mut( @@ -78,40 +97,66 @@ impl AppState { ) -> Option<&mut PerOutputWindow> { self.surface_to_output .get(surface_id) - .and_then(|key| self.outputs.get_mut(key)) + .and_then(|handle| self.outputs.get_mut(handle)) } pub fn get_output_by_layer_surface_mut( &mut self, layer_surface_id: &ObjectId, ) -> Option<&mut PerOutputWindow> { - self.outputs.values_mut().find(|window| { - window.layer_surface().as_ref().id() == *layer_surface_id - }) + self.outputs + .values_mut() + .find(|window| window.layer_surface().as_ref().id() == *layer_surface_id) } - pub fn get_key_by_surface(&self, surface_id: &ObjectId) -> Option<&OutputKey> { - self.surface_to_output.get(surface_id) + pub fn get_key_by_surface(&self, surface_id: &ObjectId) -> Option { + self.surface_to_output + .get(surface_id) + .map(|&handle| OutputKey::from(handle)) } - pub fn get_key_by_output_id(&self, output_id: &ObjectId) -> Option<&OutputKey> { - self.output_to_key.get(output_id) + pub fn get_key_by_output_id(&self, output_id: &ObjectId) -> Option { + self.output_to_handle + .get(output_id) + .map(|&handle| OutputKey::from(handle)) + } + + pub fn get_handle_by_surface(&self, surface_id: &ObjectId) -> Option { + self.surface_to_output.get(surface_id).copied() + } + + pub fn get_handle_by_output_id(&self, output_id: &ObjectId) -> Option { + self.output_to_handle.get(output_id).copied() } pub fn register_popup_surface(&mut self, popup_surface_id: ObjectId, output_key: OutputKey) { - self.surface_to_output.insert(popup_surface_id, output_key); + self.surface_to_output + .insert(popup_surface_id, output_key.handle()); } pub fn set_active_output(&mut self, key: Option) { - self.active_output = key; + self.active_output = key.map(|k| k.handle()); } - pub const fn active_output(&self) -> Option<&OutputKey> { - self.active_output.as_ref() + pub fn set_active_output_handle(&mut self, handle: Option) { + self.active_output = handle; + } + + pub fn active_output(&self) -> Option { + self.active_output.map(OutputKey::from) + } + + pub fn active_output_handle(&self) -> Option { + self.active_output } pub fn primary_output(&self) -> Option<&PerOutputWindow> { - self.outputs.values().next() + self.primary_output + .and_then(|handle| self.outputs.get(&handle)) + } + + pub fn primary_output_handle(&self) -> Option { + self.primary_output } pub fn all_outputs(&self) -> impl Iterator { @@ -122,6 +167,12 @@ impl AppState { self.outputs.values_mut() } + pub fn outputs_with_handles(&self) -> impl Iterator { + self.outputs + .iter() + .map(|(&handle, window)| (handle, window)) + } + pub const fn shared_pointer_serial(&self) -> &Rc { &self.shared_pointer_serial } @@ -150,12 +201,22 @@ impl AppState { } pub fn get_key_by_popup(&self, popup_surface_id: &ObjectId) -> Option { - self.outputs.iter().find_map(|(key, window)| { + self.outputs.iter().find_map(|(&handle, window)| { window .popup_manager() .as_ref() .and_then(|pm| pm.find_by_surface(popup_surface_id)) - .map(|_| key.clone()) + .map(|_| OutputKey::from(handle)) + }) + } + + pub fn get_handle_by_popup(&self, popup_surface_id: &ObjectId) -> Option { + self.outputs.iter().find_map(|(&handle, window)| { + window + .popup_manager() + .as_ref() + .and_then(|pm| pm.find_by_surface(popup_surface_id)) + .map(|_| handle) }) } } diff --git a/crates/composition/src/lib.rs b/crates/composition/src/lib.rs index 6ff0b2a..06a1973 100644 --- a/crates/composition/src/lib.rs +++ b/crates/composition/src/lib.rs @@ -14,6 +14,7 @@ pub use layer_shika_adapters::platform::{slint, slint_interpreter}; pub use layer_shika_domain::value_objects::anchor::AnchorEdges; pub use layer_shika_domain::value_objects::keyboard_interactivity::KeyboardInteractivity; pub use layer_shika_domain::value_objects::layer::Layer; +pub use layer_shika_domain::value_objects::output_handle::OutputHandle; pub use layer_shika_domain::value_objects::popup_positioning_mode::PopupPositioningMode; pub use layer_shika_domain::value_objects::popup_request::{ PopupAt, PopupHandle, PopupRequest, PopupSize, diff --git a/crates/composition/src/system.rs b/crates/composition/src/system.rs index f931875..7312727 100644 --- a/crates/composition/src/system.rs +++ b/crates/composition/src/system.rs @@ -14,6 +14,7 @@ use layer_shika_adapters::{ }; use layer_shika_domain::config::WindowConfig; use layer_shika_domain::errors::DomainError; +use layer_shika_domain::value_objects::output_handle::OutputHandle; use layer_shika_domain::value_objects::popup_positioning_mode::PopupPositioningMode; use layer_shika_domain::value_objects::popup_request::{PopupHandle, PopupRequest, PopupSize}; use std::cell::Cell; @@ -139,10 +140,32 @@ impl RuntimeState<'_> { .map(WindowState::component_instance) } + #[must_use] + pub fn primary_output_handle(&self) -> Option { + self.app_state.primary_output_handle() + } + + #[must_use] + pub fn active_output_handle(&self) -> Option { + self.app_state.active_output_handle() + } + + pub fn outputs(&self) -> impl Iterator { + self.app_state + .outputs_with_handles() + .map(|(handle, window)| (handle, window.component_instance())) + } + + pub fn get_output_component(&self, handle: OutputHandle) -> Option<&ComponentInstance> { + self.app_state + .get_output_by_handle(handle) + .map(WindowState::component_instance) + } + fn active_or_primary_output(&self) -> Option<&WindowState> { self.app_state .active_output() - .and_then(|key| self.app_state.get_output_by_key(key)) + .and_then(|key| self.app_state.get_output_by_key(&key)) .or_else(|| self.app_state.primary_output()) } diff --git a/crates/domain/src/value_objects/mod.rs b/crates/domain/src/value_objects/mod.rs index 28ffb54..f84a742 100644 --- a/crates/domain/src/value_objects/mod.rs +++ b/crates/domain/src/value_objects/mod.rs @@ -3,6 +3,7 @@ pub mod dimensions; pub mod keyboard_interactivity; pub mod layer; pub mod margins; +pub mod output_handle; pub mod popup_config; pub mod popup_positioning_mode; pub mod popup_request; diff --git a/crates/domain/src/value_objects/output_handle.rs b/crates/domain/src/value_objects/output_handle.rs new file mode 100644 index 0000000..8a3d8d7 --- /dev/null +++ b/crates/domain/src/value_objects/output_handle.rs @@ -0,0 +1,26 @@ +use std::sync::atomic::{AtomicUsize, Ordering}; + +static NEXT_OUTPUT_ID: AtomicUsize = AtomicUsize::new(1); + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct OutputHandle { + id: usize, +} + +impl OutputHandle { + pub fn new() -> Self { + Self { + id: NEXT_OUTPUT_ID.fetch_add(1, Ordering::Relaxed), + } + } + + pub const fn id(&self) -> usize { + self.id + } +} + +impl Default for OutputHandle { + fn default() -> Self { + Self::new() + } +} diff --git a/src/lib.rs b/src/lib.rs index 2ff80f9..1443535 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,9 +44,9 @@ pub mod prelude; pub use layer_shika_composition::{ - AnchorEdges, Error, EventLoopHandle, KeyboardInteractivity, Layer, LayerShika, PopupAt, - PopupHandle, PopupPositioningMode, PopupRequest, PopupSize, PopupWindow, Result, RuntimeState, - SlintCallbackContract, SlintCallbackNames, WindowingSystem, + AnchorEdges, Error, EventLoopHandle, KeyboardInteractivity, Layer, LayerShika, OutputHandle, + PopupAt, PopupHandle, PopupPositioningMode, PopupRequest, PopupSize, PopupWindow, Result, + RuntimeState, SlintCallbackContract, SlintCallbackNames, WindowingSystem, }; pub use layer_shika_composition::{slint, slint_interpreter}; diff --git a/src/prelude.rs b/src/prelude.rs index 89650af..f02cdd2 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -10,14 +10,14 @@ // Core API types pub use crate::{ - Error, EventLoopHandle, LayerShika, PopupWindow, Result, RuntimeState, - SlintCallbackContract, SlintCallbackNames, WindowingSystem, + Error, EventLoopHandle, LayerShika, PopupWindow, Result, RuntimeState, SlintCallbackContract, + SlintCallbackNames, WindowingSystem, }; // Domain value objects pub use crate::{ - AnchorEdges, KeyboardInteractivity, Layer, PopupAt, PopupHandle, PopupPositioningMode, - PopupRequest, PopupSize, + AnchorEdges, KeyboardInteractivity, Layer, OutputHandle, PopupAt, PopupHandle, + PopupPositioningMode, PopupRequest, PopupSize, }; // Event loop types