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(feature = "debug")]
138        iced_debug::init(iced_debug::Metadata {
139            name: P::name(),
140            theme: None,
141            can_time_travel: cfg!(feature = "time-travel"),
142        });
143
144        #[cfg(feature = "tester")]
145        let program = iced_tester::attach(self);
146
147        #[cfg(all(feature = "debug", not(feature = "tester")))]
148        let program = iced_devtools::attach(self);
149
150        #[cfg(not(any(feature = "tester", feature = "debug")))]
151        let program = self;
152
153        Ok(shell::run(program)?)
154    }
155
156    /// Sets the [`Settings`] that will be used to run the [`Daemon`].
157    pub fn settings(self, settings: Settings) -> Self {
158        Self { settings, ..self }
159    }
160
161    /// Sets the [`Settings::antialiasing`] of the [`Daemon`].
162    pub fn antialiasing(self, antialiasing: bool) -> Self {
163        Self {
164            settings: Settings {
165                antialiasing,
166                ..self.settings
167            },
168            ..self
169        }
170    }
171
172    /// Sets the default [`Font`] of the [`Daemon`].
173    pub fn default_font(self, default_font: Font) -> Self {
174        Self {
175            settings: Settings {
176                default_font,
177                ..self.settings
178            },
179            ..self
180        }
181    }
182
183    /// Adds a font to the list of fonts that will be loaded at the start of the [`Daemon`].
184    pub fn font(mut self, font: impl Into<Cow<'static, [u8]>>) -> Self {
185        self.settings.fonts.push(font.into());
186        self
187    }
188
189    /// Sets the title of the [`Daemon`].
190    pub fn title(
191        self,
192        title: impl TitleFn<P::State>,
193    ) -> Daemon<
194        impl Program<State = P::State, Message = P::Message, Theme = P::Theme>,
195    > {
196        Daemon {
197            raw: program::with_title(self.raw, move |state, window| {
198                title.title(state, window)
199            }),
200            settings: self.settings,
201            presets: self.presets,
202        }
203    }
204
205    /// Sets the subscription logic of the [`Daemon`].
206    pub fn subscription(
207        self,
208        f: impl Fn(&P::State) -> Subscription<P::Message>,
209    ) -> Daemon<
210        impl Program<State = P::State, Message = P::Message, Theme = P::Theme>,
211    > {
212        Daemon {
213            raw: program::with_subscription(self.raw, f),
214            settings: self.settings,
215            presets: self.presets,
216        }
217    }
218
219    /// Sets the theme logic of the [`Daemon`].
220    pub fn theme(
221        self,
222        f: impl ThemeFn<P::State, P::Theme>,
223    ) -> Daemon<
224        impl Program<State = P::State, Message = P::Message, Theme = P::Theme>,
225    > {
226        Daemon {
227            raw: program::with_theme(self.raw, move |state, window| {
228                f.theme(state, window)
229            }),
230            settings: self.settings,
231            presets: self.presets,
232        }
233    }
234
235    /// Sets the style logic of the [`Daemon`].
236    pub fn style(
237        self,
238        f: impl Fn(&P::State, &P::Theme) -> theme::Style,
239    ) -> Daemon<
240        impl Program<State = P::State, Message = P::Message, Theme = P::Theme>,
241    > {
242        Daemon {
243            raw: program::with_style(self.raw, f),
244            settings: self.settings,
245            presets: self.presets,
246        }
247    }
248
249    /// Sets the scale factor of the [`Daemon`].
250    pub fn scale_factor(
251        self,
252        f: impl Fn(&P::State, window::Id) -> f32,
253    ) -> Daemon<
254        impl Program<State = P::State, Message = P::Message, Theme = P::Theme>,
255    > {
256        Daemon {
257            raw: program::with_scale_factor(self.raw, f),
258            settings: self.settings,
259            presets: self.presets,
260        }
261    }
262
263    /// Sets the executor of the [`Daemon`].
264    pub fn executor<E>(
265        self,
266    ) -> Daemon<
267        impl Program<State = P::State, Message = P::Message, Theme = P::Theme>,
268    >
269    where
270        E: Executor,
271    {
272        Daemon {
273            raw: program::with_executor::<P, E>(self.raw),
274            settings: self.settings,
275            presets: self.presets,
276        }
277    }
278
279    /// Sets the boot presets of the [`Daemon`].
280    ///
281    /// Presets can be used to override the default booting strategy
282    /// of your application during testing to create reproducible
283    /// environments.
284    pub fn presets(
285        self,
286        presets: impl IntoIterator<Item = Preset<P::State, P::Message>>,
287    ) -> Self {
288        Self {
289            presets: presets.into_iter().collect(),
290            ..self
291        }
292    }
293}
294
295impl<P: Program> Program for Daemon<P> {
296    type State = P::State;
297    type Message = P::Message;
298    type Theme = P::Theme;
299    type Renderer = P::Renderer;
300    type Executor = P::Executor;
301
302    fn name() -> &'static str {
303        P::name()
304    }
305
306    fn settings(&self) -> Settings {
307        self.settings.clone()
308    }
309
310    fn window(&self) -> Option<window::Settings> {
311        None
312    }
313
314    fn boot(&self) -> (Self::State, Task<Self::Message>) {
315        self.raw.boot()
316    }
317
318    fn update(
319        &self,
320        state: &mut Self::State,
321        message: Self::Message,
322    ) -> Task<Self::Message> {
323        debug::hot(|| self.raw.update(state, message))
324    }
325
326    fn view<'a>(
327        &self,
328        state: &'a Self::State,
329        window: window::Id,
330    ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> {
331        debug::hot(|| self.raw.view(state, window))
332    }
333
334    fn title(&self, state: &Self::State, window: window::Id) -> String {
335        debug::hot(|| self.raw.title(state, window))
336    }
337
338    fn subscription(&self, state: &Self::State) -> Subscription<Self::Message> {
339        debug::hot(|| self.raw.subscription(state))
340    }
341
342    fn theme(
343        &self,
344        state: &Self::State,
345        window: iced_core::window::Id,
346    ) -> Option<Self::Theme> {
347        debug::hot(|| self.raw.theme(state, window))
348    }
349
350    fn style(&self, state: &Self::State, theme: &Self::Theme) -> theme::Style {
351        debug::hot(|| self.raw.style(state, theme))
352    }
353
354    fn scale_factor(&self, state: &Self::State, window: window::Id) -> f32 {
355        debug::hot(|| self.raw.scale_factor(state, window))
356    }
357
358    fn presets(&self) -> &[Preset<Self::State, Self::Message>] {
359        &self.presets
360    }
361}
362
363/// The title logic of some [`Daemon`].
364///
365/// This trait is implemented both for `&static str` and
366/// any closure `Fn(&State, window::Id) -> String`.
367///
368/// This trait allows the [`daemon`] builder to take any of them.
369pub trait TitleFn<State> {
370    /// Produces the title of the [`Daemon`].
371    fn title(&self, state: &State, window: window::Id) -> String;
372}
373
374impl<State> TitleFn<State> for &'static str {
375    fn title(&self, _state: &State, _window: window::Id) -> String {
376        self.to_string()
377    }
378}
379
380impl<T, State> TitleFn<State> for T
381where
382    T: Fn(&State, window::Id) -> String,
383{
384    fn title(&self, state: &State, window: window::Id) -> String {
385        self(state, window)
386    }
387}
388
389/// The view logic of some [`Daemon`].
390///
391/// This trait allows the [`daemon`] builder to take any closure that
392/// returns any `Into<Element<'_, Message>>`.
393pub trait ViewFn<'a, State, Message, Theme, Renderer> {
394    /// Produces the widget of the [`Daemon`].
395    fn view(
396        &self,
397        state: &'a State,
398        window: window::Id,
399    ) -> Element<'a, Message, Theme, Renderer>;
400}
401
402impl<'a, T, State, Message, Theme, Renderer, Widget>
403    ViewFn<'a, State, Message, Theme, Renderer> for T
404where
405    T: Fn(&'a State, window::Id) -> Widget,
406    State: 'static,
407    Widget: Into<Element<'a, Message, Theme, Renderer>>,
408{
409    fn view(
410        &self,
411        state: &'a State,
412        window: window::Id,
413    ) -> Element<'a, Message, Theme, Renderer> {
414        self(state, window).into()
415    }
416}
417
418/// The theme logic of some [`Daemon`].
419///
420/// Any implementors of this trait can be provided as an argument to
421/// [`Daemon::theme`].
422///
423/// `iced` provides two implementors:
424/// - the built-in [`Theme`] itself
425/// - and any `Fn(&State, window::Id) -> impl Into<Option<Theme>>`.
426pub trait ThemeFn<State, Theme> {
427    /// Returns the theme of the [`Daemon`] for the current state and window.
428    ///
429    /// If `None` is returned, `iced` will try to use a theme that
430    /// matches the system color scheme.
431    fn theme(&self, state: &State, window: window::Id) -> Option<Theme>;
432}
433
434impl<State> ThemeFn<State, Theme> for Theme {
435    fn theme(&self, _state: &State, _window: window::Id) -> Option<Theme> {
436        Some(self.clone())
437    }
438}
439
440impl<F, T, State, Theme> ThemeFn<State, Theme> for F
441where
442    F: Fn(&State, window::Id) -> T,
443    T: Into<Option<Theme>>,
444{
445    fn theme(&self, state: &State, window: window::Id) -> Option<Theme> {
446        (self)(state, window).into()
447    }
448}