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