Skip to main content

iced_runtime/
user_interface.rs

1//! Implement your own event loop to drive a user interface.
2use crate::core::event::{self, Event};
3use crate::core::layout;
4use crate::core::mouse;
5use crate::core::overlay;
6use crate::core::renderer;
7use crate::core::shell;
8use crate::core::widget;
9use crate::core::window;
10use crate::core::{
11    Clipboard, Element, InputMethod, Layout, Rectangle, Shell, Size, Vector, Window,
12};
13
14/// A set of interactive graphical elements with a specific [`Layout`].
15///
16/// It can be updated and drawn.
17///
18/// Iced tries to avoid dictating how to write your event loop. You are in
19/// charge of using this type in your system in any way you want.
20///
21/// # Example
22/// The [`integration`] example uses a [`UserInterface`] to integrate Iced in an
23/// existing graphical application.
24///
25/// [`integration`]: https://github.com/iced-rs/iced/tree/master/examples/integration
26pub struct UserInterface<'a, Message, Theme, Renderer> {
27    root: Element<'a, Message, Theme, Renderer>,
28    base: layout::Node,
29    state: widget::Tree,
30    overlay: Option<Overlay>,
31    bounds: Size,
32}
33
34struct Overlay {
35    layout: layout::Node,
36    interaction: mouse::Interaction,
37}
38
39impl<'a, Message, Theme, Renderer> UserInterface<'a, Message, Theme, Renderer>
40where
41    Renderer: crate::core::Renderer,
42{
43    /// Builds a user interface for an [`Element`].
44    ///
45    /// It is able to avoid expensive computations when using a [`Cache`]
46    /// obtained from a previous instance of a [`UserInterface`].
47    ///
48    /// # Example
49    /// Imagine we want to build a [`UserInterface`] for
50    /// [the counter example that we previously wrote](index.html#usage). Here
51    /// is naive way to set up our application loop:
52    ///
53    /// ```no_run
54    /// # mod iced_wgpu {
55    /// #     pub type Renderer = ();
56    /// # }
57    /// #
58    /// # pub struct Counter;
59    /// #
60    /// # impl Counter {
61    /// #     pub fn new() -> Self { Counter }
62    /// #     pub fn view(&self) -> iced_core::Element<(), (), Renderer> { unimplemented!() }
63    /// #     pub fn update(&mut self, _: ()) {}
64    /// # }
65    /// use iced_runtime::core::shell;
66    /// use iced_runtime::core::window;
67    /// use iced_runtime::core::Size;
68    /// use iced_runtime::user_interface::{self, UserInterface};
69    /// use iced_wgpu::Renderer;
70    ///
71    /// // Initialization
72    /// let mut counter = Counter::new();
73    /// let mut cache = user_interface::Cache::new();
74    /// let mut renderer = Renderer::default();
75    /// let mut window = window::Headless; // This should be a proper window, like a `winit` one
76    /// let mut waker = shell::Waker::noop();
77    /// let mut window_size = Size::new(1024.0, 768.0);
78    ///
79    /// // Application loop
80    /// loop {
81    ///     // Process system events here...
82    ///
83    ///     // Build the user interface
84    ///     let user_interface = UserInterface::build(
85    ///         counter.view(),
86    ///         window_size,
87    ///         cache,
88    ///         &mut renderer,
89    ///     );
90    ///
91    ///     // Update and draw the user interface here...
92    ///     // ...
93    ///
94    ///     // Obtain the cache for the next iteration
95    ///     cache = user_interface.into_cache();
96    /// }
97    /// ```
98    pub fn build<E: Into<Element<'a, Message, Theme, Renderer>>>(
99        root: E,
100        bounds: Size,
101        cache: Cache,
102        renderer: &mut Renderer,
103    ) -> Self {
104        let mut root = root.into();
105
106        let Cache { mut state } = cache;
107        state.diff(root.as_widget_mut());
108
109        let base = root.as_widget_mut().layout(
110            &mut state,
111            renderer,
112            &layout::Limits::new(Size::ZERO, bounds),
113        );
114
115        UserInterface {
116            root,
117            base,
118            state,
119            overlay: None,
120            bounds,
121        }
122    }
123
124    /// Updates the [`UserInterface`] by processing each provided [`Event`].
125    ///
126    /// It returns __messages__ that may have been produced as a result of user
127    /// interactions. You should feed these to your __update logic__.
128    ///
129    /// # Example
130    /// Let's allow our [counter](index.html#usage) to change state by
131    /// completing [the previous example](#example):
132    ///
133    /// ```no_run
134    /// # mod iced_wgpu {
135    /// #     pub type Renderer = ();
136    /// # }
137    /// #
138    /// # pub struct Counter;
139    /// #
140    /// # impl Counter {
141    /// #     pub fn new() -> Self { Counter }
142    /// #     pub fn view(&self) -> iced_core::Element<(), (), Renderer> { unimplemented!() }
143    /// #     pub fn update(&mut self, _: ()) {}
144    /// # }
145    /// use iced_runtime::core::mouse;
146    /// use iced_runtime::core::shell;
147    /// use iced_runtime::core::window;
148    /// use iced_runtime::core::Size;
149    /// use iced_runtime::user_interface::{self, UserInterface};
150    /// use iced_wgpu::Renderer;
151    ///
152    /// let mut counter = Counter::new();
153    /// let mut cache = user_interface::Cache::new();
154    /// let mut renderer = Renderer::default();
155    /// let mut window = window::Headless; // This should be a proper window, like a `winit` one
156    /// let mut waker = shell::Waker::noop();
157    /// let mut window_size = Size::new(1024.0, 768.0);
158    /// let mut cursor = mouse::Cursor::default();
159    ///
160    /// // Initialize our event storage
161    /// let mut events = Vec::new();
162    /// let mut messages = Vec::new();
163    ///
164    /// loop {
165    ///     // Obtain system events...
166    ///
167    ///     let mut user_interface = UserInterface::build(
168    ///         counter.view(),
169    ///         window_size,
170    ///         cache,
171    ///         &mut renderer,
172    ///     );
173    ///
174    ///     // Update the user interface
175    ///     let (state, event_statuses) = user_interface.update(
176    ///         &window,
177    ///         &waker,
178    ///         &events,
179    ///         cursor,
180    ///         &mut renderer,
181    ///         &mut messages
182    ///     );
183    ///
184    ///     cache = user_interface.into_cache();
185    ///
186    ///     // Process the produced messages
187    ///     for message in messages.drain(..) {
188    ///         counter.update(message);
189    ///     }
190    /// }
191    /// ```
192    pub fn update(
193        &mut self,
194        window: &dyn Window,
195        waker: &shell::Waker,
196        events: &[Event],
197        cursor: mouse::Cursor,
198        renderer: &mut Renderer,
199        messages: &mut Vec<Message>,
200    ) -> (State, Vec<event::Status>) {
201        let mut outdated = false;
202        let mut redraw_request = window::RedrawRequest::Wait;
203        let mut input_method = InputMethod::Disabled;
204        let mut clipboard = Clipboard::new();
205        let mut has_layout_changed = false;
206        let viewport = Rectangle::with_size(self.bounds);
207
208        let mut maybe_overlay = self
209            .root
210            .as_widget_mut()
211            .overlay(
212                &mut self.state,
213                Layout::new(&self.base),
214                renderer,
215                &viewport,
216                Vector::ZERO,
217            )
218            .map(overlay::Nested::new);
219
220        let (base_cursor, overlay_statuses, overlay_interaction) = if maybe_overlay.is_some() {
221            let bounds = self.bounds;
222
223            let mut overlay = maybe_overlay.as_mut().unwrap();
224            let mut layout = overlay.layout(renderer, bounds);
225            let mut event_statuses = Vec::new();
226
227            for event in events {
228                let mut shell = Shell::new(window, waker.clone(), messages);
229
230                overlay.update(event, Layout::new(&layout), cursor, renderer, &mut shell);
231
232                event_statuses.push(shell.event_status());
233                redraw_request = redraw_request.min(shell.redraw_request());
234                input_method.merge(shell.input_method());
235                clipboard.merge(shell.clipboard_mut());
236
237                if let Some(diff) = shell.is_layout_invalid() {
238                    drop(maybe_overlay);
239
240                    match diff {
241                        shell::Diff::Perform => {
242                            self.root.as_widget_mut().diff(&mut self.state);
243                        }
244                        shell::Diff::Skip => {}
245                    }
246
247                    self.base = self.root.as_widget_mut().layout(
248                        &mut self.state,
249                        renderer,
250                        &layout::Limits::new(Size::ZERO, self.bounds),
251                    );
252
253                    maybe_overlay = self
254                        .root
255                        .as_widget_mut()
256                        .overlay(
257                            &mut self.state,
258                            Layout::new(&self.base),
259                            renderer,
260                            &viewport,
261                            Vector::ZERO,
262                        )
263                        .map(overlay::Nested::new);
264
265                    if maybe_overlay.is_none() {
266                        break;
267                    }
268
269                    overlay = maybe_overlay.as_mut().unwrap();
270
271                    shell.revalidate_layout(|_diff| {
272                        layout = overlay.layout(renderer, bounds);
273                        has_layout_changed = true;
274                    });
275                }
276
277                if shell.are_widgets_invalid() {
278                    outdated = true;
279                }
280            }
281
282            let (base_cursor, interaction) = if let Some(overlay) = maybe_overlay.as_mut() {
283                let interaction = cursor
284                    .position()
285                    .map(|cursor_position| {
286                        overlay.mouse_interaction(
287                            Layout::new(&layout),
288                            mouse::Cursor::Available(cursor_position),
289                            renderer,
290                        )
291                    })
292                    .unwrap_or_default();
293
294                if interaction == mouse::Interaction::None {
295                    (cursor, mouse::Interaction::None)
296                } else {
297                    (mouse::Cursor::Unavailable, interaction)
298                }
299            } else {
300                (cursor, mouse::Interaction::None)
301            };
302
303            self.overlay = Some(Overlay {
304                layout,
305                interaction,
306            });
307
308            (base_cursor, event_statuses, interaction)
309        } else {
310            (
311                cursor,
312                vec![event::Status::Ignored; events.len()],
313                mouse::Interaction::None,
314            )
315        };
316
317        drop(maybe_overlay);
318
319        let event_statuses = events
320            .iter()
321            .zip(overlay_statuses)
322            .map(|(event, overlay_status)| {
323                if matches!(overlay_status, event::Status::Captured) {
324                    return overlay_status;
325                }
326
327                let mut shell = Shell::new(window, waker.clone(), messages);
328
329                self.root.as_widget_mut().update(
330                    &mut self.state,
331                    event,
332                    Layout::new(&self.base),
333                    base_cursor,
334                    renderer,
335                    &mut shell,
336                    &viewport,
337                );
338
339                if shell.event_status() == event::Status::Captured {
340                    self.overlay = None;
341                }
342
343                redraw_request = redraw_request.min(shell.redraw_request());
344                input_method.merge(shell.input_method());
345                clipboard.merge(shell.clipboard_mut());
346
347                shell.revalidate_layout(|diff| {
348                    has_layout_changed = true;
349
350                    match diff {
351                        shell::Diff::Perform => {
352                            self.root.as_widget_mut().diff(&mut self.state);
353                        }
354                        shell::Diff::Skip => {}
355                    }
356
357                    self.base = self.root.as_widget_mut().layout(
358                        &mut self.state,
359                        renderer,
360                        &layout::Limits::new(Size::ZERO, self.bounds),
361                    );
362
363                    if let Some(mut overlay) = self
364                        .root
365                        .as_widget_mut()
366                        .overlay(
367                            &mut self.state,
368                            Layout::new(&self.base),
369                            renderer,
370                            &viewport,
371                            Vector::ZERO,
372                        )
373                        .map(overlay::Nested::new)
374                    {
375                        let layout = overlay.layout(renderer, self.bounds);
376                        let interaction =
377                            overlay.mouse_interaction(Layout::new(&layout), cursor, renderer);
378
379                        self.overlay = Some(Overlay {
380                            layout,
381                            interaction,
382                        });
383                    }
384                });
385
386                if shell.are_widgets_invalid() {
387                    outdated = true;
388                }
389
390                shell.event_status().merge(overlay_status)
391            })
392            .collect();
393
394        let mouse_interaction = if overlay_interaction == mouse::Interaction::None {
395            self.root.as_widget().mouse_interaction(
396                &self.state,
397                Layout::new(&self.base),
398                base_cursor,
399                &viewport,
400                renderer,
401            )
402        } else {
403            overlay_interaction
404        };
405
406        (
407            if outdated {
408                State::Outdated
409            } else {
410                State::Updated {
411                    mouse_interaction,
412                    redraw_request,
413                    input_method,
414                    clipboard,
415                    has_layout_changed,
416                }
417            },
418            event_statuses,
419        )
420    }
421
422    /// Draws the [`UserInterface`] with the provided [`Renderer`].
423    ///
424    /// It returns the current [`mouse::Interaction`]. You should update the
425    /// icon of the mouse cursor accordingly in your system.
426    ///
427    /// [`Renderer`]: crate::core::Renderer
428    ///
429    /// # Example
430    /// We can finally draw our [counter](index.html#usage) by
431    /// [completing the last example](#example-1):
432    ///
433    /// ```no_run
434    /// # mod iced_wgpu {
435    /// #     pub type Renderer = ();
436    /// #     pub type Theme = ();
437    /// # }
438    /// #
439    /// # pub struct Counter;
440    /// #
441    /// # impl Counter {
442    /// #     pub fn new() -> Self { Counter }
443    /// #     pub fn view(&self) -> Element<(), (), Renderer> { unimplemented!() }
444    /// #     pub fn update(&mut self, _: ()) {}
445    /// # }
446    /// use iced_runtime::core::mouse;
447    /// use iced_runtime::core::renderer;
448    /// use iced_runtime::core::shell;
449    /// use iced_runtime::core::window;
450    /// use iced_runtime::core::{Element, Size};
451    /// use iced_runtime::user_interface::{self, UserInterface};
452    /// use iced_wgpu::{Renderer, Theme};
453    ///
454    /// let mut counter = Counter::new();
455    /// let mut cache = user_interface::Cache::new();
456    /// let mut renderer = Renderer::default();
457    /// let mut window = window::Headless; // This should be a proper window, like a `winit` one
458    /// let mut waker = shell::Waker::noop();
459    /// let mut window_size = Size::new(1024.0, 768.0);
460    /// let mut cursor = mouse::Cursor::default();
461    /// let mut events = Vec::new();
462    /// let mut messages = Vec::new();
463    /// let mut theme = Theme::default();
464    ///
465    /// loop {
466    ///     // Obtain system events...
467    ///
468    ///     let mut user_interface = UserInterface::build(
469    ///         counter.view(),
470    ///         window_size,
471    ///         cache,
472    ///         &mut renderer,
473    ///     );
474    ///
475    ///     // Update the user interface
476    ///     let event_statuses = user_interface.update(
477    ///         &window,
478    ///         &waker,
479    ///         &events,
480    ///         cursor,
481    ///         &mut renderer,
482    ///         &mut messages
483    ///     );
484    ///
485    ///     // Draw the user interface
486    ///     let mouse_interaction = user_interface.draw(&mut renderer, &theme, &renderer::Style::default(), cursor);
487    ///
488    ///     cache = user_interface.into_cache();
489    ///
490    ///     for message in messages.drain(..) {
491    ///         counter.update(message);
492    ///     }
493    ///
494    ///     // Update mouse cursor icon...
495    ///     // Flush rendering operations...
496    /// }
497    /// ```
498    pub fn draw(
499        &mut self,
500        renderer: &mut Renderer,
501        theme: &Theme,
502        style: &renderer::Style,
503        cursor: mouse::Cursor,
504    ) {
505        let viewport = Rectangle::with_size(self.bounds);
506        renderer.reset(viewport);
507
508        let base_cursor = match &self.overlay {
509            None
510            | Some(Overlay {
511                interaction: mouse::Interaction::None,
512                ..
513            }) => cursor,
514            _ => mouse::Cursor::Unavailable,
515        };
516
517        self.root.as_widget().draw(
518            &self.state,
519            renderer,
520            theme,
521            style,
522            Layout::new(&self.base),
523            base_cursor,
524            &viewport,
525        );
526
527        let Self {
528            overlay,
529            root,
530            base,
531            ..
532        } = self;
533
534        let Some(Overlay { layout, .. }) = overlay.as_ref() else {
535            return;
536        };
537
538        let overlay = root
539            .as_widget_mut()
540            .overlay(
541                &mut self.state,
542                Layout::new(base),
543                renderer,
544                &viewport,
545                Vector::ZERO,
546            )
547            .map(overlay::Nested::new);
548
549        if let Some(mut overlay) = overlay {
550            overlay.draw(renderer, theme, style, Layout::new(layout), cursor);
551        }
552    }
553
554    /// Applies a [`widget::Operation`] to the [`UserInterface`].
555    pub fn operate(&mut self, renderer: &Renderer, operation: &mut dyn widget::Operation) {
556        let viewport = Rectangle::with_size(self.bounds);
557
558        self.root.as_widget_mut().operate(
559            &mut self.state,
560            Layout::new(&self.base),
561            renderer,
562            operation,
563        );
564
565        if let Some(mut overlay) = self
566            .root
567            .as_widget_mut()
568            .overlay(
569                &mut self.state,
570                Layout::new(&self.base),
571                renderer,
572                &viewport,
573                Vector::ZERO,
574            )
575            .map(overlay::Nested::new)
576        {
577            if self.overlay.is_none() {
578                self.overlay = Some(Overlay {
579                    layout: overlay.layout(renderer, self.bounds),
580                    interaction: mouse::Interaction::None,
581                });
582            }
583
584            overlay.operate(
585                Layout::new(&self.overlay.as_ref().unwrap().layout),
586                renderer,
587                operation,
588            );
589        }
590    }
591
592    /// Relayouts and returns a new  [`UserInterface`] using the provided
593    /// bounds.
594    pub fn relayout(self, bounds: Size, renderer: &mut Renderer) -> Self {
595        Self::build(self.root, bounds, Cache { state: self.state }, renderer)
596    }
597
598    /// Extract the [`Cache`] of the [`UserInterface`], consuming it in the
599    /// process.
600    pub fn into_cache(self) -> Cache {
601        Cache { state: self.state }
602    }
603}
604
605/// Reusable data of a specific [`UserInterface`].
606#[derive(Debug)]
607pub struct Cache {
608    state: widget::Tree,
609}
610
611impl Cache {
612    /// Creates an empty [`Cache`].
613    ///
614    /// You should use this to initialize a [`Cache`] before building your first
615    /// [`UserInterface`].
616    pub fn new() -> Cache {
617        Cache {
618            state: widget::Tree::empty(),
619        }
620    }
621}
622
623impl Default for Cache {
624    fn default() -> Cache {
625        Cache::new()
626    }
627}
628
629/// The resulting state after updating a [`UserInterface`].
630#[derive(Debug)]
631pub enum State {
632    /// The [`UserInterface`] is outdated and needs to be rebuilt.
633    Outdated,
634
635    /// The [`UserInterface`] is up-to-date and can be reused without
636    /// rebuilding.
637    Updated {
638        /// The current [`mouse::Interaction`] of the user interface.
639        mouse_interaction: mouse::Interaction,
640        /// The [`window::RedrawRequest`] describing when a redraw should be performed.
641        redraw_request: window::RedrawRequest,
642        /// The current [`InputMethod`] strategy of the user interface.
643        input_method: InputMethod,
644        /// The set of [`Clipboard`] requests that the user interface has produced.
645        clipboard: Clipboard,
646        /// Whether the layout of the [`UserInterface`] has changed.
647        has_layout_changed: bool,
648    },
649}
650
651impl State {
652    /// Returns whether the layout of the [`UserInterface`] has changed.
653    pub fn has_layout_changed(&self) -> bool {
654        match self {
655            State::Outdated => true,
656            State::Updated {
657                has_layout_changed, ..
658            } => *has_layout_changed,
659        }
660    }
661}