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