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());
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 shell.is_layout_invalid() {
238 drop(maybe_overlay);
239
240 self.base = self.root.as_widget_mut().layout(
241 &mut self.state,
242 renderer,
243 &layout::Limits::new(Size::ZERO, self.bounds),
244 );
245
246 maybe_overlay = self
247 .root
248 .as_widget_mut()
249 .overlay(
250 &mut self.state,
251 Layout::new(&self.base),
252 renderer,
253 &viewport,
254 Vector::ZERO,
255 )
256 .map(overlay::Nested::new);
257
258 if maybe_overlay.is_none() {
259 break;
260 }
261
262 overlay = maybe_overlay.as_mut().unwrap();
263
264 shell.revalidate_layout(|| {
265 layout = overlay.layout(renderer, bounds);
266 has_layout_changed = true;
267 });
268 }
269
270 if shell.are_widgets_invalid() {
271 outdated = true;
272 }
273 }
274
275 let (base_cursor, interaction) = if let Some(overlay) = maybe_overlay.as_mut() {
276 let interaction = cursor
277 .position()
278 .map(|cursor_position| {
279 overlay.mouse_interaction(
280 Layout::new(&layout),
281 mouse::Cursor::Available(cursor_position),
282 renderer,
283 )
284 })
285 .unwrap_or_default();
286
287 if interaction == mouse::Interaction::None {
288 (cursor, mouse::Interaction::None)
289 } else {
290 (mouse::Cursor::Unavailable, interaction)
291 }
292 } else {
293 (cursor, mouse::Interaction::None)
294 };
295
296 self.overlay = Some(Overlay {
297 layout,
298 interaction,
299 });
300
301 (base_cursor, event_statuses, interaction)
302 } else {
303 (
304 cursor,
305 vec![event::Status::Ignored; events.len()],
306 mouse::Interaction::None,
307 )
308 };
309
310 drop(maybe_overlay);
311
312 let event_statuses = events
313 .iter()
314 .zip(overlay_statuses)
315 .map(|(event, overlay_status)| {
316 if matches!(overlay_status, event::Status::Captured) {
317 return overlay_status;
318 }
319
320 let mut shell = Shell::new(window, waker.clone(), messages);
321
322 self.root.as_widget_mut().update(
323 &mut self.state,
324 event,
325 Layout::new(&self.base),
326 base_cursor,
327 renderer,
328 &mut shell,
329 &viewport,
330 );
331
332 if shell.event_status() == event::Status::Captured {
333 self.overlay = None;
334 }
335
336 redraw_request = redraw_request.min(shell.redraw_request());
337 input_method.merge(shell.input_method());
338 clipboard.merge(shell.clipboard_mut());
339
340 shell.revalidate_layout(|| {
341 has_layout_changed = true;
342
343 self.base = self.root.as_widget_mut().layout(
344 &mut self.state,
345 renderer,
346 &layout::Limits::new(Size::ZERO, self.bounds),
347 );
348
349 if let Some(mut overlay) = self
350 .root
351 .as_widget_mut()
352 .overlay(
353 &mut self.state,
354 Layout::new(&self.base),
355 renderer,
356 &viewport,
357 Vector::ZERO,
358 )
359 .map(overlay::Nested::new)
360 {
361 let layout = overlay.layout(renderer, self.bounds);
362 let interaction =
363 overlay.mouse_interaction(Layout::new(&layout), cursor, renderer);
364
365 self.overlay = Some(Overlay {
366 layout,
367 interaction,
368 });
369 }
370 });
371
372 if shell.are_widgets_invalid() {
373 outdated = true;
374 }
375
376 shell.event_status().merge(overlay_status)
377 })
378 .collect();
379
380 let mouse_interaction = if overlay_interaction == mouse::Interaction::None {
381 self.root.as_widget().mouse_interaction(
382 &self.state,
383 Layout::new(&self.base),
384 base_cursor,
385 &viewport,
386 renderer,
387 )
388 } else {
389 overlay_interaction
390 };
391
392 (
393 if outdated {
394 State::Outdated
395 } else {
396 State::Updated {
397 mouse_interaction,
398 redraw_request,
399 input_method,
400 clipboard,
401 has_layout_changed,
402 }
403 },
404 event_statuses,
405 )
406 }
407
408 /// Draws the [`UserInterface`] with the provided [`Renderer`].
409 ///
410 /// It returns the current [`mouse::Interaction`]. You should update the
411 /// icon of the mouse cursor accordingly in your system.
412 ///
413 /// [`Renderer`]: crate::core::Renderer
414 ///
415 /// # Example
416 /// We can finally draw our [counter](index.html#usage) by
417 /// [completing the last example](#example-1):
418 ///
419 /// ```no_run
420 /// # mod iced_wgpu {
421 /// # pub type Renderer = ();
422 /// # pub type Theme = ();
423 /// # }
424 /// #
425 /// # pub struct Counter;
426 /// #
427 /// # impl Counter {
428 /// # pub fn new() -> Self { Counter }
429 /// # pub fn view(&self) -> Element<(), (), Renderer> { unimplemented!() }
430 /// # pub fn update(&mut self, _: ()) {}
431 /// # }
432 /// use iced_runtime::core::mouse;
433 /// use iced_runtime::core::renderer;
434 /// use iced_runtime::core::shell;
435 /// use iced_runtime::core::window;
436 /// use iced_runtime::core::{Element, Size};
437 /// use iced_runtime::user_interface::{self, UserInterface};
438 /// use iced_wgpu::{Renderer, Theme};
439 ///
440 /// let mut counter = Counter::new();
441 /// let mut cache = user_interface::Cache::new();
442 /// let mut renderer = Renderer::default();
443 /// let mut window = window::Headless; // This should be a proper window, like a `winit` one
444 /// let mut waker = shell::Waker::noop();
445 /// let mut window_size = Size::new(1024.0, 768.0);
446 /// let mut cursor = mouse::Cursor::default();
447 /// let mut events = Vec::new();
448 /// let mut messages = Vec::new();
449 /// let mut theme = Theme::default();
450 ///
451 /// loop {
452 /// // Obtain system events...
453 ///
454 /// let mut user_interface = UserInterface::build(
455 /// counter.view(),
456 /// window_size,
457 /// cache,
458 /// &mut renderer,
459 /// );
460 ///
461 /// // Update the user interface
462 /// let event_statuses = user_interface.update(
463 /// &window,
464 /// &waker,
465 /// &events,
466 /// cursor,
467 /// &mut renderer,
468 /// &mut messages
469 /// );
470 ///
471 /// // Draw the user interface
472 /// let mouse_interaction = user_interface.draw(&mut renderer, &theme, &renderer::Style::default(), cursor);
473 ///
474 /// cache = user_interface.into_cache();
475 ///
476 /// for message in messages.drain(..) {
477 /// counter.update(message);
478 /// }
479 ///
480 /// // Update mouse cursor icon...
481 /// // Flush rendering operations...
482 /// }
483 /// ```
484 pub fn draw(
485 &mut self,
486 renderer: &mut Renderer,
487 theme: &Theme,
488 style: &renderer::Style,
489 cursor: mouse::Cursor,
490 ) {
491 let viewport = Rectangle::with_size(self.bounds);
492 renderer.reset(viewport);
493
494 let base_cursor = match &self.overlay {
495 None
496 | Some(Overlay {
497 interaction: mouse::Interaction::None,
498 ..
499 }) => cursor,
500 _ => mouse::Cursor::Unavailable,
501 };
502
503 self.root.as_widget().draw(
504 &self.state,
505 renderer,
506 theme,
507 style,
508 Layout::new(&self.base),
509 base_cursor,
510 &viewport,
511 );
512
513 let Self {
514 overlay,
515 root,
516 base,
517 ..
518 } = self;
519
520 let Some(Overlay { layout, .. }) = overlay.as_ref() else {
521 return;
522 };
523
524 let overlay = root
525 .as_widget_mut()
526 .overlay(
527 &mut self.state,
528 Layout::new(base),
529 renderer,
530 &viewport,
531 Vector::ZERO,
532 )
533 .map(overlay::Nested::new);
534
535 if let Some(mut overlay) = overlay {
536 overlay.draw(renderer, theme, style, Layout::new(layout), cursor);
537 }
538 }
539
540 /// Applies a [`widget::Operation`] to the [`UserInterface`].
541 pub fn operate(&mut self, renderer: &Renderer, operation: &mut dyn widget::Operation) {
542 let viewport = Rectangle::with_size(self.bounds);
543
544 self.root.as_widget_mut().operate(
545 &mut self.state,
546 Layout::new(&self.base),
547 renderer,
548 operation,
549 );
550
551 if let Some(mut overlay) = self
552 .root
553 .as_widget_mut()
554 .overlay(
555 &mut self.state,
556 Layout::new(&self.base),
557 renderer,
558 &viewport,
559 Vector::ZERO,
560 )
561 .map(overlay::Nested::new)
562 {
563 if self.overlay.is_none() {
564 self.overlay = Some(Overlay {
565 layout: overlay.layout(renderer, self.bounds),
566 interaction: mouse::Interaction::None,
567 });
568 }
569
570 overlay.operate(
571 Layout::new(&self.overlay.as_ref().unwrap().layout),
572 renderer,
573 operation,
574 );
575 }
576 }
577
578 /// Relayouts and returns a new [`UserInterface`] using the provided
579 /// bounds.
580 pub fn relayout(self, bounds: Size, renderer: &mut Renderer) -> Self {
581 Self::build(self.root, bounds, Cache { state: self.state }, renderer)
582 }
583
584 /// Extract the [`Cache`] of the [`UserInterface`], consuming it in the
585 /// process.
586 pub fn into_cache(self) -> Cache {
587 Cache { state: self.state }
588 }
589}
590
591/// Reusable data of a specific [`UserInterface`].
592#[derive(Debug)]
593pub struct Cache {
594 state: widget::Tree,
595}
596
597impl Cache {
598 /// Creates an empty [`Cache`].
599 ///
600 /// You should use this to initialize a [`Cache`] before building your first
601 /// [`UserInterface`].
602 pub fn new() -> Cache {
603 Cache {
604 state: widget::Tree::empty(),
605 }
606 }
607}
608
609impl Default for Cache {
610 fn default() -> Cache {
611 Cache::new()
612 }
613}
614
615/// The resulting state after updating a [`UserInterface`].
616#[derive(Debug)]
617pub enum State {
618 /// The [`UserInterface`] is outdated and needs to be rebuilt.
619 Outdated,
620
621 /// The [`UserInterface`] is up-to-date and can be reused without
622 /// rebuilding.
623 Updated {
624 /// The current [`mouse::Interaction`] of the user interface.
625 mouse_interaction: mouse::Interaction,
626 /// The [`window::RedrawRequest`] describing when a redraw should be performed.
627 redraw_request: window::RedrawRequest,
628 /// The current [`InputMethod`] strategy of the user interface.
629 input_method: InputMethod,
630 /// The set of [`Clipboard`] requests that the user interface has produced.
631 clipboard: Clipboard,
632 /// Whether the layout of the [`UserInterface`] has changed.
633 has_layout_changed: bool,
634 },
635}
636
637impl State {
638 /// Returns whether the layout of the [`UserInterface`] has changed.
639 pub fn has_layout_changed(&self) -> bool {
640 match self {
641 State::Outdated => true,
642 State::Updated {
643 has_layout_changed, ..
644 } => *has_layout_changed,
645 }
646 }
647}