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 self.overlay = None;
345 });
346
347 if shell.are_widgets_invalid() {
348 outdated = true;
349 }
350
351 shell.event_status().merge(overlay_status)
352 })
353 .collect();
354
355 let mouse_interaction =
356 if overlay_interaction == mouse::Interaction::None {
357 self.root.as_widget().mouse_interaction(
358 &self.state,
359 Layout::new(&self.base),
360 base_cursor,
361 &viewport,
362 renderer,
363 )
364 } else {
365 overlay_interaction
366 };
367
368 (
369 if outdated {
370 State::Outdated
371 } else {
372 State::Updated {
373 mouse_interaction,
374 redraw_request,
375 input_method,
376 }
377 },
378 event_statuses,
379 )
380 }
381
382 /// Draws the [`UserInterface`] with the provided [`Renderer`].
383 ///
384 /// It returns the current [`mouse::Interaction`]. You should update the
385 /// icon of the mouse cursor accordingly in your system.
386 ///
387 /// [`Renderer`]: crate::core::Renderer
388 ///
389 /// # Example
390 /// We can finally draw our [counter](index.html#usage) by
391 /// [completing the last example](#example-1):
392 ///
393 /// ```no_run
394 /// # mod iced_wgpu {
395 /// # pub type Renderer = ();
396 /// # pub type Theme = ();
397 /// # }
398 /// #
399 /// # pub struct Counter;
400 /// #
401 /// # impl Counter {
402 /// # pub fn new() -> Self { Counter }
403 /// # pub fn view(&self) -> Element<(), (), Renderer> { unimplemented!() }
404 /// # pub fn update(&mut self, _: ()) {}
405 /// # }
406 /// use iced_runtime::core::clipboard;
407 /// use iced_runtime::core::mouse;
408 /// use iced_runtime::core::renderer;
409 /// use iced_runtime::core::{Element, Size};
410 /// use iced_runtime::user_interface::{self, UserInterface};
411 /// use iced_wgpu::{Renderer, Theme};
412 ///
413 /// let mut counter = Counter::new();
414 /// let mut cache = user_interface::Cache::new();
415 /// let mut renderer = Renderer::default();
416 /// let mut window_size = Size::new(1024.0, 768.0);
417 /// let mut cursor = mouse::Cursor::default();
418 /// let mut clipboard = clipboard::Null;
419 /// let mut events = Vec::new();
420 /// let mut messages = Vec::new();
421 /// let mut theme = Theme::default();
422 ///
423 /// loop {
424 /// // Obtain system events...
425 ///
426 /// let mut user_interface = UserInterface::build(
427 /// counter.view(),
428 /// window_size,
429 /// cache,
430 /// &mut renderer,
431 /// );
432 ///
433 /// // Update the user interface
434 /// let event_statuses = user_interface.update(
435 /// &events,
436 /// cursor,
437 /// &mut renderer,
438 /// &mut clipboard,
439 /// &mut messages
440 /// );
441 ///
442 /// // Draw the user interface
443 /// let mouse_interaction = user_interface.draw(&mut renderer, &theme, &renderer::Style::default(), cursor);
444 ///
445 /// cache = user_interface.into_cache();
446 ///
447 /// for message in messages.drain(..) {
448 /// counter.update(message);
449 /// }
450 ///
451 /// // Update mouse cursor icon...
452 /// // Flush rendering operations...
453 /// }
454 /// ```
455 pub fn draw(
456 &mut self,
457 renderer: &mut Renderer,
458 theme: &Theme,
459 style: &renderer::Style,
460 cursor: mouse::Cursor,
461 ) {
462 // TODO: Move to shell level (?)
463 renderer.clear();
464
465 let viewport = Rectangle::with_size(self.bounds);
466
467 let base_cursor = match &self.overlay {
468 None
469 | Some(Overlay {
470 interaction: mouse::Interaction::None,
471 ..
472 }) => cursor,
473 _ => mouse::Cursor::Unavailable,
474 };
475
476 self.root.as_widget().draw(
477 &self.state,
478 renderer,
479 theme,
480 style,
481 Layout::new(&self.base),
482 base_cursor,
483 &viewport,
484 );
485
486 let Self {
487 overlay,
488 root,
489 base,
490 ..
491 } = self;
492
493 let Some(Overlay { layout, .. }) = overlay.as_ref() else {
494 return;
495 };
496
497 let overlay = root
498 .as_widget_mut()
499 .overlay(
500 &mut self.state,
501 Layout::new(base),
502 renderer,
503 &viewport,
504 Vector::ZERO,
505 )
506 .map(overlay::Nested::new);
507
508 if let Some(mut overlay) = overlay {
509 overlay.draw(renderer, theme, style, Layout::new(layout), cursor);
510 }
511 }
512
513 /// Applies a [`widget::Operation`] to the [`UserInterface`].
514 pub fn operate(
515 &mut self,
516 renderer: &Renderer,
517 operation: &mut dyn widget::Operation,
518 ) {
519 let viewport = Rectangle::with_size(self.bounds);
520
521 self.root.as_widget().operate(
522 &mut self.state,
523 Layout::new(&self.base),
524 renderer,
525 operation,
526 );
527
528 if let Some(mut overlay) = self
529 .root
530 .as_widget_mut()
531 .overlay(
532 &mut self.state,
533 Layout::new(&self.base),
534 renderer,
535 &viewport,
536 Vector::ZERO,
537 )
538 .map(overlay::Nested::new)
539 {
540 if self.overlay.is_none() {
541 self.overlay = Some(Overlay {
542 layout: overlay.layout(renderer, self.bounds),
543 interaction: mouse::Interaction::None,
544 });
545 }
546
547 overlay.operate(
548 Layout::new(&self.overlay.as_ref().unwrap().layout),
549 renderer,
550 operation,
551 );
552 }
553 }
554
555 /// Relayouts and returns a new [`UserInterface`] using the provided
556 /// bounds.
557 pub fn relayout(self, bounds: Size, renderer: &mut Renderer) -> Self {
558 Self::build(self.root, bounds, Cache { state: self.state }, renderer)
559 }
560
561 /// Extract the [`Cache`] of the [`UserInterface`], consuming it in the
562 /// process.
563 pub fn into_cache(self) -> Cache {
564 Cache { state: self.state }
565 }
566}
567
568/// Reusable data of a specific [`UserInterface`].
569#[derive(Debug)]
570pub struct Cache {
571 state: widget::Tree,
572}
573
574impl Cache {
575 /// Creates an empty [`Cache`].
576 ///
577 /// You should use this to initialize a [`Cache`] before building your first
578 /// [`UserInterface`].
579 pub fn new() -> Cache {
580 Cache {
581 state: widget::Tree::empty(),
582 }
583 }
584}
585
586impl Default for Cache {
587 fn default() -> Cache {
588 Cache::new()
589 }
590}
591
592/// The resulting state after updating a [`UserInterface`].
593#[derive(Debug, Clone)]
594pub enum State {
595 /// The [`UserInterface`] is outdated and needs to be rebuilt.
596 Outdated,
597
598 /// The [`UserInterface`] is up-to-date and can be reused without
599 /// rebuilding.
600 Updated {
601 /// The current [`mouse::Interaction`] of the user interface.
602 mouse_interaction: mouse::Interaction,
603 /// The [`window::RedrawRequest`] describing when a redraw should be performed.
604 redraw_request: window::RedrawRequest,
605 /// The current [`InputMethod`] strategy of the user interface.
606 input_method: InputMethod,
607 },
608}