iced_layershell/
lib.rs

1#![doc = include_str!("../README.md")]
2pub mod actions;
3pub mod application;
4pub mod build_pattern;
5mod clipboard;
6mod conversion;
7mod error;
8mod event;
9pub mod multi_window;
10mod proxy;
11mod sandbox;
12
13pub mod settings;
14
15pub mod reexport {
16    pub use layershellev::NewLayerShellSettings;
17    pub use layershellev::reexport::Anchor;
18    pub use layershellev::reexport::KeyboardInteractivity;
19    pub use layershellev::reexport::Layer;
20    pub use layershellev::reexport::wayland_client::{WlRegion, wl_keyboard};
21}
22
23use actions::{LayershellCustomActions, LayershellCustomActionsWithId};
24use settings::Settings;
25
26use iced_runtime::Task;
27
28pub use iced_layershell_macros::to_layer_message;
29
30pub use error::Error;
31
32use iced::{Color, Element, Theme};
33use iced_futures::Subscription;
34
35pub use sandbox::LayerShellSandbox;
36
37pub type Result = std::result::Result<(), error::Error>;
38/// The appearance of a program.
39#[derive(Debug, Clone, Copy, PartialEq)]
40pub struct Appearance {
41    /// The background [`Color`] of the application.
42    pub background_color: Color,
43
44    /// The default text [`Color`] of the application.
45    pub text_color: Color,
46}
47
48/// The default style of a [`Application`].
49pub trait DefaultStyle {
50    /// Returns the default style of a [`Appearance`].
51    fn default_style(&self) -> Appearance;
52}
53
54impl DefaultStyle for Theme {
55    fn default_style(&self) -> Appearance {
56        default(self)
57    }
58}
59
60/// The default [`Appearance`] of a [`Application`] with the built-in [`Theme`].
61pub fn default(theme: &Theme) -> Appearance {
62    let palette = theme.extended_palette();
63
64    Appearance {
65        background_color: palette.background.base.color,
66        text_color: palette.background.base.text,
67    }
68}
69
70// layershell application
71pub trait Application: Sized {
72    /// The [`Executor`] that will run commands and subscriptions.
73    ///
74    /// The [default executor] can be a good starting point!
75    ///
76    /// [`Executor`]: Self::Executor
77    /// [default executor]: iced::executor::Default
78    type Executor: iced::Executor;
79
80    /// The type of __messages__ your [`Application`] will produce.
81    type Message: std::fmt::Debug + Send;
82
83    /// The theme of your [`Application`].
84    type Theme: Default + DefaultStyle;
85
86    /// The data needed to initialize your [`Application`].
87    type Flags;
88
89    /// Initializes the [`Application`] with the flags provided to
90    /// [`run`] as part of the [`Settings`].
91    ///
92    /// Here is where you should return the initial state of your app.
93    ///
94    /// Additionally, you can return a [`Task`] if you need to perform some
95    /// async action in the background on startup. This is useful if you want to
96    /// load state from a file, perform an initial HTTP request, etc.
97    ///
98    /// [`run`]: Self::run
99    fn new(flags: Self::Flags) -> (Self, Task<Self::Message>);
100
101    /// Returns the current title of the [`Application`].
102    ///
103    /// This title can be dynamic! The runtime will automatically update the
104    /// title of your application when necessary.
105    fn namespace(&self) -> String;
106
107    /// Handles a __message__ and updates the state of the [`Application`].
108    ///
109    /// This is where you define your __update logic__. All the __messages__,
110    /// produced by either user interactions or commands, will be handled by
111    /// this method.
112    ///
113    /// Any [`Task`] returned will be executed immediately in the background.
114    fn update(&mut self, message: Self::Message) -> Task<Self::Message>;
115
116    /// Returns the widgets to display in the [`Application`].
117    ///
118    /// These widgets can produce __messages__ based on user interaction.
119    fn view(&self) -> Element<'_, Self::Message, Self::Theme, iced::Renderer>;
120
121    /// Returns the current [`Theme`] of the [`Application`].
122    ///
123    /// [`Theme`]: Self::Theme
124    fn theme(&self) -> Self::Theme {
125        Self::Theme::default()
126    }
127
128    /// Returns the current `Style` of the [`Theme`].
129    ///
130    /// [`Theme`]: Self::Theme
131    fn style(&self, theme: &Self::Theme) -> Appearance {
132        theme.default_style()
133    }
134
135    /// Returns the event [`Subscription`] for the current state of the
136    /// application.
137    ///
138    /// A [`Subscription`] will be kept alive as long as you keep returning it,
139    /// and the __messages__ produced will be handled by
140    /// [`update`](#tymethod.update).
141    ///
142    /// By default, this method returns an empty [`Subscription`].
143    fn subscription(&self) -> Subscription<Self::Message> {
144        Subscription::none()
145    }
146
147    /// Returns the scale factor of the [`Application`].
148    ///
149    /// It can be used to dynamically control the size of the UI at runtime
150    /// (i.e. zooming).
151    ///
152    /// For instance, a scale factor of `2.0` will make widgets twice as big,
153    /// while a scale factor of `0.5` will shrink them to half their size.
154    ///
155    /// By default, it returns `1.0`.
156    fn scale_factor(&self) -> f64 {
157        1.0
158    }
159
160    /// Runs the [`Application`].
161    ///
162    /// On native platforms, this method will take control of the current thread
163    /// until the [`Application`] exits.
164    ///
165    /// On the web platform, this method __will NOT return__ unless there is an
166    /// [`Error`] during startup.
167    ///
168    /// [`Error`]: crate::Error
169    fn run(settings: Settings<Self::Flags>) -> Result
170    where
171        Self: 'static,
172        Self::Message: 'static + TryInto<LayershellCustomActions, Error = Self::Message>,
173    {
174        #[allow(clippy::needless_update)]
175        let renderer_settings = iced_graphics::Settings {
176            default_font: settings.default_font,
177            default_text_size: settings.default_text_size,
178            antialiasing: if settings.antialiasing {
179                Some(iced_graphics::Antialiasing::MSAAx4)
180            } else {
181                None
182            },
183            ..iced_graphics::Settings::default()
184        };
185
186        application::run::<Instance<Self>, Self::Executor, iced_renderer::Compositor>(
187            settings,
188            renderer_settings,
189        )
190    }
191}
192
193struct Instance<A: Application>(A);
194
195impl<A> iced_runtime::Program for Instance<A>
196where
197    A: Application,
198{
199    type Message = A::Message;
200    type Theme = A::Theme;
201    type Renderer = iced_renderer::Renderer;
202
203    fn update(&mut self, message: Self::Message) -> Task<Self::Message> {
204        self.0.update(message)
205    }
206
207    fn view(&self) -> Element<'_, Self::Message, Self::Theme, Self::Renderer> {
208        self.0.view()
209    }
210}
211
212impl<A> application::Application for Instance<A>
213where
214    A: Application,
215    A::Message: 'static + TryInto<LayershellCustomActions, Error = A::Message>,
216{
217    type Flags = A::Flags;
218
219    fn new(flags: Self::Flags) -> (Self, Task<A::Message>) {
220        let (app, command) = A::new(flags);
221
222        (Instance(app), command)
223    }
224
225    fn namespace(&self) -> String {
226        self.0.namespace()
227    }
228
229    fn theme(&self) -> A::Theme {
230        self.0.theme()
231    }
232
233    fn style(&self, theme: &Self::Theme) -> Appearance {
234        self.0.style(theme)
235    }
236
237    fn subscription(&self) -> Subscription<Self::Message> {
238        self.0.subscription()
239    }
240
241    fn scale_factor(&self) -> f64 {
242        self.0.scale_factor()
243    }
244}
245
246pub trait MultiApplication: Sized {
247    /// The [`Executor`] that will run commands and subscriptions.
248    ///
249    /// The [default executor] can be a good starting point!
250    ///
251    /// [`Executor`]: Self::Executor
252    /// [default executor]: iced::executor::Default
253    type Executor: iced::Executor;
254
255    /// The type of __messages__ your [`Application`] will produce.
256    type Message: std::fmt::Debug + Send;
257
258    /// The data needed to initialize your [`Application`].
259    type Flags;
260
261    type Theme: Default + DefaultStyle;
262
263    /// Initializes the [`Application`] with the flags provided to
264    /// [`run`] as part of the [`Settings`].
265    ///
266    /// Here is where you should return the initial state of your app.
267    ///
268    /// Additionally, you can return a [`Task`] if you need to perform some
269    /// async action in the background on startup. This is useful if you want to
270    /// load state from a file, perform an initial HTTP request, etc.
271    ///
272    /// [`run`]: Self::run
273    fn new(flags: Self::Flags) -> (Self, Task<Self::Message>);
274
275    /// Returns the current title of the `window` of the [`Application`].
276    ///
277    /// This title can be dynamic! The runtime will automatically update the
278    /// title of your window when necessary.
279    fn namespace(&self) -> String;
280
281    fn remove_id(&mut self, _id: iced_core::window::Id) {}
282    /// Handles a __message__ and updates the state of the [`Application`].
283    ///
284    /// This is where you define your __update logic__. All the __messages__,
285    /// produced by either user interactions or commands, will be handled by
286    /// this method.
287    ///
288    /// Any [`Task`] returned will be executed immediately in the background.
289    fn update(&mut self, message: Self::Message) -> Task<Self::Message>;
290
291    /// Returns the widgets to display in the `window` of the [`Application`].
292    ///
293    /// These widgets can produce __messages__ based on user interaction.
294    fn view(
295        &self,
296        window: iced::window::Id,
297    ) -> Element<'_, Self::Message, Self::Theme, iced::Renderer>;
298
299    /// Returns the current [`Theme`] of the `window` of the [`Application`].
300    ///
301    /// [`Theme`]: Self::Theme
302    #[allow(unused_variables)]
303    fn theme(&self) -> Self::Theme {
304        Self::Theme::default()
305    }
306
307    /// Returns the current `Style` of the [`Theme`].
308    ///
309    /// [`Theme`]: Self::Theme
310    fn style(&self, theme: &Self::Theme) -> Appearance {
311        theme.default_style()
312    }
313
314    /// Returns the event [`Subscription`] for the current state of the
315    /// application.
316    ///
317    /// A [`Subscription`] will be kept alive as long as you keep returning it,
318    /// and the __messages__ produced will be handled by
319    /// [`update`](#tymethod.update).
320    ///
321    /// By default, this method returns an empty [`Subscription`].
322    fn subscription(&self) -> Subscription<Self::Message> {
323        Subscription::none()
324    }
325
326    /// Returns the scale factor of the `window` of the [`Application`].
327    ///
328    /// It can be used to dynamically control the size of the UI at runtime
329    /// (i.e. zooming).
330    ///
331    /// For instance, a scale factor of `2.0` will make widgets twice as big,
332    /// while a scale factor of `0.5` will shrink them to half their size.
333    ///
334    /// By default, it returns `1.0`.
335    #[allow(unused_variables)]
336    fn scale_factor(&self, window: iced::window::Id) -> f64 {
337        1.0
338    }
339
340    /// Runs the multi-window [`Application`].
341    ///
342    /// On native platforms, this method will take control of the current thread
343    /// until the [`Application`] exits.
344    ///
345    /// On the web platform, this method __will NOT return__ unless there is an
346    /// [`Error`] during startup.
347    ///
348    /// [`Error`]: crate::Error
349    fn run(settings: Settings<Self::Flags>) -> Result
350    where
351        Self: 'static,
352        Self::Message: 'static + TryInto<LayershellCustomActionsWithId, Error = Self::Message>,
353    {
354        #[allow(clippy::needless_update)]
355        let renderer_settings = iced_graphics::Settings {
356            default_font: settings.default_font,
357            default_text_size: settings.default_text_size,
358            antialiasing: if settings.antialiasing {
359                Some(iced_graphics::Antialiasing::MSAAx4)
360            } else {
361                None
362            },
363            ..iced_graphics::Settings::default()
364        };
365
366        multi_window::run::<MultiInstance<Self>, Self::Executor, iced_renderer::Compositor>(
367            settings,
368            renderer_settings,
369        )
370    }
371}
372
373struct MultiInstance<A: MultiApplication>(A);
374
375impl<A> iced_runtime::multi_window::Program for MultiInstance<A>
376where
377    A: MultiApplication,
378{
379    type Message = A::Message;
380    type Theme = A::Theme;
381    type Renderer = iced_renderer::Renderer;
382
383    fn update(&mut self, message: Self::Message) -> Task<Self::Message> {
384        self.0.update(message)
385    }
386
387    fn view(
388        &self,
389        window: iced::window::Id,
390    ) -> Element<'_, Self::Message, Self::Theme, Self::Renderer> {
391        self.0.view(window)
392    }
393}
394
395impl<A> multi_window::Application for MultiInstance<A>
396where
397    A: MultiApplication,
398{
399    type Flags = A::Flags;
400
401    fn new(flags: Self::Flags) -> (Self, Task<A::Message>) {
402        let (app, command) = A::new(flags);
403
404        (MultiInstance(app), command)
405    }
406
407    fn namespace(&self) -> String {
408        self.0.namespace()
409    }
410
411    fn theme(&self) -> A::Theme {
412        self.0.theme()
413    }
414
415    fn style(&self, theme: &Self::Theme) -> Appearance {
416        self.0.style(theme)
417    }
418
419    fn subscription(&self) -> Subscription<Self::Message> {
420        self.0.subscription()
421    }
422
423    fn scale_factor(&self, window: iced::window::Id) -> f64 {
424        self.0.scale_factor(window)
425    }
426    fn remove_id(&mut self, id: iced_core::window::Id) {
427        self.0.remove_id(id)
428    }
429}
430#[cfg(test)]
431mod tests {
432    use super::*;
433    use iced::widget::text;
434
435    struct TestApp {
436        counter: i32,
437        scale_factor: f64,
438        namespace: String,
439    }
440
441    #[derive(Debug)]
442    enum TestMessage {
443        Increment,
444        Decrement,
445    }
446
447    impl Application for TestApp {
448        type Executor = iced::executor::Default;
449        type Message = TestMessage;
450        type Theme = Theme;
451        type Flags = (i32, f64, String);
452
453        fn new(flags: Self::Flags) -> (Self, Task<Self::Message>) {
454            let (counter, scale_factor, namespace) = flags;
455            (
456                Self {
457                    counter,
458                    scale_factor,
459                    namespace,
460                },
461                Task::none(),
462            )
463        }
464
465        fn namespace(&self) -> String {
466            self.namespace.clone()
467        }
468
469        fn update(&mut self, message: Self::Message) -> Task<Self::Message> {
470            match message {
471                TestMessage::Increment => self.counter += 1,
472                TestMessage::Decrement => self.counter -= 1,
473            }
474            Task::none()
475        }
476
477        fn view(&self) -> Element<'_, Self::Message, Self::Theme, iced::Renderer> {
478            text("Test").into()
479        }
480
481        fn scale_factor(&self) -> f64 {
482            self.scale_factor
483        }
484    }
485
486    // Test default appearance
487    #[test]
488    fn test_default_appearance() {
489        let theme = Theme::default();
490        let appearance = theme.default_style();
491        assert_eq!(appearance.background_color, Color::WHITE);
492        assert_eq!(appearance.text_color, Color::BLACK);
493    }
494
495    // Test namespace
496    #[test]
497    fn test_namespace() {
498        let app = TestApp::new((0, 1.0, "Test namespace".into())).0;
499        assert_eq!(app.namespace(), "Test namespace");
500    }
501
502    // Test scale factor
503    #[test]
504    fn test_scale_factor() {
505        let app = TestApp::new((0, 2.0, "Test scale factor".into())).0;
506        assert_eq!(app.scale_factor(), 2.0);
507    }
508
509    // Test update increment
510    #[test]
511    fn test_update_increment() {
512        let mut app = TestApp::new((0, 1.0, "Test Update".into())).0;
513        let _ = app.update(TestMessage::Increment);
514        assert_eq!(app.counter, 1);
515    }
516
517    // Test update decrement
518    #[test]
519    fn test_update_decrement() {
520        let mut app = TestApp::new((5, 1.0, "Test Update".into())).0;
521        let _ = app.update(TestMessage::Decrement);
522        assert_eq!(app.counter, 4);
523    }
524}