layer_shika_composition/
selection.rs

1use crate::{
2    Error, LayerSurfaceHandle, Shell,
3    selector::{Selector, SurfaceInfo},
4    slint_interpreter::{ComponentInstance, Value},
5};
6use layer_shika_domain::{
7    errors::DomainError, value_objects::surface_instance_id::SurfaceInstanceId,
8};
9
10/// Result of a property operation on a single surface
11#[derive(Debug)]
12pub struct PropertyError {
13    pub surface_name: String,
14    pub instance_id: SurfaceInstanceId,
15    pub error: String,
16}
17
18/// Result of an operation on multiple selected surfaces
19#[derive(Debug)]
20pub struct SelectionResult<T> {
21    pub success_count: usize,
22    pub values: Vec<T>,
23    pub failures: Vec<PropertyError>,
24}
25
26impl<T> SelectionResult<T> {
27    fn new() -> Self {
28        Self {
29            success_count: 0,
30            values: Vec::new(),
31            failures: Vec::new(),
32        }
33    }
34
35    fn add_success(&mut self, value: T) {
36        self.success_count += 1;
37        self.values.push(value);
38    }
39
40    fn add_failure(&mut self, surface_name: String, instance_id: SurfaceInstanceId, error: String) {
41        self.failures.push(PropertyError {
42            surface_name,
43            instance_id,
44            error,
45        });
46    }
47
48    pub fn is_ok(&self) -> bool {
49        self.failures.is_empty()
50    }
51
52    pub fn is_partial_failure(&self) -> bool {
53        !self.failures.is_empty() && self.success_count > 0
54    }
55
56    pub fn is_total_failure(&self) -> bool {
57        !self.failures.is_empty() && self.success_count == 0
58    }
59
60    pub fn into_result(self) -> Result<Vec<T>, Error> {
61        if self.failures.is_empty() {
62            Ok(self.values)
63        } else {
64            let error_messages: Vec<String> = self
65                .failures
66                .iter()
67                .map(|e| format!("{}[{:?}]: {}", e.surface_name, e.instance_id, e.error))
68                .collect();
69            Err(Error::Domain(DomainError::Configuration {
70                message: format!(
71                    "Operation failed on {} surface(s): {}",
72                    self.failures.len(),
73                    error_messages.join(", ")
74                ),
75            }))
76        }
77    }
78}
79
80/// A selection of surfaces matching a selector
81///
82/// Provides methods to interact with all matching surfaces at once.
83/// Created via `Shell::select()`.
84pub struct Selection<'a> {
85    shell: &'a Shell,
86    selector: Selector,
87}
88
89impl<'a> Selection<'a> {
90    pub(crate) fn new(shell: &'a Shell, selector: Selector) -> Self {
91        Self { shell, selector }
92    }
93
94    /// Registers a callback handler for all matching surfaces
95    ///
96    /// ```ignore
97    /// shell.select(Surface::named("bar"))
98    ///     .on_callback("clicked", |ctx| {
99    ///         println!("Clicked: {}", ctx.surface_name());
100    ///     });
101    /// ```
102    pub fn on_callback<F, R>(&self, callback_name: &str, handler: F) -> &Self
103    where
104        F: Fn(crate::CallbackContext) -> R + Clone + 'static,
105        R: crate::IntoValue,
106    {
107        self.shell
108            .on_internal(&self.selector, callback_name, handler);
109        self
110    }
111
112    /// Registers a callback handler that receives Slint arguments
113    ///
114    /// ```ignore
115    /// // Slint: callback item-clicked(string);
116    /// shell.select(Surface::named("menu"))
117    ///     .on_callback_with_args("item-clicked", |args, ctx| {
118    ///         if let Some(Value::String(item)) = args.get(0) {
119    ///             println!("{} clicked {}", ctx.surface_name(), item);
120    ///         }
121    ///     });
122    /// ```
123    pub fn on_callback_with_args<F, R>(&self, callback_name: &str, handler: F) -> &Self
124    where
125        F: Fn(&[Value], crate::CallbackContext) -> R + Clone + 'static,
126        R: crate::IntoValue,
127    {
128        self.shell
129            .on_with_args_internal(&self.selector, callback_name, handler);
130        self
131    }
132
133    /// Executes a function with each matching component instance
134    pub fn with_component<F>(&self, mut f: F)
135    where
136        F: FnMut(&ComponentInstance),
137    {
138        self.shell.with_selected(&self.selector, |_, component| {
139            f(component);
140        });
141    }
142
143    /// Sets a property value on all matching surfaces
144    ///
145    /// Returns a `SelectionResult` that contains information about both successes and failures.
146    /// Use `.into_result()` to convert to a standard `Result` if you want fail-fast behavior,
147    /// or inspect `.failures` to handle partial failures gracefully.
148    pub fn set_property(&self, name: &str, value: &Value) -> SelectionResult<()> {
149        let mut result = SelectionResult::new();
150        self.shell
151            .with_selected_info(&self.selector, |info, component| {
152                match component.set_property(name, value.clone()) {
153                    Ok(()) => result.add_success(()),
154                    Err(e) => {
155                        let error_msg = format!("Failed to set property '{}': {}", name, e);
156                        log::error!(
157                            "{} on surface {}[{:?}]",
158                            error_msg,
159                            info.name,
160                            info.instance_id
161                        );
162                        result.add_failure(info.name.clone(), info.instance_id, error_msg);
163                    }
164                }
165            });
166        result
167    }
168
169    /// Gets property values from all matching surfaces
170    ///
171    /// Returns a `SelectionResult` containing all successfully retrieved values and any failures.
172    /// Use `.into_result()` to convert to a standard `Result` if you want fail-fast behavior,
173    /// or inspect `.values` and `.failures` to handle partial failures gracefully.
174    pub fn get_property(&self, name: &str) -> SelectionResult<Value> {
175        let mut result = SelectionResult::new();
176        self.shell
177            .with_selected_info(&self.selector, |info, component| {
178                match component.get_property(name) {
179                    Ok(value) => result.add_success(value),
180                    Err(e) => {
181                        let error_msg = format!("Failed to get property '{}': {}", name, e);
182                        log::error!(
183                            "{} on surface {}[{:?}]",
184                            error_msg,
185                            info.name,
186                            info.instance_id
187                        );
188                        result.add_failure(info.name.clone(), info.instance_id, error_msg);
189                    }
190                }
191            });
192        result
193    }
194
195    /// Executes a configuration function with component and surface handle for matching surfaces
196    pub fn configure<F>(&self, mut f: F)
197    where
198        F: FnMut(&ComponentInstance, LayerSurfaceHandle<'_>),
199    {
200        self.shell
201            .configure_selected(&self.selector, |component, handle| {
202                f(component, handle);
203            });
204    }
205
206    /// Returns the number of surfaces matching the selector
207    pub fn count(&self) -> usize {
208        self.shell.count_selected(&self.selector)
209    }
210
211    /// Checks if no surfaces match the selector
212    pub fn is_empty(&self) -> bool {
213        self.count() == 0
214    }
215
216    /// Returns information about all matching surfaces
217    pub fn info(&self) -> Vec<SurfaceInfo> {
218        self.shell.get_selected_info(&self.selector)
219    }
220}