From 64a18153b6a10bd474772537d26f1c766e864acc Mon Sep 17 00:00:00 2001 From: drendog Date: Thu, 27 Nov 2025 23:56:41 +0100 Subject: [PATCH] feat: add popup bind anchored --- crates/composition/src/lib.rs | 9 +- crates/composition/src/popup_builder.rs | 99 +++++++++++++++++++ crates/domain/src/prelude.rs | 1 + .../src/value_objects/anchor_strategy.rs | 49 +++++++++ crates/domain/src/value_objects/mod.rs | 1 + src/lib.rs | 7 +- 6 files changed, 159 insertions(+), 7 deletions(-) create mode 100644 crates/domain/src/value_objects/anchor_strategy.rs diff --git a/crates/composition/src/lib.rs b/crates/composition/src/lib.rs index 7e12b29..06f1b2d 100644 --- a/crates/composition/src/lib.rs +++ b/crates/composition/src/lib.rs @@ -13,6 +13,7 @@ pub use builder::LayerShika; pub use layer_shika_adapters::PopupWindow; pub use layer_shika_adapters::platform::{slint, slint_interpreter}; pub use layer_shika_domain::entities::output_registry::OutputRegistry; +pub use layer_shika_domain::prelude::AnchorStrategy; 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; @@ -48,10 +49,10 @@ pub enum Error { pub mod prelude { pub use crate::{ - AnchorEdges, App, EventLoopHandle, KeyboardInteractivity, Layer, LayerShika, - OutputGeometry, OutputHandle, OutputInfo, OutputPolicy, OutputRegistry, PopupAt, - PopupBuilder, PopupHandle, PopupPositioningMode, PopupRequest, PopupSize, PopupWindow, - Result, ShellContext, ShellControl, + AnchorEdges, AnchorStrategy, App, EventLoopHandle, KeyboardInteractivity, Layer, + LayerShika, OutputGeometry, OutputHandle, OutputInfo, OutputPolicy, OutputRegistry, + PopupAt, PopupBuilder, PopupHandle, PopupPositioningMode, PopupRequest, PopupSize, + PopupWindow, Result, ShellContext, ShellControl, }; pub use crate::calloop::{Generic, Interest, Mode, PostAction, RegistrationToken, Timer}; diff --git a/crates/composition/src/popup_builder.rs b/crates/composition/src/popup_builder.rs index 41cf186..b62ecf4 100644 --- a/crates/composition/src/popup_builder.rs +++ b/crates/composition/src/popup_builder.rs @@ -1,6 +1,7 @@ use crate::Result; use crate::system::App; use layer_shika_adapters::platform::slint_interpreter::Value; +use layer_shika_domain::prelude::AnchorStrategy; use layer_shika_domain::value_objects::popup_positioning_mode::PopupPositioningMode; use layer_shika_domain::value_objects::popup_request::{PopupAt, PopupRequest, PopupSize}; @@ -196,6 +197,104 @@ impl<'a> PopupBuilder<'a> { Ok(()) } + pub fn bind_anchored(self, trigger_callback: &str, strategy: AnchorStrategy) -> Result<()> { + let component_name = self.component.clone(); + let grab = self.grab; + let close_callback = self.close_callback.clone(); + let resize_callback = self.resize_callback.clone(); + let control = self.app.control(); + + self.app.with_all_component_instances(|instance| { + let component_clone = component_name.clone(); + let control_clone = control.clone(); + let close_cb = close_callback.clone(); + let resize_cb = resize_callback.clone(); + + if let Err(e) = instance.set_callback(trigger_callback, move |args| { + if args.len() < 4 { + log::error!( + "bind_anchored callback expects 4 arguments (x, y, width, height), got {}", + args.len() + ); + return Value::Void; + } + + let anchor_x = args + .first() + .and_then(|v| v.clone().try_into().ok()) + .unwrap_or(0.0); + let anchor_y = args + .get(1) + .and_then(|v| v.clone().try_into().ok()) + .unwrap_or(0.0); + let anchor_w = args + .get(2) + .and_then(|v| v.clone().try_into().ok()) + .unwrap_or(0.0); + let anchor_h = args + .get(3) + .and_then(|v| v.clone().try_into().ok()) + .unwrap_or(0.0); + + log::debug!( + "Anchored popup triggered for '{}' at rect: ({}, {}, {}, {})", + component_clone, + anchor_x, + anchor_y, + anchor_w, + anchor_h + ); + + let mut builder = PopupRequest::builder(component_clone.clone()) + .at(PopupAt::AnchorRect { + x: anchor_x, + y: anchor_y, + w: anchor_w, + h: anchor_h, + }) + .size(PopupSize::Content) + .grab(grab); + + let mode = match strategy { + AnchorStrategy::CenterBottom => PopupPositioningMode::TopCenter, + AnchorStrategy::CenterTop => PopupPositioningMode::BottomCenter, + AnchorStrategy::RightBottom => PopupPositioningMode::TopRight, + AnchorStrategy::LeftTop => PopupPositioningMode::BottomLeft, + AnchorStrategy::RightTop => PopupPositioningMode::BottomRight, + AnchorStrategy::LeftBottom | AnchorStrategy::Cursor => { + PopupPositioningMode::TopLeft + } + }; + + builder = builder.mode(mode); + + if let Some(ref close_cb) = close_cb { + builder = builder.close_on(close_cb.clone()); + } + + if let Some(ref resize_cb) = resize_cb { + builder = builder.resize_on(resize_cb.clone()); + } + + let request = builder.build(); + + if let Err(e) = control_clone.show_popup(&request) { + log::error!("Failed to show anchored popup: {}", e); + } + + Value::Void + }) { + log::error!( + "Failed to bind anchored popup callback '{}': {}", + trigger_callback, + e + ); + } + }); + + Ok(()) + } + fn build_request(&self) -> PopupRequest { let mut builder = PopupRequest::builder(self.component.clone()) .at(self.reference) diff --git a/crates/domain/src/prelude.rs b/crates/domain/src/prelude.rs index 56119c4..e5dfb3e 100644 --- a/crates/domain/src/prelude.rs +++ b/crates/domain/src/prelude.rs @@ -8,6 +8,7 @@ pub use crate::entities::output_registry::OutputRegistry; pub use crate::errors::{DomainError, Result}; pub use crate::surface_dimensions::SurfaceDimensions; pub use crate::value_objects::anchor::AnchorEdges; +pub use crate::value_objects::anchor_strategy::AnchorStrategy; pub use crate::value_objects::dimensions::{PopupDimensions, WindowHeight}; pub use crate::value_objects::keyboard_interactivity::KeyboardInteractivity; pub use crate::value_objects::layer::Layer; diff --git a/crates/domain/src/value_objects/anchor_strategy.rs b/crates/domain/src/value_objects/anchor_strategy.rs new file mode 100644 index 0000000..f22cb8c --- /dev/null +++ b/crates/domain/src/value_objects/anchor_strategy.rs @@ -0,0 +1,49 @@ +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum AnchorStrategy { + CenterBottom, + CenterTop, + LeftBottom, + RightBottom, + LeftTop, + RightTop, + Cursor, +} + +impl AnchorStrategy { + #[must_use] + pub const fn calculate_position( + self, + anchor_x: f64, + anchor_y: f64, + anchor_w: f64, + anchor_h: f64, + popup_w: f64, + popup_h: f64, + ) -> (f64, f64) { + match self { + Self::CenterBottom => { + let center_x = anchor_x + (anchor_w / 2.0); + let x = center_x - (popup_w / 2.0); + let y = anchor_y + anchor_h; + (x, y) + } + Self::CenterTop => { + let center_x = anchor_x + (anchor_w / 2.0); + let x = center_x - (popup_w / 2.0); + let y = anchor_y - popup_h; + (x, y) + } + Self::LeftBottom => (anchor_x, anchor_y + anchor_h), + Self::RightBottom => (anchor_x + anchor_w - popup_w, anchor_y + anchor_h), + Self::LeftTop => (anchor_x, anchor_y - popup_h), + Self::RightTop => (anchor_x + anchor_w - popup_w, anchor_y - popup_h), + Self::Cursor => (anchor_x, anchor_y), + } + } +} + +impl Default for AnchorStrategy { + fn default() -> Self { + Self::CenterBottom + } +} diff --git a/crates/domain/src/value_objects/mod.rs b/crates/domain/src/value_objects/mod.rs index 12a0fd6..5c0e313 100644 --- a/crates/domain/src/value_objects/mod.rs +++ b/crates/domain/src/value_objects/mod.rs @@ -1,4 +1,5 @@ pub mod anchor; +pub mod anchor_strategy; pub mod dimensions; pub mod keyboard_interactivity; pub mod layer; diff --git a/src/lib.rs b/src/lib.rs index 2a85205..deab662 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,9 +42,10 @@ pub mod prelude; pub use layer_shika_composition::{ - AnchorEdges, App, Error, EventLoopHandle, KeyboardInteractivity, Layer, LayerShika, - OutputGeometry, OutputHandle, OutputInfo, OutputPolicy, OutputRegistry, PopupAt, PopupHandle, - PopupPositioningMode, PopupRequest, PopupSize, PopupWindow, Result, ShellContext, ShellControl, + AnchorEdges, AnchorStrategy, App, Error, EventLoopHandle, KeyboardInteractivity, Layer, + LayerShika, OutputGeometry, OutputHandle, OutputInfo, OutputPolicy, OutputRegistry, PopupAt, + PopupHandle, PopupPositioningMode, PopupRequest, PopupSize, PopupWindow, Result, ShellContext, + ShellControl, }; pub use layer_shika_composition::{slint, slint_interpreter};