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}