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.14/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 mut has_layout_changed = false;
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_mut().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                            has_layout_changed = true;
264                        });
265                    }
266
267                    if shell.are_widgets_invalid() {
268                        outdated = true;
269                    }
270                }
271
272                let (base_cursor, interaction) =
273                    if let Some(overlay) = maybe_overlay.as_mut() {
274                        let interaction = cursor
275                            .position()
276                            .map(|cursor_position| {
277                                overlay.mouse_interaction(
278                                    Layout::new(&layout),
279                                    mouse::Cursor::Available(cursor_position),
280                                    renderer,
281                                )
282                            })
283                            .unwrap_or_default();
284
285                        if interaction == mouse::Interaction::None {
286                            (cursor, mouse::Interaction::None)
287                        } else {
288                            (mouse::Cursor::Unavailable, interaction)
289                        }
290                    } else {
291                        (cursor, mouse::Interaction::None)
292                    };
293
294                self.overlay = Some(Overlay {
295                    layout,
296                    interaction,
297                });
298
299                (base_cursor, event_statuses, interaction)
300            } else {
301                (
302                    cursor,
303                    vec![event::Status::Ignored; events.len()],
304                    mouse::Interaction::None,
305                )
306            };
307
308        drop(maybe_overlay);
309
310        let event_statuses = events
311            .iter()
312            .zip(overlay_statuses)
313            .map(|(event, overlay_status)| {
314                if matches!(overlay_status, event::Status::Captured) {
315                    return overlay_status;
316                }
317
318                let mut shell = Shell::new(messages);
319
320                self.root.as_widget_mut().update(
321                    &mut self.state,
322                    event,
323                    Layout::new(&self.base),
324                    base_cursor,
325                    renderer,
326                    clipboard,
327                    &mut shell,
328                    &viewport,
329                );
330
331                if shell.event_status() == event::Status::Captured {
332                    self.overlay = None;
333                }
334
335                redraw_request = redraw_request.min(shell.redraw_request());
336                input_method.merge(shell.input_method());
337
338                shell.revalidate_layout(|| {
339                    has_layout_changed = true;
340
341                    self.base = self.root.as_widget_mut().layout(
342                        &mut self.state,
343                        renderer,
344                        &layout::Limits::new(Size::ZERO, self.bounds),
345                    );
346
347                    if let Some(mut overlay) = self
348                        .root
349                        .as_widget_mut()
350                        .overlay(
351                            &mut self.state,
352                            Layout::new(&self.base),
353                            renderer,
354                            &viewport,
355                            Vector::ZERO,
356                        )
357                        .map(overlay::Nested::new)
358                    {
359                        let layout = overlay.layout(renderer, self.bounds);
360                        let interaction = overlay.mouse_interaction(
361                            Layout::new(&layout),
362                            cursor,
363                            renderer,
364                        );
365
366                        self.overlay = Some(Overlay {
367                            layout,
368                            interaction,
369                        });
370                    }
371                });
372
373                if shell.are_widgets_invalid() {
374                    outdated = true;
375                }
376
377                shell.event_status().merge(overlay_status)
378            })
379            .collect();
380
381        let mouse_interaction =
382            if overlay_interaction == mouse::Interaction::None {
383                self.root.as_widget().mouse_interaction(
384                    &self.state,
385                    Layout::new(&self.base),
386                    base_cursor,
387                    &viewport,
388                    renderer,
389                )
390            } else {
391                overlay_interaction
392            };
393
394        (
395            if outdated {
396                State::Outdated
397            } else {
398                State::Updated {
399                    mouse_interaction,
400                    redraw_request,
401                    input_method,
402                    has_layout_changed,
403                }
404            },
405            event_statuses,
406        )
407    }
408
409    /// Draws the [`UserInterface`] with the provided [`Renderer`].
410    ///
411    /// It returns the current [`mouse::Interaction`]. You should update the
412    /// icon of the mouse cursor accordingly in your system.
413    ///
414    /// [`Renderer`]: crate::core::Renderer
415    ///
416    /// # Example
417    /// We can finally draw our [counter](index.html#usage) by
418    /// [completing the last example](#example-1):
419    ///
420    /// ```no_run
421    /// # mod iced_wgpu {
422    /// #     pub type Renderer = ();
423    /// #     pub type Theme = ();
424    /// # }
425    /// #
426    /// # pub struct Counter;
427    /// #
428    /// # impl Counter {
429    /// #     pub fn new() -> Self { Counter }
430    /// #     pub fn view(&self) -> Element<(), (), Renderer> { unimplemented!() }
431    /// #     pub fn update(&mut self, _: ()) {}
432    /// # }
433    /// use iced_runtime::core::clipboard;
434    /// use iced_runtime::core::mouse;
435    /// use iced_runtime::core::renderer;
436    /// use iced_runtime::core::{Element, Size};
437    /// use iced_runtime::user_interface::{self, UserInterface};
438    /// use iced_wgpu::{Renderer, Theme};
439    ///
440    /// let mut counter = Counter::new();
441    /// let mut cache = user_interface::Cache::new();
442    /// let mut renderer = Renderer::default();
443    /// let mut window_size = Size::new(1024.0, 768.0);
444    /// let mut cursor = mouse::Cursor::default();
445    /// let mut clipboard = clipboard::Null;
446    /// let mut events = Vec::new();
447    /// let mut messages = Vec::new();
448    /// let mut theme = Theme::default();
449    ///
450    /// loop {
451    ///     // Obtain system events...
452    ///
453    ///     let mut user_interface = UserInterface::build(
454    ///         counter.view(),
455    ///         window_size,
456    ///         cache,
457    ///         &mut renderer,
458    ///     );
459    ///
460    ///     // Update the user interface
461    ///     let event_statuses = user_interface.update(
462    ///         &events,
463    ///         cursor,
464    ///         &mut renderer,
465    ///         &mut clipboard,
466    ///         &mut messages
467    ///     );
468    ///
469    ///     // Draw the user interface
470    ///     let mouse_interaction = user_interface.draw(&mut renderer, &theme, &renderer::Style::default(), cursor);
471    ///
472    ///     cache = user_interface.into_cache();
473    ///
474    ///     for message in messages.drain(..) {
475    ///         counter.update(message);
476    ///     }
477    ///
478    ///     // Update mouse cursor icon...
479    ///     // Flush rendering operations...
480    /// }
481    /// ```
482    pub fn draw(
483        &mut self,
484        renderer: &mut Renderer,
485        theme: &Theme,
486        style: &renderer::Style,
487        cursor: mouse::Cursor,
488    ) {
489        let viewport = Rectangle::with_size(self.bounds);
490        renderer.reset(viewport);
491
492        let base_cursor = match &self.overlay {
493            None
494            | Some(Overlay {
495                interaction: mouse::Interaction::None,
496                ..
497            }) => cursor,
498            _ => mouse::Cursor::Unavailable,
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 Self {
512            overlay,
513            root,
514            base,
515            ..
516        } = self;
517
518        let Some(Overlay { layout, .. }) = overlay.as_ref() else {
519            return;
520        };
521
522        let overlay = root
523            .as_widget_mut()
524            .overlay(
525                &mut self.state,
526                Layout::new(base),
527                renderer,
528                &viewport,
529                Vector::ZERO,
530            )
531            .map(overlay::Nested::new);
532
533        if let Some(mut overlay) = overlay {
534            overlay.draw(renderer, theme, style, Layout::new(layout), cursor);
535        }
536    }
537
538    /// Applies a [`widget::Operation`] to the [`UserInterface`].
539    pub fn operate(
540        &mut self,
541        renderer: &Renderer,
542        operation: &mut dyn widget::Operation,
543    ) {
544        let viewport = Rectangle::with_size(self.bounds);
545
546        self.root.as_widget_mut().operate(
547            &mut self.state,
548            Layout::new(&self.base),
549            renderer,
550            operation,
551        );
552
553        if let Some(mut overlay) = self
554            .root
555            .as_widget_mut()
556            .overlay(
557                &mut self.state,
558                Layout::new(&self.base),
559                renderer,
560                &viewport,
561                Vector::ZERO,
562            )
563            .map(overlay::Nested::new)
564        {
565            if self.overlay.is_none() {
566                self.overlay = Some(Overlay {
567                    layout: overlay.layout(renderer, self.bounds),
568                    interaction: mouse::Interaction::None,
569                });
570            }
571
572            overlay.operate(
573                Layout::new(&self.overlay.as_ref().unwrap().layout),
574                renderer,
575                operation,
576            );
577        }
578    }
579
580    /// Relayouts and returns a new  [`UserInterface`] using the provided
581    /// bounds.
582    pub fn relayout(self, bounds: Size, renderer: &mut Renderer) -> Self {
583        Self::build(self.root, bounds, Cache { state: self.state }, renderer)
584    }
585
586    /// Extract the [`Cache`] of the [`UserInterface`], consuming it in the
587    /// process.
588    pub fn into_cache(self) -> Cache {
589        Cache { state: self.state }
590    }
591}
592
593/// Reusable data of a specific [`UserInterface`].
594#[derive(Debug)]
595pub struct Cache {
596    state: widget::Tree,
597}
598
599impl Cache {
600    /// Creates an empty [`Cache`].
601    ///
602    /// You should use this to initialize a [`Cache`] before building your first
603    /// [`UserInterface`].
604    pub fn new() -> Cache {
605        Cache {
606            state: widget::Tree::empty(),
607        }
608    }
609}
610
611impl Default for Cache {
612    fn default() -> Cache {
613        Cache::new()
614    }
615}
616
617/// The resulting state after updating a [`UserInterface`].
618#[derive(Debug, Clone)]
619pub enum State {
620    /// The [`UserInterface`] is outdated and needs to be rebuilt.
621    Outdated,
622
623    /// The [`UserInterface`] is up-to-date and can be reused without
624    /// rebuilding.
625    Updated {
626        /// The current [`mouse::Interaction`] of the user interface.
627        mouse_interaction: mouse::Interaction,
628        /// The [`window::RedrawRequest`] describing when a redraw should be performed.
629        redraw_request: window::RedrawRequest,
630        /// The current [`InputMethod`] strategy of the user interface.
631        input_method: InputMethod,
632        /// Whether the layout of the [`UserInterface`] has changed.
633        has_layout_changed: bool,
634    },
635}
636
637impl State {
638    /// Returns whether the layout of the [`UserInterface`] has changed.
639    pub fn has_layout_changed(&self) -> bool {
640        match self {
641            State::Outdated => true,
642            State::Updated {
643                has_layout_changed, ..
644            } => *has_layout_changed,
645        }
646    }
647}