Skip to main content

devela/ui/frame/
output.rs

1// devela/src/ui/frame/output.rs
2//
3//! Defines [`UiOutput`], [`UiOutputView`].
4//
5
6use crate::{LayoutReceipt, UiDraw, UiDrawList, UiDrawListView, UiView};
7
8#[doc = crate::_tags!(ui)]
9/// A completed backend-neutral UI frame over caller-chosen storage.
10#[doc = crate::_doc_meta! {
11    location("ui/frame", struct UiOutput),
12}]
13/// Aggregates the spatial, visual, and drawing records produced during one UI frame.
14///
15/// The three streams have distinct roles:
16/// - [`LayoutReceipt`] records resolved layout assignment.
17/// - [`UiView`] records visual identity, geometry, ordering, and flags.
18/// - [`UiDrawList`] contains the final painter-ordered presentation sequence.
19///
20/// `UiOutput` is a frame artifact, not the mutable [`UiFrame`]
21/// authorship context and not a concrete presentation surface.
22///
23/// `L`, `V`, and `D` determine ownership and storage. They may contain borrowed
24/// slices, fixed storage, allocated vectors, or other suitable representations.
25///
26/// [`UiFrame`]: crate::UiFrame
27#[must_use]
28#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
29pub struct UiOutput<L, V, D> {
30    layouts: L,
31    views: V,
32    draws: D,
33}
34
35#[rustfmt::skip]
36impl<L, V, D> UiOutput<L, V, D> {
37    /// Constructs a UI output from its frame record streams.
38    pub const fn from_parts(layouts: L, views: V, draws: D) -> Self {
39        Self { layouts, views, draws }
40    }
41
42    /// Returns the underlying layout-record storage.
43    pub const fn layout_storage(&self) -> &L { &self.layouts }
44    /// Returns the underlying view-record storage.
45    pub const fn view_storage(&self) -> &V { &self.views }
46    /// Returns the underlying draw-list storage.
47    pub const fn draw_list(&self) -> &D { &self.draws }
48
49    /// Consumes this output and returns its three frame record streams.
50    pub fn into_parts(self) -> (L, V, D) { (self.layouts, self.views, self.draws) }
51
52    /// Consumes this output and returns its three frame record streams.
53    pub const fn into_parts_const(self) -> (L, V, D) where Self: Copy {
54        (self.layouts, self.views, self.draws)
55    }
56}
57
58#[rustfmt::skip]
59impl<L, V, S, T, B> UiOutput<L, V, UiDrawList<S, T, B>>
60where
61    L: AsRef<[LayoutReceipt]>,
62    V: AsRef<[UiView]>,
63    B: AsRef<[UiDraw<S, T>]>,
64{
65    /// Returns this UI output as a borrowed read-only frame view.
66    pub fn as_view(&self) -> UiOutputView<'_, S, T> {
67        UiOutputView::from_parts(self.layouts.as_ref(), self.views.as_ref(), self.draws.as_view())
68    }
69    /// Returns the layout records.
70    pub fn layouts(&self) -> &[LayoutReceipt] { self.layouts.as_ref() }
71    /// Returns the visual records.
72    pub fn views(&self) -> &[UiView] { self.views.as_ref() }
73}
74
75#[doc = crate::_tags!(ui lifetime)]
76/// A borrowed read-only view over a completed backend-neutral UI frame.
77#[doc = crate::_doc_meta! {
78    location("ui/frame", struct UiOutputView),
79    #[cfg(target_pointer_width = "32")]
80    test_size_of(UiOutputView<u8, &str> = 24|192; niche Option),
81    #[cfg(target_pointer_width = "64")]
82    test_size_of(UiOutputView<u8, &str> = 48|384; niche Option),
83}]
84/// Borrows the frame record streams without taking ownership of their storage.
85///
86/// This is the canonical form intended for backend-neutral presenters.
87#[must_use]
88#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
89pub struct UiOutputView<'a, S, T = &'a str> {
90    layouts: &'a [LayoutReceipt],
91    views: &'a [UiView],
92    draws: UiDrawListView<'a, S, T>,
93}
94
95#[rustfmt::skip]
96impl<'a, S, T> UiOutputView<'a, S, T> {
97    /// Constructs a borrowed output view from its three frame record streams.
98    pub const fn from_parts(
99        layouts: &'a [LayoutReceipt],
100        views: &'a [UiView],
101        draws: UiDrawListView<'a, S, T>,
102    ) -> Self {
103        Self { layouts, views, draws }
104    }
105    /// Constructs a borrowed output view directly from record slices.
106    pub const fn from_slices(
107        layouts: &'a [LayoutReceipt],
108        views: &'a [UiView],
109        draws: &'a [UiDraw<S, T>],
110    ) -> Self {
111        Self::from_parts(layouts, views, UiDrawList::from_slice(draws))
112    }
113    /// Returns the layout records.
114    pub const fn layouts(&self) -> &'a [LayoutReceipt] { self.layouts }
115    /// Returns the visual records.
116    pub const fn views(&self) -> &'a [UiView] { self.views }
117    /// Returns the painter-ordered draw list.
118    pub const fn draw_list(&self) -> &UiDrawListView<'a, S, T> { &self.draws }
119}
120
121#[cfg(test)]
122mod _test {
123    use super::*;
124    use crate::{Ptr, UiRect};
125
126    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
127    enum Style {
128        Background,
129        Foreground,
130    }
131    fn draw(style: Style) -> UiDraw<Style> {
132        UiDraw::rect_fill(UiRect::default(), style)
133    }
134
135    #[test]
136    fn constructs_from_parts() {
137        let layouts = [LayoutReceipt::default()];
138        let views = [UiView::default()];
139        let draws = UiDrawList::from_array([draw(Style::Background), draw(Style::Foreground)]);
140        let output = UiOutput::from_parts(layouts, views, draws);
141        assert_eq!(output.layout_storage().len(), 1);
142        assert_eq!(output.view_storage().len(), 1);
143        assert_eq!(output.draw_list().len(), 2);
144    }
145    #[test]
146    fn borrowed_view_preserves_all_streams() {
147        let output = UiOutput::from_parts(
148            [LayoutReceipt::default()],
149            [UiView::default()],
150            UiDrawList::from_array([draw(Style::Background), draw(Style::Foreground)]),
151        );
152        let view = output.as_view();
153        assert_eq!(view.layouts(), output.layout_storage().as_slice());
154        assert_eq!(view.views(), output.view_storage().as_slice());
155        assert_eq!(view.draw_list().as_slice(), output.draw_list().as_slice(),);
156        assert!(Ptr::eq(view.layouts().as_ptr(), output.layout_storage().as_ptr(),));
157        assert!(Ptr::eq(view.views().as_ptr(), output.view_storage().as_ptr(),));
158        assert!(Ptr::eq(
159            view.draw_list().as_slice().as_ptr(),
160            output.draw_list().as_slice().as_ptr(),
161        ));
162    }
163    #[test]
164    fn constructs_view_directly_from_slices() {
165        let layouts = [LayoutReceipt::default()];
166        let views = [UiView::default()];
167        let draws = [draw(Style::Background), draw(Style::Foreground)];
168        let output = UiOutputView::from_slices(&layouts, &views, &draws);
169        assert_eq!(output.layouts(), &layouts);
170        assert_eq!(output.views(), &views);
171        assert_eq!(output.draw_list().as_slice(), &draws);
172    }
173    #[test]
174    fn into_parts_returns_original_storage() {
175        let layouts = [LayoutReceipt::default()];
176        let views = [UiView::default()];
177        let draws = UiDrawList::from_array([draw(Style::Background)]);
178        let output = UiOutput::from_parts(layouts, views, draws);
179        let (actual_layouts, actual_views, actual_draws) = output.into_parts();
180        assert_eq!(actual_layouts, layouts);
181        assert_eq!(actual_views, views);
182        assert_eq!(actual_draws.as_slice(), draws.as_slice());
183    }
184}