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