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