Skip to main content

slt/layout/
command.rs

1use super::*;
2
3/// Main axis direction for a container's children.
4#[non_exhaustive]
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum Direction {
7    /// Lay out children horizontally (left to right).
8    Row,
9    /// Lay out children vertically (top to bottom).
10    Column,
11}
12
13/// Arguments for [`Command::BeginContainer`].
14///
15/// Boxed inside the variant so that the surrounding `Command` enum stays
16/// small — a frame may contain hundreds of commands, and most variants
17/// (for example `EndContainer`) carry no payload.
18#[derive(Debug, Clone)]
19pub(crate) struct BeginContainerArgs {
20    pub direction: Direction,
21    pub gap: u32,
22    pub align: Align,
23    pub align_self: Option<Align>,
24    pub justify: Justify,
25    pub border: Option<Border>,
26    pub border_sides: BorderSides,
27    pub border_style: Style,
28    pub bg_color: Option<Color>,
29    pub padding: Padding,
30    pub margin: Margin,
31    pub constraints: Constraints,
32    pub title: Option<(String, Style)>,
33    pub grow: u16,
34    pub group_name: Option<std::sync::Arc<str>>,
35}
36
37/// Arguments for [`Command::BeginScrollable`].
38///
39/// Boxed for the same reason as [`BeginContainerArgs`] — keeps the
40/// `Command` enum from being dragged up to the width of this payload.
41///
42/// Note: `direction` is intentionally omitted — scrollable containers are
43/// always `Direction::Column` (vertical scroll only).
44#[derive(Debug, Clone)]
45pub(crate) struct BeginScrollableArgs {
46    pub grow: u16,
47    pub border: Option<Border>,
48    pub border_sides: BorderSides,
49    pub border_style: Style,
50    /// Background color (dark-mode resolved). Fixes #142.
51    pub bg_color: Option<Color>,
52    /// Main-axis child alignment. Fixes #142.
53    pub align: Align,
54    /// Cross-axis self alignment. Fixes #142.
55    pub align_self: Option<Align>,
56    /// Main-axis justification. Fixes #142.
57    pub justify: Justify,
58    /// Gap between children in pixels. Fixes #142.
59    pub gap: u32,
60    pub padding: Padding,
61    pub margin: Margin,
62    pub constraints: Constraints,
63    pub title: Option<(String, Style)>,
64    pub scroll_offset: u32,
65    /// Group name for hover/focus registration. Fixes #141.
66    pub group_name: Option<std::sync::Arc<str>>,
67}
68
69#[derive(Debug, Clone)]
70pub(crate) enum Command {
71    Text {
72        content: String,
73        cursor_offset: Option<usize>,
74        style: Style,
75        grow: u16,
76        align: Align,
77        wrap: bool,
78        truncate: bool,
79        margin: Margin,
80        constraints: Constraints,
81    },
82    BeginContainer(Box<BeginContainerArgs>),
83    BeginScrollable(Box<BeginScrollableArgs>),
84    Link {
85        text: String,
86        url: String,
87        style: Style,
88        margin: Margin,
89        constraints: Constraints,
90    },
91    RichText {
92        segments: Vec<(String, Style)>,
93        wrap: bool,
94        align: Align,
95        margin: Margin,
96        constraints: Constraints,
97    },
98    EndContainer,
99    BeginOverlay {
100        modal: bool,
101    },
102    EndOverlay,
103    Spacer {
104        grow: u16,
105    },
106    FocusMarker(usize),
107    InteractionMarker(usize),
108    /// Marks the next container / scrollable as opt-in flex-shrink.
109    ///
110    /// Pushed by [`crate::context::ContainerBuilder::shrink`] just before the
111    /// matching `BeginContainer` / `BeginScrollable`. Consumed by
112    /// `build_children` like [`Command::FocusMarker`] (buffered into
113    /// `pending_shrink`, applied to the next built [`super::tree::LayoutNode`]).
114    /// Closes #161.
115    ShrinkMarker,
116    RawDraw {
117        draw_id: usize,
118        constraints: Constraints,
119        grow: u16,
120        margin: Margin,
121    },
122}
123
124#[cfg(test)]
125mod size_tests {
126    use super::Command;
127
128    /// Regression guard for the `Command` enum size.
129    ///
130    /// A frame may push hundreds of `Command` values into a single `Vec`,
131    /// so every byte in the enum variant-union multiplies across the frame.
132    /// Fat variants (`BeginContainer`, `BeginScrollable`) are boxed to keep
133    /// the common 0-payload variants (e.g. `EndContainer`) cheap.
134    ///
135    /// The current ceiling reflects the largest remaining inline variant
136    /// (`Text`, which carries a `String` + `Constraints` + `Style` + small
137    /// scalars). If this test fires after a refactor, either box the new
138    /// fat variant or bump this bound with justification.
139    #[test]
140    fn command_enum_size_is_bounded() {
141        const MAX_BYTES: usize = 128;
142        let actual = std::mem::size_of::<Command>();
143        assert!(
144            actual <= MAX_BYTES,
145            "Command enum grew to {actual} bytes (limit {MAX_BYTES}); \
146             consider boxing the new fat variant"
147        );
148    }
149}