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