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