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