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 /// Signed inter-child gap (negative = overlap). See
22 /// [`crate::ContainerBuilder::gap_overlap`] (#222).
23 pub gap: i32,
24 pub align: Align,
25 pub align_self: Option<Align>,
26 pub justify: Justify,
27 pub border: Option<Border>,
28 pub border_sides: BorderSides,
29 pub border_style: Style,
30 pub bg_color: Option<Color>,
31 pub padding: Padding,
32 pub margin: Margin,
33 pub constraints: Constraints,
34 pub title: Option<(String, Style)>,
35 pub grow: u16,
36 pub group_name: Option<std::sync::Arc<str>>,
37}
38
39/// Arguments for [`Command::BeginScrollable`].
40///
41/// Boxed for the same reason as [`BeginContainerArgs`] — keeps the
42/// `Command` enum from being dragged up to the width of this payload.
43///
44/// `direction` selects the scroll axis (#247): `Direction::Column` scrolls
45/// vertically (the historic default), `Direction::Row` scrolls horizontally.
46/// Both vertical and horizontal offsets are carried so the tree builder can
47/// apply the one matching `direction` without re-querying state mid-frame.
48#[derive(Debug, Clone)]
49pub(crate) struct BeginScrollableArgs {
50 pub grow: u16,
51 /// Scroll axis: `Column` => vertical scroll, `Row` => horizontal (#247).
52 pub direction: Direction,
53 pub border: Option<Border>,
54 pub border_sides: BorderSides,
55 pub border_style: Style,
56 /// Background color (dark-mode resolved). Fixes #142.
57 pub bg_color: Option<Color>,
58 /// Main-axis child alignment. Fixes #142.
59 pub align: Align,
60 /// Cross-axis self alignment. Fixes #142.
61 pub align_self: Option<Align>,
62 /// Main-axis justification. Fixes #142.
63 pub justify: Justify,
64 /// Signed gap between children in cells (negative = overlap, #222).
65 /// Fixes #142.
66 pub gap: i32,
67 pub padding: Padding,
68 pub margin: Margin,
69 pub constraints: Constraints,
70 pub title: Option<(String, Style)>,
71 /// Vertical scroll offset in rows (used when `direction == Column`).
72 pub scroll_offset: u32,
73 /// Horizontal scroll offset in columns (used when `direction == Row`, #247).
74 pub scroll_offset_x: u32,
75 /// Group name for hover/focus registration. Fixes #141.
76 pub group_name: Option<std::sync::Arc<str>>,
77}
78
79#[derive(Debug, Clone)]
80pub(crate) enum Command {
81 Text {
82 content: String,
83 cursor_offset: Option<usize>,
84 style: Style,
85 grow: u16,
86 align: Align,
87 wrap: bool,
88 truncate: bool,
89 margin: Margin,
90 constraints: Constraints,
91 },
92 BeginContainer(Box<BeginContainerArgs>),
93 BeginScrollable(Box<BeginScrollableArgs>),
94 Link {
95 text: String,
96 url: String,
97 style: Style,
98 margin: Margin,
99 constraints: Constraints,
100 },
101 RichText {
102 segments: Vec<(String, Style)>,
103 wrap: bool,
104 align: Align,
105 margin: Margin,
106 constraints: Constraints,
107 },
108 EndContainer,
109 BeginOverlay {
110 modal: bool,
111 },
112 EndOverlay,
113 Spacer {
114 grow: u16,
115 },
116 FocusMarker(usize),
117 InteractionMarker(usize),
118 /// Marks the next container / scrollable as opt-in flex-shrink.
119 ///
120 /// Pushed by [`crate::context::ContainerBuilder::shrink`] just before the
121 /// matching `BeginContainer` / `BeginScrollable`. Consumed by
122 /// `build_children` like [`Command::FocusMarker`] (buffered into
123 /// `pending_shrink`, applied to the next built [`super::tree::LayoutNode`]).
124 /// Closes #161.
125 ShrinkMarker,
126 /// Marks the next container as a wrapping (multi-line) row, carrying the
127 /// signed cross-axis (between-line) gap.
128 ///
129 /// Pushed by [`crate::context::ContainerBuilder::wrap`] just before the
130 /// matching `BeginContainer` / `BeginScrollable`. Consumed by
131 /// `build_children` like [`Command::ShrinkMarker`] (buffered into
132 /// `pending_wrap`, applied to the next built
133 /// [`super::tree::LayoutNode`]). On a `Row` container the child layout
134 /// flows children onto subsequent lines on main-axis overflow, with the
135 /// payload as the between-line gap; on a `Column` container it is a
136 /// documented no-op. Kept a scalar variant so the `Command` enum stays
137 /// small. Closes #258.
138 WrapMarker(i32),
139 /// Sets the flex-basis (initial main-axis size, in cells) on the next
140 /// container.
141 ///
142 /// Pushed by [`crate::context::ContainerBuilder::basis`] just before the
143 /// matching `BeginContainer` / `BeginScrollable`. Buffered into
144 /// `pending_basis` and applied to the next built
145 /// [`super::tree::LayoutNode::flex_basis`]. A scalar variant so the
146 /// `Command` enum stays small. Closes #258.
147 BasisMarker(u32),
148 RawDraw {
149 draw_id: usize,
150 constraints: Constraints,
151 grow: u16,
152 margin: Margin,
153 },
154}
155
156#[cfg(test)]
157mod size_tests {
158 use super::Command;
159
160 /// Regression guard for the `Command` enum size.
161 ///
162 /// A frame may push hundreds of `Command` values into a single `Vec`,
163 /// so every byte in the enum variant-union multiplies across the frame.
164 /// Fat variants (`BeginContainer`, `BeginScrollable`) are boxed to keep
165 /// the common 0-payload variants (e.g. `EndContainer`) cheap.
166 ///
167 /// The current ceiling reflects the largest remaining inline variant
168 /// (`Text`, which carries a `String` + `Constraints` + `Style` + small
169 /// scalars). If this test fires after a refactor, either box the new
170 /// fat variant or bump this bound with justification.
171 #[test]
172 fn command_enum_size_is_bounded() {
173 const MAX_BYTES: usize = 128;
174 let actual = std::mem::size_of::<Command>();
175 assert!(
176 actual <= MAX_BYTES,
177 "Command enum grew to {actual} bytes (limit {MAX_BYTES}); \
178 consider boxing the new fat variant"
179 );
180 }
181}