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#[derive(Debug, Clone, Copy, PartialEq)]
40pub struct Appearance {
41 pub background_color: Color,
43
44 pub text_color: Color,
46}
47
48pub trait DefaultStyle {
50 fn default_style(&self) -> Appearance;
52}
53
54impl DefaultStyle for Theme {
55 fn default_style(&self) -> Appearance {
56 default(self)
57 }
58}
59
60pub 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
70pub trait Application: Sized {
72 type Executor: iced::Executor;
79
80 type Message: std::fmt::Debug + Send;
82
83 type Theme: Default + DefaultStyle;
85
86 type Flags;
88
89 fn new(flags: Self::Flags) -> (Self, Task<Self::Message>);
100
101 fn namespace(&self) -> String;
106
107 fn update(&mut self, message: Self::Message) -> Task<Self::Message>;
115
116 fn view(&self) -> Element<'_, Self::Message, Self::Theme, iced::Renderer>;
120
121 fn theme(&self) -> Self::Theme {
125 Self::Theme::default()
126 }
127
128 fn style(&self, theme: &Self::Theme) -> Appearance {
132 theme.default_style()
133 }
134
135 fn subscription(&self) -> Subscription<Self::Message> {
144 Subscription::none()
145 }
146
147 fn scale_factor(&self) -> f64 {
157 1.0
158 }
159
160 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 type Executor: iced::Executor;
254
255 type Message: std::fmt::Debug + Send;
257
258 type Flags;
260
261 type Theme: Default + DefaultStyle;
262
263 fn new(flags: Self::Flags) -> (Self, Task<Self::Message>);
274
275 fn namespace(&self) -> String;
280
281 fn remove_id(&mut self, _id: iced_core::window::Id) {}
282 fn update(&mut self, message: Self::Message) -> Task<Self::Message>;
290
291 fn view(
295 &self,
296 window: iced::window::Id,
297 ) -> Element<'_, Self::Message, Self::Theme, iced::Renderer>;
298
299 #[allow(unused_variables)]
303 fn theme(&self) -> Self::Theme {
304 Self::Theme::default()
305 }
306
307 fn style(&self, theme: &Self::Theme) -> Appearance {
311 theme.default_style()
312 }
313
314 fn subscription(&self) -> Subscription<Self::Message> {
323 Subscription::none()
324 }
325
326 #[allow(unused_variables)]
336 fn scale_factor(&self, window: iced::window::Id) -> f64 {
337 1.0
338 }
339
340 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]
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]
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]
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]
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]
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}