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