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                    if let Some(mut overlay) = self
345                        .root
346                        .as_widget_mut()
347                        .overlay(
348                            &mut self.state,
349                            Layout::new(&self.base),
350                            renderer,
351                            &viewport,
352                            Vector::ZERO,
353                        )
354                        .map(overlay::Nested::new)
355                    {
356                        let layout = overlay.layout(renderer, self.bounds);
357                        let interaction = overlay.mouse_interaction(
358                            Layout::new(&layout),
359                            cursor,
360                            renderer,
361                        );
362
363                        self.overlay = Some(Overlay {
364                            layout,
365                            interaction,
366                        });
367                    }
368                });
369
370                if shell.are_widgets_invalid() {
371                    outdated = true;
372                }
373
374                shell.event_status().merge(overlay_status)
375            })
376            .collect();
377
378        let mouse_interaction =
379            if overlay_interaction == mouse::Interaction::None {
380                self.root.as_widget().mouse_interaction(
381                    &self.state,
382                    Layout::new(&self.base),
383                    base_cursor,
384                    &viewport,
385                    renderer,
386                )
387            } else {
388                overlay_interaction
389            };
390
391        (
392            if outdated {
393                State::Outdated
394            } else {
395                State::Updated {
396                    mouse_interaction,
397                    redraw_request,
398                    input_method,
399                }
400            },
401            event_statuses,
402        )
403    }
404
405    /// Draws the [`UserInterface`] with the provided [`Renderer`].
406    ///
407    /// It returns the current [`mouse::Interaction`]. You should update the
408    /// icon of the mouse cursor accordingly in your system.
409    ///
410    /// [`Renderer`]: crate::core::Renderer
411    ///
412    /// # Example
413    /// We can finally draw our [counter](index.html#usage) by
414    /// [completing the last example](#example-1):
415    ///
416    /// ```no_run
417    /// # mod iced_wgpu {
418    /// #     pub type Renderer = ();
419    /// #     pub type Theme = ();
420    /// # }
421    /// #
422    /// # pub struct Counter;
423    /// #
424    /// # impl Counter {
425    /// #     pub fn new() -> Self { Counter }
426    /// #     pub fn view(&self) -> Element<(), (), Renderer> { unimplemented!() }
427    /// #     pub fn update(&mut self, _: ()) {}
428    /// # }
429    /// use iced_runtime::core::clipboard;
430    /// use iced_runtime::core::mouse;
431    /// use iced_runtime::core::renderer;
432    /// use iced_runtime::core::{Element, Size};
433    /// use iced_runtime::user_interface::{self, UserInterface};
434    /// use iced_wgpu::{Renderer, Theme};
435    ///
436    /// let mut counter = Counter::new();
437    /// let mut cache = user_interface::Cache::new();
438    /// let mut renderer = Renderer::default();
439    /// let mut window_size = Size::new(1024.0, 768.0);
440    /// let mut cursor = mouse::Cursor::default();
441    /// let mut clipboard = clipboard::Null;
442    /// let mut events = Vec::new();
443    /// let mut messages = Vec::new();
444    /// let mut theme = Theme::default();
445    ///
446    /// loop {
447    ///     // Obtain system events...
448    ///
449    ///     let mut user_interface = UserInterface::build(
450    ///         counter.view(),
451    ///         window_size,
452    ///         cache,
453    ///         &mut renderer,
454    ///     );
455    ///
456    ///     // Update the user interface
457    ///     let event_statuses = user_interface.update(
458    ///         &events,
459    ///         cursor,
460    ///         &mut renderer,
461    ///         &mut clipboard,
462    ///         &mut messages
463    ///     );
464    ///
465    ///     // Draw the user interface
466    ///     let mouse_interaction = user_interface.draw(&mut renderer, &theme, &renderer::Style::default(), cursor);
467    ///
468    ///     cache = user_interface.into_cache();
469    ///
470    ///     for message in messages.drain(..) {
471    ///         counter.update(message);
472    ///     }
473    ///
474    ///     // Update mouse cursor icon...
475    ///     // Flush rendering operations...
476    /// }
477    /// ```
478    pub fn draw(
479        &mut self,
480        renderer: &mut Renderer,
481        theme: &Theme,
482        style: &renderer::Style,
483        cursor: mouse::Cursor,
484    ) {
485        // TODO: Move to shell level (?)
486        renderer.clear();
487
488        let viewport = Rectangle::with_size(self.bounds);
489
490        let base_cursor = match &self.overlay {
491            None
492            | Some(Overlay {
493                interaction: mouse::Interaction::None,
494                ..
495            }) => cursor,
496            _ => mouse::Cursor::Unavailable,
497        };
498
499        self.root.as_widget().draw(
500            &self.state,
501            renderer,
502            theme,
503            style,
504            Layout::new(&self.base),
505            base_cursor,
506            &viewport,
507        );
508
509        let Self {
510            overlay,
511            root,
512            base,
513            ..
514        } = self;
515
516        let Some(Overlay { layout, .. }) = overlay.as_ref() else {
517            return;
518        };
519
520        let overlay = root
521            .as_widget_mut()
522            .overlay(
523                &mut self.state,
524                Layout::new(base),
525                renderer,
526                &viewport,
527                Vector::ZERO,
528            )
529            .map(overlay::Nested::new);
530
531        if let Some(mut overlay) = overlay {
532            overlay.draw(renderer, theme, style, Layout::new(layout), cursor);
533        }
534    }
535
536    /// Applies a [`widget::Operation`] to the [`UserInterface`].
537    pub fn operate(
538        &mut self,
539        renderer: &Renderer,
540        operation: &mut dyn widget::Operation,
541    ) {
542        let viewport = Rectangle::with_size(self.bounds);
543
544        self.root.as_widget().operate(
545            &mut self.state,
546            Layout::new(&self.base),
547            renderer,
548            operation,
549        );
550
551        if let Some(mut overlay) = self
552            .root
553            .as_widget_mut()
554            .overlay(
555                &mut self.state,
556                Layout::new(&self.base),
557                renderer,
558                &viewport,
559                Vector::ZERO,
560            )
561            .map(overlay::Nested::new)
562        {
563            if self.overlay.is_none() {
564                self.overlay = Some(Overlay {
565                    layout: overlay.layout(renderer, self.bounds),
566                    interaction: mouse::Interaction::None,
567                });
568            }
569
570            overlay.operate(
571                Layout::new(&self.overlay.as_ref().unwrap().layout),
572                renderer,
573                operation,
574            );
575        }
576    }
577
578    /// Relayouts and returns a new  [`UserInterface`] using the provided
579    /// bounds.
580    pub fn relayout(self, bounds: Size, renderer: &mut Renderer) -> Self {
581        Self::build(self.root, bounds, Cache { state: self.state }, renderer)
582    }
583
584    /// Extract the [`Cache`] of the [`UserInterface`], consuming it in the
585    /// process.
586    pub fn into_cache(self) -> Cache {
587        Cache { state: self.state }
588    }
589}
590
591/// Reusable data of a specific [`UserInterface`].
592#[derive(Debug)]
593pub struct Cache {
594    state: widget::Tree,
595}
596
597impl Cache {
598    /// Creates an empty [`Cache`].
599    ///
600    /// You should use this to initialize a [`Cache`] before building your first
601    /// [`UserInterface`].
602    pub fn new() -> Cache {
603        Cache {
604            state: widget::Tree::empty(),
605        }
606    }
607}
608
609impl Default for Cache {
610    fn default() -> Cache {
611        Cache::new()
612    }
613}
614
615/// The resulting state after updating a [`UserInterface`].
616#[derive(Debug, Clone)]
617pub enum State {
618    /// The [`UserInterface`] is outdated and needs to be rebuilt.
619    Outdated,
620
621    /// The [`UserInterface`] is up-to-date and can be reused without
622    /// rebuilding.
623    Updated {
624        /// The current [`mouse::Interaction`] of the user interface.
625        mouse_interaction: mouse::Interaction,
626        /// The [`window::RedrawRequest`] describing when a redraw should be performed.
627        redraw_request: window::RedrawRequest,
628        /// The current [`InputMethod`] strategy of the user interface.
629        input_method: InputMethod,
630    },
631}