iced/
daemon.rs

1//! Create and run daemons that run in the background.
2use crate::application;
3use crate::message;
4use crate::program::{self, Program};
5use crate::shell;
6use crate::theme;
7use crate::window;
8use crate::{Element, Executor, Font, Preset, Result, Settings, Subscription, Task, Theme};
9
10use iced_debug as debug;
11
12use std::borrow::Cow;
13
14/// Creates an iced [`Daemon`] given its boot, update, and view logic.
15///
16/// A [`Daemon`] will not open a window by default, but will run silently
17/// instead until a [`Task`] from [`window::open`] is returned by its update logic.
18///
19/// Furthermore, a [`Daemon`] will not stop running when all its windows are closed.
20/// In order to completely terminate a [`Daemon`], its process must be interrupted or
21/// its update logic must produce a [`Task`] from [`exit`].
22///
23/// [`exit`]: crate::exit
24pub fn daemon<State, Message, Theme, Renderer>(
25    boot: impl application::BootFn<State, Message>,
26    update: impl application::UpdateFn<State, Message>,
27    view: impl for<'a> ViewFn<'a, State, Message, Theme, Renderer>,
28) -> Daemon<impl Program<State = State, Message = Message, Theme = Theme>>
29where
30    State: 'static,
31    Message: Send + 'static,
32    Theme: theme::Base,
33    Renderer: program::Renderer,
34{
35    use std::marker::PhantomData;
36
37    struct Instance<State, Message, Theme, Renderer, Boot, Update, View> {
38        boot: Boot,
39        update: Update,
40        view: View,
41        _state: PhantomData<State>,
42        _message: PhantomData<Message>,
43        _theme: PhantomData<Theme>,
44        _renderer: PhantomData<Renderer>,
45    }
46
47    impl<State, Message, Theme, Renderer, Boot, Update, View> Program
48        for Instance<State, Message, Theme, Renderer, Boot, Update, View>
49    where
50        Message: Send + 'static,
51        Theme: theme::Base,
52        Renderer: program::Renderer,
53        Boot: application::BootFn<State, Message>,
54        Update: application::UpdateFn<State, Message>,
55        View: for<'a> self::ViewFn<'a, State, Message, Theme, Renderer>,
56    {
57        type State = State;
58        type Message = Message;
59        type Theme = Theme;
60        type Renderer = Renderer;
61        type Executor = iced_futures::backend::default::Executor;
62
63        fn name() -> &'static str {
64            let name = std::any::type_name::<State>();
65
66            name.split("::").next().unwrap_or("a_cool_daemon")
67        }
68
69        fn settings(&self) -> Settings {
70            Settings::default()
71        }
72
73        fn window(&self) -> Option<iced_core::window::Settings> {
74            None
75        }
76
77        fn boot(&self) -> (Self::State, Task<Self::Message>) {
78            self.boot.boot()
79        }
80
81        fn update(&self, state: &mut Self::State, message: Self::Message) -> Task<Self::Message> {
82            self.update.update(state, message)
83        }
84
85        fn view<'a>(
86            &self,
87            state: &'a Self::State,
88            window: window::Id,
89        ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> {
90            self.view.view(state, window)
91        }
92    }
93
94    Daemon {
95        raw: Instance {
96            boot,
97            update,
98            view,
99            _state: PhantomData,
100            _message: PhantomData,
101            _theme: PhantomData,
102            _renderer: PhantomData,
103        },
104        settings: Settings::default(),
105        presets: Vec::new(),
106    }
107}
108
109/// The underlying definition and configuration of an iced daemon.
110///
111/// You can use this API to create and run iced applications
112/// step by step—without coupling your logic to a trait
113/// or a specific type.
114///
115/// You can create a [`Daemon`] with the [`daemon`] helper.
116#[derive(Debug)]
117pub struct Daemon<P: Program> {
118    raw: P,
119    settings: Settings,
120    presets: Vec<Preset<P::State, P::Message>>,
121}
122
123impl<P: Program> Daemon<P> {
124    /// Runs the [`Daemon`].
125    pub fn run(self) -> Result
126    where
127        Self: 'static,
128        P::Message: message::MaybeDebug + message::MaybeClone,
129    {
130        #[cfg(feature = "debug")]
131        iced_debug::init(iced_debug::Metadata {
132            name: P::name(),
133            theme: None,
134            can_time_travel: cfg!(feature = "time-travel"),
135        });
136
137        #[cfg(feature = "tester")]
138        let program = iced_tester::attach(self);
139
140        #[cfg(all(feature = "debug", not(feature = "tester")))]
141        let program = iced_devtools::attach(self);
142
143        #[cfg(not(any(feature = "tester", feature = "debug")))]
144        let program = self;
145
146        Ok(shell::run(program)?)
147    }
148
149    /// Sets the [`Settings`] that will be used to run the [`Daemon`].
150    pub fn settings(self, settings: Settings) -> Self {
151        Self { settings, ..self }
152    }
153
154    /// Sets the [`Settings::antialiasing`] of the [`Daemon`].
155    pub fn antialiasing(self, antialiasing: bool) -> Self {
156        Self {
157            settings: Settings {
158                antialiasing,
159                ..self.settings
160            },
161            ..self
162        }
163    }
164
165    /// Sets the default [`Font`] of the [`Daemon`].
166    pub fn default_font(self, default_font: Font) -> Self {
167        Self {
168            settings: Settings {
169                default_font,
170                ..self.settings
171            },
172            ..self
173        }
174    }
175
176    /// Adds a font to the list of fonts that will be loaded at the start of the [`Daemon`].
177    pub fn font(mut self, font: impl Into<Cow<'static, [u8]>>) -> Self {
178        self.settings.fonts.push(font.into());
179        self
180    }
181
182    /// Sets the title of the [`Daemon`].
183    pub fn title(
184        self,
185        title: impl TitleFn<P::State>,
186    ) -> Daemon<impl Program<State = P::State, Message = P::Message, Theme = P::Theme>> {
187        Daemon {
188            raw: program::with_title(self.raw, move |state, window| title.title(state, window)),
189            settings: self.settings,
190            presets: self.presets,
191        }
192    }
193
194    /// Sets the subscription logic of the [`Daemon`].
195    pub fn subscription(
196        self,
197        f: impl Fn(&P::State) -> Subscription<P::Message>,
198    ) -> Daemon<impl Program<State = P::State, Message = P::Message, Theme = P::Theme>> {
199        Daemon {
200            raw: program::with_subscription(self.raw, f),
201            settings: self.settings,
202            presets: self.presets,
203        }
204    }
205
206    /// Sets the theme logic of the [`Daemon`].
207    pub fn theme(
208        self,
209        f: impl ThemeFn<P::State, P::Theme>,
210    ) -> Daemon<impl Program<State = P::State, Message = P::Message, Theme = P::Theme>> {
211        Daemon {
212            raw: program::with_theme(self.raw, move |state, window| f.theme(state, window)),
213            settings: self.settings,
214            presets: self.presets,
215        }
216    }
217
218    /// Sets the style logic of the [`Daemon`].
219    pub fn style(
220        self,
221        f: impl Fn(&P::State, &P::Theme) -> theme::Style,
222    ) -> Daemon<impl Program<State = P::State, Message = P::Message, Theme = P::Theme>> {
223        Daemon {
224            raw: program::with_style(self.raw, f),
225            settings: self.settings,
226            presets: self.presets,
227        }
228    }
229
230    /// Sets the scale factor of the [`Daemon`].
231    pub fn scale_factor(
232        self,
233        f: impl Fn(&P::State, window::Id) -> f32,
234    ) -> Daemon<impl Program<State = P::State, Message = P::Message, Theme = P::Theme>> {
235        Daemon {
236            raw: program::with_scale_factor(self.raw, f),
237            settings: self.settings,
238            presets: self.presets,
239        }
240    }
241
242    /// Sets the executor of the [`Daemon`].
243    pub fn executor<E>(
244        self,
245    ) -> Daemon<impl Program<State = P::State, Message = P::Message, Theme = P::Theme>>
246    where
247        E: Executor,
248    {
249        Daemon {
250            raw: program::with_executor::<P, E>(self.raw),
251            settings: self.settings,
252            presets: self.presets,
253        }
254    }
255
256    /// Sets the boot presets of the [`Daemon`].
257    ///
258    /// Presets can be used to override the default booting strategy
259    /// of your application during testing to create reproducible
260    /// environments.
261    pub fn presets(self, presets: impl IntoIterator<Item = Preset<P::State, P::Message>>) -> Self {
262        Self {
263            presets: presets.into_iter().collect(),
264            ..self
265        }
266    }
267}
268
269impl<P: Program> Program for Daemon<P> {
270    type State = P::State;
271    type Message = P::Message;
272    type Theme = P::Theme;
273    type Renderer = P::Renderer;
274    type Executor = P::Executor;
275
276    fn name() -> &'static str {
277        P::name()
278    }
279
280    fn settings(&self) -> Settings {
281        self.settings.clone()
282    }
283
284    fn window(&self) -> Option<window::Settings> {
285        None
286    }
287
288    fn boot(&self) -> (Self::State, Task<Self::Message>) {
289        self.raw.boot()
290    }
291
292    fn update(&self, state: &mut Self::State, message: Self::Message) -> Task<Self::Message> {
293        debug::hot(|| self.raw.update(state, message))
294    }
295
296    fn view<'a>(
297        &self,
298        state: &'a Self::State,
299        window: window::Id,
300    ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> {
301        debug::hot(|| self.raw.view(state, window))
302    }
303
304    fn title(&self, state: &Self::State, window: window::Id) -> String {
305        debug::hot(|| self.raw.title(state, window))
306    }
307
308    fn subscription(&self, state: &Self::State) -> Subscription<Self::Message> {
309        debug::hot(|| self.raw.subscription(state))
310    }
311
312    fn theme(&self, state: &Self::State, window: iced_core::window::Id) -> Option<Self::Theme> {
313        debug::hot(|| self.raw.theme(state, window))
314    }
315
316    fn style(&self, state: &Self::State, theme: &Self::Theme) -> theme::Style {
317        debug::hot(|| self.raw.style(state, theme))
318    }
319
320    fn scale_factor(&self, state: &Self::State, window: window::Id) -> f32 {
321        debug::hot(|| self.raw.scale_factor(state, window))
322    }
323
324    fn presets(&self) -> &[Preset<Self::State, Self::Message>] {
325        &self.presets
326    }
327}
328
329/// The title logic of some [`Daemon`].
330///
331/// This trait is implemented both for `&static str` and
332/// any closure `Fn(&State, window::Id) -> String`.
333///
334/// This trait allows the [`daemon`] builder to take any of them.
335pub trait TitleFn<State> {
336    /// Produces the title of the [`Daemon`].
337    fn title(&self, state: &State, window: window::Id) -> String;
338}
339
340impl<State> TitleFn<State> for &'static str {
341    fn title(&self, _state: &State, _window: window::Id) -> String {
342        self.to_string()
343    }
344}
345
346impl<T, State> TitleFn<State> for T
347where
348    T: Fn(&State, window::Id) -> String,
349{
350    fn title(&self, state: &State, window: window::Id) -> String {
351        self(state, window)
352    }
353}
354
355/// The view logic of some [`Daemon`].
356///
357/// This trait allows the [`daemon`] builder to take any closure that
358/// returns any `Into<Element<'_, Message>>`.
359pub trait ViewFn<'a, State, Message, Theme, Renderer> {
360    /// Produces the widget of the [`Daemon`].
361    fn view(&self, state: &'a State, window: window::Id) -> Element<'a, Message, Theme, Renderer>;
362}
363
364impl<'a, T, State, Message, Theme, Renderer, Widget> ViewFn<'a, State, Message, Theme, Renderer>
365    for T
366where
367    T: Fn(&'a State, window::Id) -> Widget,
368    State: 'static,
369    Widget: Into<Element<'a, Message, Theme, Renderer>>,
370{
371    fn view(&self, state: &'a State, window: window::Id) -> Element<'a, Message, Theme, Renderer> {
372        self(state, window).into()
373    }
374}
375
376/// The theme logic of some [`Daemon`].
377///
378/// Any implementors of this trait can be provided as an argument to
379/// [`Daemon::theme`].
380///
381/// `iced` provides two implementors:
382/// - the built-in [`Theme`] itself
383/// - and any `Fn(&State, window::Id) -> impl Into<Option<Theme>>`.
384pub trait ThemeFn<State, Theme> {
385    /// Returns the theme of the [`Daemon`] for the current state and window.
386    ///
387    /// If `None` is returned, `iced` will try to use a theme that
388    /// matches the system color scheme.
389    fn theme(&self, state: &State, window: window::Id) -> Option<Theme>;
390}
391
392impl<State> ThemeFn<State, Theme> for Theme {
393    fn theme(&self, _state: &State, _window: window::Id) -> Option<Theme> {
394        Some(self.clone())
395    }
396}
397
398impl<F, T, State, Theme> ThemeFn<State, Theme> for F
399where
400    F: Fn(&State, window::Id) -> T,
401    T: Into<Option<Theme>>,
402{
403    fn theme(&self, state: &State, window: window::Id) -> Option<Theme> {
404        (self)(state, window).into()
405    }
406}