From 6a0abe201016134f7d762b652c0c88522d8e0687 Mon Sep 17 00:00:00 2001 From: ShootingStarDragons Date: Mon, 2 Dec 2024 23:16:56 +0900 Subject: [PATCH] feat: add build_pattern for iced_layershell --- Cargo.lock | 19 + .../counter_mulit_pattern/Cargo.toml | 18 + .../counter_mulit_pattern/src/main.rs | 273 +++++ iced_examples/counter_pattern/Cargo.toml | 17 + iced_examples/counter_pattern/src/main.rs | 154 +++ iced_layershell/src/build_pattern.rs | 66 ++ .../src/build_pattern/application.rs | 805 +++++++++++++ iced_layershell/src/build_pattern/daemon.rs | 1010 +++++++++++++++++ iced_layershell/src/lib.rs | 7 +- 9 files changed, 2367 insertions(+), 2 deletions(-) create mode 100644 iced_examples/counter_mulit_pattern/Cargo.toml create mode 100644 iced_examples/counter_mulit_pattern/src/main.rs create mode 100644 iced_examples/counter_pattern/Cargo.toml create mode 100644 iced_examples/counter_pattern/src/main.rs create mode 100644 iced_layershell/src/build_pattern.rs create mode 100644 iced_layershell/src/build_pattern/application.rs create mode 100644 iced_layershell/src/build_pattern/daemon.rs diff --git a/Cargo.lock b/Cargo.lock index 713af68..95a1e24 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -809,6 +809,25 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "counter_multi_pattern" +version = "0.11.0-rc3" +dependencies = [ + "iced", + "iced_layershell", + "iced_runtime", + "tracing-subscriber", +] + +[[package]] +name = "counter_pattern" +version = "0.11.0-rc3" +dependencies = [ + "iced", + "iced_layershell", + "iced_runtime", +] + [[package]] name = "cpufeatures" version = "0.2.14" diff --git a/iced_examples/counter_mulit_pattern/Cargo.toml b/iced_examples/counter_mulit_pattern/Cargo.toml new file mode 100644 index 0000000..d15f694 --- /dev/null +++ b/iced_examples/counter_mulit_pattern/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "counter_multi_pattern" +authors.workspace = true +edition.workspace = true +version.workspace = true +license.workspace = true +repository.workspace = true +description.workspace = true +keywords.workspace = true +readme.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +iced.workspace = true +iced_runtime.workspace = true +iced_layershell.workspace = true +tracing-subscriber.workspace = true diff --git a/iced_examples/counter_mulit_pattern/src/main.rs b/iced_examples/counter_mulit_pattern/src/main.rs new file mode 100644 index 0000000..ad1b756 --- /dev/null +++ b/iced_examples/counter_mulit_pattern/src/main.rs @@ -0,0 +1,273 @@ +use std::collections::HashMap; + +use iced::widget::{button, column, container, row, text, text_input}; +use iced::window::Id; +use iced::{event, Alignment, Element, Event, Length, Task as Command}; +use iced_layershell::actions::{IcedNewMenuSettings, MenuDirection}; +use iced_runtime::window::Action as WindowAction; +use iced_runtime::{task, Action}; + +use iced_layershell::build_pattern::{daemon, MainSettings}; +use iced_layershell::reexport::{Anchor, KeyboardInteractivity, Layer, NewLayerShellSettings}; +use iced_layershell::settings::{LayerShellSettings, StartMode}; +use iced_layershell::{to_layer_message, LayerSingleton}; + +pub fn main() -> Result<(), iced_layershell::Error> { + tracing_subscriber::fmt().init(); + daemon( + Counter::namespace, + Counter::update, + Counter::view, + Counter::id_info, + Counter::set_id_info, + Counter::remove_id, + ) + .subscription(Counter::subscription) + .settings(MainSettings { + layer_settings: LayerShellSettings { + size: Some((0, 400)), + exclusive_zone: 400, + anchor: Anchor::Bottom | Anchor::Left | Anchor::Right, + start_mode: StartMode::AllScreens, + ..Default::default() + }, + ..Default::default() + }) + .run_with(|| Counter::new("Hello")) +} + +#[derive(Debug, Default)] +struct Counter { + value: i32, + text: String, + ids: HashMap, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, LayerSingleton)] +enum WindowInfo { + #[singleton] + Left, + #[singleton] + Right, + PopUp, +} + +#[derive(Debug, Clone, Copy)] +enum WindowDirection { + Top(Id), + Left(Id), + Right(Id), + Bottom(Id), +} + +#[to_layer_message(multi, info_name = "WindowInfo")] +#[derive(Debug, Clone)] +enum Message { + IncrementPressed, + DecrementPressed, + NewWindowLeft, + NewWindowRight, + Close(Id), + TextInput(String), + Direction(WindowDirection), + IcedEvent(Event), +} + +impl Counter { + fn window_id(&self, info: &WindowInfo) -> Option<&iced::window::Id> { + for (k, v) in self.ids.iter() { + if info == v { + return Some(k); + } + } + None + } +} + +impl Counter { + fn new(text: &str) -> (Self, Command) { + ( + Self { + value: 0, + text: text.to_string(), + ids: HashMap::new(), + }, + Command::none(), + ) + } + + fn id_info(&self, id: iced::window::Id) -> Option { + self.ids.get(&id).cloned() + } + + fn set_id_info(&mut self, id: iced::window::Id, info: WindowInfo) { + self.ids.insert(id, info); + } + + fn remove_id(&mut self, id: iced::window::Id) { + self.ids.remove(&id); + } + + fn namespace(&self) -> String { + String::from("Counter - Iced") + } + + fn subscription(&self) -> iced::Subscription { + event::listen().map(Message::IcedEvent) + } + + fn update(&mut self, message: Message) -> Command { + use iced::keyboard; + use iced::keyboard::key::Named; + use iced::Event; + match message { + Message::IcedEvent(event) => { + match event { + Event::Keyboard(keyboard::Event::KeyPressed { + key: keyboard::Key::Named(Named::Escape), + .. + }) => { + if let Some(id) = self.window_id(&WindowInfo::Left) { + return iced_runtime::task::effect(Action::Window( + WindowAction::Close(*id), + )); + } + } + Event::Mouse(iced::mouse::Event::ButtonPressed(iced::mouse::Button::Right)) => { + return Command::done(Message::NewMenu { + settings: IcedNewMenuSettings { + size: (100, 100), + direction: MenuDirection::Up, + }, + info: WindowInfo::PopUp, + }); + } + _ => {} + } + Command::none() + } + Message::IncrementPressed => { + self.value += 1; + Command::none() + } + Message::DecrementPressed => { + self.value -= 1; + Command::none() + } + Message::TextInput(text) => { + self.text = text; + Command::none() + } + Message::Direction(direction) => match direction { + WindowDirection::Left(id) => Command::done(Message::AnchorSizeChange { + id, + anchor: Anchor::Top | Anchor::Left | Anchor::Bottom, + size: (400, 0), + }), + WindowDirection::Right(id) => Command::done(Message::AnchorSizeChange { + id, + anchor: Anchor::Top | Anchor::Right | Anchor::Bottom, + size: (400, 0), + }), + WindowDirection::Bottom(id) => Command::done(Message::AnchorSizeChange { + id, + anchor: Anchor::Left | Anchor::Right | Anchor::Bottom, + size: (0, 400), + }), + WindowDirection::Top(id) => Command::done(Message::AnchorSizeChange { + id, + anchor: Anchor::Left | Anchor::Right | Anchor::Top, + size: (0, 400), + }), + }, + Message::NewWindowLeft => Command::done(Message::NewLayerShell { + settings: NewLayerShellSettings { + size: Some((100, 100)), + exclusive_zone: None, + anchor: Anchor::Left | Anchor::Bottom, + layer: Layer::Top, + margin: None, + keyboard_interactivity: KeyboardInteractivity::Exclusive, + use_last_output: false, + ..Default::default() + }, + info: WindowInfo::Left, + }), + Message::NewWindowRight => Command::done(Message::NewLayerShell { + settings: NewLayerShellSettings { + size: Some((100, 100)), + exclusive_zone: None, + anchor: Anchor::Right | Anchor::Bottom, + layer: Layer::Top, + margin: None, + keyboard_interactivity: KeyboardInteractivity::Exclusive, + use_last_output: false, + ..Default::default() + }, + info: WindowInfo::Right, + }), + Message::Close(id) => task::effect(Action::Window(WindowAction::Close(id))), + _ => unreachable!(), + } + } + + fn view(&self, id: iced::window::Id) -> Element { + if let Some(WindowInfo::Left) = self.id_info(id) { + return button("close left").on_press(Message::Close(id)).into(); + } + if let Some(WindowInfo::Right) = self.id_info(id) { + return button("close right").on_press(Message::Close(id)).into(); + } + if let Some(WindowInfo::PopUp) = self.id_info(id) { + return container(button("close PopUp").on_press(Message::Close(id))) + .center_x(Length::Fill) + .center_y(Length::Fill) + .style(|_theme| container::Style { + background: Some(iced::Color::new(0., 0.5, 0.7, 0.6).into()), + ..Default::default() + }) + //.style(Container::Custom(Box::new(BlackMenu))) + .width(Length::Fill) + .height(Length::Fill) + .into(); + } + let center = column![ + button("Increment").on_press(Message::IncrementPressed), + button("Decrement").on_press(Message::DecrementPressed), + text(self.value).size(50), + button("newwindowLeft").on_press(Message::NewWindowLeft), + button("newwindowRight").on_press(Message::NewWindowRight), + ] + .align_x(Alignment::Center) + .padding(20) + .width(Length::Fill) + .height(Length::Fill); + row![ + button("left") + .on_press(Message::Direction(WindowDirection::Left(id))) + .height(Length::Fill), + column![ + button("top") + .on_press(Message::Direction(WindowDirection::Top(id))) + .width(Length::Fill), + center, + text_input("hello", &self.text) + .on_input(Message::TextInput) + .padding(10), + button("bottom") + .on_press(Message::Direction(WindowDirection::Bottom(id))) + .width(Length::Fill), + ] + .width(Length::Fill), + button("right") + .on_press(Message::Direction(WindowDirection::Right(id))) + .height(Length::Fill), + ] + .padding(20) + .spacing(10) + //.align_items(Alignment::Center) + .width(Length::Fill) + .height(Length::Fill) + .into() + } +} diff --git a/iced_examples/counter_pattern/Cargo.toml b/iced_examples/counter_pattern/Cargo.toml new file mode 100644 index 0000000..0018ec0 --- /dev/null +++ b/iced_examples/counter_pattern/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "counter_pattern" +authors.workspace = true +edition.workspace = true +version.workspace = true +license.workspace = true +repository.workspace = true +description.workspace = true +keywords.workspace = true +readme.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +iced.workspace = true +iced_runtime.workspace = true +iced_layershell.workspace = true diff --git a/iced_examples/counter_pattern/src/main.rs b/iced_examples/counter_pattern/src/main.rs new file mode 100644 index 0000000..42466ab --- /dev/null +++ b/iced_examples/counter_pattern/src/main.rs @@ -0,0 +1,154 @@ +use iced::widget::{button, column, row, text, text_input}; +use iced::{event, Alignment, Color, Element, Event, Length, Task as Command}; +use iced_layershell::build_pattern::{application, MainSettings}; +use iced_layershell::reexport::Anchor; +use iced_layershell::settings::{LayerShellSettings, StartMode}; +use iced_layershell::to_layer_message; + +pub fn main() -> Result<(), iced_layershell::Error> { + let args: Vec = std::env::args().collect(); + + let mut binded_output_name = None; + if args.len() >= 2 { + binded_output_name = Some(args[1].to_string()) + } + + let start_mode = match binded_output_name { + Some(output) => StartMode::TargetScreen(output), + None => StartMode::Active, + }; + + application(namespace, update, view) + .style(style) + .subscription(subscription) + .settings(MainSettings { + layer_settings: LayerShellSettings { + size: Some((0, 400)), + exclusive_zone: 400, + anchor: Anchor::Bottom | Anchor::Left | Anchor::Right, + start_mode, + ..Default::default() + }, + ..Default::default() + }) + .run() +} + +#[derive(Default)] +struct Counter { + value: i32, + text: String, +} + +#[derive(Debug, Clone, Copy)] +enum WindowDirection { + Top, + Left, + Right, + Bottom, +} + +#[to_layer_message] +#[derive(Debug, Clone)] +enum Message { + IncrementPressed, + DecrementPressed, + TextInput(String), + Direction(WindowDirection), + IcedEvent(Event), +} + +fn namespace(_: &Counter) -> String { + String::from("Counter - Iced") +} + +fn subscription(_: &Counter) -> iced::Subscription { + event::listen().map(Message::IcedEvent) +} + +fn update(counter: &mut Counter, message: Message) -> Command { + match message { + Message::IcedEvent(event) => { + println!("hello {event:?}"); + Command::none() + } + Message::IncrementPressed => { + counter.value += 1; + Command::none() + } + Message::DecrementPressed => { + counter.value -= 1; + Command::none() + } + Message::TextInput(text) => { + counter.text = text; + Command::none() + } + + Message::Direction(direction) => match direction { + WindowDirection::Left => Command::done(Message::AnchorSizeChange( + Anchor::Left | Anchor::Top | Anchor::Bottom, + (400, 0), + )), + WindowDirection::Right => Command::done(Message::AnchorSizeChange( + Anchor::Right | Anchor::Top | Anchor::Bottom, + (400, 0), + )), + WindowDirection::Bottom => Command::done(Message::AnchorSizeChange( + Anchor::Bottom | Anchor::Left | Anchor::Right, + (0, 400), + )), + WindowDirection::Top => Command::done(Message::AnchorSizeChange( + Anchor::Top | Anchor::Left | Anchor::Right, + (0, 400), + )), + }, + _ => unreachable!(), + } +} + +fn view(counter: &Counter) -> Element { + let center = column![ + button("Increment").on_press(Message::IncrementPressed), + text(counter.value).size(50), + button("Decrement").on_press(Message::DecrementPressed) + ] + .align_x(Alignment::Center) + .padding(20) + .width(Length::Fill) + .height(Length::Fill); + row![ + button("left") + .on_press(Message::Direction(WindowDirection::Left)) + .height(Length::Fill), + column![ + button("top") + .on_press(Message::Direction(WindowDirection::Top)) + .width(Length::Fill), + center, + text_input("hello", &counter.text) + .on_input(Message::TextInput) + .padding(10), + button("bottom") + .on_press(Message::Direction(WindowDirection::Bottom)) + .width(Length::Fill), + ] + .width(Length::Fill), + button("right") + .on_press(Message::Direction(WindowDirection::Right)) + .height(Length::Fill), + ] + .padding(20) + .spacing(10) + .width(Length::Fill) + .height(Length::Fill) + .into() +} + +fn style(_counter: &Counter, theme: &iced::Theme) -> iced_layershell::Appearance { + use iced_layershell::Appearance; + Appearance { + background_color: Color::TRANSPARENT, + text_color: theme.palette().text, + } +} diff --git a/iced_layershell/src/build_pattern.rs b/iced_layershell/src/build_pattern.rs new file mode 100644 index 0000000..98aeb7a --- /dev/null +++ b/iced_layershell/src/build_pattern.rs @@ -0,0 +1,66 @@ +mod application; +mod daemon; +use std::borrow::Cow; + +use iced::{Font, Pixels}; + +use crate::settings::{LayerShellSettings, VirtualKeyboardSettings}; + +/// The renderer of some [`Program`]. +pub trait Renderer: iced_core::text::Renderer + iced_graphics::compositor::Default {} + +impl Renderer for T where T: iced_core::text::Renderer + iced_graphics::compositor::Default {} + +#[derive(Debug)] +pub struct MainSettings { + /// The identifier of the application. + /// + /// If provided, this identifier may be used to identify the application or + /// communicate with it through the windowing system. + pub id: Option, + + /// settings for layer shell + pub layer_settings: LayerShellSettings, + /// The data needed to initialize an [`Application`]. + /// + /// The fonts to load on boot. + pub fonts: Vec>, + + /// The default [`Font`] to be used. + /// + /// By default, it uses [`Family::SansSerif`](iced::font::Family::SansSerif). + pub default_font: Font, + + /// The text size that will be used by default. + /// + /// The default value is `16.0`. + pub default_text_size: Pixels, + + /// If set to true, the renderer will try to perform antialiasing for some + /// primitives. + /// + /// Enabling it can produce a smoother result in some widgets, like the + /// `Canvas`, at a performance cost. + /// + /// By default, it is disabled. + /// + pub antialiasing: bool, + + pub virtual_keyboard_support: Option, +} +impl Default for MainSettings { + fn default() -> Self { + MainSettings { + id: None, + fonts: Vec::new(), + layer_settings: LayerShellSettings::default(), + default_font: Font::default(), + default_text_size: Pixels(16.0), + antialiasing: false, + virtual_keyboard_support: None, + } + } +} + +pub use application::application; +pub use daemon::daemon; diff --git a/iced_layershell/src/build_pattern/application.rs b/iced_layershell/src/build_pattern/application.rs new file mode 100644 index 0000000..404ebd8 --- /dev/null +++ b/iced_layershell/src/build_pattern/application.rs @@ -0,0 +1,805 @@ +use std::borrow::Cow; + +use iced::Font; +use iced::{Element, Task}; + +use crate::actions::LayershellCustomActions; + +use crate::settings::LayerShellSettings; +use crate::DefaultStyle; + +use super::Renderer; +use crate::Settings; + +use crate::Result; + +use super::MainSettings; + +// layershell application +pub trait Program: Sized { + /// The [`Executor`] that will run commands and subscriptions. + /// + /// The [default executor] can be a good starting point! + /// + /// [`Executor`]: Self::Executor + /// [default executor]: iced::executor::Default + type Executor: iced::Executor; + type State; + type Renderer: Renderer; + + /// The type of __messages__ your [`Application`] will produce. + type Message: std::fmt::Debug + + Send + + 'static + + TryInto; + + /// The theme of your [`Application`]. + type Theme: Default + DefaultStyle; + + /// Initializes the [`Application`] with the flags provided to + /// [`run`] as part of the [`Settings`]. + /// + /// Here is where you should return the initial state of your app. + /// + /// Additionally, you can return a [`Task`] if you need to perform some + /// async action in the background on startup. This is useful if you want to + /// load state from a file, perform an initial HTTP request, etc. + /// + /// + /// This title can be dynamic! The runtime will automatically update the + /// title of your application when necessary. + fn namespace(&self, _state: &Self::State) -> String { + "A cool iced application".to_string() + } + + /// Handles a __message__ and updates the state of the [`Application`]. + /// + /// This is where you define your __update logic__. All the __messages__, + /// produced by either user interactions or commands, will be handled by + /// this method. + /// + /// Any [`Task`] returned will be executed immediately in the background. + fn update(&self, state: &mut Self::State, message: Self::Message) -> Task; + + /// Returns the widgets to display in the [`Application`]. + /// + /// These widgets can produce __messages__ based on user interaction. + fn view<'a>( + &self, + state: &'a Self::State, + ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer>; + + /// Returns the current [`Theme`] of the [`Application`]. + /// + /// [`Theme`]: Self::Theme + fn theme(&self, _state: &Self::State) -> Self::Theme { + Self::Theme::default() + } + + /// Returns the current `Style` of the [`Theme`]. + /// + /// [`Theme`]: Self::Theme + fn style(&self, _state: &Self::State, theme: &Self::Theme) -> crate::Appearance { + theme.default_style() + } + + /// Returns the event [`Subscription`] for the current state of the + /// application. + /// + /// A [`Subscription`] will be kept alive as long as you keep returning it, + /// and the __messages__ produced will be handled by + /// [`update`](#tymethod.update). + /// + /// By default, this method returns an empty [`Subscription`]. + fn subscription(&self, _state: &Self::State) -> iced::Subscription { + iced::Subscription::none() + } + + /// Returns the scale factor of the [`Application`]. + /// + /// It can be used to dynamically control the size of the UI at runtime + /// (i.e. zooming). + /// + /// For instance, a scale factor of `2.0` will make widgets twice as big, + /// while a scale factor of `0.5` will shrink them to half their size. + /// + /// By default, it returns `1.0`. + fn scale_factor(&self, _state: &Self::State) -> f64 { + 1.0 + } + + fn run_with(self, settings: MainSettings, initialize: I) -> Result + where + Self: 'static, + I: FnOnce() -> (Self::State, Task) + 'static, + { + use std::marker::PhantomData; + struct Instance { + program: P, + state: P::State, + _initialize: PhantomData, + } + + impl (P::State, Task)> iced_runtime::Program + for Instance + { + type Message = P::Message; + type Theme = P::Theme; + type Renderer = P::Renderer; + fn update(&mut self, message: Self::Message) -> Task { + self.program.update(&mut self.state, message) + } + + fn view(&self) -> crate::Element<'_, Self::Message, Self::Theme, Self::Renderer> { + self.program.view(&self.state) + } + } + + impl (P::State, Task)> + crate::application::Application for Instance + { + type Flags = (P, I); + + fn new((program, initialize): Self::Flags) -> (Self, Task) { + let (state, task) = initialize(); + + ( + Self { + program, + state, + _initialize: PhantomData, + }, + task, + ) + } + + fn namespace(&self) -> String { + self.program.namespace(&self.state) + } + + fn subscription(&self) -> iced::Subscription { + self.program.subscription(&self.state) + } + + fn theme(&self) -> Self::Theme { + self.program.theme(&self.state) + } + + fn style(&self, theme: &Self::Theme) -> crate::Appearance { + self.program.style(&self.state, theme) + } + + fn scale_factor(&self) -> f64 { + self.program.scale_factor(&self.state) + } + } + + let real_settings = Settings { + flags: (self, initialize), + id: settings.id, + default_font: settings.default_font, + layer_settings: settings.layer_settings, + fonts: settings.fonts, + default_text_size: settings.default_text_size, + antialiasing: settings.antialiasing, + virtual_keyboard_support: settings.virtual_keyboard_support, + }; + #[allow(clippy::needless_update)] + let renderer_settings = iced_graphics::Settings { + default_font: settings.default_font, + default_text_size: settings.default_text_size, + antialiasing: if settings.antialiasing { + Some(iced_graphics::Antialiasing::MSAAx4) + } else { + None + }, + ..iced_graphics::Settings::default() + }; + + crate::application::run::< + Instance, + Self::Executor, + ::Compositor, + >(real_settings, renderer_settings) + } + + fn run(self, settings: MainSettings) -> Result + where + Self: 'static, + Self::State: Default, + { + self.run_with(settings, || (Self::State::default(), Task::none())) + } +} + +pub trait NameSpace { + /// Produces the title of the [`Application`]. + fn namespace(&self, state: &State) -> String; +} + +impl NameSpace for &'static str { + fn namespace(&self, _state: &State) -> String { + self.to_string() + } +} + +impl NameSpace for T +where + T: Fn(&State) -> String, +{ + fn namespace(&self, state: &State) -> String { + self(state) + } +} + +/// The update logic of some [`Application`]. +/// +/// This trait allows the [`application`] builder to take any closure that +/// returns any `Into>`. +pub trait Update { + /// Processes the message and updates the state of the [`Application`]. + fn update(&self, state: &mut State, message: Message) -> impl Into>; +} + +impl Update for () { + fn update(&self, _state: &mut State, _message: Message) -> impl Into> {} +} + +impl Update for T +where + T: Fn(&mut State, Message) -> C, + C: Into>, +{ + fn update(&self, state: &mut State, message: Message) -> impl Into> { + self(state, message) + } +} + +/// The view logic of some [`Application`]. +/// +/// This trait allows the [`application`] builder to take any closure that +/// returns any `Into>`. +pub trait View<'a, State, Message, Theme, Renderer> { + /// Produces the widget of the [`Application`]. + fn view(&self, state: &'a State) -> impl Into>; +} + +impl<'a, T, State, Message, Theme, Renderer, Widget> View<'a, State, Message, Theme, Renderer> for T +where + T: Fn(&'a State) -> Widget, + State: 'static, + Widget: Into>, +{ + fn view(&self, state: &'a State) -> impl Into> { + self(state) + } +} + +#[derive(Debug)] +pub struct SingleApplication { + raw: A, + settings: MainSettings, +} + +pub fn application( + namespace: impl NameSpace, + update: impl Update, + view: impl for<'a> self::View<'a, State, Message, Theme, Renderer>, +) -> SingleApplication> +where + State: 'static, + Message: 'static + TryInto + Send + std::fmt::Debug, + Theme: Default + DefaultStyle, + Renderer: self::Renderer, +{ + use std::marker::PhantomData; + struct Instance { + update: Update, + view: View, + _state: PhantomData, + _message: PhantomData, + _theme: PhantomData, + _renderer: PhantomData, + } + impl Program + for Instance + where + Message: + 'static + TryInto + Send + std::fmt::Debug, + Theme: Default + DefaultStyle, + Renderer: self::Renderer, + Update: self::Update, + View: for<'a> self::View<'a, State, Message, Theme, Renderer>, + { + type State = State; + type Renderer = Renderer; + type Message = Message; + type Theme = Theme; + type Executor = iced_futures::backend::default::Executor; + + fn update(&self, state: &mut Self::State, message: Self::Message) -> Task { + self.update.update(state, message).into() + } + + fn view<'a>( + &self, + state: &'a Self::State, + ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { + self.view.view(state).into() + } + } + SingleApplication { + raw: Instance { + update, + view, + _state: PhantomData, + _message: PhantomData, + _theme: PhantomData, + _renderer: PhantomData, + }, + settings: MainSettings::default(), + } + .namespace(namespace) +} + +pub fn with_executor( + program: P, +) -> impl Program { + use std::marker::PhantomData; + + struct WithExecutor { + program: P, + executor: PhantomData, + } + + impl Program for WithExecutor + where + E: iced_futures::Executor, + { + type State = P::State; + type Message = P::Message; + type Theme = P::Theme; + type Renderer = P::Renderer; + type Executor = E; + + fn namespace(&self, state: &Self::State) -> String { + self.program.namespace(state) + } + + fn update(&self, state: &mut Self::State, message: Self::Message) -> Task { + self.program.update(state, message) + } + + fn view<'a>( + &self, + state: &'a Self::State, + ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { + self.program.view(state) + } + + fn subscription(&self, state: &Self::State) -> iced::Subscription { + self.program.subscription(state) + } + + fn theme(&self, state: &Self::State) -> Self::Theme { + self.program.theme(state) + } + + fn style(&self, state: &Self::State, theme: &Self::Theme) -> crate::Appearance { + self.program.style(state, theme) + } + + fn scale_factor(&self, state: &Self::State) -> f64 { + self.program.scale_factor(state) + } + } + + WithExecutor { + program, + executor: PhantomData::, + } +} + +fn with_namespace( + program: P, + namespace: impl Fn(&P::State) -> String, +) -> impl Program { + struct WithNamespace { + program: P, + namespace: NameSpace, + } + impl Program for WithNamespace + where + P: Program, + Namespace: Fn(&P::State) -> String, + { + type State = P::State; + type Message = P::Message; + type Theme = P::Theme; + type Renderer = P::Renderer; + type Executor = P::Executor; + + fn namespace(&self, state: &Self::State) -> String { + (self.namespace)(state) + } + + fn update(&self, state: &mut Self::State, message: Self::Message) -> Task { + self.program.update(state, message) + } + + fn view<'a>( + &self, + state: &'a Self::State, + ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { + self.program.view(state) + } + + fn theme(&self, state: &Self::State) -> Self::Theme { + self.program.theme(state) + } + + fn subscription(&self, state: &Self::State) -> iced::Subscription { + self.program.subscription(state) + } + + fn style(&self, state: &Self::State, theme: &Self::Theme) -> crate::Appearance { + self.program.style(state, theme) + } + + fn scale_factor(&self, state: &Self::State) -> f64 { + self.program.scale_factor(state) + } + } + + WithNamespace { program, namespace } +} + +pub fn with_subscription( + program: P, + f: impl Fn(&P::State) -> iced::Subscription, +) -> impl Program { + struct WithSubscription { + program: P, + subscription: F, + } + + impl Program for WithSubscription + where + F: Fn(&P::State) -> iced::Subscription, + { + type State = P::State; + type Message = P::Message; + type Theme = P::Theme; + type Renderer = P::Renderer; + type Executor = P::Executor; + + fn subscription(&self, state: &Self::State) -> iced::Subscription { + (self.subscription)(state) + } + + fn update(&self, state: &mut Self::State, message: Self::Message) -> Task { + self.program.update(state, message) + } + + fn view<'a>( + &self, + state: &'a Self::State, + ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { + self.program.view(state) + } + + fn namespace(&self, state: &Self::State) -> String { + self.program.namespace(state) + } + + fn theme(&self, state: &Self::State) -> Self::Theme { + self.program.theme(state) + } + + fn style(&self, state: &Self::State, theme: &Self::Theme) -> crate::Appearance { + self.program.style(state, theme) + } + + fn scale_factor(&self, state: &Self::State) -> f64 { + self.program.scale_factor(state) + } + } + + WithSubscription { + program, + subscription: f, + } +} + +pub fn with_theme( + program: P, + f: impl Fn(&P::State) -> P::Theme, +) -> impl Program { + struct WithTheme { + program: P, + theme: F, + } + + impl Program for WithTheme + where + F: Fn(&P::State) -> P::Theme, + { + type State = P::State; + type Message = P::Message; + type Theme = P::Theme; + type Renderer = P::Renderer; + type Executor = P::Executor; + + fn theme(&self, state: &Self::State) -> Self::Theme { + (self.theme)(state) + } + + fn namespace(&self, state: &Self::State) -> String { + self.program.namespace(state) + } + + fn update(&self, state: &mut Self::State, message: Self::Message) -> Task { + self.program.update(state, message) + } + + fn view<'a>( + &self, + state: &'a Self::State, + ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { + self.program.view(state) + } + + fn subscription(&self, state: &Self::State) -> iced::Subscription { + self.program.subscription(state) + } + + fn style(&self, state: &Self::State, theme: &Self::Theme) -> crate::Appearance { + self.program.style(state, theme) + } + + fn scale_factor(&self, state: &Self::State) -> f64 { + self.program.scale_factor(state) + } + } + + WithTheme { program, theme: f } +} + +pub fn with_style( + program: P, + f: impl Fn(&P::State, &P::Theme) -> crate::Appearance, +) -> impl Program { + struct WithStyle { + program: P, + style: F, + } + + impl Program for WithStyle + where + F: Fn(&P::State, &P::Theme) -> crate::Appearance, + { + type State = P::State; + type Message = P::Message; + type Theme = P::Theme; + type Renderer = P::Renderer; + type Executor = P::Executor; + + fn style(&self, state: &Self::State, theme: &Self::Theme) -> crate::Appearance { + (self.style)(state, theme) + } + + fn namespace(&self, state: &Self::State) -> String { + self.program.namespace(state) + } + + fn update(&self, state: &mut Self::State, message: Self::Message) -> Task { + self.program.update(state, message) + } + + fn view<'a>( + &self, + state: &'a Self::State, + ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { + self.program.view(state) + } + + fn subscription(&self, state: &Self::State) -> iced::Subscription { + self.program.subscription(state) + } + + fn theme(&self, state: &Self::State) -> Self::Theme { + self.program.theme(state) + } + + fn scale_factor(&self, state: &Self::State) -> f64 { + self.program.scale_factor(state) + } + } + + WithStyle { program, style: f } +} + +pub fn with_scale_factor( + program: P, + f: impl Fn(&P::State) -> f64, +) -> impl Program { + struct WithScaleFactor { + program: P, + scale_factor: F, + } + + impl Program for WithScaleFactor + where + F: Fn(&P::State) -> f64, + { + type State = P::State; + type Message = P::Message; + type Theme = P::Theme; + type Renderer = P::Renderer; + type Executor = P::Executor; + + fn namespace(&self, state: &Self::State) -> String { + self.program.namespace(state) + } + + fn update(&self, state: &mut Self::State, message: Self::Message) -> Task { + self.program.update(state, message) + } + + fn view<'a>( + &self, + state: &'a Self::State, + ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { + self.program.view(state) + } + + fn subscription(&self, state: &Self::State) -> iced::Subscription { + self.program.subscription(state) + } + + fn theme(&self, state: &Self::State) -> Self::Theme { + self.program.theme(state) + } + + fn style(&self, state: &Self::State, theme: &Self::Theme) -> crate::Appearance { + self.program.style(state, theme) + } + + fn scale_factor(&self, state: &Self::State) -> f64 { + (self.scale_factor)(state) + } + } + + WithScaleFactor { + program, + scale_factor: f, + } +} + +impl SingleApplication

