diff --git a/crates/adapters/src/wayland/config.rs b/crates/adapters/src/wayland/config.rs index a557c37..e34b741 100644 --- a/crates/adapters/src/wayland/config.rs +++ b/crates/adapters/src/wayland/config.rs @@ -1,5 +1,6 @@ use layer_shika_domain::config::SurfaceConfig as DomainSurfaceConfig; use layer_shika_domain::value_objects::anchor::AnchorEdges; +use layer_shika_domain::value_objects::handle::SurfaceHandle; use layer_shika_domain::value_objects::keyboard_interactivity::KeyboardInteractivity as DomainKeyboardInteractivity; use layer_shika_domain::value_objects::layer::Layer; use layer_shika_domain::value_objects::margins::Margins; @@ -23,6 +24,7 @@ pub(crate) struct LayerSurfaceConfig { #[derive(Clone)] pub struct WaylandSurfaceConfig { + pub surface_handle: SurfaceHandle, pub surface_name: String, pub height: u32, pub width: u32, @@ -41,12 +43,14 @@ pub struct WaylandSurfaceConfig { impl WaylandSurfaceConfig { #[must_use] pub fn from_domain_config( + surface_handle: SurfaceHandle, surface_name: impl Into, component_definition: ComponentDefinition, compilation_result: Option>, domain_config: DomainSurfaceConfig, ) -> Self { Self { + surface_handle, surface_name: surface_name.into(), height: domain_config.dimensions.height(), width: domain_config.dimensions.width(), diff --git a/crates/adapters/src/wayland/outputs/output_manager.rs b/crates/adapters/src/wayland/outputs/output_manager.rs index aa57354..cddec68 100644 --- a/crates/adapters/src/wayland/outputs/output_manager.rs +++ b/crates/adapters/src/wayland/outputs/output_manager.rs @@ -146,6 +146,7 @@ impl OutputManager { app_state.add_output( output_id, + self.config.surface_handle, &self.config.surface_name, main_surface_id, surface, diff --git a/crates/adapters/src/wayland/shell_adapter.rs b/crates/adapters/src/wayland/shell_adapter.rs index 32b8376..964c015 100644 --- a/crates/adapters/src/wayland/shell_adapter.rs +++ b/crates/adapters/src/wayland/shell_adapter.rs @@ -13,6 +13,7 @@ use crate::wayland::{ surface_state::SurfaceState, }, }; +use layer_shika_domain::value_objects::handle::SurfaceHandle; use smithay_client_toolkit::reexports::protocols_wlr::layer_shell::v1::client::zwlr_layer_surface_v1::ZwlrLayerSurfaceV1; use crate::{ errors::{EventLoopError, LayerShikaError, RenderingError, Result}, @@ -51,6 +52,7 @@ struct OutputSetup { main_surface_id: ObjectId, window: Rc, builder: SurfaceStateBuilder, + surface_handle: SurfaceHandle, shell_surface_name: String, } @@ -199,6 +201,7 @@ impl WaylandShellSystem { main_surface_id, window, builder, + surface_handle: config.surface_handle, shell_surface_name: config.surface_name.clone(), }); } @@ -254,6 +257,7 @@ impl WaylandShellSystem { app_state.add_shell_surface( &setup.output_id, + setup.surface_handle, &setup.shell_surface_name, setup.main_surface_id, per_output_surface, @@ -479,6 +483,7 @@ impl WaylandShellSystem { main_surface_id, window, builder, + surface_handle: shell_config.config.surface_handle, shell_surface_name: shell_config.name.clone(), }); } diff --git a/crates/adapters/src/wayland/surfaces/app_state.rs b/crates/adapters/src/wayland/surfaces/app_state.rs index 8e1b3cd..4adb9ef 100644 --- a/crates/adapters/src/wayland/surfaces/app_state.rs +++ b/crates/adapters/src/wayland/surfaces/app_state.rs @@ -3,6 +3,7 @@ use super::surface_state::SurfaceState; use crate::wayland::managed_proxies::ManagedWlPointer; use crate::wayland::outputs::{OutputManager, OutputMapping}; use layer_shika_domain::entities::output_registry::OutputRegistry; +use layer_shika_domain::value_objects::handle::SurfaceHandle; use layer_shika_domain::value_objects::output_handle::OutputHandle; use layer_shika_domain::value_objects::output_info::OutputInfo; use std::cell::RefCell; @@ -16,14 +17,14 @@ pub type PerOutputSurface = SurfaceState; #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ShellSurfaceKey { pub output_handle: OutputHandle, - pub surface_name: String, + pub surface_handle: SurfaceHandle, } impl ShellSurfaceKey { - pub fn new(output_handle: OutputHandle, surface_name: impl Into) -> Self { + pub const fn new(output_handle: OutputHandle, surface_handle: SurfaceHandle) -> Self { Self { output_handle, - surface_name: surface_name.into(), + surface_handle, } } } @@ -33,6 +34,7 @@ pub struct AppState { output_mapping: OutputMapping, surfaces: HashMap, surface_to_key: HashMap, + surface_handle_to_name: HashMap, _pointer: ManagedWlPointer, shared_pointer_serial: Rc, output_manager: Option>>, @@ -47,6 +49,7 @@ impl AppState { output_mapping: OutputMapping::new(), surfaces: HashMap::new(), surface_to_key: HashMap::new(), + surface_handle_to_name: HashMap::new(), _pointer: pointer, shared_pointer_serial: shared_serial, output_manager: None, @@ -78,6 +81,7 @@ impl AppState { pub fn add_shell_surface( &mut self, output_id: &ObjectId, + surface_handle: SurfaceHandle, surface_name: &str, main_surface_id: ObjectId, surface_state: PerOutputSurface, @@ -91,19 +95,28 @@ impl AppState { h }); - let key = ShellSurfaceKey::new(handle, surface_name); + let key = ShellSurfaceKey::new(handle, surface_handle); self.surface_to_key.insert(main_surface_id, key.clone()); self.surfaces.insert(key, surface_state); + self.surface_handle_to_name + .insert(surface_handle, surface_name.to_string()); } pub fn add_output( &mut self, output_id: &ObjectId, + surface_handle: SurfaceHandle, surface_name: &str, main_surface_id: ObjectId, surface_state: PerOutputSurface, ) { - self.add_shell_surface(output_id, surface_name, main_surface_id, surface_state); + self.add_shell_surface( + output_id, + surface_handle, + surface_name, + main_surface_id, + surface_state, + ); } pub fn remove_output(&mut self, handle: OutputHandle) -> Vec { @@ -139,21 +152,21 @@ impl AppState { self.surfaces.get_mut(key) } - pub fn get_surface_by_name( + pub fn get_surface_by_instance( &self, + surface_handle: SurfaceHandle, output_handle: OutputHandle, - shell_window_name: &str, ) -> Option<&PerOutputSurface> { - let key = ShellSurfaceKey::new(output_handle, shell_window_name); + let key = ShellSurfaceKey::new(output_handle, surface_handle); self.surfaces.get(&key) } - pub fn get_surface_by_name_mut( + pub fn get_surface_by_instance_mut( &mut self, + surface_handle: SurfaceHandle, output_handle: OutputHandle, - shell_window_name: &str, ) -> Option<&mut PerOutputSurface> { - let key = ShellSurfaceKey::new(output_handle, shell_window_name); + let key = ShellSurfaceKey::new(output_handle, surface_handle); self.surfaces.get_mut(&key) } @@ -213,6 +226,12 @@ impl AppState { .find(|surface| surface.layer_surface().as_ref().id() == *layer_surface_id) } + pub fn get_surface_name(&self, surface_handle: SurfaceHandle) -> Option<&str> { + self.surface_handle_to_name + .get(&surface_handle) + .map(String::as_str) + } + pub fn get_key_by_surface(&self, surface_id: &ObjectId) -> Option<&ShellSurfaceKey> { self.surface_to_key.get(surface_id) } @@ -280,11 +299,17 @@ impl AppState { pub fn surfaces_for_output( &self, handle: OutputHandle, - ) -> impl Iterator { + ) -> impl Iterator + '_ { self.surfaces .iter() .filter(move |(k, _)| k.output_handle == handle) - .map(|(k, v)| (k.surface_name.as_str(), v)) + .map(|(k, v)| { + let name = self + .surface_handle_to_name + .get(&k.surface_handle) + .map_or("unknown", String::as_str); + (name, v) + }) } pub fn surfaces_with_keys( @@ -360,30 +385,101 @@ impl AppState { pub fn shell_surface_names(&self) -> Vec<&str> { let mut names: Vec<_> = self - .surfaces - .keys() - .map(|k| k.surface_name.as_str()) + .surface_handle_to_name + .values() + .map(String::as_str) .collect(); names.sort_unstable(); names.dedup(); names } - pub fn surfaces_by_name(&self, surface_name: &str) -> impl Iterator { + pub fn surfaces_by_name(&self, surface_name: &str) -> Vec<&PerOutputSurface> { + let matching_handles: Vec = self + .surface_handle_to_name + .iter() + .filter(|(_, n)| n.as_str() == surface_name) + .map(|(h, _)| *h) + .collect(); + self.surfaces .iter() - .filter(move |(k, _)| k.surface_name == surface_name) + .filter(|(k, _)| matching_handles.contains(&k.surface_handle)) .map(|(_, v)| v) + .collect() } - pub fn surfaces_by_name_mut( - &mut self, - surface_name: &str, - ) -> impl Iterator { + pub fn surfaces_by_name_mut(&mut self, surface_name: &str) -> Vec<&mut PerOutputSurface> { + let matching_handles: Vec = self + .surface_handle_to_name + .iter() + .filter(|(_, n)| n.as_str() == surface_name) + .map(|(h, _)| *h) + .collect(); + self.surfaces .iter_mut() - .filter(move |(k, _)| k.surface_name == surface_name) + .filter(|(k, _)| matching_handles.contains(&k.surface_handle)) .map(|(_, v)| v) + .collect() + } + + pub fn surfaces_by_handle(&self, handle: SurfaceHandle) -> Vec<&PerOutputSurface> { + self.surfaces + .iter() + .filter(|(k, _)| k.surface_handle == handle) + .map(|(_, v)| v) + .collect() + } + + pub fn surfaces_by_handle_mut(&mut self, handle: SurfaceHandle) -> Vec<&mut PerOutputSurface> { + self.surfaces + .iter_mut() + .filter(|(k, _)| k.surface_handle == handle) + .map(|(_, v)| v) + .collect() + } + + pub fn surfaces_by_name_and_output( + &self, + name: &str, + output: OutputHandle, + ) -> Vec<&PerOutputSurface> { + let matching_handles: Vec = self + .surface_handle_to_name + .iter() + .filter(|(_, n)| n.as_str() == name) + .map(|(h, _)| *h) + .collect(); + + self.surfaces + .iter() + .filter(|(k, _)| { + k.output_handle == output && matching_handles.contains(&k.surface_handle) + }) + .map(|(_, v)| v) + .collect() + } + + pub fn surfaces_by_name_and_output_mut( + &mut self, + name: &str, + output: OutputHandle, + ) -> Vec<&mut PerOutputSurface> { + let matching_handles: Vec = self + .surface_handle_to_name + .iter() + .filter(|(_, n)| n.as_str() == name) + .map(|(h, _)| *h) + .collect(); + + self.surfaces + .iter_mut() + .filter(|(k, _)| { + k.output_handle == output && matching_handles.contains(&k.surface_handle) + }) + .map(|(_, v)| v) + .collect() } pub fn get_output_by_handle(&self, handle: OutputHandle) -> Option<&PerOutputSurface> { @@ -420,10 +516,17 @@ impl AppState { } pub fn remove_surfaces_by_name(&mut self, surface_name: &str) -> Vec { + let matching_handles: Vec = self + .surface_handle_to_name + .iter() + .filter(|(_, n)| n.as_str() == surface_name) + .map(|(h, _)| *h) + .collect(); + let keys_to_remove: Vec<_> = self .surfaces .keys() - .filter(|k| k.surface_name == surface_name) + .filter(|k| matching_handles.contains(&k.surface_handle)) .cloned() .collect(); @@ -435,7 +538,7 @@ impl AppState { } self.surface_to_key - .retain(|_, k| k.surface_name != surface_name); + .retain(|_, k| !matching_handles.contains(&k.surface_handle)); removed } diff --git a/crates/composition/src/lib.rs b/crates/composition/src/lib.rs index 58e7f07..e1f96d0 100644 --- a/crates/composition/src/lib.rs +++ b/crates/composition/src/lib.rs @@ -32,13 +32,15 @@ pub use layer_shika_domain::value_objects::popup_positioning_mode::PopupPosition pub use layer_shika_domain::value_objects::popup_request::{ PopupHandle, PopupPlacement, PopupRequest, PopupSize, }; +pub use layer_shika_domain::value_objects::surface_instance_id::SurfaceInstanceId; pub use layer_surface::{LayerSurfaceHandle, ShellSurfaceConfigHandler}; pub use popup_builder::PopupBuilder; pub use selection::Selection; pub use selector::{Output, Selector, Surface, SurfaceInfo}; pub use shell_runtime::{DEFAULT_SURFACE_NAME, ShellRuntime}; pub use system::{ - EventDispatchContext, RuntimeSurfaceConfigBuilder, ShellControl, SurfaceControlHandle, + CallbackContext, EventDispatchContext, RuntimeSurfaceConfigBuilder, ShellControl, + SurfaceControlHandle, SurfaceTarget, }; pub use value_conversion::IntoValue; diff --git a/crates/composition/src/selection.rs b/crates/composition/src/selection.rs index e255b1d..f430465 100644 --- a/crates/composition/src/selection.rs +++ b/crates/composition/src/selection.rs @@ -22,7 +22,7 @@ impl<'a> Selection<'a> { pub fn on_callback(&mut self, callback_name: &str, handler: F) -> &mut Self where - F: Fn(crate::ShellControl) -> R + Clone + 'static, + F: Fn(crate::CallbackContext) -> R + Clone + 'static, R: crate::IntoValue, { self.shell @@ -32,7 +32,7 @@ impl<'a> Selection<'a> { pub fn on_callback_with_args(&mut self, callback_name: &str, handler: F) -> &mut Self where - F: Fn(&[Value], crate::ShellControl) -> R + Clone + 'static, + F: Fn(&[Value], crate::CallbackContext) -> R + Clone + 'static, R: crate::IntoValue, { self.shell diff --git a/crates/composition/src/shell.rs b/crates/composition/src/shell.rs index 9d60fbf..f404f1c 100644 --- a/crates/composition/src/shell.rs +++ b/crates/composition/src/shell.rs @@ -5,7 +5,8 @@ use crate::shell_config::{CompiledUiSource, ShellConfig}; use crate::shell_runtime::ShellRuntime; use crate::surface_registry::{SurfaceDefinition, SurfaceEntry, SurfaceRegistry}; use crate::system::{ - EventDispatchContext, PopupCommand, ShellCommand, ShellControl, SurfaceCommand, + CallbackContext, EventDispatchContext, PopupCommand, ShellCommand, ShellControl, + SurfaceCommand, SurfaceTarget, }; use crate::value_conversion::IntoValue; use crate::{Error, Result}; @@ -26,6 +27,7 @@ use layer_shika_domain::prelude::{ use layer_shika_domain::value_objects::handle::SurfaceHandle; use layer_shika_domain::value_objects::output_handle::OutputHandle; use layer_shika_domain::value_objects::output_info::OutputInfo; +use layer_shika_domain::value_objects::surface_instance_id::SurfaceInstanceId; use spin_on::spin_on; use std::cell::RefCell; use std::path::{Path, PathBuf}; @@ -379,7 +381,7 @@ impl Shell { })?; Self::new_single_window(compilation_result, definition) } else { - Self::new_multi_window(compilation_result, definitions) + Self::new_multi_window(compilation_result, &definitions) } } @@ -398,7 +400,9 @@ impl Shell { }) })?; + let handle = SurfaceHandle::new(); let wayland_config = WaylandSurfaceConfig::from_domain_config( + handle, &definition.component, component_definition, Some(Rc::clone(&compilation_result)), @@ -411,7 +415,6 @@ impl Shell { let (sender, receiver) = channel::channel(); let mut registry = SurfaceRegistry::new(); - let handle = SurfaceHandle::new(); let entry = SurfaceEntry::new(handle, definition.component.clone(), definition); registry.insert(entry)?; @@ -433,9 +436,9 @@ impl Shell { fn new_multi_window( compilation_result: Rc, - definitions: Vec, + definitions: &[SurfaceDefinition], ) -> Result { - let shell_configs: Vec = definitions + let shell_configs_with_handles: Vec<(SurfaceHandle, ShellSurfaceConfig)> = definitions .iter() .map(|def| { let component_definition = compilation_result @@ -449,29 +452,39 @@ impl Shell { }) })?; + let handle = SurfaceHandle::new(); let wayland_config = WaylandSurfaceConfig::from_domain_config( + handle, &def.component, component_definition, Some(Rc::clone(&compilation_result)), def.config.clone(), ); - Ok(ShellSurfaceConfig { - name: def.component.clone(), - config: wayland_config, - }) + Ok(( + handle, + ShellSurfaceConfig { + name: def.component.clone(), + config: wayland_config, + }, + )) }) .collect::>>()?; + let shell_configs: Vec = shell_configs_with_handles + .iter() + .map(|(_, cfg)| cfg.clone()) + .collect(); + let inner = layer_shika_adapters::WaylandShellSystem::new_multi(&shell_configs)?; let inner_rc: Rc> = Rc::new(RefCell::new(inner)); let (sender, receiver) = channel::channel(); let mut registry = SurfaceRegistry::new(); - for definition in definitions { - let handle = SurfaceHandle::new(); - let entry = SurfaceEntry::new(handle, definition.component.clone(), definition); + for ((handle, _), definition) in shell_configs_with_handles.iter().zip(definitions.iter()) { + let entry = + SurfaceEntry::new(*handle, definition.component.clone(), definition.clone()); registry.insert(entry)?; } @@ -558,100 +571,152 @@ impl Shell { } } + fn resolve_surface_target<'a>( + ctx: &'a mut EventDispatchContext<'_>, + target: &SurfaceTarget, + ) -> Vec<&'a mut SurfaceState> { + match target { + SurfaceTarget::ByInstance(id) => { + if let Some(surface) = ctx.surface_by_instance_mut(id.surface(), id.output()) { + vec![surface] + } else { + log::warn!( + "Surface instance not found: handle {:?} on output {:?}", + id.surface(), + id.output() + ); + vec![] + } + } + SurfaceTarget::ByHandle(handle) => ctx.surfaces_by_handle_mut(*handle), + SurfaceTarget::ByName(name) => ctx.surfaces_by_name_mut(name), + SurfaceTarget::ByNameAndOutput { name, output } => { + ctx.surfaces_by_name_and_output_mut(name, *output) + } + } + } + + fn apply_surface_resize( + ctx: &mut EventDispatchContext<'_>, + target: &SurfaceTarget, + width: u32, + height: u32, + ) { + log::debug!( + "Surface command: Resize {:?} to {}x{}", + target, + width, + height + ); + for surface in Self::resolve_surface_target(ctx, target) { + let handle = LayerSurfaceHandle::from_window_state(surface); + handle.set_size(width, height); + handle.commit(); + surface.update_size_with_compositor_logic(width, height); + } + } + + fn apply_surface_config_change( + ctx: &mut EventDispatchContext<'_>, + target: &SurfaceTarget, + operation: &str, + apply: F, + ) where + F: Fn(&LayerSurfaceHandle<'_>), + { + log::debug!("Surface command: {} {:?}", operation, target); + for surface in Self::resolve_surface_target(ctx, target) { + let handle = LayerSurfaceHandle::from_window_state(surface); + apply(&handle); + handle.commit(); + } + } + + fn apply_full_config( + ctx: &mut EventDispatchContext<'_>, + target: &SurfaceTarget, + config: &SurfaceConfig, + ) { + log::debug!("Surface command: ApplyConfig {:?}", target); + for surface in Self::resolve_surface_target(ctx, target) { + let handle = LayerSurfaceHandle::from_window_state(surface); + + handle.set_size(config.dimensions.width(), config.dimensions.height()); + handle.set_anchor_edges(config.anchor); + handle.set_exclusive_zone(config.exclusive_zone); + handle.set_margins(config.margin); + handle.set_layer(config.layer); + handle.set_keyboard_interactivity(config.keyboard_interactivity); + handle.commit(); + + surface.update_size_with_compositor_logic( + config.dimensions.width(), + config.dimensions.height(), + ); + } + } + fn handle_surface_command(command: SurfaceCommand, ctx: &mut EventDispatchContext<'_>) { match command { SurfaceCommand::Resize { - name, + target, width, height, } => { - log::debug!("Surface command: Resize '{}' to {}x{}", name, width, height); - for surface in ctx.surfaces_by_name_mut(&name) { - let handle = LayerSurfaceHandle::from_window_state(surface); - handle.set_size(width, height); - handle.commit(); - - surface.update_size_with_compositor_logic(width, height); - } + Self::apply_surface_resize(ctx, &target, width, height); } - SurfaceCommand::SetAnchor { name, anchor } => { - log::debug!("Surface command: SetAnchor '{}' to {:?}", name, anchor); - for surface in ctx.surfaces_by_name(&name) { - let handle = LayerSurfaceHandle::from_window_state(surface); + SurfaceCommand::SetAnchor { target, anchor } => { + Self::apply_surface_config_change(ctx, &target, "SetAnchor", |handle| { handle.set_anchor_edges(anchor); - handle.commit(); - } + }); } - SurfaceCommand::SetExclusiveZone { name, zone } => { - log::debug!("Surface command: SetExclusiveZone '{}' to {}", name, zone); - for surface in ctx.surfaces_by_name(&name) { - let handle = LayerSurfaceHandle::from_window_state(surface); + SurfaceCommand::SetExclusiveZone { target, zone } => { + Self::apply_surface_config_change(ctx, &target, "SetExclusiveZone", |handle| { handle.set_exclusive_zone(zone); - handle.commit(); - } + }); } - SurfaceCommand::SetMargins { name, margins } => { - log::debug!("Surface command: SetMargins '{}' to {:?}", name, margins); - for surface in ctx.surfaces_by_name(&name) { - let handle = LayerSurfaceHandle::from_window_state(surface); + SurfaceCommand::SetMargins { target, margins } => { + Self::apply_surface_config_change(ctx, &target, "SetMargins", |handle| { handle.set_margins(margins); - handle.commit(); - } + }); } - SurfaceCommand::SetLayer { name, layer } => { - log::debug!("Surface command: SetLayer '{}' to {:?}", name, layer); - for surface in ctx.surfaces_by_name(&name) { - let handle = LayerSurfaceHandle::from_window_state(surface); + SurfaceCommand::SetLayer { target, layer } => { + Self::apply_surface_config_change(ctx, &target, "SetLayer", |handle| { handle.set_layer(layer); - handle.commit(); - } + }); } - SurfaceCommand::SetKeyboardInteractivity { name, mode } => { - log::debug!( - "Surface command: SetKeyboardInteractivity '{}' to {:?}", - name, - mode + SurfaceCommand::SetKeyboardInteractivity { target, mode } => { + Self::apply_surface_config_change( + ctx, + &target, + "SetKeyboardInteractivity", + |handle| { + handle.set_keyboard_interactivity(mode); + }, ); - for surface in ctx.surfaces_by_name(&name) { - let handle = LayerSurfaceHandle::from_window_state(surface); - handle.set_keyboard_interactivity(mode); - handle.commit(); - } } - SurfaceCommand::SetOutputPolicy { name, policy } => { + SurfaceCommand::SetOutputPolicy { target, policy } => { log::debug!( - "Surface command: SetOutputPolicy '{}' to {:?}", - name, + "Surface command: SetOutputPolicy {:?} to {:?}", + target, policy ); log::warn!( "SetOutputPolicy is not yet implemented - requires runtime surface spawning" ); } - SurfaceCommand::SetScaleFactor { name, factor } => { - log::debug!("Surface command: SetScaleFactor '{}' to {:?}", name, factor); + SurfaceCommand::SetScaleFactor { target, factor } => { + log::debug!( + "Surface command: SetScaleFactor {:?} to {:?}", + target, + factor + ); log::warn!( "SetScaleFactor is not yet implemented - requires runtime surface property updates" ); } - SurfaceCommand::ApplyConfig { name, config } => { - log::debug!("Surface command: ApplyConfig '{}'", name); - for surface in ctx.surfaces_by_name_mut(&name) { - let handle = LayerSurfaceHandle::from_window_state(surface); - - handle.set_size(config.dimensions.width(), config.dimensions.height()); - handle.set_anchor_edges(config.anchor); - handle.set_exclusive_zone(config.exclusive_zone); - handle.set_margins(config.margin); - handle.set_layer(config.layer); - handle.set_keyboard_interactivity(config.keyboard_interactivity); - handle.commit(); - - surface.update_size_with_compositor_logic( - config.dimensions.width(), - config.dimensions.height(), - ); - } + SurfaceCommand::ApplyConfig { target, config } => { + Self::apply_full_config(ctx, &target, &config); } } @@ -699,7 +764,9 @@ impl Shell { }) })?; + let handle = SurfaceHandle::new(); let wayland_config = WaylandSurfaceConfig::from_domain_config( + handle, &definition.component, component_definition, Some(Rc::clone(&self.compilation_result)), @@ -789,7 +856,7 @@ impl Shell { system .app_state() .surfaces_by_name(name) - .next() + .first() .map(|surface| f(surface.component_instance())) .ok_or_else(|| { Error::Domain(DomainError::Configuration { @@ -877,7 +944,7 @@ impl Shell { callback_name: &str, handler: F, ) where - F: Fn(ShellControl) -> R + Clone + 'static, + F: Fn(CallbackContext) -> R + Clone + 'static, R: IntoValue, { let control = self.control(); @@ -886,26 +953,44 @@ impl Shell { let (primary, active) = self.get_output_handles(); for (key, surface) in system.app_state().surfaces_with_keys() { + let surface_handle = key.surface_handle; + let output_handle = key.output_handle; + + let surface_name = self.registry.by_handle(surface_handle).map_or_else( + || format!("Unknown-{}", surface_handle.id()), + |entry| entry.name.clone(), + ); + let surface_info = crate::SurfaceInfo { - name: key.surface_name.clone(), - output: key.output_handle, + name: surface_name.clone(), + output: output_handle, }; - let output_info = system.app_state().get_output_info(key.output_handle); + let output_info = system.app_state().get_output_info(output_handle); if selector.matches(&surface_info, output_info, primary, active) { + let instance_id = SurfaceInstanceId::new(surface_handle, output_handle); + let handler_rc = Rc::clone(&handler); let control_clone = control.clone(); - if let Err(e) = surface - .component_instance() - .set_callback(callback_name, move |_args| { - handler_rc(control_clone.clone()).into_value() - }) + let surface_name_clone = surface_name.clone(); + + if let Err(e) = + surface + .component_instance() + .set_callback(callback_name, move |_args| { + let ctx = CallbackContext::new( + instance_id, + surface_name_clone.clone(), + control_clone.clone(), + ); + handler_rc(ctx).into_value() + }) { log::error!( "Failed to register callback '{}' on surface '{}': {}", callback_name, - key.surface_name, + surface_name, e ); } @@ -919,7 +1004,7 @@ impl Shell { callback_name: &str, handler: F, ) where - F: Fn(&[Value], ShellControl) -> R + Clone + 'static, + F: Fn(&[Value], CallbackContext) -> R + Clone + 'static, R: IntoValue, { let control = self.control(); @@ -928,26 +1013,44 @@ impl Shell { let (primary, active) = self.get_output_handles(); for (key, surface) in system.app_state().surfaces_with_keys() { + let surface_handle = key.surface_handle; + let output_handle = key.output_handle; + + let surface_name = self.registry.by_handle(surface_handle).map_or_else( + || format!("Unknown-{}", surface_handle.id()), + |entry| entry.name.clone(), + ); + let surface_info = crate::SurfaceInfo { - name: key.surface_name.clone(), - output: key.output_handle, + name: surface_name.clone(), + output: output_handle, }; - let output_info = system.app_state().get_output_info(key.output_handle); + let output_info = system.app_state().get_output_info(output_handle); if selector.matches(&surface_info, output_info, primary, active) { + let instance_id = SurfaceInstanceId::new(surface_handle, output_handle); + let handler_rc = Rc::clone(&handler); let control_clone = control.clone(); - if let Err(e) = surface - .component_instance() - .set_callback(callback_name, move |args| { - handler_rc(args, control_clone.clone()).into_value() - }) + let surface_name_clone = surface_name.clone(); + + if let Err(e) = + surface + .component_instance() + .set_callback(callback_name, move |args| { + let ctx = CallbackContext::new( + instance_id, + surface_name_clone.clone(), + control_clone.clone(), + ); + handler_rc(args, ctx).into_value() + }) { log::error!( "Failed to register callback '{}' on surface '{}': {}", callback_name, - key.surface_name, + surface_name, e ); } @@ -963,15 +1066,19 @@ impl Shell { let (primary, active) = self.get_output_handles(); for (key, surface) in system.app_state().surfaces_with_keys() { + let surface_name = system + .app_state() + .get_surface_name(key.surface_handle) + .unwrap_or("unknown"); let surface_info = crate::SurfaceInfo { - name: key.surface_name.clone(), + name: surface_name.to_string(), output: key.output_handle, }; let output_info = system.app_state().get_output_info(key.output_handle); if selector.matches(&surface_info, output_info, primary, active) { - f(&key.surface_name, surface.component_instance()); + f(surface_name, surface.component_instance()); } } } @@ -984,8 +1091,12 @@ impl Shell { let (primary, active) = self.get_output_handles(); for (key, surface) in system.app_state().surfaces_with_keys() { + let surface_name = system + .app_state() + .get_surface_name(key.surface_handle) + .unwrap_or("unknown"); let surface_info = crate::SurfaceInfo { - name: key.surface_name.clone(), + name: surface_name.to_string(), output: key.output_handle, }; @@ -1006,8 +1117,12 @@ impl Shell { .app_state() .surfaces_with_keys() .filter(|(key, _)| { + let surface_name = system + .app_state() + .get_surface_name(key.surface_handle) + .unwrap_or("unknown"); let surface_info = crate::SurfaceInfo { - name: key.surface_name.clone(), + name: surface_name.to_string(), output: key.output_handle, }; @@ -1026,8 +1141,12 @@ impl Shell { .app_state() .surfaces_with_keys() .filter_map(|(key, _)| { + let surface_name = system + .app_state() + .get_surface_name(key.surface_handle) + .unwrap_or("unknown"); let surface_info = crate::SurfaceInfo { - name: key.surface_name.clone(), + name: surface_name.to_string(), output: key.output_handle, }; @@ -1100,8 +1219,8 @@ impl ShellEventContext<'_> { pub fn get_surface_component(&self, name: &str) -> Option<&ComponentInstance> { self.app_state .surfaces_by_name(name) - .next() - .map(SurfaceState::component_instance) + .first() + .map(|s| s.component_instance()) } pub fn all_surface_components(&self) -> impl Iterator { diff --git a/crates/composition/src/surface_registry.rs b/crates/composition/src/surface_registry.rs index b752757..1dc814a 100644 --- a/crates/composition/src/surface_registry.rs +++ b/crates/composition/src/surface_registry.rs @@ -1,7 +1,6 @@ -use crate::{Error, Result}; +use crate::Result; use layer_shika_adapters::platform::slint_interpreter::ComponentInstance; use layer_shika_domain::config::SurfaceConfig; -use layer_shika_domain::errors::DomainError; use layer_shika_domain::value_objects::handle::SurfaceHandle; use layer_shika_domain::value_objects::output_handle::OutputHandle; use std::collections::HashMap; @@ -63,7 +62,7 @@ impl SurfaceEntry { pub struct SurfaceRegistry { entries: HashMap, - by_name: HashMap, + by_name: HashMap>, by_component: HashMap>, next_spawn_order: usize, } @@ -79,12 +78,6 @@ impl SurfaceRegistry { } pub fn insert(&mut self, mut entry: SurfaceEntry) -> Result<()> { - if self.by_name.contains_key(&entry.name) { - return Err(Error::Domain(DomainError::Configuration { - message: format!("Surface with name '{}' already exists", entry.name), - })); - } - entry.metadata.spawn_order = self.next_spawn_order; self.next_spawn_order += 1; @@ -92,7 +85,7 @@ impl SurfaceRegistry { let name = entry.name.clone(); let component = entry.component.clone(); - self.by_name.insert(name, handle); + self.by_name.entry(name).or_default().push(handle); self.by_component.entry(component).or_default().push(handle); @@ -104,7 +97,12 @@ impl SurfaceRegistry { pub fn remove(&mut self, handle: SurfaceHandle) -> Option { let entry = self.entries.remove(&handle)?; - self.by_name.remove(&entry.name); + if let Some(handles) = self.by_name.get_mut(&entry.name) { + handles.retain(|&h| h != handle); + if handles.is_empty() { + self.by_name.remove(&entry.name); + } + } if let Some(handles) = self.by_component.get_mut(&entry.component) { handles.retain(|&h| h != handle); @@ -120,20 +118,44 @@ impl SurfaceRegistry { self.entries.get(&handle) } + pub fn by_handle(&self, handle: SurfaceHandle) -> Option<&SurfaceEntry> { + self.entries.get(&handle) + } + pub fn get_mut(&mut self, handle: SurfaceHandle) -> Option<&mut SurfaceEntry> { self.entries.get_mut(&handle) } - pub fn by_name(&self, name: &str) -> Option<&SurfaceEntry> { - self.by_name.get(name).and_then(|h| self.entries.get(h)) + pub fn by_handle_mut(&mut self, handle: SurfaceHandle) -> Option<&mut SurfaceEntry> { + self.entries.get_mut(&handle) } - pub fn by_name_mut(&mut self, name: &str) -> Option<&mut SurfaceEntry> { - self.by_name.get(name).and_then(|h| self.entries.get_mut(h)) + pub fn by_name(&self, name: &str) -> Vec<&SurfaceEntry> { + self.by_name + .get(name) + .map(|handles| handles.iter().filter_map(|h| self.entries.get(h)).collect()) + .unwrap_or_default() + } + + pub fn by_name_mut(&mut self, name: &str) -> Vec<&mut SurfaceEntry> { + let handles: Vec = self.by_name.get(name).cloned().unwrap_or_default(); + + let entries_ptr = std::ptr::addr_of_mut!(self.entries); + + handles + .iter() + .filter_map(|h| unsafe { (*entries_ptr).get_mut(h) }) + .collect() } pub fn handle_by_name(&self, name: &str) -> Option { - self.by_name.get(name).copied() + self.by_name + .get(name) + .and_then(|handles| handles.first().copied()) + } + + pub fn handles_by_name(&self, name: &str) -> Vec { + self.by_name.get(name).cloned().unwrap_or_default() } pub fn name_by_handle(&self, handle: SurfaceHandle) -> Option<&str> { diff --git a/crates/composition/src/system.rs b/crates/composition/src/system.rs index f8cfa90..15d42fb 100644 --- a/crates/composition/src/system.rs +++ b/crates/composition/src/system.rs @@ -14,12 +14,14 @@ use layer_shika_domain::prelude::{ AnchorEdges, KeyboardInteractivity, Layer, Margins, OutputPolicy, ScaleFactor, }; use layer_shika_domain::value_objects::dimensions::{PopupDimensions, SurfaceDimension}; +use layer_shika_domain::value_objects::handle::SurfaceHandle; use layer_shika_domain::value_objects::output_handle::OutputHandle; use layer_shika_domain::value_objects::output_info::OutputInfo; use layer_shika_domain::value_objects::popup_positioning_mode::PopupPositioningMode; use layer_shika_domain::value_objects::popup_request::{ PopupHandle, PopupPlacement, PopupRequest, PopupSize, }; +use layer_shika_domain::value_objects::surface_instance_id::SurfaceInstanceId; use std::cell::Cell; use std::rc::Rc; @@ -33,42 +35,50 @@ pub enum PopupCommand { }, } +#[derive(Debug, Clone)] +pub enum SurfaceTarget { + ByInstance(SurfaceInstanceId), + ByHandle(SurfaceHandle), + ByName(String), + ByNameAndOutput { name: String, output: OutputHandle }, +} + pub enum SurfaceCommand { Resize { - name: String, + target: SurfaceTarget, width: u32, height: u32, }, SetAnchor { - name: String, + target: SurfaceTarget, anchor: AnchorEdges, }, SetExclusiveZone { - name: String, + target: SurfaceTarget, zone: i32, }, SetMargins { - name: String, + target: SurfaceTarget, margins: Margins, }, SetLayer { - name: String, + target: SurfaceTarget, layer: Layer, }, SetOutputPolicy { - name: String, + target: SurfaceTarget, policy: OutputPolicy, }, SetScaleFactor { - name: String, + target: SurfaceTarget, factor: ScaleFactor, }, SetKeyboardInteractivity { - name: String, + target: SurfaceTarget, mode: KeyboardInteractivity, }, ApplyConfig { - name: String, + target: SurfaceTarget, config: SurfaceConfig, }, } @@ -79,6 +89,63 @@ pub enum ShellCommand { Render, } +pub struct CallbackContext { + instance_id: SurfaceInstanceId, + surface_name: String, + control: ShellControl, +} + +impl CallbackContext { + pub fn new( + instance_id: SurfaceInstanceId, + surface_name: String, + control: ShellControl, + ) -> Self { + Self { + instance_id, + surface_name, + control, + } + } + + pub const fn instance_id(&self) -> &SurfaceInstanceId { + &self.instance_id + } + + pub const fn surface_handle(&self) -> SurfaceHandle { + self.instance_id.surface() + } + + pub const fn output_handle(&self) -> OutputHandle { + self.instance_id.output() + } + + pub fn surface_name(&self) -> &str { + &self.surface_name + } + + pub const fn control(&self) -> &ShellControl { + &self.control + } + + pub fn this_instance(&self) -> SurfaceControlHandle { + self.control.surface_instance(&self.instance_id) + } + + pub fn all_surface_instances(&self) -> SurfaceControlHandle { + self.control.surface_by_handle(self.surface_handle()) + } + + pub fn all_named(&self) -> SurfaceControlHandle { + self.control.surface_by_name(&self.surface_name) + } + + pub fn all_named_on_this_output(&self) -> SurfaceControlHandle { + self.control + .surface_by_name_and_output(&self.surface_name, self.output_handle()) + } +} + /// Handle for runtime control of shell operations /// /// Provides methods to manipulate surfaces, show popups, and request redraws. @@ -162,12 +229,44 @@ impl ShellControl { }) } - pub fn surface(&self, name: impl Into) -> SurfaceControlHandle { + pub fn surface_instance(&self, id: &SurfaceInstanceId) -> SurfaceControlHandle { SurfaceControlHandle { - name: name.into(), + target: SurfaceTarget::ByInstance(*id), sender: self.sender.clone(), } } + + pub fn surface_by_handle(&self, handle: SurfaceHandle) -> SurfaceControlHandle { + SurfaceControlHandle { + target: SurfaceTarget::ByHandle(handle), + sender: self.sender.clone(), + } + } + + pub fn surface_by_name(&self, name: impl Into) -> SurfaceControlHandle { + SurfaceControlHandle { + target: SurfaceTarget::ByName(name.into()), + sender: self.sender.clone(), + } + } + + pub fn surface_by_name_and_output( + &self, + name: impl Into, + output: OutputHandle, + ) -> SurfaceControlHandle { + SurfaceControlHandle { + target: SurfaceTarget::ByNameAndOutput { + name: name.into(), + output, + }, + sender: self.sender.clone(), + } + } + + pub fn surface(&self, name: impl Into) -> SurfaceControlHandle { + self.surface_by_name(name) + } } /// Handle for runtime control of a specific surface @@ -175,7 +274,7 @@ impl ShellControl { /// Allows modifying surface properties like size, anchor, layer, and margins at runtime. /// Obtained via `ShellControl::surface()`. pub struct SurfaceControlHandle { - name: String, + target: SurfaceTarget, sender: channel::Sender, } @@ -183,7 +282,7 @@ impl SurfaceControlHandle { pub fn resize(&self, width: u32, height: u32) -> Result<()> { self.sender .send(ShellCommand::Surface(SurfaceCommand::Resize { - name: self.name.clone(), + target: self.target.clone(), width, height, })) @@ -205,7 +304,7 @@ impl SurfaceControlHandle { pub fn set_anchor(&self, anchor: AnchorEdges) -> Result<()> { self.sender .send(ShellCommand::Surface(SurfaceCommand::SetAnchor { - name: self.name.clone(), + target: self.target.clone(), anchor, })) .map_err(|_| { @@ -219,7 +318,7 @@ impl SurfaceControlHandle { pub fn set_exclusive_zone(&self, zone: i32) -> Result<()> { self.sender .send(ShellCommand::Surface(SurfaceCommand::SetExclusiveZone { - name: self.name.clone(), + target: self.target.clone(), zone, })) .map_err(|_| { @@ -233,7 +332,7 @@ impl SurfaceControlHandle { pub fn set_margins(&self, margins: impl Into) -> Result<()> { self.sender .send(ShellCommand::Surface(SurfaceCommand::SetMargins { - name: self.name.clone(), + target: self.target.clone(), margins: margins.into(), })) .map_err(|_| { @@ -247,7 +346,7 @@ impl SurfaceControlHandle { pub fn set_layer(&self, layer: Layer) -> Result<()> { self.sender .send(ShellCommand::Surface(SurfaceCommand::SetLayer { - name: self.name.clone(), + target: self.target.clone(), layer, })) .map_err(|_| { @@ -260,7 +359,7 @@ impl SurfaceControlHandle { pub fn set_output_policy(&self, policy: OutputPolicy) -> Result<()> { self.sender .send(ShellCommand::Surface(SurfaceCommand::SetOutputPolicy { - name: self.name.clone(), + target: self.target.clone(), policy, })) .map_err(|_| { @@ -274,7 +373,7 @@ impl SurfaceControlHandle { pub fn set_scale_factor(&self, factor: ScaleFactor) -> Result<()> { self.sender .send(ShellCommand::Surface(SurfaceCommand::SetScaleFactor { - name: self.name.clone(), + target: self.target.clone(), factor, })) .map_err(|_| { @@ -289,7 +388,7 @@ impl SurfaceControlHandle { self.sender .send(ShellCommand::Surface( SurfaceCommand::SetKeyboardInteractivity { - name: self.name.clone(), + target: self.target.clone(), mode, }, )) @@ -305,7 +404,7 @@ impl SurfaceControlHandle { pub fn apply_config(&self, config: SurfaceConfig) -> Result<()> { self.sender .send(ShellCommand::Surface(SurfaceCommand::ApplyConfig { - name: self.name.clone(), + target: self.target.clone(), config, })) .map_err(|_| { @@ -428,15 +527,32 @@ fn extract_dimensions_from_callback(args: &[Value]) -> PopupDimensions { } impl EventDispatchContext<'_> { - pub(crate) fn surfaces_by_name(&self, name: &str) -> impl Iterator { - self.app_state.surfaces_by_name(name) + pub(crate) fn surface_by_instance_mut( + &mut self, + surface_handle: SurfaceHandle, + output_handle: OutputHandle, + ) -> Option<&mut SurfaceState> { + self.app_state + .get_surface_by_instance_mut(surface_handle, output_handle) } - pub(crate) fn surfaces_by_name_mut( + pub(crate) fn surfaces_by_handle_mut( + &mut self, + handle: SurfaceHandle, + ) -> Vec<&mut SurfaceState> { + self.app_state.surfaces_by_handle_mut(handle) + } + + pub(crate) fn surfaces_by_name_mut(&mut self, name: &str) -> Vec<&mut SurfaceState> { + self.app_state.surfaces_by_name_mut(name) + } + + pub(crate) fn surfaces_by_name_and_output_mut( &mut self, name: &str, - ) -> impl Iterator { - self.app_state.surfaces_by_name_mut(name) + output: OutputHandle, + ) -> Vec<&mut SurfaceState> { + self.app_state.surfaces_by_name_and_output_mut(name, output) } pub fn with_surface(&self, name: &str, f: F) -> Result @@ -466,8 +582,8 @@ impl EventDispatchContext<'_> { fn get_surface_component(&self, name: &str) -> Option<&ComponentInstance> { self.app_state .surfaces_by_name(name) - .next() - .map(SurfaceState::component_instance) + .first() + .map(|s| s.component_instance()) } #[must_use] @@ -895,15 +1011,12 @@ impl EventDispatchContext<'_> { where F: FnOnce(&ComponentInstance, LayerSurfaceHandle<'_>), { - let surface = self - .app_state - .surfaces_by_name(name) - .next() - .ok_or_else(|| { - Error::Domain(DomainError::Configuration { - message: format!("Surface '{}' not found", name), - }) - })?; + let surfaces = self.app_state.surfaces_by_name(name); + let surface = surfaces.first().ok_or_else(|| { + Error::Domain(DomainError::Configuration { + message: format!("Surface '{}' not found", name), + }) + })?; let handle = LayerSurfaceHandle::from_window_state(surface); let component = surface.component_instance(); diff --git a/crates/domain/src/value_objects/mod.rs b/crates/domain/src/value_objects/mod.rs index 8156499..7fcb45e 100644 --- a/crates/domain/src/value_objects/mod.rs +++ b/crates/domain/src/value_objects/mod.rs @@ -11,4 +11,5 @@ pub mod output_policy; pub mod popup_config; pub mod popup_positioning_mode; pub mod popup_request; +pub mod surface_instance_id; pub mod ui_source; diff --git a/crates/domain/src/value_objects/surface_instance_id.rs b/crates/domain/src/value_objects/surface_instance_id.rs new file mode 100644 index 0000000..92c4b3b --- /dev/null +++ b/crates/domain/src/value_objects/surface_instance_id.rs @@ -0,0 +1,24 @@ +use crate::value_objects::handle::{Handle, Surface}; +use crate::value_objects::output_handle::OutputHandle; + +pub type SurfaceHandle = Handle; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct SurfaceInstanceId { + surface: SurfaceHandle, + output: OutputHandle, +} + +impl SurfaceInstanceId { + pub const fn new(surface: SurfaceHandle, output: OutputHandle) -> Self { + Self { surface, output } + } + + pub const fn surface(self) -> SurfaceHandle { + self.surface + } + + pub const fn output(self) -> OutputHandle { + self.output + } +} diff --git a/examples/runtime-surface-config/README.md b/examples/runtime-surface-config/README.md index 22b6ceb..dd13351 100644 --- a/examples/runtime-surface-config/README.md +++ b/examples/runtime-surface-config/README.md @@ -1,20 +1,20 @@ # Runtime Surface Config Example -This example demonstrates layer-shika's runtime surface configuration capabilities using the Surface Control API. +This example demonstrates layer-shika's runtime surface configuration capabilities using the instance-based Surface Control API. ## Features Demonstrated 1. **Dynamic Sizing**: Toggle between compact (32px) and expanded (64px) bar heights 2. **Anchor Position Control**: Switch between top and bottom screen edges at runtime 3. **Layer Management**: Cycle through Background, Bottom, Top, and Overlay layers -4. **Channel-based UI Updates**: Use event loop channels to update UI state from callbacks -5. **Surface Control API**: Manipulate surfaces via callback handlers +4. **Instance-based Targeting**: Use `CallbackContext` to control the specific surface that invoked the callback +5. **Slint State Management**: State lives in Slint component properties, Rust callbacks apply Wayland configuration ## Controls - **Expand/Collapse Button**: Toggle between 32px and 64px bar heights - **Switch Anchor**: Toggle between top and bottom screen positions -- **Switch Layer**: Cycle through Background → Bottom → Top → Overlay layers +- **Switch Layer**: Cycle through Top → Overlay → Bottom → Background layers ## Running the Example @@ -24,50 +24,61 @@ cargo run -p runtime-surface-config ## Implementation Highlights -### Control from Slint Callbacks +### Slint-managed State -```rust -shell.on("Bar", "toggle-size", move |control| { - control.surface("Bar") - .configure() - .size(0, new_size) - .exclusive_zone(new_size as i32) - .margins(0, 0, 0, 0) - .apply()?; +The Slint component owns all state via properties: - Value::Struct(Struct::from_iter([("expanded".into(), is_expanded.into())])) -})?; -``` +```slint +export component Bar inherits Window { + in-out property is-expanded: false; + in-out property current-anchor: "Top"; + in-out property current-layer: "Top"; -### Channel-based UI Updates + callback toggle-size(bool); + callback switch-anchor(string); + callback switch-layer(string); -```rust -let (_token, sender) = handle.add_channel(|message: UiUpdate, app_state| { - for surface in app_state.all_outputs() { - let component = surface.component_instance(); - match &message { - UiUpdate::IsExpanded(is_expanded) => { - component.set_property("is-expanded", (*is_expanded).into())?; - } - // ... other updates + Button { + text: is-expanded ? "Collapse" : "Expand"; + clicked => { + root.is-expanded = !root.is-expanded; + toggle-size(root.is-expanded); } } -})?; +} +``` + +### Instance-based Control from Callbacks + +Callbacks receive `CallbackContext` and use `this_instance()` to target only the specific surface: + +```rust +shell.select(Surface::named("Bar")) + .on_callback_with_args("toggle-size", move |args, control| { + let is_expanded = args.first() + .and_then(|v| v.clone().try_into().ok()) + .unwrap_or(false); + + let new_size = if is_expanded { 64 } else { 32 }; + + control.this_instance() + .configure() + .size(0, new_size) + .exclusive_zone(new_size.try_into().unwrap_or(32)) + .apply()?; + + }); ``` ## API Patterns -This example showcases the Surface Control API pattern: +- Send configuration commands via `.configure()` builder or individual methods +- Commands execute asynchronously in the event loop +- Multiple surfaces with the same name can be controlled independently -**SurfaceControlHandle** (channel-based): +**State Management**: -- Accessible via `control.surface(name)` in callback handlers -- Safe to call from Slint callbacks -- Commands execute asynchronously in event loop - -**Event Loop Channels**: - -- Use `add_channel` to create message handlers -- Send messages from callbacks to update UI state -- Process messages in event loop context -- Access all surfaces via `app_state.all_outputs()` +- Slint component properties hold all UI state +- Slint logic computes next states (e.g., toggling, cycling) +- Rust callbacks receive new values and apply Wayland configuration +- No Rust-side state synchronization needed diff --git a/examples/runtime-surface-config/src/main.rs b/examples/runtime-surface-config/src/main.rs index 6f5202d..4bf5795 100644 --- a/examples/runtime-surface-config/src/main.rs +++ b/examples/runtime-surface-config/src/main.rs @@ -1,201 +1,115 @@ use layer_shika::calloop::channel::Sender; use layer_shika::prelude::*; use layer_shika::slint::SharedString; -use layer_shika::slint_interpreter::{Struct, Value}; -use std::cell::RefCell; +use layer_shika::slint_interpreter::Value; use std::path::PathBuf; use std::rc::Rc; #[derive(Debug)] enum UiUpdate { - IsExpanded(bool), - CurrentAnchor(String), - CurrentLayer(String), -} -enum AnchorPosition { - Top, - Bottom, + ToggleSize, + SwitchAnchor, + SwitchLayer, } -struct BarState { - is_expanded: bool, - current_anchor: AnchorPosition, - current_layer: Layer, -} - -impl BarState { - fn new() -> Self { - Self { - is_expanded: false, - current_anchor: AnchorPosition::Top, - current_layer: Layer::Top, - } - } - - fn anchor_name(&self) -> &'static str { - match self.current_anchor { - AnchorPosition::Top => "Top", - AnchorPosition::Bottom => "Bottom", - } - } - - fn next_anchor(&mut self) { - self.current_anchor = match self.current_anchor { - AnchorPosition::Top => AnchorPosition::Bottom, - AnchorPosition::Bottom => AnchorPosition::Top, - }; - } - - fn get_anchor_edges(&self) -> AnchorEdges { - match self.current_anchor { - AnchorPosition::Top => AnchorEdges::top_bar(), - AnchorPosition::Bottom => AnchorEdges::bottom_bar(), - } - } - - fn layer_name(&self) -> &'static str { - match self.current_layer { - Layer::Background => "Background", - Layer::Bottom => "Bottom", - Layer::Top => "Top", - Layer::Overlay => "Overlay", - } - } - - fn next_layer(&mut self) -> Layer { - self.current_layer = match self.current_layer { - Layer::Background => Layer::Bottom, - Layer::Bottom => Layer::Top, - Layer::Top => Layer::Overlay, - Layer::Overlay => Layer::Background, - }; - self.current_layer - } -} - -fn setup_toggle_size_callback( - sender: &Rc>, - shell: &Shell, - state: &Rc>, -) { - let state_clone = Rc::clone(state); +fn setup_toggle_size_callback(sender: &Rc>, shell: &Shell) { let sender_clone = Rc::clone(sender); - shell - .select(Surface::named("Bar")) - .on_callback("toggle-size", move |control| { - let is_expanded = { - let mut st = state_clone.borrow_mut(); - st.is_expanded = !st.is_expanded; + shell.select(Surface::named("Bar")).on_callback_with_args( + "toggle-size", + move |args, control| { + let is_expanded = args + .first() + .and_then(|v| v.clone().try_into().ok()) + .unwrap_or(false); - let new_size = if st.is_expanded { 64 } else { 32 }; + let new_size = if is_expanded { 64 } else { 32 }; + let (width, height) = (0, new_size); - let (width, height) = match st.current_anchor { - AnchorPosition::Top | AnchorPosition::Bottom => { - log::info!("Resizing horizontal bar to {}px", new_size); - (0, new_size) - } - }; + log::info!( + "Toggling bar size to {}px (expanded: {})", + new_size, + is_expanded + ); - if let Err(e) = control - .surface("Bar") - .configure() - .size(width, height) - .exclusive_zone(new_size.try_into().unwrap_or(32)) - .margins((0, 0, 0, 0)) - .apply() - { - log::error!("Failed to apply configuration: {}", e); - } - - log::info!( - "Updated bar state: size={}, is_expanded={}", - new_size, - st.is_expanded - ); - - st.is_expanded - }; - - if let Err(e) = sender_clone.send(UiUpdate::IsExpanded(is_expanded)) { - log::error!("Failed to send UI update: {}", e); + if let Err(e) = control + .this_instance() + .configure() + .size(width, height) + .exclusive_zone(new_size.try_into().unwrap_or(32)) + .apply() + { + log::error!("Failed to apply configuration: {}", e); } - Value::Struct(Struct::from_iter([("expanded".into(), is_expanded.into())])) - }); -} - -fn setup_anchor_switch_callback( - sender: &Rc>, - shell: &Shell, - state: &Rc>, -) { - let state_clone = Rc::clone(state); - let sender_clone = Rc::clone(sender); - shell - .select(Surface::named("Bar")) - .on_callback("switch-anchor", move |control| { - let anchor_name = { - let mut st = state_clone.borrow_mut(); - st.next_anchor(); - - log::info!("Switching to anchor: {}", st.anchor_name()); - - let bar = control.surface("Bar"); - if let Err(e) = bar.set_anchor(st.get_anchor_edges()) { - log::error!("Failed to apply config: {}", e); - } - - st.anchor_name() - }; - - if let Err(e) = sender_clone.send(UiUpdate::CurrentAnchor(anchor_name.to_string())) { + if let Err(e) = sender_clone.send(UiUpdate::ToggleSize) { log::error!("Failed to send UI update: {}", e); } - - log::info!("Switched to {} anchor", anchor_name); - - Value::Struct(Struct::from_iter([( - "anchor".into(), - SharedString::from(anchor_name).into(), - )])) - }); + }, + ); } -fn setup_layer_switch_callback( - sender: &Rc>, - shell: &Shell, - state: &Rc>, -) { - let state_clone = Rc::clone(state); +fn setup_anchor_switch_callback(sender: &Rc>, shell: &Shell) { let sender_clone = Rc::clone(sender); - shell - .select(Surface::named("Bar")) - .on_callback("switch-layer", move |control| { - let layer_name = { - let mut st = state_clone.borrow_mut(); - let new_layer = st.next_layer(); + shell.select(Surface::named("Bar")).on_callback_with_args( + "switch-anchor", + move |args, control| { + let new_anchor = args + .first() + .and_then(|v| match v { + Value::String(s) => Some(s.as_str()), + _ => None, + }) + .unwrap_or("Top"); - log::info!("Switching to layer: {:?}", new_layer); - - let bar = control.surface("Bar"); - if let Err(e) = bar.set_layer(new_layer) { - log::error!("Failed to set layer: {}", e); - } - - st.layer_name() + let anchor_edges = match new_anchor { + "Bottom" => AnchorEdges::bottom_bar(), + _ => AnchorEdges::top_bar(), }; - if let Err(e) = sender_clone.send(UiUpdate::CurrentLayer(layer_name.to_string())) { - log::error!("Failed to send UI update: {}", e); + log::info!("Switching anchor to: {}", new_anchor); + + if let Err(e) = control.this_instance().set_anchor(anchor_edges) { + log::error!("Failed to apply anchor config: {}", e); } - log::info!("Switched to {} layer", layer_name); + if let Err(e) = sender_clone.send(UiUpdate::SwitchAnchor) { + log::error!("Failed to send UI update: {}", e); + } + }, + ); +} - Value::Struct(Struct::from_iter([( - "layer".into(), - SharedString::from(layer_name).into(), - )])) - }); +fn setup_layer_switch_callback(sender: &Rc>, shell: &Shell) { + let sender_clone = Rc::clone(sender); + shell.select(Surface::named("Bar")).on_callback_with_args( + "switch-layer", + move |args, control| { + let new_layer_str = args + .first() + .and_then(|v| match v { + Value::String(s) => Some(s.as_str()), + _ => None, + }) + .unwrap_or("Top"); + + let new_layer = match new_layer_str { + "Background" => Layer::Background, + "Bottom" => Layer::Bottom, + "Overlay" => Layer::Overlay, + _ => Layer::Top, + }; + + log::info!("Switching layer to: {:?}", new_layer); + + if let Err(e) = control.this_instance().set_layer(new_layer) { + log::error!("Failed to set layer: {}", e); + } + + if let Err(e) = sender_clone.send(UiUpdate::SwitchLayer) { + log::error!("Failed to send UI update: {}", e); + } + }, + ); } fn main() -> Result<()> { @@ -208,8 +122,6 @@ fn main() -> Result<()> { let ui_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("ui/bar.slint"); - let state = Rc::new(RefCell::new(BarState::new())); - let mut shell = Shell::from_file(ui_path) .surface("Bar") .height(32) @@ -218,65 +130,34 @@ fn main() -> Result<()> { .namespace("runtime-control-example") .build()?; - shell.select(Surface::all()).with_component(|component| { - log::info!("Initializing properties for Bar surface"); - let state_ref = state.borrow(); + shell + .select(Surface::named("Bar")) + .with_component(|component| { + log::info!("Initializing properties for Bar surface"); - let set_property = |name: &str, value: Value| { - if let Err(e) = component.set_property(name, value) { - log::error!("Failed to set initial {}: {}", name, e); - } - }; + let set_property = |name: &str, value: Value| { + if let Err(e) = component.set_property(name, value) { + log::error!("Failed to set initial {}: {}", name, e); + } + }; - set_property("is-expanded", state_ref.is_expanded.into()); - set_property( - "current-anchor", - SharedString::from(state_ref.anchor_name()).into(), - ); - set_property( - "current-layer", - SharedString::from(state_ref.layer_name()).into(), - ); + set_property("is-expanded", false.into()); + set_property("current-anchor", SharedString::from("Top").into()); + set_property("current-layer", SharedString::from("Top").into()); - log::info!("Initialized properties for Bar surface"); - }); + log::info!("Initialized properties for Bar surface"); + }); let handle = shell.event_loop_handle(); - let (_token, sender) = handle.add_channel(|message: UiUpdate, app_state| { + let (_token, sender) = handle.add_channel(|message: UiUpdate, _app_state| { log::info!("Received UI update: {:?}", message); - - for surface in app_state.all_outputs() { - let component = surface.component_instance(); - - match &message { - UiUpdate::IsExpanded(is_expanded) => { - if let Err(e) = component.set_property("is-expanded", (*is_expanded).into()) { - log::error!("Failed to set is-expanded: {}", e); - } - } - UiUpdate::CurrentAnchor(anchor) => { - if let Err(e) = component - .set_property("current-anchor", SharedString::from(anchor.as_str()).into()) - { - log::error!("Failed to set current-anchor: {}", e); - } - } - UiUpdate::CurrentLayer(layer) => { - if let Err(e) = component - .set_property("current-layer", SharedString::from(layer.as_str()).into()) - { - log::error!("Failed to set current-layer: {}", e); - } - } - } - } })?; let sender_rc = Rc::new(sender); - setup_toggle_size_callback(&sender_rc, &shell, &state); - setup_anchor_switch_callback(&sender_rc, &shell, &state); - setup_layer_switch_callback(&sender_rc, &shell, &state); + setup_toggle_size_callback(&sender_rc, &shell); + setup_anchor_switch_callback(&sender_rc, &shell); + setup_layer_switch_callback(&sender_rc, &shell); shell.run()?; Ok(()) diff --git a/examples/runtime-surface-config/ui/bar.slint b/examples/runtime-surface-config/ui/bar.slint index 24771fa..870da44 100644 --- a/examples/runtime-surface-config/ui/bar.slint +++ b/examples/runtime-surface-config/ui/bar.slint @@ -6,9 +6,29 @@ export component Bar inherits Window { in-out property current-anchor: "Top"; in-out property current-layer: "Top"; - callback toggle-size() -> {expanded: bool}; - callback switch-anchor() -> {anchor: string}; - callback switch-layer() -> {layer: string}; + callback toggle-size(bool); + callback switch-anchor(string); + callback switch-layer(string); + + function next-anchor(current: string) -> string { + if (current == "Top") { + return "Bottom"; + } else { + return "Top"; + } + } + + function next-layer(current: string) -> string { + if (current == "Top") { + return "Overlay"; + } else if (current == "Overlay") { + return "Bottom"; + } else if (current == "Bottom") { + return "Background"; + } else { + return "Top"; + } + } HorizontalLayout { spacing: 12px; @@ -37,21 +57,24 @@ export component Bar inherits Window { Button { text: is-expanded ? "Collapse" : "Expand"; clicked => { - root.is-expanded = toggle-size().expanded; + root.is-expanded = !root.is-expanded; + toggle-size(root.is-expanded); } } Button { text: "Switch Anchor"; clicked => { - root.current-anchor = switch-anchor().anchor; + root.current-anchor = next-anchor(root.current-anchor); + switch-anchor(root.current-anchor); } } Button { text: "Switch Layer"; clicked => { - root.current-layer = switch-layer().layer; + root.current-layer = next-layer(root.current-layer); + switch-layer(root.current-layer); } } } diff --git a/src/lib.rs b/src/lib.rs index 7a93dd6..4584719 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -131,7 +131,7 @@ pub mod shell; pub mod slint_integration; pub mod window; -pub use layer_shika_composition::{Error, Handle, Result, SurfaceHandle}; +pub use layer_shika_composition::{CallbackContext, Error, Handle, Result, SurfaceHandle, SurfaceInstanceId, SurfaceTarget}; pub use shell::{ CompiledUiSource, DEFAULT_COMPONENT_NAME, DEFAULT_SURFACE_NAME, LayerSurfaceHandle, Output, diff --git a/src/prelude.rs b/src/prelude.rs index 3656de8..da4dfa3 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -26,7 +26,7 @@ pub use crate::event::{EventDispatchContext, EventLoopHandle, ShellEventLoop}; pub use crate::slint_integration::{PopupWindow, slint, slint_interpreter}; -pub use crate::{Error, Handle, Result, SurfaceHandle}; +pub use crate::{CallbackContext, Error, Handle, Result, SurfaceHandle, SurfaceInstanceId, SurfaceTarget}; pub use layer_shika_composition::prelude::{ Anchor, LogicalSize, Margins, PhysicalSize, ScaleFactor, SurfaceConfig, SurfaceDimension,