ailloli_ui_runtime/app/presentation.rs
1//! Pure lifecycle and deferred intents for native window presentations.
2
3use std::fmt;
4
5use ailloli_ui_core::{LogicalWindowId, Size};
6
7use super::{FrameWorkPlan, WindowChromeOp};
8
9/// Monotonic generation of a native presentation attached to a logical window.
10///
11/// A generation distinguishes successive native/surface attachments for the
12/// same logical window. Public construction permits any `u64`; lifecycle
13/// attachment increments with checked arithmetic and never wraps.
14///
15/// # Examples
16///
17/// ```
18/// use ailloli_ui_runtime::app::PresentationGeneration;
19/// assert_eq!(PresentationGeneration::INITIAL.get(), 0);
20/// assert!(PresentationGeneration::new(2) > PresentationGeneration::new(1));
21/// ```
22#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
23pub struct PresentationGeneration(u64);
24
25/// Provides the operations defined for PresentationGeneration.
26impl PresentationGeneration {
27 /// Initial sentinel before any native presentation has attached.
28 ///
29 /// # Examples
30 ///
31 /// ```
32 /// use ailloli_ui_runtime::app::PresentationGeneration;
33 /// assert_eq!(PresentationGeneration::INITIAL, PresentationGeneration::default());
34 /// ```
35 pub const INITIAL: Self = Self(0);
36
37 /// Wraps an explicit generation value without validation.
38 ///
39 /// # Examples
40 ///
41 /// ```
42 /// use ailloli_ui_runtime::app::PresentationGeneration;
43 /// assert_eq!(PresentationGeneration::new(u64::MAX).get(), u64::MAX);
44 /// ```
45 pub const fn new(value: u64) -> Self {
46 Self(value)
47 }
48
49 /// Returns the underlying generation integer.
50 ///
51 /// # Examples
52 ///
53 /// ```
54 /// use ailloli_ui_runtime::app::PresentationGeneration;
55 /// assert_eq!(PresentationGeneration::new(7).get(), 7);
56 /// assert_eq!(PresentationGeneration::new(7).to_string(), "7");
57 /// ```
58 pub const fn get(self) -> u64 {
59 self.0
60 }
61
62 /// Returns the next generation or `None` at `u64::MAX`.
63 fn checked_next(self) -> Option<Self> {
64 self.0.checked_add(1).map(Self)
65 }
66}
67
68/// Provider-neutral frame work pending for one exact presentation.
69///
70/// This host boundary intentionally exposes only the logical presentation,
71/// its attachment generation, and the aggregate frame stages. Retained-tree
72/// identifiers and exact dirty roots remain private to the runtime.
73///
74/// The type is public only so presentation adapters in sibling crates can
75/// consume it. It remains reachable through the lower-level
76/// `ailloli_ui::runtime` module alias, but the item itself is hidden from
77/// generated documentation and is not re-exported at the facade top level or
78/// through its prelude.
79#[doc(hidden)]
80#[derive(Debug, Clone, PartialEq, Eq)]
81pub struct PresentationWorkPlan {
82 /// Stable logical-window identity.
83 logical_window_id: LogicalWindowId,
84 /// Exact native-presentation generation addressed by the work.
85 presentation_generation: PresentationGeneration,
86 /// Aggregate retained stages required by every tree in this presentation.
87 frame_work_plan: FrameWorkPlan,
88}
89
90impl PresentationWorkPlan {
91 /// Creates one presentation-scoped aggregate without exposing tree roots.
92 pub(crate) const fn new(
93 logical_window_id: LogicalWindowId,
94 presentation_generation: PresentationGeneration,
95 frame_work_plan: FrameWorkPlan,
96 ) -> Self {
97 Self {
98 logical_window_id,
99 presentation_generation,
100 frame_work_plan,
101 }
102 }
103
104 /// Returns the stable logical window addressed by this work.
105 pub fn logical_window_id(&self) -> &LogicalWindowId {
106 &self.logical_window_id
107 }
108
109 /// Returns the exact presentation generation addressed by this work.
110 pub const fn presentation_generation(&self) -> PresentationGeneration {
111 self.presentation_generation
112 }
113
114 /// Returns the aggregate build/layout/paint stages required by the frame.
115 pub const fn frame_work_plan(&self) -> FrameWorkPlan {
116 self.frame_work_plan
117 }
118}
119
120/// Implements the fmt::Display contract for PresentationGeneration.
121impl fmt::Display for PresentationGeneration {
122 /// Formats the value for human-readable diagnostics.
123 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
124 self.0.fmt(formatter)
125 }
126}
127
128/// Provider-neutral lifecycle state for a logical window presentation.
129///
130/// This enum models host/surface availability without owning a native window or
131/// GPU resource. It is non-exhaustive for downstream consumers.
132///
133/// # Examples
134///
135/// ```
136/// use ailloli_ui_runtime::app::{PresentationState, PresentationUnavailableReason};
137/// assert_ne!(PresentationState::Declared, PresentationState::Ready);
138/// assert_eq!(PresentationState::Unavailable(PresentationUnavailableReason::ZeroExtent), PresentationState::Unavailable(PresentationUnavailableReason::ZeroExtent));
139/// ```
140#[non_exhaustive]
141#[derive(Debug, Clone, Copy, PartialEq, Eq)]
142pub enum PresentationState {
143 /// Logical window exists but creation has not been authorized.
144 Declared,
145 /// Host may create/attach a native presentation.
146 CreationAllowed,
147 /// Native presentation is attached and accepts this generation's events.
148 Ready,
149 /// Presentation is detached during host suspension.
150 Suspended,
151 /// Presentation cannot currently be used for the recorded reason.
152 Unavailable(PresentationUnavailableReason),
153 /// Terminal lifecycle state; only another Destroy event is accepted.
154 Destroyed,
155}
156
157/// Reason a native presentation cannot currently be used.
158///
159/// The enum is non-exhaustive. It provides diagnosis only; recovery occurs by
160/// applying [`PresentationEvent::Retry`] or allowing creation again.
161///
162/// # Examples
163///
164/// ```
165/// use ailloli_ui_runtime::app::PresentationUnavailableReason;
166/// assert_ne!(PresentationUnavailableReason::ZeroExtent, PresentationUnavailableReason::SurfaceLost);
167/// ```
168#[non_exhaustive]
169#[derive(Debug, Clone, Copy, PartialEq, Eq)]
170pub enum PresentationUnavailableReason {
171 /// Logical width or height was zero/non-positive for surface creation.
172 ZeroExtent,
173 /// A previously usable rendering surface was lost.
174 SurfaceLost,
175 /// No surface/configuration compatible with the host was available.
176 NoCompatibleSurface,
177 /// The native presentation provider itself is unavailable.
178 HostUnavailable,
179}
180
181/// Input accepted by the pure presentation reducer.
182///
183/// The enum is non-exhaustive. Events carry no native handles and are safe to
184/// use in deterministic lifecycle tests.
185///
186/// # Examples
187///
188/// ```
189/// use ailloli_ui_runtime::app::{PresentationEvent, PresentationUnavailableReason};
190/// assert_ne!(PresentationEvent::AllowCreation, PresentationEvent::Destroy);
191/// assert_eq!(PresentationEvent::Unavailable(PresentationUnavailableReason::SurfaceLost), PresentationEvent::Unavailable(PresentationUnavailableReason::SurfaceLost));
192/// ```
193#[non_exhaustive]
194#[derive(Debug, Clone, Copy, PartialEq, Eq)]
195pub enum PresentationEvent {
196 /// Authorize transitioning toward native attachment.
197 AllowCreation,
198 /// Report successful native attachment and advance generation.
199 Attached,
200 /// Detach/suspend presentation without destroying the logical window.
201 Suspend,
202 /// Record a non-fatal unavailable state.
203 Unavailable(PresentationUnavailableReason),
204 /// Move an unavailable presentation back to creation-allowed.
205 Retry,
206 /// Enter the terminal destroyed state.
207 Destroy,
208}
209
210/// Result of one pure lifecycle reduction.
211///
212/// `generation_changed` is true only for successful attachment and always equals
213/// `generation !=` the reducer's input generation.
214///
215/// # Examples
216///
217/// ```
218/// use ailloli_ui_runtime::app::{reduce_presentation, PresentationEvent, PresentationGeneration, PresentationState};
219/// let reduced = reduce_presentation(PresentationState::Declared, PresentationGeneration::INITIAL, PresentationEvent::AllowCreation)?;
220/// assert_eq!(reduced.state, PresentationState::CreationAllowed);
221/// assert!(!reduced.generation_changed);
222/// # Ok::<(), ailloli_ui_runtime::app::PresentationTransitionError>(())
223/// ```
224#[derive(Debug, Clone, Copy, PartialEq, Eq)]
225pub struct PresentationReduction {
226 /// State after applying the event.
227 pub state: PresentationState,
228 /// Generation after applying the event.
229 pub generation: PresentationGeneration,
230 /// Whether generation advanced during this reduction.
231 pub generation_changed: bool,
232}
233
234/// Invalid presentation lifecycle transition.
235///
236/// Errors carry no native-resource detail and leave [`PresentationLifecycle`]
237/// unchanged when returned through its [`PresentationLifecycle::apply`] method.
238///
239/// # Examples
240///
241/// ```
242/// use ailloli_ui_runtime::app::PresentationTransitionError;
243/// assert_eq!(PresentationTransitionError::AttachmentNotAllowed.to_string(), "a presentation can only attach after creation is allowed");
244/// ```
245#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
246pub enum PresentationTransitionError {
247 #[error("a destroyed presentation cannot transition")]
248 /// The presentation has already reached its terminal destroyed state.
249 Destroyed,
250 #[error("a presentation can only attach after creation is allowed")]
251 /// Attachment was requested before creation became allowed.
252 AttachmentNotAllowed,
253 #[error("presentation generation is exhausted")]
254 /// Incrementing the `u64` presentation generation would overflow.
255 GenerationExhausted,
256 #[error("presentation retry requires an unavailable presentation")]
257 /// Retry was requested from a state other than unavailable.
258 RetryNotAllowed,
259}
260
261/// Reduces one lifecycle event without accessing a native host or GPU.
262///
263/// `Attached` is valid only from [`PresentationState::CreationAllowed`] and
264/// checked-increments generation. `Retry` is valid only from `Unavailable`.
265/// `AllowCreation` is idempotent for already-allowed/ready states and re-enables
266/// suspended or unavailable states. Suspend, Unavailable, and Destroy otherwise
267/// replace any non-destroyed state. Destroy is idempotent once destroyed; every
268/// other event then returns [`PresentationTransitionError::Destroyed`].
269///
270/// # Errors
271///
272/// Returns a transition error for disallowed attach/retry, generation overflow,
273/// or any non-Destroy event after destruction.
274///
275/// # Examples
276///
277/// ```
278/// use ailloli_ui_runtime::app::{reduce_presentation, PresentationEvent, PresentationGeneration, PresentationState};
279/// let allowed = reduce_presentation(PresentationState::Declared, PresentationGeneration::INITIAL, PresentationEvent::AllowCreation)?;
280/// let ready = reduce_presentation(allowed.state, allowed.generation, PresentationEvent::Attached)?;
281/// assert_eq!(ready.state, PresentationState::Ready);
282/// assert_eq!(ready.generation.get(), 1);
283/// assert!(ready.generation_changed);
284/// # Ok::<(), ailloli_ui_runtime::app::PresentationTransitionError>(())
285/// ```
286pub fn reduce_presentation(
287 state: PresentationState,
288 generation: PresentationGeneration,
289 event: PresentationEvent,
290) -> Result<PresentationReduction, PresentationTransitionError> {
291 use PresentationEvent as Input;
292 use PresentationState as State;
293
294 if state == State::Destroyed {
295 return if matches!(event, Input::Destroy) {
296 Ok(PresentationReduction {
297 state,
298 generation,
299 generation_changed: false,
300 })
301 } else {
302 Err(PresentationTransitionError::Destroyed)
303 };
304 }
305
306 let mut next_generation = generation;
307 let next_state = match event {
308 Input::AllowCreation => match state {
309 State::Declared | State::Suspended | State::Unavailable(_) => State::CreationAllowed,
310 State::CreationAllowed | State::Ready => state,
311 State::Destroyed => unreachable!("destroyed is handled above"),
312 },
313 Input::Attached => {
314 if state != State::CreationAllowed {
315 return Err(PresentationTransitionError::AttachmentNotAllowed);
316 }
317 next_generation = generation
318 .checked_next()
319 .ok_or(PresentationTransitionError::GenerationExhausted)?;
320 State::Ready
321 }
322 Input::Suspend => State::Suspended,
323 Input::Unavailable(reason) => State::Unavailable(reason),
324 Input::Retry => match state {
325 State::Unavailable(_) => State::CreationAllowed,
326 _ => return Err(PresentationTransitionError::RetryNotAllowed),
327 },
328 Input::Destroy => State::Destroyed,
329 };
330
331 Ok(PresentationReduction {
332 state: next_state,
333 generation: next_generation,
334 generation_changed: generation != next_generation,
335 })
336}
337
338/// Pure retained lifecycle model for one logical window.
339///
340/// The logical ID survives suspension and attachment generations. The type owns
341/// no native resource and can be cloned for deterministic state inspection.
342///
343/// # Examples
344///
345/// ```
346/// use ailloli_ui_runtime::app::{PresentationGeneration, PresentationLifecycle, PresentationState};
347/// let lifecycle = PresentationLifecycle::new("main");
348/// assert_eq!(lifecycle.logical_window_id().as_str(), "main");
349/// assert_eq!(lifecycle.state(), PresentationState::Declared);
350/// assert_eq!(lifecycle.generation(), PresentationGeneration::INITIAL);
351/// ```
352#[derive(Debug, Clone, PartialEq, Eq)]
353pub struct PresentationLifecycle {
354 /// Stable provider-neutral logical-window identity.
355 logical_window_id: LogicalWindowId,
356 /// Current pure lifecycle state.
357 state: PresentationState,
358 /// Current attachment generation.
359 generation: PresentationGeneration,
360}
361
362/// Provides the operations defined for PresentationLifecycle.
363impl PresentationLifecycle {
364 /// Creates a declared logical window at generation zero.
365 ///
366 /// The ID is stored exactly by [`LogicalWindowId`]; empty IDs are
367 /// representable at this layer and host declaration logic owns uniqueness.
368 ///
369 /// # Examples
370 ///
371 /// ```
372 /// use ailloli_ui_runtime::app::{PresentationGeneration, PresentationLifecycle, PresentationState};
373 /// let lifecycle = PresentationLifecycle::new(String::from("settings"));
374 /// assert_eq!(lifecycle.state(), PresentationState::Declared);
375 /// assert_eq!(lifecycle.generation(), PresentationGeneration::INITIAL);
376 /// ```
377 pub fn new(logical_window_id: impl Into<LogicalWindowId>) -> Self {
378 Self {
379 logical_window_id: logical_window_id.into(),
380 state: PresentationState::Declared,
381 generation: PresentationGeneration::INITIAL,
382 }
383 }
384
385 /// Borrows the stable logical-window identifier.
386 ///
387 /// # Examples
388 ///
389 /// ```
390 /// use ailloli_ui_runtime::app::PresentationLifecycle;
391 /// let lifecycle = PresentationLifecycle::new("main");
392 /// assert_eq!(lifecycle.logical_window_id().as_str(), "main");
393 /// ```
394 pub fn logical_window_id(&self) -> &LogicalWindowId {
395 &self.logical_window_id
396 }
397
398 /// Returns the current lifecycle state.
399 ///
400 /// # Examples
401 ///
402 /// ```
403 /// use ailloli_ui_runtime::app::{PresentationLifecycle, PresentationState};
404 /// assert_eq!(PresentationLifecycle::new("main").state(), PresentationState::Declared);
405 /// ```
406 pub const fn state(&self) -> PresentationState {
407 self.state
408 }
409
410 /// Returns the current native-attachment generation.
411 ///
412 /// # Examples
413 ///
414 /// ```
415 /// use ailloli_ui_runtime::app::{PresentationGeneration, PresentationLifecycle};
416 /// assert_eq!(PresentationLifecycle::new("main").generation(), PresentationGeneration::INITIAL);
417 /// ```
418 pub const fn generation(&self) -> PresentationGeneration {
419 self.generation
420 }
421
422 /// Returns whether events from `generation` belong to the ready presentation.
423 ///
424 /// Matching generations are rejected unless state is exactly
425 /// [`PresentationState::Ready`], preventing stale events during suspension,
426 /// recreation, or destruction.
427 ///
428 /// # Examples
429 ///
430 /// ```
431 /// use ailloli_ui_runtime::app::{PresentationEvent, PresentationGeneration, PresentationLifecycle};
432 /// let mut lifecycle = PresentationLifecycle::new("main");
433 /// assert!(!lifecycle.accepts(PresentationGeneration::INITIAL));
434 /// lifecycle.apply(PresentationEvent::AllowCreation)?;
435 /// lifecycle.apply(PresentationEvent::Attached)?;
436 /// assert!(lifecycle.accepts(PresentationGeneration::new(1)));
437 /// # Ok::<(), ailloli_ui_runtime::app::PresentationTransitionError>(())
438 /// ```
439 pub fn accepts(&self, generation: PresentationGeneration) -> bool {
440 self.state == PresentationState::Ready && self.generation == generation
441 }
442
443 /// Applies one pure reducer event and commits it only on success.
444 ///
445 /// # Errors
446 ///
447 /// Returns the same errors as [`reduce_presentation`]. State and generation
448 /// remain unchanged when reduction fails.
449 ///
450 /// # Examples
451 ///
452 /// ```
453 /// use ailloli_ui_runtime::app::{PresentationEvent, PresentationGeneration, PresentationLifecycle, PresentationState};
454 /// let mut lifecycle = PresentationLifecycle::new("main");
455 /// lifecycle.apply(PresentationEvent::AllowCreation)?;
456 /// let attached = lifecycle.apply(PresentationEvent::Attached)?;
457 /// assert_eq!(lifecycle.state(), PresentationState::Ready);
458 /// assert_eq!(attached.generation, PresentationGeneration::new(1));
459 /// # Ok::<(), ailloli_ui_runtime::app::PresentationTransitionError>(())
460 /// ```
461 pub fn apply(
462 &mut self,
463 event: PresentationEvent,
464 ) -> Result<PresentationReduction, PresentationTransitionError> {
465 let reduction = reduce_presentation(self.state, self.generation, event)?;
466 self.state = reduction.state;
467 self.generation = reduction.generation;
468 Ok(reduction)
469 }
470}
471
472/// Provider-neutral cursor intent retained while a presentation is unavailable.
473///
474/// Hosts map these roles to their platform cursor set. The enum is
475/// non-exhaustive for downstream consumers.
476///
477/// # Examples
478///
479/// ```
480/// use ailloli_ui_runtime::app::PresentationCursor;
481/// assert_ne!(PresentationCursor::Default, PresentationCursor::Pointer);
482/// ```
483#[non_exhaustive]
484#[derive(Debug, Clone, Copy, PartialEq, Eq)]
485pub enum PresentationCursor {
486 /// Platform default arrow/pointer.
487 Default,
488 /// Clickable-link or button pointer.
489 Pointer,
490 /// Text insertion cursor.
491 Text,
492 /// Horizontal resize cursor.
493 ResizeX,
494 /// Vertical resize cursor.
495 ResizeY,
496}
497
498/// A window operation that can be retained until presentation is ready.
499///
500/// Values perform no host operation themselves. Titles and sizes are stored
501/// without validation; size components are logical pixels and may be negative
502/// or non-finite at this layer. The enum is non-exhaustive.
503///
504/// # Examples
505///
506/// ```
507/// use ailloli_ui_core::Size;
508/// use ailloli_ui_runtime::app::PresentationIntent;
509/// assert_eq!(PresentationIntent::SetInnerSize(Size::new(800.0, 600.0)), PresentationIntent::SetInnerSize(Size::new(800.0, 600.0)));
510/// ```
511#[non_exhaustive]
512#[derive(Debug, Clone, PartialEq)]
513pub enum PresentationIntent {
514 /// Replace the native window title; an empty title is valid.
515 SetTitle(String),
516 /// Request a logical inner size without normalization.
517 SetInnerSize(Size),
518 /// Replace the current provider-neutral cursor role.
519 SetCursor(PresentationCursor),
520 /// Queue a non-coalesced title-bar/window-chrome operation.
521 WindowChrome(WindowChromeOp),
522 /// Request at least one redraw.
523 Redraw,
524}
525
526/// Coalescing store for presentation operations requested while detached.
527///
528/// Title, size, cursor, and redraw use last-value/boolean coalescing. Chrome
529/// operations retain every value in insertion order without a capacity bound.
530/// Cloning duplicates all owned titles and queued operations.
531///
532/// # Examples
533///
534/// ```
535/// use ailloli_ui_runtime::app::{PendingPresentationIntents, PresentationIntent};
536/// let mut pending = PendingPresentationIntents::default();
537/// pending.push(PresentationIntent::SetTitle("first".into()));
538/// pending.push(PresentationIntent::SetTitle("last".into()));
539/// assert_eq!(pending.drain(), [PresentationIntent::SetTitle("last".into())]);
540/// ```
541#[derive(Debug, Default, Clone, PartialEq)]
542pub struct PendingPresentationIntents {
543 /// Last requested title.
544 title: Option<String>,
545 /// Last requested logical inner size.
546 inner_size: Option<Size>,
547 /// Last requested cursor role.
548 cursor: Option<PresentationCursor>,
549 /// All queued chrome operations in request order.
550 chrome: Vec<WindowChromeOp>,
551 /// Coalesced redraw request bit.
552 redraw: bool,
553}
554
555/// Provides the operations defined for PendingPresentationIntents.
556impl PendingPresentationIntents {
557 /// Coalesces or appends one deferred operation.
558 ///
559 /// State-setting intents overwrite the prior value; redraw becomes true;
560 /// chrome operations append and can grow the vector without bound.
561 ///
562 /// # Examples
563 ///
564 /// ```
565 /// use ailloli_ui_runtime::app::{PendingPresentationIntents, PresentationIntent};
566 /// let mut pending = PendingPresentationIntents::default();
567 /// pending.push(PresentationIntent::Redraw);
568 /// pending.push(PresentationIntent::Redraw);
569 /// assert_eq!(pending.drain(), [PresentationIntent::Redraw]);
570 /// ```
571 pub fn push(&mut self, intent: PresentationIntent) {
572 match intent {
573 PresentationIntent::SetTitle(title) => self.title = Some(title),
574 PresentationIntent::SetInnerSize(size) => self.inner_size = Some(size),
575 PresentationIntent::SetCursor(cursor) => self.cursor = Some(cursor),
576 PresentationIntent::WindowChrome(operation) => self.chrome.push(operation),
577 PresentationIntent::Redraw => self.redraw = true,
578 }
579 }
580
581 /// Returns whether no coalesced or queued operation is pending.
582 ///
583 /// # Examples
584 ///
585 /// ```
586 /// use ailloli_ui_runtime::app::{PendingPresentationIntents, PresentationIntent};
587 /// let mut pending = PendingPresentationIntents::default();
588 /// assert!(pending.is_empty());
589 /// pending.push(PresentationIntent::Redraw);
590 /// assert!(!pending.is_empty());
591 /// ```
592 pub fn is_empty(&self) -> bool {
593 self.title.is_none()
594 && self.inner_size.is_none()
595 && self.cursor.is_none()
596 && self.chrome.is_empty()
597 && !self.redraw
598 }
599
600 /// Drains coalesced intents in deterministic replay order.
601 ///
602 /// Replay order is title, size, cursor, every chrome operation in insertion
603 /// order, then redraw, regardless of original interleaving. All stored state
604 /// is cleared; a second drain is empty.
605 ///
606 /// # Examples
607 ///
608 /// ```
609 /// use ailloli_ui_core::Size;
610 /// use ailloli_ui_runtime::app::{PendingPresentationIntents, PresentationCursor, PresentationIntent};
611 /// let mut pending = PendingPresentationIntents::default();
612 /// pending.push(PresentationIntent::Redraw);
613 /// pending.push(PresentationIntent::SetCursor(PresentationCursor::Text));
614 /// pending.push(PresentationIntent::SetInnerSize(Size::new(10.0, 20.0)));
615 /// assert_eq!(pending.drain(), [
616 /// PresentationIntent::SetInnerSize(Size::new(10.0, 20.0)),
617 /// PresentationIntent::SetCursor(PresentationCursor::Text),
618 /// PresentationIntent::Redraw,
619 /// ]);
620 /// assert!(pending.is_empty());
621 /// ```
622 pub fn drain(&mut self) -> Vec<PresentationIntent> {
623 let mut intents = Vec::with_capacity(4 + self.chrome.len());
624 if let Some(title) = self.title.take() {
625 intents.push(PresentationIntent::SetTitle(title));
626 }
627 if let Some(size) = self.inner_size.take() {
628 intents.push(PresentationIntent::SetInnerSize(size));
629 }
630 if let Some(cursor) = self.cursor.take() {
631 intents.push(PresentationIntent::SetCursor(cursor));
632 }
633 intents.extend(self.chrome.drain(..).map(PresentationIntent::WindowChrome));
634 if std::mem::take(&mut self.redraw) {
635 intents.push(PresentationIntent::Redraw);
636 }
637 intents
638 }
639}