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}