{ + pub fn run(self) -> Result + where + Self: 'static, + P::State: Default, + { + self.raw.run(self.settings) + } + + pub fn run_with(self, initialize: I) -> Result + where + Self: 'static, + I: FnOnce() -> (P::State, Task) + 'static, + { + self.raw.run_with(self.settings, initialize) + } + pub fn settings(self, settings: MainSettings) -> Self { + Self { settings, ..self } + } + + /// Sets the [`Settings::antialiasing`] of the [`Application`]. + pub fn antialiasing(self, antialiasing: bool) -> Self { + Self { + settings: MainSettings { + antialiasing, + ..self.settings + }, + ..self + } + } + + /// Sets the default [`Font`] of the [`Application`]. + pub fn default_font(self, default_font: Font) -> Self { + Self { + settings: MainSettings { + default_font, + ..self.settings + }, + ..self + } + } + + pub fn layer_settings(self, layer_settings: LayerShellSettings) -> Self { + Self { + settings: MainSettings { + layer_settings, + ..self.settings + }, + ..self + } + } + + /// Adds a font to the list of fonts that will be loaded at the start of the [`Application`]. + pub fn font(mut self, font: impl Into>) -> Self { + self.settings.fonts.push(font.into()); + self + } + + pub fn namespace( + self, + namespace: impl NameSpace, + ) -> SingleApplication> + { + SingleApplication { + raw: with_namespace(self.raw, move |state| namespace.namespace(state)), + settings: self.settings, + } + } + /// Sets the style logic of the [`Application`]. + pub fn style( + self, + f: impl Fn(&P::State, &P::Theme) -> crate::Appearance, + ) -> SingleApplication> + { + SingleApplication { + raw: with_style(self.raw, f), + settings: self.settings, + } + } + /// Sets the subscription logic of the [`Application`]. + pub fn subscription( + self, + f: impl Fn(&P::State) -> iced::Subscription, + ) -> SingleApplication> + { + SingleApplication { + raw: with_subscription(self.raw, f), + settings: self.settings, + } + } + + /// Sets the theme logic of the [`Application`]. + pub fn theme( + self, + f: impl Fn(&P::State) -> P::Theme, + ) -> SingleApplication> + { + SingleApplication { + raw: with_theme(self.raw, f), + settings: self.settings, + } + } + + /// Sets the scale factor of the [`Application`]. + pub fn scale_factor( + self, + f: impl Fn(&P::State) -> f64, + ) -> SingleApplication> + { + SingleApplication { + raw: with_scale_factor(self.raw, f), + settings: self.settings, + } + } + /// Sets the executor of the [`Application`]. + pub fn executor( + self, + ) -> SingleApplication> + where + E: iced_futures::Executor, + { + SingleApplication { + raw: with_executor::(self.raw), + settings: self.settings, + } + } +} diff --git a/iced_layershell/src/build_pattern/daemon.rs b/iced_layershell/src/build_pattern/daemon.rs new file mode 100644 index 0000000..f4853f3 --- /dev/null +++ b/iced_layershell/src/build_pattern/daemon.rs @@ -0,0 +1,1010 @@ +use std::borrow::Cow; + +use iced::Font; +use iced::{Element, Task}; + +use crate::actions::{IsSingleton, LayershellCustomActionsWithIdAndInfo}; + +use crate::settings::LayerShellSettings; +use crate::DefaultStyle; + +use super::Renderer; +use crate::Settings; + +use crate::Result; + +use super::MainSettings; + +// layershell application +pub trait Program: Sized { + /// The [`Executor`] that will run commands and subscriptions. + /// + /// The [default executor] can be a good starting point! + /// + /// [`Executor`]: Self::Executor + /// [default executor]: iced::executor::Default + type Executor: iced::Executor; + type State; + type Renderer: Renderer; + + type WindowInfo: Clone + PartialEq + IsSingleton; + /// The type of __messages__ your [`Application`] will produce. + type Message: std::fmt::Debug + + Send + + 'static + + TryInto, Error = Self::Message>; + + /// The theme of your [`Application`]. + type Theme: Default + DefaultStyle; + fn id_info( + &self, + _state: &Self::State, + _id: iced_core::window::Id, + ) -> Option { + None + } + + fn set_id_info( + &self, + _state: &mut Self::State, + _id: iced_core::window::Id, + _info: Self::WindowInfo, + ) { + } + fn remove_id(&self, _state: &mut Self::State, _id: iced_core::window::Id) {} + + /// Initializes the [`Application`] with the flags provided to + /// [`run`] as part of the [`Settings`]. + /// + /// Here is where you should return the initial state of your app. + /// + /// Additionally, you can return a [`Task`] if you need to perform some + /// async action in the background on startup. This is useful if you want to + /// load state from a file, perform an initial HTTP request, etc. + /// + /// + /// This title can be dynamic! The runtime will automatically update the + /// title of your application when necessary. + fn namespace(&self, _state: &Self::State) -> String { + "A cool iced application".to_string() + } + + /// Handles a __message__ and updates the state of the [`Application`]. + /// + /// This is where you define your __update logic__. All the __messages__, + /// produced by either user interactions or commands, will be handled by + /// this method. + /// + /// Any [`Task`] returned will be executed immediately in the background. + fn update(&self, state: &mut Self::State, message: Self::Message) -> Task; + + /// Returns the widgets to display in the [`Application`]. + /// + /// These widgets can produce __messages__ based on user interaction. + fn view<'a>( + &self, + state: &'a Self::State, + _window: iced_core::window::Id, + ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer>; + + /// Returns the current [`Theme`] of the [`Application`]. + /// + /// [`Theme`]: Self::Theme + fn theme(&self, _state: &Self::State) -> Self::Theme { + Self::Theme::default() + } + + /// Returns the current `Style` of the [`Theme`]. + /// + /// [`Theme`]: Self::Theme + fn style(&self, _state: &Self::State, theme: &Self::Theme) -> crate::Appearance { + theme.default_style() + } + + /// Returns the event [`Subscription`] for the current state of the + /// application. + /// + /// A [`Subscription`] will be kept alive as long as you keep returning it, + /// and the __messages__ produced will be handled by + /// [`update`](#tymethod.update). + /// + /// By default, this method returns an empty [`Subscription`]. + fn subscription(&self, _state: &Self::State) -> iced::Subscription { + iced::Subscription::none() + } + + /// Returns the scale factor of the [`Application`]. + /// + /// It can be used to dynamically control the size of the UI at runtime + /// (i.e. zooming). + /// + /// For instance, a scale factor of `2.0` will make widgets twice as big, + /// while a scale factor of `0.5` will shrink them to half their size. + /// + /// By default, it returns `1.0`. + fn scale_factor(&self, _state: &Self::State, _window: iced_core::window::Id) -> f64 { + 1.0 + } + + fn run_with(self, settings: MainSettings, initialize: I) -> Result + where + Self: 'static, + I: FnOnce() -> (Self::State, Task) + 'static, + { + use std::marker::PhantomData; + struct Instance { + program: P, + state: P::State, + _initialize: PhantomData, + } + + impl (P::State, Task)> + iced_runtime::multi_window::Program for Instance + { + type Message = P::Message; + type Theme = P::Theme; + type Renderer = P::Renderer; + fn update(&mut self, message: Self::Message) -> Task { + self.program.update(&mut self.state, message) + } + + fn view( + &self, + window: iced_core::window::Id, + ) -> crate::Element<'_, Self::Message, Self::Theme, Self::Renderer> { + self.program.view(&self.state, window) + } + } + + impl (P::State, Task)> + crate::multi_window::Application for Instance + { + type Flags = (P, I); + type WindowInfo = P::WindowInfo; + + fn new((program, initialize): Self::Flags) -> (Self, Task) { + let (state, task) = initialize(); + + ( + Self { + program, + state, + _initialize: PhantomData, + }, + task, + ) + } + + fn id_info(&self, id: iced_core::window::Id) -> Option { + self.program.id_info(&self.state, id) + } + fn set_id_info(&mut self, id: iced_core::window::Id, info: Self::WindowInfo) { + self.program.set_id_info(&mut self.state, id, info) + } + fn remove_id(&mut self, id: iced_core::window::Id) { + self.program.remove_id(&mut self.state, id) + } + fn namespace(&self) -> String { + self.program.namespace(&self.state) + } + + fn subscription(&self) -> iced::Subscription { + self.program.subscription(&self.state) + } + + fn theme(&self) -> Self::Theme { + self.program.theme(&self.state) + } + + fn style(&self, theme: &Self::Theme) -> crate::Appearance { + self.program.style(&self.state, theme) + } + + fn scale_factor(&self, window: iced_core::window::Id) -> f64 { + self.program.scale_factor(&self.state, window) + } + } + + let real_settings = Settings { + flags: (self, initialize), + id: settings.id, + default_font: settings.default_font, + layer_settings: settings.layer_settings, + fonts: settings.fonts, + default_text_size: settings.default_text_size, + antialiasing: settings.antialiasing, + virtual_keyboard_support: settings.virtual_keyboard_support, + }; + #[allow(clippy::needless_update)] + let renderer_settings = iced_graphics::Settings { + default_font: settings.default_font, + default_text_size: settings.default_text_size, + antialiasing: if settings.antialiasing { + Some(iced_graphics::Antialiasing::MSAAx4) + } else { + None + }, + ..iced_graphics::Settings::default() + }; + + crate::multi_window::run::< + Instance, + Self::Executor, + ::Compositor, + >(real_settings, renderer_settings) + } + + fn run(self, settings: MainSettings) -> Result + where + Self: 'static, + Self::State: Default, + { + self.run_with(settings, || (Self::State::default(), Task::none())) + } +} + +pub trait NameSpace { + /// Produces the title of the [`Application`]. + fn namespace(&self, state: &State) -> String; +} + +impl NameSpace for &'static str { + fn namespace(&self, _state: &State) -> String { + self.to_string() + } +} + +impl NameSpace for T +where + T: Fn(&State) -> String, +{ + fn namespace(&self, state: &State) -> String { + self(state) + } +} + +/// The update logic of some [`Application`]. +/// +/// This trait allows the [`application`] builder to take any closure that +/// returns any `Into>`. +pub trait Update { + /// Processes the message and updates the state of the [`Application`]. + fn update(&self, state: &mut State, message: Message) -> impl Into>; +} + +impl Update for () { + fn update(&self, _state: &mut State, _message: Message) -> impl Into> {} +} + +pub trait IdInfo { + fn id_info(&self, state: &State, id: iced_core::window::Id) -> Option; +} + +impl IdInfo for T +where + T: Fn(&State, iced_core::window::Id) -> Option, +{ + fn id_info(&self, state: &State, id: iced_core::window::Id) -> Option { + self(state, id) + } +} +pub trait SetIdInfo { + fn set_id_info(&self, state: &mut State, id: iced_core::window::Id, window_info: WindowInfo); +} + +impl SetIdInfo for T +where + T: Fn(&mut State, iced_core::window::Id, WindowInfo), +{ + fn set_id_info(&self, state: &mut State, id: iced_core::window::Id, window_info: WindowInfo) { + self(state, id, window_info) + } +} +pub trait RemoveId { + fn remove_id(&self, state: &mut State, id: iced_core::window::Id); +} + +impl RemoveId for T +where + T: Fn(&mut State, iced_core::window::Id), +{ + fn remove_id(&self, state: &mut State, id: iced_core::window::Id) { + self(state, id) + } +} + +impl Update for T +where + T: Fn(&mut State, Message) -> C, + C: Into>, +{ + fn update(&self, state: &mut State, message: Message) -> impl Into> { + self(state, message) + } +} + +/// The view logic of some [`Application`]. +/// +/// This trait allows the [`application`] builder to take any closure that +/// returns any `Into>`. +pub trait View<'a, State, Message, Theme, Renderer> { + /// Produces the widget of the [`Application`]. + fn view( + &self, + state: &'a State, + window: iced_core::window::Id, + ) -> impl Into>; +} + +impl<'a, T, State, Message, Theme, Renderer, Widget> View<'a, State, Message, Theme, Renderer> for T +where + T: Fn(&'a State, iced_core::window::Id) -> Widget, + State: 'static, + Widget: Into>, +{ + fn view( + &self, + state: &'a State, + window: iced_core::window::Id, + ) -> impl Into> { + self(state, window) + } +} + +#[derive(Debug)] +pub struct Daemon { + raw: A, + settings: MainSettings, +} + +pub fn daemon( + namespace: impl NameSpace, + update: impl Update, + view: impl for<'a> self::View<'a, State, Message, Theme, Renderer>, + id_info: impl IdInfo, + set_id_info: impl SetIdInfo, + remove_id: impl RemoveId, +) -> Daemon> +where + State: 'static, + WindowInfo: Clone + PartialEq + IsSingleton, + Message: 'static + + TryInto, Error = Message> + + Send + + std::fmt::Debug, + Theme: Default + DefaultStyle, + Renderer: self::Renderer, +{ + use std::marker::PhantomData; + struct Instance< + State, + Message, + Theme, + Renderer, + Update, + View, + IdInfo, + SetIdInfo, + RemoveId, + WindowInfo, + > { + update: Update, + view: View, + id_info: IdInfo, + set_id_info: SetIdInfo, + remove_id: RemoveId, + _state: PhantomData, + _message: PhantomData, + _theme: PhantomData, + _renderer: PhantomData, + _windowinfo: PhantomData, + } + impl< + State, + Message, + Theme, + Renderer, + Update, + View, + WindowInfo, + IdInfo, + SetIdInfo, + RemoveId, + > Program + for Instance< + State, + Message, + Theme, + Renderer, + Update, + View, + IdInfo, + SetIdInfo, + RemoveId, + WindowInfo, + > + where + WindowInfo: Clone + PartialEq + IsSingleton, + Message: 'static + + TryInto, Error = Message> + + Send + + std::fmt::Debug, + Theme: Default + DefaultStyle, + IdInfo: self::IdInfo, + SetIdInfo: self::SetIdInfo, + RemoveId: self::RemoveId, + Renderer: self::Renderer, + Update: self::Update, + View: for<'a> self::View<'a, State, Message, Theme, Renderer>, + { + type State = State; + type Renderer = Renderer; + type Message = Message; + type Theme = Theme; + type WindowInfo = WindowInfo; + type Executor = iced_futures::backend::default::Executor; + + fn update(&self, state: &mut Self::State, message: Self::Message) -> Task { + self.update.update(state, message).into() + } + fn id_info( + &self, + state: &Self::State, + id: iced_core::window::Id, + ) -> Option { + self.id_info.id_info(state, id) + } + fn remove_id(&self, state: &mut Self::State, id: iced_core::window::Id) { + self.remove_id.remove_id(state, id) + } + fn set_id_info( + &self, + state: &mut Self::State, + id: iced_core::window::Id, + info: Self::WindowInfo, + ) { + self.set_id_info.set_id_info(state, id, info) + } + + fn view<'a>( + &self, + state: &'a Self::State, + window: iced_core::window::Id, + ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { + self.view.view(state, window).into() + } + } + Daemon { + raw: Instance { + update, + view, + id_info, + set_id_info, + remove_id, + + _state: PhantomData, + _message: PhantomData, + _theme: PhantomData, + _renderer: PhantomData, + _windowinfo: PhantomData, + }, + settings: MainSettings::default(), + } + .namespace(namespace) +} + +fn with_namespace( + program: P, + namespace: impl Fn(&P::State) -> String, +) -> impl Program +{ + struct WithNamespace { + program: P, + namespace: NameSpace, + } + impl Program for WithNamespace + where + P: Program, + Namespace: Fn(&P::State) -> String, + { + type State = P::State; + type Message = P::Message; + type Theme = P::Theme; + type Renderer = P::Renderer; + type Executor = P::Executor; + type WindowInfo = P::WindowInfo; + + fn namespace(&self, state: &Self::State) -> String { + (self.namespace)(state) + } + + fn update(&self, state: &mut Self::State, message: Self::Message) -> Task { + self.program.update(state, message) + } + + fn view<'a>( + &self, + state: &'a Self::State, + window: iced_core::window::Id, + ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { + self.program.view(state, window) + } + + fn theme(&self, state: &Self::State) -> Self::Theme { + self.program.theme(state) + } + + fn subscription(&self, state: &Self::State) -> iced::Subscription { + self.program.subscription(state) + } + + fn style(&self, state: &Self::State, theme: &Self::Theme) -> crate::Appearance { + self.program.style(state, theme) + } + + fn scale_factor(&self, state: &Self::State, window: iced_core::window::Id) -> f64 { + self.program.scale_factor(state, window) + } + } + + WithNamespace { program, namespace } +} + +pub fn with_subscription( + program: P, + f: impl Fn(&P::State) -> iced::Subscription, +) -> impl Program +{ + struct WithSubscription { + program: P, + subscription: F, + } + + impl Program for WithSubscription + where + F: Fn(&P::State) -> iced::Subscription, + { + type State = P::State; + type Message = P::Message; + type Theme = P::Theme; + type Renderer = P::Renderer; + type Executor = P::Executor; + type WindowInfo = P::WindowInfo; + + fn subscription(&self, state: &Self::State) -> iced::Subscription { + (self.subscription)(state) + } + + fn update(&self, state: &mut Self::State, message: Self::Message) -> Task { + self.program.update(state, message) + } + + fn view<'a>( + &self, + state: &'a Self::State, + window: iced_core::window::Id, + ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { + self.program.view(state, window) + } + + fn namespace(&self, state: &Self::State) -> String { + self.program.namespace(state) + } + + fn theme(&self, state: &Self::State) -> Self::Theme { + self.program.theme(state) + } + + fn style(&self, state: &Self::State, theme: &Self::Theme) -> crate::Appearance { + self.program.style(state, theme) + } + + fn scale_factor(&self, state: &Self::State, window: iced_core::window::Id) -> f64 { + self.program.scale_factor(state, window) + } + } + + WithSubscription { + program, + subscription: f, + } +} + +pub fn with_theme( + program: P, + f: impl Fn(&P::State) -> P::Theme, +) -> impl Program +{ + struct WithTheme { + program: P, + theme: F, + } + + impl Program for WithTheme + where + F: Fn(&P::State) -> P::Theme, + { + type State = P::State; + type Message = P::Message; + type Theme = P::Theme; + type Renderer = P::Renderer; + type Executor = P::Executor; + type WindowInfo = P::WindowInfo; + + fn theme(&self, state: &Self::State) -> Self::Theme { + (self.theme)(state) + } + + fn namespace(&self, state: &Self::State) -> String { + self.program.namespace(state) + } + + fn update(&self, state: &mut Self::State, message: Self::Message) -> Task { + self.program.update(state, message) + } + + fn view<'a>( + &self, + state: &'a Self::State, + window: iced_core::window::Id, + ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { + self.program.view(state, window) + } + + fn subscription(&self, state: &Self::State) -> iced::Subscription { + self.program.subscription(state) + } + + fn style(&self, state: &Self::State, theme: &Self::Theme) -> crate::Appearance { + self.program.style(state, theme) + } + + fn scale_factor(&self, state: &Self::State, window: iced_core::window::Id) -> f64 { + self.program.scale_factor(state, window) + } + } + + WithTheme { program, theme: f } +} + +pub fn with_style( + program: P, + f: impl Fn(&P::State, &P::Theme) -> crate::Appearance, +) -> impl Program +{ + struct WithStyle { + program: P, + style: F, + } + + impl Program for WithStyle + where + F: Fn(&P::State, &P::Theme) -> crate::Appearance, + { + type State = P::State; + type Message = P::Message; + type Theme = P::Theme; + type Renderer = P::Renderer; + type Executor = P::Executor; + type WindowInfo = P::WindowInfo; + + fn style(&self, state: &Self::State, theme: &Self::Theme) -> crate::Appearance { + (self.style)(state, theme) + } + + fn namespace(&self, state: &Self::State) -> String { + self.program.namespace(state) + } + + fn update(&self, state: &mut Self::State, message: Self::Message) -> Task { + self.program.update(state, message) + } + + fn view<'a>( + &self, + state: &'a Self::State, + window: iced_core::window::Id, + ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { + self.program.view(state, window) + } + + fn subscription(&self, state: &Self::State) -> iced::Subscription { + self.program.subscription(state) + } + + fn theme(&self, state: &Self::State) -> Self::Theme { + self.program.theme(state) + } + + fn scale_factor(&self, state: &Self::State, window: iced_core::window::Id) -> f64 { + self.program.scale_factor(state, window) + } + } + + WithStyle { program, style: f } +} + +pub fn with_scale_factor( + program: P, + f: impl Fn(&P::State, iced_core::window::Id) -> f64, +) -> impl Program +{ + struct WithScaleFactor { + program: P, + scale_factor: F, + } + + impl Program for WithScaleFactor + where + F: Fn(&P::State, iced_core::window::Id) -> f64, + { + type State = P::State; + type Message = P::Message; + type Theme = P::Theme; + type Renderer = P::Renderer; + type Executor = P::Executor; + type WindowInfo = P::WindowInfo; + + fn namespace(&self, state: &Self::State) -> String { + self.program.namespace(state) + } + + fn update(&self, state: &mut Self::State, message: Self::Message) -> Task { + self.program.update(state, message) + } + + fn view<'a>( + &self, + state: &'a Self::State, + window: iced_core::window::Id, + ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { + self.program.view(state, window) + } + + fn subscription(&self, state: &Self::State) -> iced::Subscription { + self.program.subscription(state) + } + + fn theme(&self, state: &Self::State) -> Self::Theme { + self.program.theme(state) + } + + fn style(&self, state: &Self::State, theme: &Self::Theme) -> crate::Appearance { + self.program.style(state, theme) + } + + fn scale_factor(&self, state: &Self::State, window: iced_core::window::Id) -> f64 { + (self.scale_factor)(state, window) + } + } + + WithScaleFactor { + program, + scale_factor: f, + } +} + +pub fn with_executor( + program: P, +) -> impl Program +{ + use std::marker::PhantomData; + + struct WithExecutor { + program: P, + executor: PhantomData, + } + + impl Program for WithExecutor + where + E: iced_futures::Executor, + { + type State = P::State; + type Message = P::Message; + type Theme = P::Theme; + type Renderer = P::Renderer; + type Executor = E; + type WindowInfo = P::WindowInfo; + + fn namespace(&self, state: &Self::State) -> String { + self.program.namespace(state) + } + + fn update(&self, state: &mut Self::State, message: Self::Message) -> Task { + self.program.update(state, message) + } + + fn view<'a>( + &self, + state: &'a Self::State, + window: iced_core::window::Id, + ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { + self.program.view(state, window) + } + + fn subscription(&self, state: &Self::State) -> iced::Subscription { + self.program.subscription(state) + } + + fn theme(&self, state: &Self::State) -> Self::Theme { + self.program.theme(state) + } + + fn style(&self, state: &Self::State, theme: &Self::Theme) -> crate::Appearance { + self.program.style(state, theme) + } + + fn scale_factor(&self, state: &Self::State, window: iced_core::window::Id) -> f64 { + self.program.scale_factor(state, window) + } + } + + WithExecutor { + program, + executor: PhantomData::, + } +} +impl Daemon

{ + pub fn run(self) -> Result + where + Self: 'static, + P::State: Default, + { + self.raw.run(self.settings) + } + + pub fn run_with(self, initialize: I) -> Result + where + Self: 'static, + I: FnOnce() -> (P::State, Task) + 'static, + { + self.raw.run_with(self.settings, initialize) + } + pub fn settings(self, settings: MainSettings) -> Self { + Self { settings, ..self } + } + + /// Sets the [`Settings::antialiasing`] of the [`Application`]. + pub fn antialiasing(self, antialiasing: bool) -> Self { + Self { + settings: MainSettings { + antialiasing, + ..self.settings + }, + ..self + } + } + + /// Sets the default [`Font`] of the [`Application`]. + pub fn default_font(self, default_font: Font) -> Self { + Self { + settings: MainSettings { + default_font, + ..self.settings + }, + ..self + } + } + + pub fn layer_settings(self, layer_settings: LayerShellSettings) -> Self { + Self { + settings: MainSettings { + layer_settings, + ..self.settings + }, + ..self + } + } + + /// Adds a font to the list of fonts that will be loaded at the start of the [`Application`]. + pub fn font(mut self, font: impl Into>) -> Self { + self.settings.fonts.push(font.into()); + self + } + + pub fn namespace( + self, + namespace: impl NameSpace, + ) -> Daemon< + impl Program< + State = P::State, + Message = P::Message, + Theme = P::Theme, + WindowInfo = P::WindowInfo, + >, + > { + Daemon { + raw: with_namespace(self.raw, move |state| namespace.namespace(state)), + settings: self.settings, + } + } + /// Sets the style logic of the [`Application`]. + pub fn style( + self, + f: impl Fn(&P::State, &P::Theme) -> crate::Appearance, + ) -> Daemon< + impl Program< + State = P::State, + Message = P::Message, + Theme = P::Theme, + WindowInfo = P::WindowInfo, + >, + > { + Daemon { + raw: with_style(self.raw, f), + settings: self.settings, + } + } + /// Sets the subscription logic of the [`Application`]. + pub fn subscription( + self, + f: impl Fn(&P::State) -> iced::Subscription, + ) -> Daemon< + impl Program< + State = P::State, + Message = P::Message, + Theme = P::Theme, + WindowInfo = P::WindowInfo, + >, + > { + Daemon { + raw: with_subscription(self.raw, f), + settings: self.settings, + } + } + + /// Sets the theme logic of the [`Application`]. + pub fn theme( + self, + f: impl Fn(&P::State) -> P::Theme, + ) -> Daemon< + impl Program< + State = P::State, + Message = P::Message, + Theme = P::Theme, + WindowInfo = P::WindowInfo, + >, + > { + Daemon { + raw: with_theme(self.raw, f), + settings: self.settings, + } + } + + /// Sets the scale factor of the [`Application`]. + pub fn scale_factor( + self, + f: impl Fn(&P::State, iced_core::window::Id) -> f64, + ) -> Daemon< + impl Program< + State = P::State, + Message = P::Message, + Theme = P::Theme, + WindowInfo = P::WindowInfo, + >, + > { + Daemon { + raw: with_scale_factor(self.raw, f), + settings: self.settings, + } + } + /// Sets the executor of the [`Application`]. + pub fn executor( + self, + ) -> Daemon< + impl Program< + State = P::State, + Message = P::Message, + Theme = P::Theme, + WindowInfo = P::WindowInfo, + >, + > + where + E: iced_futures::Executor, + { + Daemon { + raw: with_executor::(self.raw), + settings: self.settings, + } + } +} diff --git a/iced_layershell/src/lib.rs b/iced_layershell/src/lib.rs index 0dc8853..3496aef 100644 --- a/iced_layershell/src/lib.rs +++ b/iced_layershell/src/lib.rs @@ -1,6 +1,7 @@ #![doc = include_str!("../README.md")] pub mod actions; pub mod application; +pub mod build_pattern; mod clipboard; mod conversion; mod error; @@ -34,6 +35,8 @@ use iced_futures::Subscription; pub use sandbox::LayerShellSandbox; +pub type Result = std::result::Result<(), error::Error>; + /// The appearance of a program. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Appearance { @@ -165,7 +168,7 @@ pub trait Application: Sized { /// [`Error`] during startup. /// /// [`Error`]: crate::Error - fn run(settings: Settings) -> Result<(), error::Error> + fn run(settings: Settings) -> Result where Self: 'static, Self::Message: 'static + TryInto, @@ -352,7 +355,7 @@ pub trait MultiApplication: Sized { /// [`Error`] during startup. /// /// [`Error`]: crate::Error - fn run(settings: Settings) -> Result<(), error::Error> + fn run(settings: Settings) -> Result where Self: 'static, ::WindowInfo: Clone + PartialEq + IsSingleton,