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