Skip to main content

nice_plug_iced/
application.rs

1use crate::{NiceGuiContext, iced};
2
3use iced::message;
4use iced::program::{self, Program};
5use iced::theme;
6use iced::window;
7use iced::{Element, Executor, Font, Never, Preset, Subscription, Task, Theme};
8
9use iced_debug as debug;
10
11use std::borrow::Cow;
12use std::sync::{Arc, Mutex};
13
14pub mod timed;
15pub use timed::timed;
16
17mod editor_state;
18pub use editor_state::EditorState;
19
20pub fn application<State, EState, Message, Theme, Renderer>(
21    editor_state: EditorState<EState>,
22    nice_ctx: NiceGuiContext,
23    boot: impl BootFn<State, EState, Message>,
24    update: impl UpdateFn<State, Message>,
25    view: impl for<'a> ViewFn<'a, State, Message, Theme, Renderer>,
26) -> Application<impl Program<State = State, Message = Message, Theme = Theme>>
27where
28    State: 'static,
29    EState: Send + 'static,
30    Message: Send + 'static + message::MaybeDebug + message::MaybeClone,
31    Theme: theme::Base,
32    Renderer: program::Renderer,
33{
34    use std::marker::PhantomData;
35
36    struct Instance<State, EState, Boot, Message, Theme, Renderer, Update, View> {
37        nice_ctx: NiceGuiContext,
38        editor_state: Arc<Mutex<Option<EState>>>,
39        boot: Boot,
40        update: Update,
41        view: View,
42        _state: PhantomData<State>,
43        _message: PhantomData<Message>,
44        _theme: PhantomData<Theme>,
45        _renderer: PhantomData<Renderer>,
46    }
47
48    impl<State, EState, Boot, Message, Theme, Renderer, Update, View> Program
49        for Instance<State, EState, Boot, Message, Theme, Renderer, Update, View>
50    where
51        EState: Send + 'static,
52        Message: Send + 'static,
53        Theme: theme::Base,
54        Renderer: program::Renderer,
55        Boot: self::BootFn<State, EState, Message>,
56        Update: self::UpdateFn<State, Message>,
57        View: for<'a> self::ViewFn<'a, State, Message, Theme, Renderer>,
58    {
59        type State = State;
60        type Message = Message;
61        type Theme = Theme;
62        type Renderer = Renderer;
63        type Executor = iced_futures::backend::default::Executor;
64
65        fn name() -> &'static str {
66            let name = std::any::type_name::<State>();
67
68            name.split("::").next().unwrap_or("a_cool_application")
69        }
70
71        fn boot(&self) -> (State, Task<Message>) {
72            let editor_state = EditorState::from_shared(&self.editor_state);
73
74            self.boot.boot(editor_state, self.nice_ctx.clone())
75        }
76
77        fn update(&self, state: &mut Self::State, message: Self::Message) -> Task<Self::Message> {
78            self.update.update(state, message)
79        }
80
81        fn view<'a>(
82            &self,
83            state: &'a Self::State,
84            _window: window::Id,
85        ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> {
86            self.view.view(state)
87        }
88
89        fn settings(&self) -> iced::Settings {
90            iced::Settings::default()
91        }
92
93        fn window(&self) -> Option<iced::core::window::Settings> {
94            Some(window::Settings::default())
95        }
96    }
97
98    Application {
99        raw: Instance {
100            nice_ctx,
101            editor_state: editor_state.into_shared(),
102            boot,
103            update,
104            view,
105            _state: PhantomData,
106            _message: PhantomData,
107            _theme: PhantomData,
108            _renderer: PhantomData,
109        },
110        iced_settings: iced::Settings::default(),
111        presets: Vec::new(),
112    }
113}
114
115/// The underlying definition and configuration of an iced application.
116///
117/// You can use this API to create and run iced applications
118/// step by step—without coupling your logic to a trait
119/// or a specific type.
120///
121/// You can create an [`Application`] with the [`application`] helper.
122pub struct Application<P: Program> {
123    raw: P,
124    iced_settings: iced::Settings,
125    presets: Vec<Preset<P::State, P::Message>>,
126}
127
128impl<P: Program + Send> Application<P>
129where
130    P::Message: message::MaybeDebug + message::MaybeClone,
131{
132    /// Runs the [`Application`]
133    pub fn run(self) -> impl Program
134    where
135        Self: 'static,
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(all(feature = "debug", not(target_arch = "wasm32")))]
145        let program = iced_devtools::attach(ApplicationInner {
146            raw: self.raw,
147            iced_settings: self.iced_settings,
148            presets: self.presets,
149        });
150
151        #[cfg(not(any(all(feature = "debug", not(target_arch = "wasm32")))))]
152        let program = ApplicationInner {
153            raw: self.raw,
154            iced_settings: self.iced_settings,
155            presets: self.presets,
156        };
157
158        program
159    }
160
161    /// Sets the [`Settings`](iced::Settings) that will be used to run the [`Application`].
162    pub fn settings(self, settings: iced::Settings) -> Self {
163        Self {
164            iced_settings: settings,
165            ..self
166        }
167    }
168
169    /// Sets the [`Settings::antialiasing`](iced::Settings) field of the
170    /// [`Application`].
171    pub fn antialiasing(self, antialiasing: bool) -> Self {
172        Self {
173            iced_settings: iced::Settings {
174                antialiasing,
175                ..self.iced_settings
176            },
177            ..self
178        }
179    }
180
181    /// Sets the default [`Font`] of the [`Application`].
182    pub fn default_font(self, default_font: Font) -> Self {
183        Self {
184            iced_settings: iced::Settings {
185                default_font,
186                ..self.iced_settings
187            },
188            ..self
189        }
190    }
191
192    /// Adds a font to the list of fonts that will be loaded at the start of the [`Application`].
193    pub fn font(mut self, font: impl Into<Cow<'static, [u8]>>) -> Self {
194        self.iced_settings.fonts.push(font.into());
195        self
196    }
197
198    /// Sets the title of the [`Application`].
199    pub fn title(
200        self,
201        title: impl TitleFn<P::State>,
202    ) -> Application<impl Program<State = P::State, Message = P::Message, Theme = P::Theme>> {
203        Application {
204            raw: program::with_title(self.raw, move |state, _window| title.title(state)),
205            iced_settings: self.iced_settings,
206            presets: self.presets,
207        }
208    }
209
210    /// Sets the subscription logic of the [`Application`].
211    pub fn subscription(
212        self,
213        f: impl Fn(&P::State) -> Subscription<P::Message>,
214    ) -> Application<impl Program<State = P::State, Message = P::Message, Theme = P::Theme>> {
215        Application {
216            raw: program::with_subscription(self.raw, f),
217            iced_settings: self.iced_settings,
218            presets: self.presets,
219        }
220    }
221
222    /// Sets the theme logic of the [`Application`].
223    pub fn theme(
224        self,
225        f: impl ThemeFn<P::State, P::Theme>,
226    ) -> Application<impl Program<State = P::State, Message = P::Message, Theme = P::Theme>> {
227        Application {
228            raw: program::with_theme(self.raw, move |state, _window| f.theme(state)),
229            iced_settings: self.iced_settings,
230            presets: self.presets,
231        }
232    }
233
234    /// Sets the style logic of the [`Application`].
235    pub fn style(
236        self,
237        f: impl Fn(&P::State, &P::Theme) -> theme::Style,
238    ) -> Application<impl Program<State = P::State, Message = P::Message, Theme = P::Theme>> {
239        Application {
240            raw: program::with_style(self.raw, f),
241            iced_settings: self.iced_settings,
242            presets: self.presets,
243        }
244    }
245
246    /// Sets the scale factor of the [`Application`].
247    pub fn scale_factor(
248        self,
249        f: impl Fn(&P::State) -> f32,
250    ) -> Application<impl Program<State = P::State, Message = P::Message, Theme = P::Theme>> {
251        Application {
252            raw: program::with_scale_factor(self.raw, move |state, _window| f(state)),
253            iced_settings: self.iced_settings,
254            presets: self.presets,
255        }
256    }
257
258    /// Sets the executor of the [`Application`].
259    pub fn executor<E>(
260        self,
261    ) -> Application<impl Program<State = P::State, Message = P::Message, Theme = P::Theme>>
262    where
263        E: Executor,
264    {
265        Application {
266            raw: program::with_executor::<P, E>(self.raw),
267            iced_settings: self.iced_settings,
268            presets: self.presets,
269        }
270    }
271
272    /// Sets the boot presets of the [`Application`].
273    ///
274    /// Presets can be used to override the default booting strategy
275    /// of your application during testing to create reproducible
276    /// environments.
277    pub fn presets(self, presets: impl IntoIterator<Item = Preset<P::State, P::Message>>) -> Self {
278        Self {
279            presets: presets.into_iter().collect(),
280            ..self
281        }
282    }
283}
284
285/// The underlying definition and configuration of an iced application.
286///
287/// You can use this API to create and run iced applications
288/// step by step—without coupling your logic to a trait
289/// or a specific type.
290///
291/// You can create an [`Application`] with the [`application`] helper.
292pub(crate) struct ApplicationInner<P: Program> {
293    raw: P,
294    iced_settings: iced::Settings,
295    presets: Vec<Preset<P::State, P::Message>>,
296}
297
298impl<P: Program> Program for ApplicationInner<P> {
299    type State = P::State;
300    type Message = P::Message;
301    type Theme = P::Theme;
302    type Renderer = P::Renderer;
303    type Executor = P::Executor;
304
305    fn name() -> &'static str {
306        P::name()
307    }
308
309    fn settings(&self) -> iced::Settings {
310        self.iced_settings.clone()
311    }
312
313    fn window(&self) -> Option<window::Settings> {
314        // Unused by the baseview backend
315        Some(window::Settings::default())
316    }
317
318    fn boot(&self) -> (Self::State, Task<Self::Message>) {
319        self.raw.boot()
320    }
321
322    fn update(&self, state: &mut Self::State, message: Self::Message) -> 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(&self, state: &Self::State, window: iced::window::Id) -> Option<Self::Theme> {
343        debug::hot(|| self.raw.theme(state, window))
344    }
345
346    fn style(&self, state: &Self::State, theme: &Self::Theme) -> theme::Style {
347        debug::hot(|| self.raw.style(state, theme))
348    }
349
350    fn scale_factor(&self, state: &Self::State, window: window::Id) -> f32 {
351        debug::hot(|| self.raw.scale_factor(state, window))
352    }
353
354    fn presets(&self) -> &[Preset<Self::State, Self::Message>] {
355        &self.presets
356    }
357}
358
359/// The logic to initialize the `State` of some [`Application`].
360///
361/// This trait is implemented for both `Fn() -> State` and
362/// `Fn() -> (State, Task<Message>)`.
363///
364/// In practice, this means that [`application`] can both take
365/// simple functions like `State::default` and more advanced ones
366/// that return a [`Task`].
367pub trait BootFn<State, EState: Send + 'static, Message> {
368    /// Initializes the [`Application`] state.
369    fn boot(
370        &self,
371        editor_state: EditorState<EState>,
372        nice_ctx: NiceGuiContext,
373    ) -> (State, Task<Message>);
374}
375
376impl<T, C, State, EState: Send + 'static, Message> BootFn<State, EState, Message> for T
377where
378    T: Fn(EditorState<EState>, NiceGuiContext) -> C,
379    C: IntoBoot<State, Message>,
380{
381    fn boot(
382        &self,
383        editor_state: EditorState<EState>,
384        nice_ctx: NiceGuiContext,
385    ) -> (State, Task<Message>) {
386        self(editor_state, nice_ctx).into_boot()
387    }
388}
389
390/// The initial state of some [`Application`].
391pub trait IntoBoot<State, Message> {
392    /// Turns some type into the initial state of some [`Application`].
393    fn into_boot(self) -> (State, Task<Message>);
394}
395
396impl<State, Message> IntoBoot<State, Message> for State {
397    fn into_boot(self) -> (State, Task<Message>) {
398        (self, Task::none())
399    }
400}
401
402impl<State, Message> IntoBoot<State, Message> for (State, Task<Message>) {
403    fn into_boot(self) -> (State, Task<Message>) {
404        self
405    }
406}
407
408/// The title logic of some [`Application`].
409///
410/// This trait is implemented both for `&static str` and
411/// any closure `Fn(&State) -> String`.
412///
413/// This trait allows the [`application`] builder to take any of them.
414pub trait TitleFn<State> {
415    /// Produces the title of the [`Application`].
416    fn title(&self, state: &State) -> String;
417}
418
419impl<State> TitleFn<State> for &'static str {
420    fn title(&self, _state: &State) -> String {
421        self.to_string()
422    }
423}
424
425impl<T, State> TitleFn<State> for T
426where
427    T: Fn(&State) -> String,
428{
429    fn title(&self, state: &State) -> String {
430        self(state)
431    }
432}
433
434/// The update logic of some [`Application`].
435///
436/// This trait allows the [`application`] builder to take any closure that
437/// returns any `Into<Task<Message>>`.
438pub trait UpdateFn<State, Message> {
439    /// Processes the message and updates the state of the [`Application`].
440    fn update(&self, state: &mut State, message: Message) -> Task<Message>;
441}
442
443impl<State> UpdateFn<State, Never> for () {
444    fn update(&self, _state: &mut State, _message: Never) -> Task<Never> {
445        Task::none()
446    }
447}
448
449impl<T, State, Message, C> UpdateFn<State, Message> for T
450where
451    T: Fn(&mut State, Message) -> C,
452    C: Into<Task<Message>>,
453{
454    fn update(&self, state: &mut State, message: Message) -> Task<Message> {
455        self(state, message).into()
456    }
457}
458
459/// The view logic of some [`Application`].
460///
461/// This trait allows the [`application`] builder to take any closure that
462/// returns any `Into<Element<'_, Message>>`.
463pub trait ViewFn<'a, State, Message, Theme, Renderer> {
464    /// Produces the widget of the [`Application`].
465    fn view(&self, state: &'a State) -> Element<'a, Message, Theme, Renderer>;
466}
467
468impl<'a, T, State, Message, Theme, Renderer, Widget> ViewFn<'a, State, Message, Theme, Renderer>
469    for T
470where
471    T: Fn(&'a State) -> Widget,
472    State: 'static,
473    Widget: Into<Element<'a, Message, Theme, Renderer>>,
474{
475    fn view(&self, state: &'a State) -> Element<'a, Message, Theme, Renderer> {
476        self(state).into()
477    }
478}
479
480/// The theme logic of some [`Application`].
481///
482/// Any implementors of this trait can be provided as an argument to
483/// [`Application::theme`].
484///
485/// `iced` provides two implementors:
486/// - the built-in [`Theme`] itself
487/// - and any `Fn(&State) -> impl Into<Option<Theme>>`.
488pub trait ThemeFn<State, Theme> {
489    /// Returns the theme of the [`Application`] for the current state.
490    ///
491    /// If `None` is returned, `iced` will try to use a theme that
492    /// matches the system color scheme.
493    fn theme(&self, state: &State) -> Option<Theme>;
494}
495
496impl<State> ThemeFn<State, Theme> for Theme {
497    fn theme(&self, _state: &State) -> Option<Theme> {
498        Some(self.clone())
499    }
500}
501
502impl<F, T, State, Theme> ThemeFn<State, Theme> for F
503where
504    F: Fn(&State) -> T,
505    T: Into<Option<Theme>>,
506{
507    fn theme(&self, state: &State) -> Option<Theme> {
508        (self)(state).into()
509    }
510}