iced_runtime/
user_interface.rs

1//! Implement your own event loop to drive a user interface.
2
3use iced_core::clipboard::DndDestinationRectangles;
4use iced_core::widget::tree::NAMED;
5
6use crate::core::event::{self, Event};
7use crate::core::layout;
8use crate::core::mouse;
9use crate::core::renderer;
10use crate::core::widget;
11use crate::core::window;
12use crate::core::{Clipboard, Element, Layout, Rectangle, Shell, Size, Vector};
13use crate::overlay;
14
15/// A set of interactive graphical elements with a specific [`Layout`].
16///
17/// It can be updated and drawn.
18///
19/// Iced tries to avoid dictating how to write your event loop. You are in
20/// charge of using this type in your system in any way you want.
21///
22/// # Example
23/// The [`integration`] example uses a [`UserInterface`] to integrate Iced in an
24/// existing graphical application.
25///
26/// [`integration`]: https://github.com/iced-rs/iced/tree/0.13/examples/integration
27#[allow(missing_debug_implementations)]
28pub struct UserInterface<'a, Message, Theme, Renderer> {
29    root: Element<'a, Message, Theme, Renderer>,
30    base: layout::Node,
31    state: widget::Tree,
32    overlay: Option<layout::Node>,
33    bounds: Size,
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        NAMED.with(|named| {
101            let mut guard = named.borrow_mut();
102            *guard = state.take_all_named();
103        });
104
105        state.diff(root.as_widget_mut());
106
107        let base = root.as_widget().layout(
108            &mut state,
109            renderer,
110            &layout::Limits::new(Size::ZERO, bounds),
111        );
112
113        NAMED.with(|named| {
114            named.borrow_mut().clear();
115        });
116
117        UserInterface {
118            root,
119            base,
120            state,
121            overlay: None,
122            bounds,
123        }
124    }
125
126    /// Updates the [`UserInterface`] by processing each provided [`Event`].
127    ///
128    /// It returns __messages__ that may have been produced as a result of user
129    /// interactions. You should feed these to your __update logic__.
130    ///
131    /// # Example
132    /// Let's allow our [counter](index.html#usage) to change state by
133    /// completing [the previous example](#example):
134    ///
135    /// ```no_run
136    /// # mod iced_wgpu {
137    /// #     pub type Renderer = ();
138    /// # }
139    /// #
140    /// # pub struct Counter;
141    /// #
142    /// # impl Counter {
143    /// #     pub fn new() -> Self { Counter }
144    /// #     pub fn view(&self) -> iced_core::Element<(), (), Renderer> { unimplemented!() }
145    /// #     pub fn update(&mut self, _: ()) {}
146    /// # }
147    /// use iced_runtime::core::clipboard;
148    /// use iced_runtime::core::mouse;
149    /// use iced_runtime::core::Size;
150    /// use iced_runtime::user_interface::{self, UserInterface};
151    /// use iced_wgpu::Renderer;
152    ///
153    /// let mut counter = Counter::new();
154    /// let mut cache = user_interface::Cache::new();
155    /// let mut renderer = Renderer::default();
156    /// let mut window_size = Size::new(1024.0, 768.0);
157    /// let mut cursor = mouse::Cursor::default();
158    /// let mut clipboard = clipboard::Null;
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    ///         &events,
177    ///         cursor,
178    ///         &mut renderer,
179    ///         &mut clipboard,
180    ///         &mut messages
181    ///     );
182    ///
183    ///     cache = user_interface.into_cache();
184    ///
185    ///     // Process the produced messages
186    ///     for message in messages.drain(..) {
187    ///         counter.update(message);
188    ///     }
189    /// }
190    /// ```
191    pub fn update(
192        &mut self,
193        events: &[Event],
194        cursor: mouse::Cursor,
195        renderer: &mut Renderer,
196        clipboard: &mut dyn Clipboard,
197        messages: &mut Vec<Message>,
198    ) -> (State, Vec<event::Status>) {
199        use std::mem::ManuallyDrop;
200
201        let mut outdated = false;
202        let mut redraw_request = None;
203
204        let mut manual_overlay = ManuallyDrop::new(
205            self.root
206                .as_widget_mut()
207                .overlay(
208                    &mut self.state,
209                    Layout::new(&self.base),
210                    renderer,
211                    Vector::ZERO,
212                )
213                .map(overlay::Nested::new),
214        );
215
216        let (base_cursor, overlay_statuses) = if manual_overlay.is_some() {
217            let bounds = self.bounds;
218
219            let mut overlay = manual_overlay.as_mut().unwrap();
220            let mut layout = overlay.layout(renderer, bounds);
221            let mut event_statuses = Vec::new();
222
223            for event in events.iter().cloned() {
224                let mut shell = Shell::new(messages);
225
226                let event_status = overlay.on_event(
227                    event,
228                    Layout::new(&layout),
229                    cursor,
230                    renderer,
231                    clipboard,
232                    &mut shell,
233                );
234
235                event_statuses.push(event_status);
236
237                match (redraw_request, shell.redraw_request()) {
238                    (None, Some(at)) => {
239                        redraw_request = Some(at);
240                    }
241                    (Some(current), Some(new)) if new < current => {
242                        redraw_request = Some(new);
243                    }
244                    _ => {}
245                }
246
247                if shell.is_layout_invalid() {
248                    let _ = ManuallyDrop::into_inner(manual_overlay);
249
250                    self.base = self.root.as_widget().layout(
251                        &mut self.state,
252                        renderer,
253                        &layout::Limits::new(Size::ZERO, self.bounds),
254                    );
255
256                    manual_overlay = ManuallyDrop::new(
257                        self.root
258                            .as_widget_mut()
259                            .overlay(
260                                &mut self.state,
261                                Layout::new(&self.base),
262                                renderer,
263                                Vector::ZERO,
264                            )
265                            .map(overlay::Nested::new),
266                    );
267
268                    if manual_overlay.is_none() {
269                        break;
270                    }
271
272                    overlay = manual_overlay.as_mut().unwrap();
273
274                    shell.revalidate_layout(|| {
275                        layout = overlay.layout(renderer, bounds);
276                    });
277                }
278
279                if shell.are_widgets_invalid() {
280                    outdated = true;
281                }
282            }
283
284            let base_cursor = if manual_overlay
285                .as_mut()
286                .and_then(|overlay| {
287                    cursor.position().map(|cursor_position| {
288                        overlay.is_over(
289                            Layout::new(&layout),
290                            renderer,
291                            cursor_position,
292                        )
293                    })
294                })
295                .unwrap_or_default()
296            {
297                mouse::Cursor::Unavailable
298            } else {
299                cursor
300            };
301
302            self.overlay = Some(layout);
303
304            (base_cursor, event_statuses)
305        } else {
306            (cursor, vec![event::Status::Ignored; events.len()])
307        };
308
309        let viewport = Rectangle::with_size(self.bounds);
310
311        let _ = ManuallyDrop::into_inner(manual_overlay);
312
313        let event_statuses = events
314            .iter()
315            .cloned()
316            .zip(overlay_statuses)
317            .map(|(event, overlay_status)| {
318                if matches!(overlay_status, event::Status::Captured) {
319                    return overlay_status;
320                }
321
322                let mut shell = Shell::new(messages);
323
324                let event_status = self.root.as_widget_mut().on_event(
325                    &mut self.state,
326                    event,
327                    Layout::new(&self.base),
328                    base_cursor,
329                    renderer,
330                    clipboard,
331                    &mut shell,
332                    &viewport,
333                );
334
335                if matches!(event_status, event::Status::Captured) {
336                    self.overlay = None;
337                }
338
339                match (redraw_request, shell.redraw_request()) {
340                    (None, Some(at)) => {
341                        redraw_request = Some(at);
342                    }
343                    (Some(current), Some(new)) if new < current => {
344                        redraw_request = Some(new);
345                    }
346                    _ => {}
347                }
348
349                shell.revalidate_layout(|| {
350                    self.base = self.root.as_widget().layout(
351                        &mut self.state,
352                        renderer,
353                        &layout::Limits::new(Size::ZERO, self.bounds),
354                    );
355
356                    self.overlay = None;
357                });
358
359                if shell.are_widgets_invalid() {
360                    outdated = true;
361                }
362
363                event_status.merge(overlay_status)
364            })
365            .collect();
366
367        (
368            if outdated {
369                State::Outdated
370            } else {
371                State::Updated { redraw_request }
372            },
373            event_statuses,
374        )
375    }
376
377    /// Draws the [`UserInterface`] with the provided [`Renderer`].
378    ///
379    /// It returns the current [`mouse::Interaction`]. You should update the
380    /// icon of the mouse cursor accordingly in your system.
381    ///
382    /// [`Renderer`]: crate::core::Renderer
383    ///
384    /// # Example
385    /// We can finally draw our [counter](index.html#usage) by
386    /// [completing the last example](#example-1):
387    ///
388    /// ```no_run
389    /// # mod iced_wgpu {
390    /// #     pub type Renderer = ();
391    /// #     pub type Theme = ();
392    /// # }
393    /// #
394    /// # pub struct Counter;
395    /// #
396    /// # impl Counter {
397    /// #     pub fn new() -> Self { Counter }
398    /// #     pub fn view(&self) -> Element<(), (), Renderer> { unimplemented!() }
399    /// #     pub fn update(&mut self, _: ()) {}
400    /// # }
401    /// use iced_runtime::core::clipboard;
402    /// use iced_runtime::core::mouse;
403    /// use iced_runtime::core::renderer;
404    /// use iced_runtime::core::{Element, Size};
405    /// use iced_runtime::user_interface::{self, UserInterface};
406    /// use iced_wgpu::{Renderer, Theme};
407    ///
408    /// let mut counter = Counter::new();
409    /// let mut cache = user_interface::Cache::new();
410    /// let mut renderer = Renderer::default();
411    /// let mut window_size = Size::new(1024.0, 768.0);
412    /// let mut cursor = mouse::Cursor::default();
413    /// let mut clipboard = clipboard::Null;
414    /// let mut events = Vec::new();
415    /// let mut messages = Vec::new();
416    /// let mut theme = Theme::default();
417    ///
418    /// loop {
419    ///     // Obtain system events...
420    ///
421    ///     let mut user_interface = UserInterface::build(
422    ///         counter.view(),
423    ///         window_size,
424    ///         cache,
425    ///         &mut renderer,
426    ///     );
427    ///
428    ///     // Update the user interface
429    ///     let event_statuses = user_interface.update(
430    ///         &events,
431    ///         cursor,
432    ///         &mut renderer,
433    ///         &mut clipboard,
434    ///         &mut messages
435    ///     );
436    ///
437    ///     // Draw the user interface
438    ///     let mouse_interaction = user_interface.draw(&mut renderer, &theme, &renderer::Style::default(), cursor);
439    ///
440    ///     cache = user_interface.into_cache();
441    ///
442    ///     for message in messages.drain(..) {
443    ///         counter.update(message);
444    ///     }
445    ///
446    ///     // Update mouse cursor icon...
447    ///     // Flush rendering operations...
448    /// }
449    /// ```
450    pub fn draw(
451        &mut self,
452        renderer: &mut Renderer,
453        theme: &Theme,
454        style: &renderer::Style,
455        cursor: mouse::Cursor,
456    ) -> mouse::Interaction {
457        // TODO: Move to shell level (?)
458        renderer.clear();
459
460        let viewport = Rectangle::with_size(self.bounds);
461
462        let base_cursor = if let Some(mut overlay) = self
463            .root
464            .as_widget_mut()
465            .overlay(
466                &mut self.state,
467                Layout::new(&self.base),
468                renderer,
469                Vector::ZERO,
470            )
471            .map(overlay::Nested::new)
472        {
473            let overlay_layout = self
474                .overlay
475                .take()
476                .unwrap_or_else(|| overlay.layout(renderer, self.bounds));
477
478            let cursor = if cursor
479                .position()
480                .map(|cursor_position| {
481                    overlay.is_over(
482                        Layout::new(&overlay_layout),
483                        renderer,
484                        cursor_position,
485                    )
486                })
487                .unwrap_or_default()
488            {
489                mouse::Cursor::Unavailable
490            } else {
491                cursor
492            };
493
494            self.overlay = Some(overlay_layout);
495
496            cursor
497        } else {
498            cursor
499        };
500
501        self.root.as_widget().draw(
502            &self.state,
503            renderer,
504            theme,
505            style,
506            Layout::new(&self.base),
507            base_cursor,
508            &viewport,
509        );
510
511        let base_interaction = self.root.as_widget().mouse_interaction(
512            &self.state,
513            Layout::new(&self.base),
514            base_cursor,
515            &viewport,
516            renderer,
517        );
518
519        let Self {
520            overlay,
521            root,
522            base,
523            ..
524        } = self;
525
526        // TODO: Currently, we need to call Widget::overlay twice to
527        // implement the painter's algorithm properly.
528        //
529        // Once we have a proper persistent widget tree, we should be able to
530        // avoid this additional call.
531        overlay
532            .as_ref()
533            .and_then(|layout| {
534                root.as_widget_mut()
535                    .overlay(
536                        &mut self.state,
537                        Layout::new(base),
538                        renderer,
539                        Vector::ZERO,
540                    )
541                    .map(overlay::Nested::new)
542                    .map(|mut overlay| {
543                        let overlay_interaction = overlay.mouse_interaction(
544                            Layout::new(layout),
545                            cursor,
546                            &viewport,
547                            renderer,
548                        );
549
550                        overlay.draw(
551                            renderer,
552                            theme,
553                            style,
554                            Layout::new(layout),
555                            cursor,
556                        );
557
558                        if cursor
559                            .position()
560                            .map(|cursor_position| {
561                                overlay.is_over(
562                                    Layout::new(layout),
563                                    renderer,
564                                    cursor_position,
565                                )
566                            })
567                            .unwrap_or_default()
568                        {
569                            overlay_interaction
570                        } else {
571                            base_interaction
572                        }
573                    })
574            })
575            .unwrap_or(base_interaction)
576    }
577
578    /// Applies a [`widget::Operation`] to the [`UserInterface`].
579    pub fn operate(
580        &mut self,
581        renderer: &Renderer,
582        operation: &mut dyn widget::Operation,
583    ) {
584        self.root.as_widget().operate(
585            &mut self.state,
586            Layout::new(&self.base),
587            renderer,
588            operation,
589        );
590
591        if let Some(mut overlay) = self
592            .root
593            .as_widget_mut()
594            .overlay(
595                &mut self.state,
596                Layout::new(&self.base),
597                renderer,
598                Vector::ZERO,
599            )
600            .map(overlay::Nested::new)
601        {
602            if self.overlay.is_none() {
603                self.overlay = Some(overlay.layout(renderer, self.bounds));
604            }
605
606            overlay.operate(
607                Layout::new(self.overlay.as_ref().unwrap()),
608                renderer,
609                operation,
610            );
611        }
612    }
613
614    /// Relayouts and returns a new  [`UserInterface`] using the provided
615    /// bounds.
616    pub fn relayout(self, bounds: Size, renderer: &mut Renderer) -> Self {
617        Self::build(self.root, bounds, Cache { state: self.state }, renderer)
618    }
619
620    /// Extract the [`Cache`] of the [`UserInterface`], consuming it in the
621    /// process.
622    pub fn into_cache(self) -> Cache {
623        Cache { state: self.state }
624    }
625
626    /// get a11y nodes
627    #[cfg(feature = "a11y")]
628    pub fn a11y_nodes(
629        &self,
630        cursor: mouse::Cursor,
631    ) -> iced_accessibility::A11yTree {
632        self.root.as_widget().a11y_nodes(
633            Layout::new(&self.base),
634            &self.state,
635            cursor,
636        )
637    }
638
639    /// Find widget with given id
640    pub fn find(&self, id: &widget::Id) -> Option<&widget::Tree> {
641        self.state.find(id)
642    }
643
644    /// Get the destination rectangles for the user interface.
645    pub fn dnd_rectangles(
646        &self,
647        prev_capacity: usize,
648        renderer: &Renderer,
649    ) -> DndDestinationRectangles {
650        let mut ret = DndDestinationRectangles::with_capacity(prev_capacity);
651        self.root.as_widget().drag_destinations(
652            &self.state,
653            Layout::new(&self.base),
654            renderer,
655            &mut ret,
656        );
657        ret
658    }
659}
660
661/// Reusable data of a specific [`UserInterface`].
662#[derive(Debug)]
663pub struct Cache {
664    state: widget::Tree,
665}
666
667impl Cache {
668    /// Creates an empty [`Cache`].
669    ///
670    /// You should use this to initialize a [`Cache`] before building your first
671    /// [`UserInterface`].
672    pub fn new() -> Cache {
673        Cache {
674            state: widget::Tree::empty(),
675        }
676    }
677}
678
679impl Default for Cache {
680    fn default() -> Cache {
681        Cache::new()
682    }
683}
684
685/// The resulting state after updating a [`UserInterface`].
686#[derive(Debug, Clone, Copy)]
687pub enum State {
688    /// The [`UserInterface`] is outdated and needs to be rebuilt.
689    Outdated,
690
691    /// The [`UserInterface`] is up-to-date and can be reused without
692    /// rebuilding.
693    Updated {
694        /// The [`window::RedrawRequest`] when a redraw should be performed.
695        redraw_request: Option<window::RedrawRequest>,
696    },
697}