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