From a32140d41d77ffd4bc78e703dfeeb079ec8123c4 Mon Sep 17 00:00:00 2001 From: drendog Date: Mon, 3 Nov 2025 17:30:52 +0100 Subject: [PATCH] feat: make popup requests explicit and typed --- adapters/src/wayland/shell_adapter.rs | 24 ++- .../src/wayland/surfaces/popup_manager.rs | 28 +--- composition/src/system.rs | 149 +++++++++-------- domain/src/value_objects/mod.rs | 1 + domain/src/value_objects/popup_request.rs | 151 ++++++++++++++++++ 5 files changed, 252 insertions(+), 101 deletions(-) create mode 100644 domain/src/value_objects/popup_request.rs diff --git a/adapters/src/wayland/shell_adapter.rs b/adapters/src/wayland/shell_adapter.rs index c412f99..7c5d510 100644 --- a/adapters/src/wayland/shell_adapter.rs +++ b/adapters/src/wayland/shell_adapter.rs @@ -180,11 +180,25 @@ impl WaylandWindowingSystem { let serial = serial_holder.get(); - let params = popup_manager_clone.take_pending_popup_config(); + let params = if let Some((request, width, height)) = popup_manager_clone.take_pending_popup_request() { + log::info!( + "Using popup request: component='{}', position=({}, {}), size={}x{}, mode={:?}", + request.component, + request.at.position().0, + request.at.position().1, + width, + height, + request.mode + ); - let params = if let Some(mut p) = params { - p.last_pointer_serial = serial; - p + CreatePopupParams { + last_pointer_serial: serial, + reference_x: request.at.position().0, + reference_y: request.at.position().1, + width, + height, + positioning_mode: request.mode, + } } else { let output_size = popup_manager_clone.output_size(); #[allow(clippy::cast_precision_loss)] @@ -192,7 +206,7 @@ impl WaylandWindowingSystem { #[allow(clippy::cast_precision_loss)] let default_height = output_size.height as f32; - log::warn!("No popup config provided, using output size ({default_width}x{default_height}) as defaults"); + log::warn!("No popup request provided, using output size ({default_width}x{default_height}) as defaults"); CreatePopupParams { last_pointer_serial: serial, reference_x: 0.0, diff --git a/adapters/src/wayland/surfaces/popup_manager.rs b/adapters/src/wayland/surfaces/popup_manager.rs index 4b4918d..bcc32b2 100644 --- a/adapters/src/wayland/surfaces/popup_manager.rs +++ b/adapters/src/wayland/surfaces/popup_manager.rs @@ -3,6 +3,7 @@ use crate::rendering::egl::context::EGLContext; use crate::rendering::femtovg::popup_window::PopupWindow; use layer_shika_domain::value_objects::popup_config::PopupConfig; use layer_shika_domain::value_objects::popup_positioning_mode::PopupPositioningMode; +use layer_shika_domain::value_objects::popup_request::PopupRequest; use log::info; use slab::Slab; use slint::{platform::femtovg_renderer::FemtoVGRenderer, PhysicalSize, WindowSize}; @@ -72,7 +73,7 @@ pub struct PopupManager { popups: RefCell>, current_scale_factor: RefCell, current_output_size: RefCell, - pending_popup_config: RefCell>, + pending_popup_request: RefCell>, last_popup_key: RefCell>, } @@ -84,33 +85,18 @@ impl PopupManager { popups: RefCell::new(Slab::new()), current_scale_factor: RefCell::new(initial_scale_factor), current_output_size: RefCell::new(PhysicalSize::new(0, 0)), - pending_popup_config: RefCell::new(None), + pending_popup_request: RefCell::new(None), last_popup_key: RefCell::new(None), } } - pub fn set_pending_popup_config( - &self, - reference_x: f32, - reference_y: f32, - width: f32, - height: f32, - positioning_mode: PopupPositioningMode, - ) { - let last_pointer_serial = 0; - *self.pending_popup_config.borrow_mut() = Some(CreatePopupParams { - last_pointer_serial, - reference_x, - reference_y, - width, - height, - positioning_mode, - }); + pub fn set_pending_popup_request(&self, request: PopupRequest, width: f32, height: f32) { + *self.pending_popup_request.borrow_mut() = Some((request, width, height)); } #[must_use] - pub fn take_pending_popup_config(&self) -> Option { - self.pending_popup_config.borrow_mut().take() + pub fn take_pending_popup_request(&self) -> Option<(PopupRequest, f32, f32)> { + self.pending_popup_request.borrow_mut().take() } pub fn close_current_popup(&self) { diff --git a/composition/src/system.rs b/composition/src/system.rs index 0aab00c..b304799 100644 --- a/composition/src/system.rs +++ b/composition/src/system.rs @@ -16,6 +16,9 @@ use layer_shika_adapters::wayland::{ use layer_shika_domain::config::WindowConfig; use layer_shika_domain::errors::DomainError; use layer_shika_domain::value_objects::popup_positioning_mode::PopupPositioningMode; +use layer_shika_domain::value_objects::popup_request::{ + PopupAt, PopupHandle, PopupRequest, PopupSize, +}; use std::cell::{Ref, RefCell}; use std::os::unix::io::AsFd; use std::rc::{Rc, Weak}; @@ -129,6 +132,69 @@ impl RuntimeState<'_> { self.window_state.compilation_result() } + pub fn show_popup(&mut self, req: PopupRequest) -> Result<()> { + let compilation_result = self.compilation_result().ok_or_else(|| { + Error::Domain(DomainError::Configuration { + message: "No compilation result available for popup creation".to_string(), + }) + })?; + + let definition = compilation_result + .component(&req.component) + .ok_or_else(|| { + Error::Domain(DomainError::Configuration { + message: format!( + "{} component not found in compilation result", + req.component + ), + }) + })?; + + self.close_current_popup()?; + + let (width, height) = match req.size { + PopupSize::Fixed { w, h } => { + log::debug!("Using fixed popup size: {}x{}", w, h); + (w, h) + } + PopupSize::Content => self.measure_popup_dimensions(&definition)?, + }; + + let popup_manager = self + .window_state + .popup_manager() + .as_ref() + .ok_or_else(|| { + Error::Domain(DomainError::Configuration { + message: "No popup manager available".to_string(), + }) + }) + .map(Rc::clone)?; + + log::debug!( + "Setting pending popup request for '{}' with dimensions {}x{} at position ({}, {}), mode: {:?}", + req.component, + width, + height, + req.at.position().0, + req.at.position().1, + req.mode + ); + + popup_manager.set_pending_popup_request(req, width, height); + + Self::create_popup_instance(&definition, &popup_manager)?; + + Ok(()) + } + + pub fn close_popup(&mut self, handle: PopupHandle) -> Result<()> { + if let Some(popup_manager) = self.window_state.popup_manager() { + popup_manager.destroy_popup(handle.key()); + } + Ok(()) + } + pub fn close_current_popup(&mut self) -> Result<()> { if let Some(popup_manager) = self.window_state.popup_manager() { popup_manager.close_current_popup(); @@ -208,78 +274,6 @@ impl RuntimeState<'_> { Ok(instance) } - - pub fn show_popup_component( - &mut self, - component_name: &str, - position: Option<(f32, f32)>, - size: Option<(f32, f32)>, - positioning_mode: PopupPositioningMode, - ) -> Result<()> { - let compilation_result = self.compilation_result().ok_or_else(|| { - Error::Domain(DomainError::Configuration { - message: "No compilation result available for popup creation".to_string(), - }) - })?; - - let definition = compilation_result - .component(component_name) - .ok_or_else(|| { - Error::Domain(DomainError::Configuration { - message: format!( - "{} component not found in compilation result", - component_name - ), - }) - })?; - - self.close_current_popup()?; - - let (width, height) = if let Some(explicit_size) = size { - log::debug!( - "Using explicit popup size: {}x{}", - explicit_size.0, - explicit_size.1 - ); - explicit_size - } else { - self.measure_popup_dimensions(&definition)? - }; - - let popup_manager = self - .window_state - .popup_manager() - .as_ref() - .ok_or_else(|| { - Error::Domain(DomainError::Configuration { - message: "No popup manager available".to_string(), - }) - }) - .map(Rc::clone)?; - - let (reference_x, reference_y) = position.unwrap_or((0.0, 0.0)); - - popup_manager.set_pending_popup_config( - reference_x, - reference_y, - width, - height, - positioning_mode, - ); - - log::debug!( - "Creating final popup instance with dimensions {}x{} at position ({}, {}), mode: {:?}", - width, - height, - reference_x, - reference_y, - positioning_mode - ); - - Self::create_popup_instance(&definition, &popup_manager)?; - - Ok(()) - } } pub struct WindowingSystem { @@ -344,9 +338,14 @@ impl WindowingSystem { let (_token, sender) = event_loop_handle.add_channel( move |(component_name, x, y): (String, f32, f32), mut state| { let mode = *popup_mode_for_channel.borrow(); - if let Err(e) = - state.show_popup_component(&component_name, Some((x, y)), None, mode) - { + + let request = PopupRequest::builder(component_name) + .at(PopupAt::absolute(x, y)) + .size(PopupSize::content()) + .mode(mode) + .build(); + + if let Err(e) = state.show_popup(request) { log::error!("Failed to show popup: {}", e); } }, diff --git a/domain/src/value_objects/mod.rs b/domain/src/value_objects/mod.rs index 713894b..28ffb54 100644 --- a/domain/src/value_objects/mod.rs +++ b/domain/src/value_objects/mod.rs @@ -5,3 +5,4 @@ pub mod layer; pub mod margins; pub mod popup_config; pub mod popup_positioning_mode; +pub mod popup_request; diff --git a/domain/src/value_objects/popup_request.rs b/domain/src/value_objects/popup_request.rs new file mode 100644 index 0000000..f5c2b29 --- /dev/null +++ b/domain/src/value_objects/popup_request.rs @@ -0,0 +1,151 @@ +use super::popup_positioning_mode::PopupPositioningMode; + +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +pub struct PopupHandle(usize); + +impl PopupHandle { + #[must_use] + pub const fn new(key: usize) -> Self { + Self(key) + } + + #[must_use] + pub const fn key(self) -> usize { + self.0 + } +} + +#[derive(Debug, Clone)] +pub struct PopupRequest { + pub component: String, + pub at: PopupAt, + pub size: PopupSize, + pub mode: PopupPositioningMode, +} + +impl PopupRequest { + #[must_use] + pub fn new( + component: String, + at: PopupAt, + size: PopupSize, + mode: PopupPositioningMode, + ) -> Self { + Self { + component, + at, + size, + mode, + } + } + + #[must_use] + pub fn builder(component: String) -> PopupRequestBuilder { + PopupRequestBuilder::new(component) + } +} + +#[derive(Debug, Clone)] +pub enum PopupAt { + Absolute { x: f32, y: f32 }, + Cursor, + AnchorRect { x: f32, y: f32, w: f32, h: f32 }, +} + +impl PopupAt { + #[must_use] + pub const fn absolute(x: f32, y: f32) -> Self { + Self::Absolute { x, y } + } + + #[must_use] + pub const fn cursor() -> Self { + Self::Cursor + } + + #[must_use] + pub const fn anchor_rect(x: f32, y: f32, w: f32, h: f32) -> Self { + Self::AnchorRect { x, y, w, h } + } + + #[must_use] + pub const fn position(&self) -> (f32, f32) { + match *self { + Self::Absolute { x, y } | Self::AnchorRect { x, y, .. } => (x, y), + Self::Cursor => (0.0, 0.0), + } + } +} + +#[derive(Debug, Clone)] +pub enum PopupSize { + Fixed { w: f32, h: f32 }, + Content, +} + +impl PopupSize { + #[must_use] + pub const fn fixed(w: f32, h: f32) -> Self { + Self::Fixed { w, h } + } + + #[must_use] + pub const fn content() -> Self { + Self::Content + } + + #[must_use] + pub const fn dimensions(&self) -> Option<(f32, f32)> { + match *self { + Self::Fixed { w, h } => Some((w, h)), + Self::Content => None, + } + } +} + +pub struct PopupRequestBuilder { + component: String, + at: PopupAt, + size: PopupSize, + mode: PopupPositioningMode, +} + +impl PopupRequestBuilder { + #[must_use] + pub fn new(component: String) -> Self { + Self { + component, + at: PopupAt::Cursor, + size: PopupSize::Content, + mode: PopupPositioningMode::default(), + } + } + + #[must_use] + pub const fn at(mut self, at: PopupAt) -> Self { + self.at = at; + self + } + + #[must_use] + pub const fn size(mut self, size: PopupSize) -> Self { + self.size = size; + self + } + + #[must_use] + pub const fn mode(mut self, mode: PopupPositioningMode) -> Self { + self.mode = mode; + self + } + + #[must_use] + pub fn build(self) -> PopupRequest { + PopupRequest { + component: self.component, + at: self.at, + size: self.size, + mode: self.mode, + } + } +}