From aa5052b56677c6e85c39235c86059735a63011d6 Mon Sep 17 00:00:00 2001 From: drendog Date: Mon, 3 Nov 2025 15:06:24 +0100 Subject: [PATCH] feat: add popup abstraction --- Cargo.lock | 1 + composition/Cargo.toml | 1 + composition/src/system.rs | 181 +++++++++++++++++++++++++++++++++++++- 3 files changed, 180 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5cf1a42..bc7435d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1857,6 +1857,7 @@ version = "0.1.0" dependencies = [ "layer-shika-adapters", "layer-shika-domain", + "log", "spin_on", "thiserror 2.0.17", ] diff --git a/composition/Cargo.toml b/composition/Cargo.toml index e822126..5c38bd6 100644 --- a/composition/Cargo.toml +++ b/composition/Cargo.toml @@ -16,5 +16,6 @@ workspace = true [dependencies] layer-shika-adapters.workspace = true layer-shika-domain.workspace = true +log.workspace = true spin_on.workspace = true thiserror.workspace = true diff --git a/composition/src/system.rs b/composition/src/system.rs index cb5d7ef..238aa89 100644 --- a/composition/src/system.rs +++ b/composition/src/system.rs @@ -4,14 +4,18 @@ use layer_shika_adapters::platform::calloop::{ EventSource, Generic, Interest, Mode, PostAction, RegistrationToken, TimeoutAction, Timer, channel, }; +use layer_shika_adapters::platform::slint::ComponentHandle; use layer_shika_adapters::platform::slint_interpreter::{ - CompilationResult, ComponentDefinition, ComponentInstance, + CompilationResult, ComponentDefinition, ComponentInstance, Value, }; use layer_shika_adapters::wayland::{ config::WaylandWindowConfig, shell_adapter::WaylandWindowingSystem, surfaces::surface_state::WindowState, }; +use layer_shika_adapters::{clear_popup_config, close_current_popup, set_popup_config}; use layer_shika_domain::config::WindowConfig; +use layer_shika_domain::errors::DomainError; +use layer_shika_domain::value_objects::popup_positioning_mode::PopupPositioningMode; use std::cell::{Ref, RefCell}; use std::os::unix::io::AsFd; use std::rc::{Rc, Weak}; @@ -124,10 +128,97 @@ impl RuntimeState<'_> { pub fn compilation_result(&self) -> Option> { self.window_state.compilation_result() } + + pub fn show_popup_component( + &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 + ), + }) + })?; + + close_current_popup(); + + let temp_instance = definition.create().map_err(|e| { + Error::Domain(DomainError::Configuration { + message: format!("Failed to create temporary popup instance: {}", e), + }) + })?; + temp_instance.hide().map_err(|e| { + Error::Domain(DomainError::Configuration { + message: format!("Failed to hide temporary popup instance: {}", e), + }) + })?; + + let width: f32 = temp_instance + .get_property("popup-width") + .ok() + .and_then(|v| v.try_into().ok()) + .or(size.map(|(w, _)| w)) + .unwrap_or(300.0); + + let height: f32 = temp_instance + .get_property("popup-height") + .ok() + .and_then(|v| v.try_into().ok()) + .or(size.map(|(_, h)| h)) + .unwrap_or(400.0); + + drop(temp_instance); + close_current_popup(); + + if let Some((reference_x, reference_y)) = position { + set_popup_config(reference_x, reference_y, width, height, positioning_mode); + } else { + clear_popup_config(); + } + + let instance = definition.create().map_err(|e| { + Error::Domain(DomainError::Configuration { + message: format!("Failed to create popup instance: {}", e), + }) + })?; + + instance + .set_callback("closed", move |_| { + close_current_popup(); + Value::Void + }) + .map_err(|e| { + Error::Domain(DomainError::Configuration { + message: format!("Failed to set popup closed callback: {}", e), + }) + })?; + + instance.show().map_err(|e| { + Error::Domain(DomainError::Configuration { + message: format!("Failed to show popup instance: {}", e), + }) + })?; + + Ok(()) + } } pub struct WindowingSystem { inner: Rc>, + popup_positioning_mode: Rc>, } impl WindowingSystem { @@ -143,9 +234,93 @@ impl WindowingSystem { ); let inner = WaylandWindowingSystem::new(wayland_config)?; - Ok(Self { + let system = Self { inner: Rc::new(RefCell::new(inner)), - }) + popup_positioning_mode: Rc::new(RefCell::new(PopupPositioningMode::Center)), + }; + + system.register_popup_callbacks()?; + + Ok(system) + } + + fn register_popup_callbacks(&self) -> Result<()> { + let component_instance = self.component_instance(); + + let popup_mode_clone = Rc::clone(&self.popup_positioning_mode); + component_instance + .set_callback("set_popup_positioning_mode", move |args| { + let center_x: bool = args + .first() + .and_then(|v| v.clone().try_into().ok()) + .unwrap_or(false); + let center_y: bool = args + .get(1) + .and_then(|v| v.clone().try_into().ok()) + .unwrap_or(false); + + let mode = PopupPositioningMode::from_flags(center_x, center_y); + *popup_mode_clone.borrow_mut() = mode; + Value::Void + }) + .map_err(|e| { + Error::Domain(DomainError::Configuration { + message: format!( + "Failed to register set_popup_positioning_mode callback: {}", + e + ), + }) + })?; + + let event_loop_handle = self.event_loop_handle(); + let popup_mode_for_channel = Rc::clone(&self.popup_positioning_mode); + + let (_token, sender) = event_loop_handle.add_channel( + move |(component_name, x, y): (String, f32, f32), state| { + let mode = *popup_mode_for_channel.borrow(); + if let Err(e) = + state.show_popup_component(&component_name, Some((x, y)), None, mode) + { + log::error!("Failed to show popup: {}", e); + } + }, + )?; + + component_instance + .set_callback("show_popup", move |args| { + use layer_shika_adapters::platform::slint::SharedString; + + let component_name: SharedString = args + .first() + .and_then(|v| v.clone().try_into().ok()) + .unwrap_or_else(|| SharedString::from("")); + + if component_name.is_empty() { + log::error!("show_popup called without component name"); + return Value::Void; + } + + let x: f32 = args + .get(1) + .and_then(|v| v.clone().try_into().ok()) + .unwrap_or(0.0); + let y: f32 = args + .get(2) + .and_then(|v| v.clone().try_into().ok()) + .unwrap_or(0.0); + + if sender.send((component_name.to_string(), x, y)).is_err() { + log::error!("Failed to send popup request through channel"); + } + Value::Void + }) + .map_err(|e| { + Error::Domain(DomainError::Configuration { + message: format!("Failed to register show_popup callback: {}", e), + }) + })?; + + Ok(()) } #[must_use]