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