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