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