cosmic/app/
multi_window.rs

1// Copyright 2024 System76 <info@system76.com>
2// SPDX-License-Identifier: MPL-2.0
3
4//! Create and run daemons that run in the background.
5//! Copied from iced 0.13, but adds optional initial window
6
7use iced::application;
8use iced::window;
9use iced::{
10    self, Program,
11    program::{self, with_style, with_subscription, with_theme, with_title},
12    runtime::{Appearance, DefaultStyle},
13};
14use iced::{Element, Result, Settings, Subscription, Task};
15
16use std::marker::PhantomData;
17
18pub(crate) struct Instance<State, Message, Theme, Renderer, Update, View, Executor> {
19    update: Update,
20    view: View,
21    _state: PhantomData<State>,
22    _message: PhantomData<Message>,
23    _theme: PhantomData<Theme>,
24    _renderer: PhantomData<Renderer>,
25    _executor: PhantomData<Executor>,
26}
27
28/// Creates an iced [`MultiWindow`] given its title, update, and view logic.
29pub fn multi_window<State, Message, Theme, Renderer, Executor>(
30    title: impl Title<State>,
31    update: impl application::Update<State, Message>,
32    view: impl for<'a> self::View<'a, State, Message, Theme, Renderer>,
33) -> MultiWindow<impl Program<State = State, Message = Message, Theme = Theme>>
34where
35    State: 'static,
36    Message: Send + std::fmt::Debug + 'static,
37    Theme: Default + DefaultStyle,
38    Renderer: program::Renderer,
39    Executor: iced::Executor,
40{
41    use std::marker::PhantomData;
42
43    impl<State, Message, Theme, Renderer, Update, View, Executor> Program
44        for Instance<State, Message, Theme, Renderer, Update, View, Executor>
45    where
46        Message: Send + std::fmt::Debug + 'static,
47        Theme: Default + DefaultStyle,
48        Renderer: program::Renderer,
49        Update: application::Update<State, Message>,
50        View: for<'a> self::View<'a, State, Message, Theme, Renderer>,
51        Executor: iced::Executor,
52    {
53        type State = State;
54        type Message = Message;
55        type Theme = Theme;
56        type Renderer = Renderer;
57        type Executor = Executor;
58
59        fn update(&self, state: &mut Self::State, message: Self::Message) -> Task<Self::Message> {
60            self.update.update(state, message).into()
61        }
62
63        fn view<'a>(
64            &self,
65            state: &'a Self::State,
66            window: window::Id,
67        ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> {
68            self.view.view(state, window).into()
69        }
70    }
71
72    MultiWindow {
73        raw: Instance {
74            update,
75            view,
76            _state: PhantomData,
77            _message: PhantomData,
78            _theme: PhantomData,
79            _renderer: PhantomData,
80            _executor: PhantomData::<Executor>,
81        },
82        settings: Settings::default(),
83        window: None,
84    }
85    .title(title)
86}
87
88/// The underlying definition and configuration of an iced daemon.
89///
90/// You can use this API to create and run iced applications
91/// step by step—without coupling your logic to a trait
92/// or a specific type.
93///
94/// You can create a [`MultiWindow`] with the [`daemon`] helper.
95#[derive(Debug)]
96pub struct MultiWindow<P: Program> {
97    raw: P,
98    settings: Settings,
99    window: Option<window::Settings>,
100}
101
102impl<P: Program> MultiWindow<P> {
103    #[cfg(any(feature = "winit", feature = "wayland"))]
104    /// Runs the [`MultiWindow`].
105    ///
106    /// The state of the [`MultiWindow`] must implement [`Default`].
107    /// If your state does not implement [`Default`], use [`run_with`]
108    /// instead.
109    ///
110    /// [`run_with`]: Self::run_with
111    pub fn run(self) -> Result
112    where
113        Self: 'static,
114        P::State: Default,
115    {
116        self.raw.run(self.settings, self.window)
117    }
118
119    #[cfg(any(feature = "winit", feature = "wayland"))]
120    /// Runs the [`MultiWindow`] with a closure that creates the initial state.
121    pub fn run_with<I>(self, initialize: I) -> Result
122    where
123        Self: 'static,
124        I: FnOnce() -> (P::State, Task<P::Message>) + 'static,
125    {
126        self.raw.run_with(self.settings, self.window, initialize)
127    }
128
129    /// Sets the [`Settings`] that will be used to run the [`MultiWindow`].
130    pub fn settings(self, settings: Settings) -> Self {
131        Self { settings, ..self }
132    }
133
134    /// Sets the [`Title`] of the [`MultiWindow`].
135    pub(crate) fn title(
136        self,
137        title: impl Title<P::State>,
138    ) -> MultiWindow<impl Program<State = P::State, Message = P::Message, Theme = P::Theme>> {
139        MultiWindow {
140            raw: with_title(self.raw, move |state, window| title.title(state, window)),
141            settings: self.settings,
142            window: self.window,
143        }
144    }
145
146    /// Sets the subscription logic of the [`MultiWindow`].
147    pub fn subscription(
148        self,
149        f: impl Fn(&P::State) -> Subscription<P::Message>,
150    ) -> MultiWindow<impl Program<State = P::State, Message = P::Message, Theme = P::Theme>> {
151        MultiWindow {
152            raw: with_subscription(self.raw, f),
153            settings: self.settings,
154            window: self.window,
155        }
156    }
157
158    /// Sets the theme logic of the [`MultiWindow`].
159    pub fn theme(
160        self,
161        f: impl Fn(&P::State, window::Id) -> P::Theme,
162    ) -> MultiWindow<impl Program<State = P::State, Message = P::Message, Theme = P::Theme>> {
163        MultiWindow {
164            raw: with_theme(self.raw, f),
165            settings: self.settings,
166            window: self.window,
167        }
168    }
169
170    /// Sets the style logic of the [`MultiWindow`].
171    pub fn style(
172        self,
173        f: impl Fn(&P::State, &P::Theme) -> Appearance,
174    ) -> MultiWindow<impl Program<State = P::State, Message = P::Message, Theme = P::Theme>> {
175        MultiWindow {
176            raw: with_style(self.raw, f),
177            settings: self.settings,
178            window: self.window,
179        }
180    }
181
182    /// Sets the window settings of the [`MultiWindow`].
183    pub fn window(self, window: window::Settings) -> Self {
184        Self {
185            raw: self.raw,
186            settings: self.settings,
187            window: Some(window),
188        }
189    }
190}
191
192/// The title logic of some [`MultiWindow`].
193///
194/// This trait is implemented both for `&static str` and
195/// any closure `Fn(&State, window::Id) -> String`.
196///
197/// This trait allows the [`daemon`] builder to take any of them.
198pub trait Title<State> {
199    /// Produces the title of the [`MultiWindow`].
200    fn title(&self, state: &State, window: window::Id) -> String;
201}
202
203impl<State> Title<State> for &'static str {
204    fn title(&self, _state: &State, _window: window::Id) -> String {
205        (*self).to_string()
206    }
207}
208
209impl<T, State> Title<State> for T
210where
211    T: Fn(&State, window::Id) -> String,
212{
213    fn title(&self, state: &State, window: window::Id) -> String {
214        self(state, window)
215    }
216}
217
218/// The view logic of some [`MultiWindow`].
219///
220/// This trait allows the [`daemon`] builder to take any closure that
221/// returns any `Into<Element<'_, Message>>`.
222pub trait View<'a, State, Message, Theme, Renderer> {
223    /// Produces the widget of the [`MultiWindow`].
224    fn view(
225        &self,
226        state: &'a State,
227        window: window::Id,
228    ) -> impl Into<Element<'a, Message, Theme, Renderer>>;
229}
230
231impl<'a, T, State, Message, Theme, Renderer, Widget> View<'a, State, Message, Theme, Renderer> for T
232where
233    T: Fn(&'a State, window::Id) -> Widget,
234    State: 'static,
235    Widget: Into<Element<'a, Message, Theme, Renderer>>,
236{
237    fn view(
238        &self,
239        state: &'a State,
240        window: window::Id,
241    ) -> impl Into<Element<'a, Message, Theme, Renderer>> {
242        self(state, window)
243    }
244}