From 0756fc0ef72f71e36309fd9f115646f7de2922ec Mon Sep 17 00:00:00 2001 From: drendog Date: Wed, 26 Nov 2025 02:07:51 +0100 Subject: [PATCH] refactor: extract popup builder and consolidate popup config --- crates/composition/src/lib.rs | 7 +- crates/composition/src/popup_builder.rs | 216 +++++++++++++++++++++ crates/composition/src/system.rs | 100 +++++++--- crates/composition/src/value_conversion.rs | 53 +++++ 4 files changed, 343 insertions(+), 33 deletions(-) create mode 100644 crates/composition/src/popup_builder.rs create mode 100644 crates/composition/src/value_conversion.rs diff --git a/crates/composition/src/lib.rs b/crates/composition/src/lib.rs index f9c516d..7e12b29 100644 --- a/crates/composition/src/lib.rs +++ b/crates/composition/src/lib.rs @@ -1,7 +1,9 @@ #![allow(clippy::pub_use)] mod builder; +mod popup_builder; mod system; +mod value_conversion; use layer_shika_adapters::errors::LayerShikaError; use layer_shika_domain::errors::DomainError; @@ -21,6 +23,7 @@ pub use layer_shika_domain::value_objects::popup_positioning_mode::PopupPosition pub use layer_shika_domain::value_objects::popup_request::{ PopupAt, PopupHandle, PopupRequest, PopupSize, }; +pub use popup_builder::PopupBuilder; pub use system::{App, EventLoopHandle, ShellContext, ShellControl}; pub mod calloop { @@ -47,8 +50,8 @@ pub mod prelude { pub use crate::{ AnchorEdges, App, EventLoopHandle, KeyboardInteractivity, Layer, LayerShika, OutputGeometry, OutputHandle, OutputInfo, OutputPolicy, OutputRegistry, PopupAt, - PopupHandle, PopupPositioningMode, PopupRequest, PopupSize, PopupWindow, Result, - ShellContext, ShellControl, + 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 new file mode 100644 index 0000000..41cf186 --- /dev/null +++ b/crates/composition/src/popup_builder.rs @@ -0,0 +1,216 @@ +use crate::Result; +use crate::system::App; +use layer_shika_adapters::platform::slint_interpreter::Value; +use layer_shika_domain::value_objects::popup_positioning_mode::PopupPositioningMode; +use layer_shika_domain::value_objects::popup_request::{PopupAt, PopupRequest, PopupSize}; + +pub struct PopupBuilder<'a> { + app: &'a App, + component: String, + reference: PopupAt, + anchor: PopupPositioningMode, + size: PopupSize, + grab: bool, + close_callback: Option, + resize_callback: Option, +} + +impl<'a> PopupBuilder<'a> { + pub(crate) fn new(app: &'a App, component: String) -> Self { + Self { + app, + component, + reference: PopupAt::Cursor, + anchor: PopupPositioningMode::TopLeft, + size: PopupSize::Content, + grab: false, + close_callback: None, + resize_callback: None, + } + } + + #[must_use] + pub fn relative_to_cursor(mut self) -> Self { + self.reference = PopupAt::Cursor; + self + } + + #[must_use] + pub fn relative_to_point(mut self, x: f32, y: f32) -> Self { + self.reference = PopupAt::Absolute { x, y }; + self + } + + #[must_use] + pub fn relative_to_rect(mut self, x: f32, y: f32, w: f32, h: f32) -> Self { + self.reference = PopupAt::AnchorRect { x, y, w, h }; + self + } + + #[must_use] + pub const fn anchor(mut self, anchor: PopupPositioningMode) -> Self { + self.anchor = anchor; + self + } + + #[must_use] + pub fn anchor_top_left(mut self) -> Self { + self.anchor = PopupPositioningMode::TopLeft; + self + } + + #[must_use] + pub fn anchor_top_center(mut self) -> Self { + self.anchor = PopupPositioningMode::TopCenter; + self + } + + #[must_use] + pub fn anchor_top_right(mut self) -> Self { + self.anchor = PopupPositioningMode::TopRight; + self + } + + #[must_use] + pub fn anchor_center_left(mut self) -> Self { + self.anchor = PopupPositioningMode::CenterLeft; + self + } + + #[must_use] + pub fn anchor_center(mut self) -> Self { + self.anchor = PopupPositioningMode::Center; + self + } + + #[must_use] + pub fn anchor_center_right(mut self) -> Self { + self.anchor = PopupPositioningMode::CenterRight; + self + } + + #[must_use] + pub fn anchor_bottom_left(mut self) -> Self { + self.anchor = PopupPositioningMode::BottomLeft; + self + } + + #[must_use] + pub fn anchor_bottom_center(mut self) -> Self { + self.anchor = PopupPositioningMode::BottomCenter; + self + } + + #[must_use] + pub fn anchor_bottom_right(mut self) -> Self { + self.anchor = PopupPositioningMode::BottomRight; + self + } + + #[must_use] + pub const fn size(mut self, size: PopupSize) -> Self { + self.size = size; + self + } + + #[must_use] + pub fn fixed_size(mut self, w: f32, h: f32) -> Self { + self.size = PopupSize::Fixed { w, h }; + self + } + + #[must_use] + pub fn content_size(mut self) -> Self { + self.size = PopupSize::Content; + self + } + + #[must_use] + pub const fn grab(mut self, enable: bool) -> Self { + self.grab = enable; + self + } + + #[must_use] + pub fn close_on(mut self, callback_name: impl Into) -> Self { + self.close_callback = Some(callback_name.into()); + self + } + + #[must_use] + pub fn resize_on(mut self, callback_name: impl Into) -> Self { + self.resize_callback = Some(callback_name.into()); + self + } + + pub fn bind(self, trigger_callback: &str) -> Result<()> { + let request = self.build_request(); + let control = self.app.control(); + + self.app.with_all_component_instances(|instance| { + let request_clone = request.clone(); + let control_clone = control.clone(); + + if let Err(e) = instance.set_callback(trigger_callback, move |_args| { + if let Err(e) = control_clone.show_popup(&request_clone) { + log::error!("Failed to show popup: {}", e); + } + Value::Void + }) { + log::error!( + "Failed to bind popup callback '{}': {}", + trigger_callback, + e + ); + } + }); + + Ok(()) + } + + pub fn toggle(self, trigger_callback: &str) -> Result<()> { + let request = self.build_request(); + let control = self.app.control(); + let component_name = request.component.clone(); + + self.app.with_all_component_instances(|instance| { + let request_clone = request.clone(); + let control_clone = control.clone(); + let component_clone = component_name.clone(); + + if let Err(e) = instance.set_callback(trigger_callback, move |_args| { + log::debug!("Toggle callback for component: {}", component_clone); + if let Err(e) = control_clone.show_popup(&request_clone) { + log::error!("Failed to toggle popup: {}", e); + } + Value::Void + }) { + log::error!( + "Failed to bind toggle popup callback '{}': {}", + trigger_callback, + e + ); + } + }); + + Ok(()) + } + + fn build_request(&self) -> PopupRequest { + let mut builder = PopupRequest::builder(self.component.clone()) + .at(self.reference) + .size(self.size) + .mode(self.anchor) + .grab(self.grab); + + if let Some(ref close_cb) = self.close_callback { + builder = builder.close_on(close_cb.clone()); + } + + if let Some(ref resize_cb) = self.resize_callback { + builder = builder.resize_on(resize_cb.clone()); + } + + builder.build() + } +} diff --git a/crates/composition/src/system.rs b/crates/composition/src/system.rs index cfbc260..c93b9df 100644 --- a/crates/composition/src/system.rs +++ b/crates/composition/src/system.rs @@ -1,3 +1,5 @@ +use crate::popup_builder::PopupBuilder; +use crate::value_conversion::IntoValue; use crate::{Error, Result}; use layer_shika_adapters::errors::EventLoopError; use layer_shika_adapters::platform::calloop::{ @@ -14,10 +16,13 @@ use layer_shika_adapters::{ use layer_shika_domain::config::WindowConfig; use layer_shika_domain::entities::output_registry::OutputRegistry; use layer_shika_domain::errors::DomainError; +use layer_shika_domain::value_objects::dimensions::PopupDimensions; use layer_shika_domain::value_objects::output_handle::OutputHandle; use layer_shika_domain::value_objects::output_info::OutputInfo; -use layer_shika_domain::value_objects::dimensions::PopupDimensions; -use layer_shika_domain::value_objects::popup_request::{PopupHandle, PopupRequest, PopupSize}; +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::Cell; use std::cell::RefCell; use std::os::unix::io::AsFd; @@ -51,14 +56,39 @@ impl ShellControl { }) } + pub fn show_popup_at_cursor(&self, component: impl Into) -> Result<()> { + let request = PopupRequest::builder(component.into()) + .at(PopupAt::Cursor) + .build(); + self.show_popup(&request) + } + + pub fn show_popup_centered(&self, component: impl Into) -> Result<()> { + let request = PopupRequest::builder(component.into()) + .at(PopupAt::Cursor) + .mode(PopupPositioningMode::Center) + .build(); + self.show_popup(&request) + } + + pub fn show_popup_at_position( + &self, + component: impl Into, + x: f32, + y: f32, + ) -> Result<()> { + let request = PopupRequest::builder(component.into()) + .at(PopupAt::Absolute { x, y }) + .build(); + self.show_popup(&request) + } + pub fn close_popup(&self, handle: PopupHandle) -> Result<()> { - self.sender - .send(PopupCommand::Close(handle)) - .map_err(|_| { - Error::Domain(DomainError::Configuration { - message: "Failed to send popup close command: channel closed".to_string(), - }) + self.sender.send(PopupCommand::Close(handle)).map_err(|_| { + Error::Domain(DomainError::Configuration { + message: "Failed to send popup close command: channel closed".to_string(), }) + }) } pub fn resize_popup(&self, handle: PopupHandle, width: f32, height: f32) -> Result<()> { @@ -547,7 +577,11 @@ impl ShellContext<'_> { ); if control - .resize_popup(PopupHandle::new(popup_key), dimensions.width, dimensions.height) + .resize_popup( + PopupHandle::new(popup_key), + dimensions.width, + dimensions.height, + ) .is_err() { log::error!("Failed to resize popup through control"); @@ -659,7 +693,8 @@ impl App { match command { PopupCommand::Show(request) => { - if let Err(e) = shell_context.show_popup(&request, Some(control.clone())) + if let Err(e) = + shell_context.show_popup(&request, Some(control.clone())) { log::error!("Failed to show popup: {}", e); } @@ -674,9 +709,12 @@ impl App { width, height, } => { - if let Err(e) = - shell_context.resize_popup(handle, width, height, Some(control.clone())) - { + if let Err(e) = shell_context.resize_popup( + handle, + width, + height, + Some(control.clone()), + ) { log::error!("Failed to resize popup: {}", e); } } @@ -709,17 +747,18 @@ impl App { } } - pub fn on_callback(&self, callback_name: &str, handler: F) -> Result<()> + pub fn on(&self, callback_name: &str, handler: F) -> Result<()> where - F: Fn(&[Value], ShellControl) -> Value + 'static, + F: Fn(ShellControl) -> R + 'static, + R: IntoValue, { let control = self.control(); let handler = Rc::new(handler); self.with_all_component_instances(|instance| { let handler_rc = Rc::clone(&handler); let control_clone = control.clone(); - if let Err(e) = instance.set_callback(callback_name, move |args| { - handler_rc(args, control_clone.clone()) + if let Err(e) = instance.set_callback(callback_name, move |_args| { + handler_rc(control_clone.clone()).into_value() }) { log::error!( "Failed to register callback '{}' on component: {}", @@ -731,35 +770,34 @@ impl App { Ok(()) } - pub fn bind_popup(&self, callback_name: &str, config_builder: F) -> Result<()> + pub fn on_with_args(&self, callback_name: &str, handler: F) -> Result<()> where - F: Fn() -> PopupRequest + 'static, + F: Fn(&[Value], ShellControl) -> R + 'static, + R: IntoValue, { let control = self.control(); - let builder = Rc::new(config_builder); - + let handler = Rc::new(handler); self.with_all_component_instances(|instance| { - let builder_clone = Rc::clone(&builder); + let handler_rc = Rc::clone(&handler); let control_clone = control.clone(); - - if let Err(e) = instance.set_callback(callback_name, move |_args| { - let request = builder_clone(); - if let Err(e) = control_clone.show_popup(&request) { - log::error!("Failed to show popup: {}", e); - } - Value::Void + if let Err(e) = instance.set_callback(callback_name, move |args| { + handler_rc(args, control_clone.clone()).into_value() }) { log::error!( - "Failed to bind popup callback '{}': {}", + "Failed to register callback '{}' on component: {}", callback_name, e ); } }); - Ok(()) } + #[must_use] + pub fn popup(&self, component_name: impl Into) -> PopupBuilder<'_> { + PopupBuilder::new(self, component_name.into()) + } + pub fn run(&mut self) -> Result<()> { self.inner.borrow_mut().run()?; Ok(()) diff --git a/crates/composition/src/value_conversion.rs b/crates/composition/src/value_conversion.rs new file mode 100644 index 0000000..f3212dd --- /dev/null +++ b/crates/composition/src/value_conversion.rs @@ -0,0 +1,53 @@ +use layer_shika_adapters::platform::slint_interpreter::Value; + +pub trait IntoValue { + fn into_value(self) -> Value; +} + +impl IntoValue for () { + fn into_value(self) -> Value { + Value::Void + } +} + +impl IntoValue for Value { + fn into_value(self) -> Value { + self + } +} + +impl IntoValue for bool { + fn into_value(self) -> Value { + Value::Bool(self) + } +} + +impl IntoValue for i32 { + fn into_value(self) -> Value { + Value::Number(f64::from(self)) + } +} + +impl IntoValue for f32 { + fn into_value(self) -> Value { + Value::Number(f64::from(self)) + } +} + +impl IntoValue for f64 { + fn into_value(self) -> Value { + Value::Number(self) + } +} + +impl IntoValue for String { + fn into_value(self) -> Value { + Value::String(self.into()) + } +} + +impl IntoValue for &str { + fn into_value(self) -> Value { + Value::String(self.into()) + } +}