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::keyboard;
4use crate::core::layout;
5use crate::core::mouse;
6use crate::core::overlay;
7use crate::core::renderer;
8use crate::core::text;
9use crate::core::widget;
10use crate::core::widget::operation::{self, Operation};
11use crate::core::window;
12use crate::core::{Clipboard, Element, InputMethod, Layout, Point, Rectangle, Shell, Size, Vector};
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    selection: Selection,
33}
34
35struct Overlay {
36    layout: layout::Node,
37    interaction: mouse::Interaction,
38}
39
40impl<'a, Message, Theme, Renderer> UserInterface<'a, Message, Theme, Renderer>
41where
42    Renderer: crate::core::Renderer,
43{
44    /// Builds a user interface for an [`Element`].
45    ///
46    /// It is able to avoid expensive computations when using a [`Cache`]
47    /// obtained from a previous instance of a [`UserInterface`].
48    ///
49    /// # Example
50    /// Imagine we want to build a [`UserInterface`] for
51    /// [the counter example that we previously wrote](index.html#usage). Here
52    /// is naive way to set up our application loop:
53    ///
54    /// ```no_run
55    /// # mod iced_wgpu {
56    /// #     pub type Renderer = ();
57    /// # }
58    /// #
59    /// # pub struct Counter;
60    /// #
61    /// # impl Counter {
62    /// #     pub fn new() -> Self { Counter }
63    /// #     pub fn view(&self) -> iced_core::Element<(), (), Renderer> { unimplemented!() }
64    /// #     pub fn update(&mut self, _: ()) {}
65    /// # }
66    /// use iced_runtime::core::Size;
67    /// use iced_runtime::user_interface::{self, UserInterface};
68    /// use iced_wgpu::Renderer;
69    ///
70    /// // Initialization
71    /// let mut counter = Counter::new();
72    /// let mut cache = user_interface::Cache::new();
73    /// let mut renderer = Renderer::default();
74    /// let mut window_size = Size::new(1024.0, 768.0);
75    ///
76    /// // Application loop
77    /// loop {
78    ///     // Process system events here...
79    ///
80    ///     // Build the user interface
81    ///     let user_interface = UserInterface::build(
82    ///         counter.view(),
83    ///         window_size,
84    ///         cache,
85    ///         &mut renderer,
86    ///     );
87    ///
88    ///     // Update and draw the user interface here...
89    ///     // ...
90    ///
91    ///     // Obtain the cache for the next iteration
92    ///     cache = user_interface.into_cache();
93    /// }
94    /// ```
95    pub fn build<E: Into<Element<'a, Message, Theme, Renderer>>>(
96        root: E,
97        bounds: Size,
98        cache: Cache,
99        renderer: &mut Renderer,
100    ) -> Self {
101        let mut root = root.into();
102
103        let Cache {
104            mut state,
105            selection,
106        } = 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            selection,
122        }
123    }
124
125    /// Updates the [`UserInterface`] by processing each provided [`Event`].
126    ///
127    /// It returns __messages__ that may have been produced as a result of user
128    /// interactions. You should feed these to your __update logic__.
129    ///
130    /// # Example
131    /// Let's allow our [counter](index.html#usage) to change state by
132    /// completing [the previous example](#example):
133    ///
134    /// ```no_run
135    /// # mod iced_wgpu {
136    /// #     pub type Renderer = ();
137    /// # }
138    /// #
139    /// # pub struct Counter;
140    /// #
141    /// # impl Counter {
142    /// #     pub fn new() -> Self { Counter }
143    /// #     pub fn view(&self) -> iced_core::Element<(), (), Renderer> { unimplemented!() }
144    /// #     pub fn update(&mut self, _: ()) {}
145    /// # }
146    /// use iced_runtime::core::mouse;
147    /// use iced_runtime::core::Size;
148    /// use iced_runtime::user_interface::{self, UserInterface};
149    /// use iced_wgpu::Renderer;
150    ///
151    /// let mut counter = Counter::new();
152    /// let mut cache = user_interface::Cache::new();
153    /// let mut renderer = Renderer::default();
154    /// let mut window_size = Size::new(1024.0, 768.0);
155    /// let mut cursor = mouse::Cursor::default();
156    ///
157    /// // Initialize our event storage
158    /// let mut events = Vec::new();
159    /// let mut messages = Vec::new();
160    ///
161    /// loop {
162    ///     // Obtain system events...
163    ///
164    ///     let mut user_interface = UserInterface::build(
165    ///         counter.view(),
166    ///         window_size,
167    ///         cache,
168    ///         &mut renderer,
169    ///     );
170    ///
171    ///     // Update the user interface
172    ///     let (state, event_statuses) = user_interface.update(
173    ///         &events,
174    ///         cursor,
175    ///         &mut renderer,
176    ///         &mut messages
177    ///     );
178    ///
179    ///     cache = user_interface.into_cache();
180    ///
181    ///     // Process the produced messages
182    ///     for message in messages.drain(..) {
183    ///         counter.update(message);
184    ///     }
185    /// }
186    /// ```
187    pub fn update(
188        &mut self,
189        events: &[Event],
190        cursor: mouse::Cursor,
191        renderer: &mut Renderer,
192        messages: &mut Vec<Message>,
193    ) -> (State, Vec<event::Status>) {
194        let mut outdated = false;
195        let mut redraw_request = window::RedrawRequest::Wait;
196        let mut input_method = InputMethod::Disabled;
197        let mut clipboard = Clipboard::new();
198        let mut has_layout_changed = false;
199        let mut auto_focus_pending = false;
200        let viewport = Rectangle::with_size(self.bounds);
201
202        let mut maybe_overlay = self
203            .root
204            .as_widget_mut()
205            .overlay(
206                &mut self.state,
207                Layout::new(&self.base),
208                renderer,
209                &viewport,
210                Vector::ZERO,
211            )
212            .map(overlay::Nested::new);
213
214        let (base_cursor, overlay_statuses, overlay_interaction) = if maybe_overlay.is_some() {
215            let bounds = self.bounds;
216
217            let mut overlay = maybe_overlay.as_mut().unwrap();
218            let mut layout = overlay.layout(renderer, bounds);
219            let mut event_statuses = Vec::new();
220
221            for event in events {
222                let mut shell = Shell::new(messages);
223
224                overlay.update(event, Layout::new(&layout), cursor, renderer, &mut shell);
225
226                event_statuses.push(shell.event_status());
227                redraw_request = redraw_request.min(shell.redraw_request());
228                input_method.merge(shell.input_method());
229                clipboard.merge(shell.clipboard_mut());
230
231                if shell.is_layout_invalid() {
232                    drop(maybe_overlay);
233
234                    self.base = self.root.as_widget_mut().layout(
235                        &mut self.state,
236                        renderer,
237                        &layout::Limits::new(Size::ZERO, self.bounds),
238                    );
239
240                    maybe_overlay = self
241                        .root
242                        .as_widget_mut()
243                        .overlay(
244                            &mut self.state,
245                            Layout::new(&self.base),
246                            renderer,
247                            &viewport,
248                            Vector::ZERO,
249                        )
250                        .map(overlay::Nested::new);
251
252                    if maybe_overlay.is_none() {
253                        break;
254                    }
255
256                    overlay = maybe_overlay.as_mut().unwrap();
257
258                    shell.revalidate_layout(|| {
259                        layout = overlay.layout(renderer, bounds);
260                        has_layout_changed = true;
261                    });
262                }
263
264                if shell.are_widgets_invalid() {
265                    outdated = true;
266                }
267
268                if shell.is_auto_focus_pending() {
269                    auto_focus_pending = true;
270                }
271            }
272
273            let (base_cursor, interaction) = 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        // The latest drag to apply, kept apart from `self.selection.drag` so a
311        // release in the same batch as the last move doesn't drop it.
312        let mut reselect = None;
313        let mut select_all = false;
314        let mut publish_primary = false;
315
316        let event_statuses = events
317            .iter()
318            .zip(overlay_statuses)
319            .map(|(event, overlay_status)| {
320                if matches!(overlay_status, event::Status::Captured) {
321                    return overlay_status;
322                }
323
324                let mut shell = Shell::new(messages);
325
326                self.root.as_widget_mut().update(
327                    &mut self.state,
328                    event,
329                    Layout::new(&self.base),
330                    base_cursor,
331                    renderer,
332                    &mut shell,
333                    &viewport,
334                );
335
336                if shell.event_status() == event::Status::Captured {
337                    self.overlay = None;
338                }
339
340                redraw_request = redraw_request.min(shell.redraw_request());
341                input_method.merge(shell.input_method());
342                {
343                    let shell_cb = shell.clipboard_mut();
344                    clipboard.merge(shell_cb);
345                }
346
347                shell.revalidate_layout(|| {
348                    has_layout_changed = true;
349
350                    self.base = self.root.as_widget_mut().layout(
351                        &mut self.state,
352                        renderer,
353                        &layout::Limits::new(Size::ZERO, self.bounds),
354                    );
355
356                    if let Some(mut overlay) = self
357                        .root
358                        .as_widget_mut()
359                        .overlay(
360                            &mut self.state,
361                            Layout::new(&self.base),
362                            renderer,
363                            &viewport,
364                            Vector::ZERO,
365                        )
366                        .map(overlay::Nested::new)
367                    {
368                        let layout = overlay.layout(renderer, self.bounds);
369                        let interaction =
370                            overlay.mouse_interaction(Layout::new(&layout), cursor, renderer);
371
372                        self.overlay = Some(Overlay {
373                            layout,
374                            interaction,
375                        });
376                    }
377                });
378
379                if shell.are_widgets_invalid() {
380                    outdated = true;
381                }
382
383                if shell.is_auto_focus_pending() {
384                    auto_focus_pending = true;
385                }
386
387                let status = shell.event_status().merge(overlay_status);
388
389                // Text selection belongs to the runtime: it starts wherever no
390                // widget took the press, and crosses every selectable widget.
391                match event {
392                    Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => {
393                        self.selection.modifiers = *modifiers;
394                    }
395                    Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
396                        if status == event::Status::Ignored =>
397                    {
398                        if let Some(position) = base_cursor.position() {
399                            let click = mouse::Click::new(
400                                position,
401                                mouse::Button::Left,
402                                self.selection.last_click,
403                            );
404
405                            let start = self
406                                .selection
407                                .anchor
408                                .filter(|_| {
409                                    self.selection.modifiers.shift()
410                                        && click.kind() == mouse::click::Kind::Single
411                                })
412                                .unwrap_or(position);
413
414                            self.selection.last_click = Some(click);
415                            self.selection.anchor = Some(start);
416                            self.selection.drag = Some((start, position));
417                            self.selection.target = match click.kind() {
418                                mouse::click::Kind::Single => text::Target::Character,
419                                mouse::click::Kind::Double => text::Target::Word,
420                                mouse::click::Kind::Triple => text::Target::Line,
421                            };
422                            reselect = self.selection.drag;
423                        }
424                    }
425                    Event::Mouse(mouse::Event::CursorMoved { position }) => {
426                        if let Some((_, end)) = self.selection.drag.as_mut()
427                            && end != position
428                        {
429                            *end = *position;
430                            reselect = self.selection.drag;
431                        }
432                    }
433                    Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
434                        if self.selection.drag.take().is_some() =>
435                    {
436                        publish_primary = true;
437                    }
438                    Event::Keyboard(keyboard::Event::KeyPressed {
439                        key: keyboard::Key::Character(c),
440                        modifiers,
441                        repeat: false,
442                        ..
443                    }) if status == event::Status::Ignored
444                        && modifiers.command()
445                        && !modifiers.alt()
446                        && !modifiers.shift() =>
447                    {
448                        match c.as_str() {
449                            // A widget's own copy, such as an app-managed
450                            // selection's, wins.
451                            "c" if clipboard.write.is_none() => {
452                                if let Some(text) = self.copy_selection(renderer) {
453                                    clipboard.write =
454                                        Some(crate::core::clipboard::Content::Text(text));
455                                }
456                            }
457                            // Only once the selection has been used, so the
458                            // shortcut doesn't paint a window that never asked.
459                            "a" if self.selection.anchor.is_some() => select_all = true,
460                            _ => {}
461                        }
462                    }
463                    _ => {}
464                }
465
466                status
467            })
468            .collect();
469
470        if let Some((start, end)) = reselect {
471            let mut select = operation::selection::select(start, end, self.selection.target);
472            self.operate(renderer, &mut select);
473            redraw_request = window::RedrawRequest::NextFrame;
474        }
475
476        if select_all {
477            self.operate(renderer, &mut operation::selection::select_all());
478            redraw_request = window::RedrawRequest::NextFrame;
479        }
480
481        if (publish_primary || select_all)
482            && let Some(text) = self.copy_selection(renderer)
483        {
484            clipboard.primary_write = Some(text);
485        }
486
487        let mouse_interaction = if overlay_interaction == mouse::Interaction::None {
488            self.root.as_widget().mouse_interaction(
489                &self.state,
490                Layout::new(&self.base),
491                base_cursor,
492                &viewport,
493                renderer,
494            )
495        } else {
496            overlay_interaction
497        };
498
499        (
500            if outdated {
501                State::Outdated {
502                    clipboard,
503                    auto_focus_pending,
504                }
505            } else {
506                State::Updated {
507                    mouse_interaction,
508                    redraw_request,
509                    input_method,
510                    clipboard,
511                    has_layout_changed,
512                    auto_focus_pending,
513                }
514            },
515            event_statuses,
516        )
517    }
518
519    /// Draws the [`UserInterface`] with the provided [`Renderer`].
520    ///
521    /// It returns the current [`mouse::Interaction`]. You should update the
522    /// icon of the mouse cursor accordingly in your system.
523    ///
524    /// [`Renderer`]: crate::core::Renderer
525    ///
526    /// # Example
527    /// We can finally draw our [counter](index.html#usage) by
528    /// [completing the last example](#example-1):
529    ///
530    /// ```no_run
531    /// # mod iced_wgpu {
532    /// #     pub type Renderer = ();
533    /// #     pub type Theme = ();
534    /// # }
535    /// #
536    /// # pub struct Counter;
537    /// #
538    /// # impl Counter {
539    /// #     pub fn new() -> Self { Counter }
540    /// #     pub fn view(&self) -> Element<(), (), Renderer> { unimplemented!() }
541    /// #     pub fn update(&mut self, _: ()) {}
542    /// # }
543    /// use iced_runtime::core::mouse;
544    /// use iced_runtime::core::renderer;
545    /// use iced_runtime::core::{Element, Size};
546    /// use iced_runtime::user_interface::{self, UserInterface};
547    /// use iced_wgpu::{Renderer, Theme};
548    ///
549    /// let mut counter = Counter::new();
550    /// let mut cache = user_interface::Cache::new();
551    /// let mut renderer = Renderer::default();
552    /// let mut window_size = Size::new(1024.0, 768.0);
553    /// let mut cursor = mouse::Cursor::default();
554    /// let mut events = Vec::new();
555    /// let mut messages = Vec::new();
556    /// let mut theme = Theme::default();
557    ///
558    /// loop {
559    ///     // Obtain system events...
560    ///
561    ///     let mut user_interface = UserInterface::build(
562    ///         counter.view(),
563    ///         window_size,
564    ///         cache,
565    ///         &mut renderer,
566    ///     );
567    ///
568    ///     // Update the user interface
569    ///     let event_statuses = user_interface.update(
570    ///         &events,
571    ///         cursor,
572    ///         &mut renderer,
573    ///         &mut messages
574    ///     );
575    ///
576    ///     // Draw the user interface
577    ///     let mouse_interaction = user_interface.draw(&mut renderer, &theme, &renderer::Style::default(), cursor);
578    ///
579    ///     cache = user_interface.into_cache();
580    ///
581    ///     for message in messages.drain(..) {
582    ///         counter.update(message);
583    ///     }
584    ///
585    ///     // Update mouse cursor icon...
586    ///     // Flush rendering operations...
587    /// }
588    /// ```
589    pub fn draw(
590        &mut self,
591        renderer: &mut Renderer,
592        theme: &Theme,
593        style: &renderer::Style,
594        cursor: mouse::Cursor,
595    ) {
596        let viewport = Rectangle::with_size(self.bounds);
597        renderer.reset(viewport);
598
599        let base_cursor = match &self.overlay {
600            None
601            | Some(Overlay {
602                interaction: mouse::Interaction::None,
603                ..
604            }) => cursor,
605            _ => mouse::Cursor::Unavailable,
606        };
607
608        self.root.as_widget().draw(
609            &self.state,
610            renderer,
611            theme,
612            style,
613            Layout::new(&self.base),
614            base_cursor,
615            &viewport,
616        );
617
618        let Self {
619            overlay,
620            root,
621            base,
622            ..
623        } = self;
624
625        let Some(Overlay { layout, .. }) = overlay.as_ref() else {
626            return;
627        };
628
629        let overlay = root
630            .as_widget_mut()
631            .overlay(
632                &mut self.state,
633                Layout::new(base),
634                renderer,
635                &viewport,
636                Vector::ZERO,
637            )
638            .map(overlay::Nested::new);
639
640        if let Some(mut overlay) = overlay {
641            // Above everything, including anything the base tree is holding
642            // back for the pass that follows the backdrop blur — which is
643            // drawn last and so would otherwise cover it. A dialog on a pane
644            // of frosted glass is exactly that, and a menu opened inside one
645            // was being painted over by the card it belonged to: built, sized,
646            // taking clicks where it should be, and invisible.
647            //
648            // Only when there is such a pass to join. Deferring content is
649            // what makes the renderer copy the scene and composite it, and an
650            // interface with no blur in it should not start paying for that
651            // the moment a tooltip appears.
652            if renderer.has_post_blur_content() {
653                renderer.with_post_blur_layer(viewport, |renderer| {
654                    overlay.draw(renderer, theme, style, Layout::new(layout), cursor);
655                });
656            } else {
657                overlay.draw(renderer, theme, style, Layout::new(layout), cursor);
658            }
659        }
660    }
661
662    /// Applies a [`widget::Operation`] to the [`UserInterface`].
663    pub fn operate(&mut self, renderer: &Renderer, operation: &mut dyn widget::Operation) {
664        let viewport = Rectangle::with_size(self.bounds);
665
666        self.root.as_widget_mut().operate(
667            &mut self.state,
668            Layout::new(&self.base),
669            renderer,
670            operation,
671        );
672
673        if let Some(mut overlay) = self
674            .root
675            .as_widget_mut()
676            .overlay(
677                &mut self.state,
678                Layout::new(&self.base),
679                renderer,
680                &viewport,
681                Vector::ZERO,
682            )
683            .map(overlay::Nested::new)
684        {
685            if self.overlay.is_none() {
686                self.overlay = Some(Overlay {
687                    layout: overlay.layout(renderer, self.bounds),
688                    interaction: mouse::Interaction::None,
689                });
690            }
691
692            overlay.operate(
693                Layout::new(&self.overlay.as_ref().unwrap().layout),
694                renderer,
695                operation,
696            );
697        }
698    }
699
700    /// The text selected across the selectable widgets, one line per widget.
701    fn copy_selection(&mut self, renderer: &Renderer) -> Option<String> {
702        let mut copy = operation::selection::copy();
703        self.operate(renderer, &mut operation::black_box(&mut copy));
704
705        match copy.finish() {
706            operation::Outcome::Some(text) => Some(text),
707            _ => None,
708        }
709    }
710
711    /// Relayouts and returns a new  [`UserInterface`] using the provided
712    /// bounds.
713    pub fn relayout(self, bounds: Size, renderer: &mut Renderer) -> Self {
714        Self::build(
715            self.root,
716            bounds,
717            Cache {
718                state: self.state,
719                selection: self.selection,
720            },
721            renderer,
722        )
723    }
724
725    /// Returns the size of the root content after layout.
726    ///
727    /// This is the actual size that the content wants to be, which may be
728    /// smaller than the bounds passed to `build` or `relayout`.
729    pub fn content_size(&self) -> Size {
730        self.base.size()
731    }
732
733    /// Extract the [`Cache`] of the [`UserInterface`], consuming it in the
734    /// process.
735    pub fn into_cache(self) -> Cache {
736        Cache {
737            state: self.state,
738            selection: self.selection,
739        }
740    }
741}
742
743/// Reusable data of a specific [`UserInterface`].
744#[derive(Debug)]
745pub struct Cache {
746    state: widget::Tree,
747    selection: Selection,
748}
749
750/// The text selection the runtime drives across selectable widgets.
751#[derive(Debug, Clone, Copy, Default)]
752struct Selection {
753    last_click: Option<mouse::Click>,
754    /// Where the latest selection started, so a shift-click can extend it.
755    anchor: Option<Point>,
756    /// The drag in progress: where it started and where the cursor is now.
757    drag: Option<(Point, Point)>,
758    target: text::Target,
759    modifiers: keyboard::Modifiers,
760}
761
762impl Cache {
763    /// Creates an empty [`Cache`].
764    ///
765    /// You should use this to initialize a [`Cache`] before building your first
766    /// [`UserInterface`].
767    pub fn new() -> Cache {
768        Cache {
769            state: widget::Tree::empty(),
770            selection: Selection::default(),
771        }
772    }
773}
774
775impl Default for Cache {
776    fn default() -> Cache {
777        Cache::new()
778    }
779}
780
781/// The resulting state after updating a [`UserInterface`].
782#[derive(Debug)]
783pub enum State {
784    /// The [`UserInterface`] is outdated and needs to be rebuilt.
785    ///
786    /// Clipboard requests from the update cycle are still preserved so they
787    /// can be processed even when the widget tree needs rebuilding.
788    Outdated {
789        /// The set of [`Clipboard`] requests that the user interface has produced.
790        clipboard: Clipboard,
791        /// Whether an auto-focus operation should be run after the tree is rebuilt.
792        auto_focus_pending: bool,
793    },
794
795    /// The [`UserInterface`] is up-to-date and can be reused without
796    /// rebuilding.
797    Updated {
798        /// The current [`mouse::Interaction`] of the user interface.
799        mouse_interaction: mouse::Interaction,
800        /// The [`window::RedrawRequest`] describing when a redraw should be performed.
801        redraw_request: window::RedrawRequest,
802        /// The current [`InputMethod`] strategy of the user interface.
803        input_method: InputMethod,
804        /// The set of [`Clipboard`] requests that the user interface has produced.
805        clipboard: Clipboard,
806        /// Whether the layout of the [`UserInterface`] has changed.
807        has_layout_changed: bool,
808        /// Whether an auto-focus operation should be run after the tree is rebuilt.
809        auto_focus_pending: bool,
810    },
811}
812
813impl State {
814    /// Returns whether the layout of the [`UserInterface`] has changed.
815    pub fn has_layout_changed(&self) -> bool {
816        match self {
817            State::Outdated { .. } => true,
818            State::Updated {
819                has_layout_changed, ..
820            } => *has_layout_changed,
821        }
822    }
823
824    /// Returns whether an auto-focus operation is pending.
825    pub fn is_auto_focus_pending(&self) -> bool {
826        match self {
827            State::Outdated {
828                auto_focus_pending, ..
829            }
830            | State::Updated {
831                auto_focus_pending, ..
832            } => *auto_focus_pending,
833        }
834    }
835}