Skip to main content

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::shell;
8use crate::core::widget;
9use crate::core::window;
10use crate::core::{
11    Clipboard, Element, InputMethod, Layout, Rectangle, Shell, Size, Vector, Window,
12};
13
14/// A set of interactive graphical elements with a specific [`Layout`].
15///
16/// It can be updated and drawn.
17///
18/// Iced tries to avoid dictating how to write your event loop. You are in
19/// charge of using this type in your system in any way you want.
20///
21/// # Example
22/// The [`integration`] example uses a [`UserInterface`] to integrate Iced in an
23/// existing graphical application.
24///
25/// [`integration`]: https://github.com/iced-rs/iced/tree/master/examples/integration
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::shell;
66    /// use iced_runtime::core::window;
67    /// use iced_runtime::core::Size;
68    /// use iced_runtime::user_interface::{self, UserInterface};
69    /// use iced_wgpu::Renderer;
70    ///
71    /// // Initialization
72    /// let mut counter = Counter::new();
73    /// let mut cache = user_interface::Cache::new();
74    /// let mut renderer = Renderer::default();
75    /// let mut window = window::Headless; // This should be a proper window, like a `winit` one
76    /// let mut waker = shell::Waker::noop();
77    /// let mut window_size = Size::new(1024.0, 768.0);
78    ///
79    /// // Application loop
80    /// loop {
81    ///     // Process system events here...
82    ///
83    ///     // Build the user interface
84    ///     let user_interface = UserInterface::build(
85    ///         counter.view(),
86    ///         window_size,
87    ///         cache,
88    ///         &mut renderer,
89    ///     );
90    ///
91    ///     // Update and draw the user interface here...
92    ///     // ...
93    ///
94    ///     // Obtain the cache for the next iteration
95    ///     cache = user_interface.into_cache();
96    /// }
97    /// ```
98    pub fn build<E: Into<Element<'a, Message, Theme, Renderer>>>(
99        root: E,
100        bounds: Size,
101        cache: Cache,
102        renderer: &mut Renderer,
103    ) -> Self {
104        let mut root = root.into();
105
106        let Cache { mut state } = cache;
107        state.diff(root.as_widget());
108
109        let base = root.as_widget_mut().layout(
110            &mut state,
111            renderer,
112            &layout::Limits::new(Size::ZERO, bounds),
113        );
114
115        UserInterface {
116            root,
117            base,
118            state,
119            overlay: None,
120            bounds,
121        }
122    }
123
124    /// Updates the [`UserInterface`] by processing each provided [`Event`].
125    ///
126    /// It returns __messages__ that may have been produced as a result of user
127    /// interactions. You should feed these to your __update logic__.
128    ///
129    /// # Example
130    /// Let's allow our [counter](index.html#usage) to change state by
131    /// completing [the previous example](#example):
132    ///
133    /// ```no_run
134    /// # mod iced_wgpu {
135    /// #     pub type Renderer = ();
136    /// # }
137    /// #
138    /// # pub struct Counter;
139    /// #
140    /// # impl Counter {
141    /// #     pub fn new() -> Self { Counter }
142    /// #     pub fn view(&self) -> iced_core::Element<(), (), Renderer> { unimplemented!() }
143    /// #     pub fn update(&mut self, _: ()) {}
144    /// # }
145    /// use iced_runtime::core::mouse;
146    /// use iced_runtime::core::shell;
147    /// use iced_runtime::core::window;
148    /// use iced_runtime::core::Size;
149    /// use iced_runtime::user_interface::{self, UserInterface};
150    /// use iced_wgpu::Renderer;
151    ///
152    /// let mut counter = Counter::new();
153    /// let mut cache = user_interface::Cache::new();
154    /// let mut renderer = Renderer::default();
155    /// let mut window = window::Headless; // This should be a proper window, like a `winit` one
156    /// let mut waker = shell::Waker::noop();
157    /// let mut window_size = Size::new(1024.0, 768.0);
158    /// let mut cursor = mouse::Cursor::default();
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    ///         &window,
177    ///         &waker,
178    ///         &events,
179    ///         cursor,
180    ///         &mut renderer,
181    ///         &mut messages
182    ///     );
183    ///
184    ///     cache = user_interface.into_cache();
185    ///
186    ///     // Process the produced messages
187    ///     for message in messages.drain(..) {
188    ///         counter.update(message);
189    ///     }
190    /// }
191    /// ```
192    pub fn update(
193        &mut self,
194        window: &dyn Window,
195        waker: &shell::Waker,
196        events: &[Event],
197        cursor: mouse::Cursor,
198        renderer: &mut Renderer,
199        messages: &mut Vec<Message>,
200    ) -> (State, Vec<event::Status>) {
201        let mut outdated = false;
202        let mut redraw_request = window::RedrawRequest::Wait;
203        let mut input_method = InputMethod::Disabled;
204        let mut clipboard = Clipboard::new();
205        let mut has_layout_changed = false;
206        let viewport = Rectangle::with_size(self.bounds);
207
208        let mut maybe_overlay = self
209            .root
210            .as_widget_mut()
211            .overlay(
212                &mut self.state,
213                Layout::new(&self.base),
214                renderer,
215                &viewport,
216                Vector::ZERO,
217            )
218            .map(overlay::Nested::new);
219
220        let (base_cursor, overlay_statuses, overlay_interaction) = if maybe_overlay.is_some() {
221            let bounds = self.bounds;
222
223            let mut overlay = maybe_overlay.as_mut().unwrap();
224            let mut layout = overlay.layout(renderer, bounds);
225            let mut event_statuses = Vec::new();
226
227            for event in events {
228                let mut shell = Shell::new(window, waker.clone(), messages);
229
230                overlay.update(event, Layout::new(&layout), cursor, renderer, &mut shell);
231
232                event_statuses.push(shell.event_status());
233                redraw_request = redraw_request.min(shell.redraw_request());
234                input_method.merge(shell.input_method());
235                clipboard.merge(shell.clipboard_mut());
236
237                if shell.is_layout_invalid() {
238                    drop(maybe_overlay);
239
240                    self.base = self.root.as_widget_mut().layout(
241                        &mut self.state,
242                        renderer,
243                        &layout::Limits::new(Size::ZERO, self.bounds),
244                    );
245
246                    maybe_overlay = self
247                        .root
248                        .as_widget_mut()
249                        .overlay(
250                            &mut self.state,
251                            Layout::new(&self.base),
252                            renderer,
253                            &viewport,
254                            Vector::ZERO,
255                        )
256                        .map(overlay::Nested::new);
257
258                    if maybe_overlay.is_none() {
259                        break;
260                    }
261
262                    overlay = maybe_overlay.as_mut().unwrap();
263
264                    shell.revalidate_layout(|| {
265                        layout = overlay.layout(renderer, bounds);
266                        has_layout_changed = true;
267                    });
268                }
269
270                if shell.are_widgets_invalid() {
271                    outdated = true;
272                }
273            }
274
275            let (base_cursor, interaction) = if let Some(overlay) = maybe_overlay.as_mut() {
276                let interaction = cursor
277                    .position()
278                    .map(|cursor_position| {
279                        overlay.mouse_interaction(
280                            Layout::new(&layout),
281                            mouse::Cursor::Available(cursor_position),
282                            renderer,
283                        )
284                    })
285                    .unwrap_or_default();
286
287                if interaction == mouse::Interaction::None {
288                    (cursor, mouse::Interaction::None)
289                } else {
290                    (mouse::Cursor::Unavailable, interaction)
291                }
292            } else {
293                (cursor, mouse::Interaction::None)
294            };
295
296            self.overlay = Some(Overlay {
297                layout,
298                interaction,
299            });
300
301            (base_cursor, event_statuses, interaction)
302        } else {
303            (
304                cursor,
305                vec![event::Status::Ignored; events.len()],
306                mouse::Interaction::None,
307            )
308        };
309
310        drop(maybe_overlay);
311
312        let event_statuses = events
313            .iter()
314            .zip(overlay_statuses)
315            .map(|(event, overlay_status)| {
316                if matches!(overlay_status, event::Status::Captured) {
317                    return overlay_status;
318                }
319
320                let mut shell = Shell::new(window, waker.clone(), messages);
321
322                self.root.as_widget_mut().update(
323                    &mut self.state,
324                    event,
325                    Layout::new(&self.base),
326                    base_cursor,
327                    renderer,
328                    &mut shell,
329                    &viewport,
330                );
331
332                if shell.event_status() == event::Status::Captured {
333                    self.overlay = None;
334                }
335
336                redraw_request = redraw_request.min(shell.redraw_request());
337                input_method.merge(shell.input_method());
338                clipboard.merge(shell.clipboard_mut());
339
340                shell.revalidate_layout(|| {
341                    has_layout_changed = true;
342
343                    self.base = self.root.as_widget_mut().layout(
344                        &mut self.state,
345                        renderer,
346                        &layout::Limits::new(Size::ZERO, self.bounds),
347                    );
348
349                    if let Some(mut overlay) = self
350                        .root
351                        .as_widget_mut()
352                        .overlay(
353                            &mut self.state,
354                            Layout::new(&self.base),
355                            renderer,
356                            &viewport,
357                            Vector::ZERO,
358                        )
359                        .map(overlay::Nested::new)
360                    {
361                        let layout = overlay.layout(renderer, self.bounds);
362                        let interaction =
363                            overlay.mouse_interaction(Layout::new(&layout), cursor, renderer);
364
365                        self.overlay = Some(Overlay {
366                            layout,
367                            interaction,
368                        });
369                    }
370                });
371
372                if shell.are_widgets_invalid() {
373                    outdated = true;
374                }
375
376                shell.event_status().merge(overlay_status)
377            })
378            .collect();
379
380        let mouse_interaction = if overlay_interaction == mouse::Interaction::None {
381            self.root.as_widget().mouse_interaction(
382                &self.state,
383                Layout::new(&self.base),
384                base_cursor,
385                &viewport,
386                renderer,
387            )
388        } else {
389            overlay_interaction
390        };
391
392        (
393            if outdated {
394                State::Outdated
395            } else {
396                State::Updated {
397                    mouse_interaction,
398                    redraw_request,
399                    input_method,
400                    clipboard,
401                    has_layout_changed,
402                }
403            },
404            event_statuses,
405        )
406    }
407
408    /// Draws the [`UserInterface`] with the provided [`Renderer`].
409    ///
410    /// It returns the current [`mouse::Interaction`]. You should update the
411    /// icon of the mouse cursor accordingly in your system.
412    ///
413    /// [`Renderer`]: crate::core::Renderer
414    ///
415    /// # Example
416    /// We can finally draw our [counter](index.html#usage) by
417    /// [completing the last example](#example-1):
418    ///
419    /// ```no_run
420    /// # mod iced_wgpu {
421    /// #     pub type Renderer = ();
422    /// #     pub type Theme = ();
423    /// # }
424    /// #
425    /// # pub struct Counter;
426    /// #
427    /// # impl Counter {
428    /// #     pub fn new() -> Self { Counter }
429    /// #     pub fn view(&self) -> Element<(), (), Renderer> { unimplemented!() }
430    /// #     pub fn update(&mut self, _: ()) {}
431    /// # }
432    /// use iced_runtime::core::mouse;
433    /// use iced_runtime::core::renderer;
434    /// use iced_runtime::core::shell;
435    /// use iced_runtime::core::window;
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 = window::Headless; // This should be a proper window, like a `winit` one
444    /// let mut waker = shell::Waker::noop();
445    /// let mut window_size = Size::new(1024.0, 768.0);
446    /// let mut cursor = mouse::Cursor::default();
447    /// let mut events = Vec::new();
448    /// let mut messages = Vec::new();
449    /// let mut theme = Theme::default();
450    ///
451    /// loop {
452    ///     // Obtain system events...
453    ///
454    ///     let mut user_interface = UserInterface::build(
455    ///         counter.view(),
456    ///         window_size,
457    ///         cache,
458    ///         &mut renderer,
459    ///     );
460    ///
461    ///     // Update the user interface
462    ///     let event_statuses = user_interface.update(
463    ///         &window,
464    ///         &waker,
465    ///         &events,
466    ///         cursor,
467    ///         &mut renderer,
468    ///         &mut messages
469    ///     );
470    ///
471    ///     // Draw the user interface
472    ///     let mouse_interaction = user_interface.draw(&mut renderer, &theme, &renderer::Style::default(), cursor);
473    ///
474    ///     cache = user_interface.into_cache();
475    ///
476    ///     for message in messages.drain(..) {
477    ///         counter.update(message);
478    ///     }
479    ///
480    ///     // Update mouse cursor icon...
481    ///     // Flush rendering operations...
482    /// }
483    /// ```
484    pub fn draw(
485        &mut self,
486        renderer: &mut Renderer,
487        theme: &Theme,
488        style: &renderer::Style,
489        cursor: mouse::Cursor,
490    ) {
491        let viewport = Rectangle::with_size(self.bounds);
492        renderer.reset(viewport);
493
494        let base_cursor = match &self.overlay {
495            None
496            | Some(Overlay {
497                interaction: mouse::Interaction::None,
498                ..
499            }) => cursor,
500            _ => mouse::Cursor::Unavailable,
501        };
502
503        self.root.as_widget().draw(
504            &self.state,
505            renderer,
506            theme,
507            style,
508            Layout::new(&self.base),
509            base_cursor,
510            &viewport,
511        );
512
513        let Self {
514            overlay,
515            root,
516            base,
517            ..
518        } = self;
519
520        let Some(Overlay { layout, .. }) = overlay.as_ref() else {
521            return;
522        };
523
524        let overlay = root
525            .as_widget_mut()
526            .overlay(
527                &mut self.state,
528                Layout::new(base),
529                renderer,
530                &viewport,
531                Vector::ZERO,
532            )
533            .map(overlay::Nested::new);
534
535        if let Some(mut overlay) = overlay {
536            overlay.draw(renderer, theme, style, Layout::new(layout), cursor);
537        }
538    }
539
540    /// Applies a [`widget::Operation`] to the [`UserInterface`].
541    pub fn operate(&mut self, renderer: &Renderer, operation: &mut dyn widget::Operation) {
542        let viewport = Rectangle::with_size(self.bounds);
543
544        self.root.as_widget_mut().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)]
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        /// The set of [`Clipboard`] requests that the user interface has produced.
631        clipboard: Clipboard,
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}