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