refactor: surface identifier refactor

This commit is contained in:
drendog 2025-12-12 05:47:45 +01:00
parent 2c484ef392
commit 71089fc7af
Signed by: dwenya
GPG key ID: 8DD77074645332D0
16 changed files with 765 additions and 456 deletions

View file

@ -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<String>,
component_definition: ComponentDefinition,
compilation_result: Option<Rc<CompilationResult>>,
domain_config: DomainSurfaceConfig,
) -> Self {
Self {
surface_handle,
surface_name: surface_name.into(),
height: domain_config.dimensions.height(),
width: domain_config.dimensions.width(),

View file

@ -146,6 +146,7 @@ impl OutputManager {
app_state.add_output(
output_id,
self.config.surface_handle,
&self.config.surface_name,
main_surface_id,
surface,

View file

@ -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<FemtoVGWindow>,
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(),
});
}

View file

@ -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<String>) -> 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<ShellSurfaceKey, PerOutputSurface>,
surface_to_key: HashMap<ObjectId, ShellSurfaceKey>,
surface_handle_to_name: HashMap<SurfaceHandle, String>,
_pointer: ManagedWlPointer,
shared_pointer_serial: Rc<SharedPointerSerial>,
output_manager: Option<Rc<RefCell<OutputManager>>>,
@ -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<PerOutputSurface> {
@ -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<Item = (&str, &PerOutputSurface)> {
) -> impl Iterator<Item = (&str, &PerOutputSurface)> + '_ {
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<Item = &PerOutputSurface> {
pub fn surfaces_by_name(&self, surface_name: &str) -> Vec<&PerOutputSurface> {
let matching_handles: Vec<SurfaceHandle> = 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<Item = &mut PerOutputSurface> {
pub fn surfaces_by_name_mut(&mut self, surface_name: &str) -> Vec<&mut PerOutputSurface> {
let matching_handles: Vec<SurfaceHandle> = 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<SurfaceHandle> = 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<SurfaceHandle> = 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<PerOutputSurface> {
let matching_handles: Vec<SurfaceHandle> = 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
}

View file

@ -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;

View file

@ -22,7 +22,7 @@ impl<'a> Selection<'a> {
pub fn on_callback<F, R>(&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<F, R>(&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

View file

@ -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<CompilationResult>,
definitions: Vec<SurfaceDefinition>,
definitions: &[SurfaceDefinition],
) -> Result<Self> {
let shell_configs: Vec<ShellSurfaceConfig> = 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::<Result<Vec<_>>>()?;
let shell_configs: Vec<ShellSurfaceConfig> = shell_configs_with_handles
.iter()
.map(|(_, cfg)| cfg.clone())
.collect();
let inner = layer_shika_adapters::WaylandShellSystem::new_multi(&shell_configs)?;
let inner_rc: Rc<RefCell<dyn WaylandSystemOps>> = 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<F>(
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<Item = &ComponentInstance> {

View file

@ -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<SurfaceHandle, SurfaceEntry>,
by_name: HashMap<String, SurfaceHandle>,
by_name: HashMap<String, Vec<SurfaceHandle>>,
by_component: HashMap<String, Vec<SurfaceHandle>>,
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<SurfaceEntry> {
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<SurfaceHandle> = 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<SurfaceHandle> {
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<SurfaceHandle> {
self.by_name.get(name).cloned().unwrap_or_default()
}
pub fn name_by_handle(&self, handle: SurfaceHandle) -> Option<&str> {

View file

@ -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<String>) -> 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<String>) -> SurfaceControlHandle {
SurfaceControlHandle {
target: SurfaceTarget::ByName(name.into()),
sender: self.sender.clone(),
}
}
pub fn surface_by_name_and_output(
&self,
name: impl Into<String>,
output: OutputHandle,
) -> SurfaceControlHandle {
SurfaceControlHandle {
target: SurfaceTarget::ByNameAndOutput {
name: name.into(),
output,
},
sender: self.sender.clone(),
}
}
pub fn surface(&self, name: impl Into<String>) -> 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<ShellCommand>,
}
@ -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<Margins>) -> 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<Item = &SurfaceState> {
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<Item = &mut SurfaceState> {
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<F, R>(&self, name: &str, f: F) -> Result<R>
@ -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();

View file

@ -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;

View file

@ -0,0 +1,24 @@
use crate::value_objects::handle::{Handle, Surface};
use crate::value_objects::output_handle::OutputHandle;
pub type SurfaceHandle = Handle<Surface>;
#[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
}
}

View file

@ -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 <bool> is-expanded: false;
in-out property <string> current-anchor: "Top";
in-out property <string> 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

View file

@ -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<Sender<UiUpdate>>,
shell: &Shell,
state: &Rc<RefCell<BarState>>,
) {
let state_clone = Rc::clone(state);
fn setup_toggle_size_callback(sender: &Rc<Sender<UiUpdate>>, 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<Sender<UiUpdate>>,
shell: &Shell,
state: &Rc<RefCell<BarState>>,
) {
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<Sender<UiUpdate>>,
shell: &Shell,
state: &Rc<RefCell<BarState>>,
) {
let state_clone = Rc::clone(state);
fn setup_anchor_switch_callback(sender: &Rc<Sender<UiUpdate>>, 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<Sender<UiUpdate>>, 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(())

View file

@ -6,9 +6,29 @@ export component Bar inherits Window {
in-out property <string> current-anchor: "Top";
in-out property <string> 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);
}
}
}

View file

@ -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,

View file

@ -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,