1use egui::{Id, Pos2, Rect, Response, Sense, Ui, UiBuilder, emath::GuiRounding as _};
2
3#[derive(Clone, Copy)]
4pub(crate) enum CellSize {
5 Absolute(f32),
7
8 Remainder,
10}
11
12pub(crate) enum CellDirection {
22 Horizontal,
24
25 Vertical,
27}
28
29#[derive(Clone, Copy, Default)]
31pub(crate) struct StripLayoutFlags {
32 pub(crate) clip: bool,
33 pub(crate) striped: bool,
34 pub(crate) hovered: bool,
35 pub(crate) selected: bool,
36 pub(crate) overline: bool,
37
38 pub(crate) sizing_pass: bool,
40}
41
42pub struct StripLayout<'l> {
44 pub(crate) ui: &'l mut Ui,
45 direction: CellDirection,
46 pub(crate) rect: Rect,
47 pub(crate) cursor: Pos2,
48
49 max: Pos2,
52
53 cell_layout: egui::Layout,
54 sense: Sense,
55}
56
57impl<'l> StripLayout<'l> {
58 pub(crate) fn new(
59 ui: &'l mut Ui,
60 direction: CellDirection,
61 cell_layout: egui::Layout,
62 sense: Sense,
63 ) -> Self {
64 let rect = ui.available_rect_before_wrap();
65 let pos = rect.left_top();
66
67 Self {
68 ui,
69 direction,
70 rect,
71 cursor: pos,
72 max: pos,
73 cell_layout,
74 sense,
75 }
76 }
77
78 fn cell_rect(&self, width: &CellSize, height: &CellSize) -> Rect {
79 Rect {
80 min: self.cursor,
81 max: Pos2 {
82 x: match width {
83 CellSize::Absolute(width) => self.cursor.x + width,
84 CellSize::Remainder => self.rect.right(),
85 },
86 y: match height {
87 CellSize::Absolute(height) => self.cursor.y + height,
88 CellSize::Remainder => self.rect.bottom(),
89 },
90 },
91 }
92 }
93
94 fn set_pos(&mut self, rect: Rect) {
95 self.max.x = self.max.x.max(rect.right());
96 self.max.y = self.max.y.max(rect.bottom());
97
98 match self.direction {
99 CellDirection::Horizontal => {
100 self.cursor.x = rect.right() + self.ui.spacing().item_spacing.x;
101 }
102 CellDirection::Vertical => {
103 self.cursor.y = rect.bottom() + self.ui.spacing().item_spacing.y;
104 }
105 }
106 }
107
108 pub(crate) fn empty(&mut self, width: CellSize, height: CellSize) {
109 self.set_pos(self.cell_rect(&width, &height));
110 }
111
112 pub(crate) fn add(
116 &mut self,
117 flags: StripLayoutFlags,
118 width: CellSize,
119 height: CellSize,
120 child_ui_id_salt: Id,
121 add_cell_contents: impl FnOnce(&mut Ui),
122 ) -> (Rect, Response) {
123 let max_rect = self.cell_rect(&width, &height);
124
125 let item_spacing = self.ui.spacing().item_spacing;
127 let gapless_rect = max_rect.expand2(0.5 * item_spacing).round_ui();
128
129 if flags.striped {
130 self.ui.painter().rect_filled(
131 gapless_rect,
132 egui::CornerRadius::ZERO,
133 self.ui.visuals().faint_bg_color,
134 );
135 }
136
137 if flags.selected {
138 self.ui.painter().rect_filled(
139 gapless_rect,
140 egui::CornerRadius::ZERO,
141 self.ui.visuals().selection.bg_fill,
142 );
143 }
144
145 if flags.hovered && !flags.selected && self.sense.interactive() {
146 self.ui.painter().rect_filled(
147 gapless_rect,
148 egui::CornerRadius::ZERO,
149 self.ui.visuals().widgets.hovered.bg_fill,
150 );
151 }
152
153 let mut child_ui = self.cell(flags, max_rect, child_ui_id_salt, add_cell_contents);
154
155 let used_rect = child_ui.min_rect();
156
157 child_ui.set_min_size(max_rect.size());
159
160 let allocation_rect = if self.ui.is_sizing_pass() {
161 used_rect
162 } else if flags.clip {
163 max_rect
164 } else {
165 max_rect | used_rect
166 };
167
168 self.set_pos(allocation_rect);
169
170 self.ui.advance_cursor_after_rect(allocation_rect);
171
172 let response = child_ui.response();
173
174 (used_rect, response)
175 }
176
177 pub fn end_line(&mut self) {
179 match self.direction {
180 CellDirection::Horizontal => {
181 self.cursor.y = self.max.y + self.ui.spacing().item_spacing.y;
182 self.cursor.x = self.rect.left();
183 }
184 CellDirection::Vertical => {
185 self.cursor.x = self.max.x + self.ui.spacing().item_spacing.x;
186 self.cursor.y = self.rect.top();
187 }
188 }
189 }
190
191 pub(crate) fn skip_space(&mut self, delta: egui::Vec2) {
193 let before = self.cursor;
194 self.cursor += delta;
195 let rect = Rect::from_two_pos(before, self.cursor);
196 self.ui.allocate_rect(rect, Sense::hover());
197 }
198
199 fn cell(
201 &mut self,
202 flags: StripLayoutFlags,
203 max_rect: Rect,
204 child_ui_id_salt: egui::Id,
205 add_cell_contents: impl FnOnce(&mut Ui),
206 ) -> Ui {
207 let mut ui_builder = UiBuilder::new()
208 .id_salt(child_ui_id_salt)
209 .ui_stack_info(egui::UiStackInfo::new(egui::UiKind::TableCell))
210 .max_rect(max_rect)
211 .layout(self.cell_layout)
212 .sense(self.sense);
213 if flags.sizing_pass {
214 ui_builder = ui_builder.sizing_pass();
215 }
216
217 let mut child_ui = self.ui.new_child(ui_builder);
218
219 if flags.clip {
220 let margin = egui::Vec2::splat(self.ui.visuals().clip_rect_margin);
221 let margin = margin.min(0.5 * self.ui.spacing().item_spacing);
222 let clip_rect = max_rect.expand2(margin);
223 child_ui.shrink_clip_rect(clip_rect);
224
225 if !child_ui.is_sizing_pass() {
226 child_ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Truncate);
228 }
229 }
230
231 if flags.selected {
232 let stroke_color = child_ui.style().visuals.selection.stroke.color;
233 child_ui.style_mut().visuals.override_text_color = Some(stroke_color);
234 }
235
236 if flags.overline {
237 child_ui.painter().hline(
238 max_rect.x_range(),
239 max_rect.top(),
240 child_ui.visuals().widgets.noninteractive.bg_stroke,
241 );
242 }
243
244 add_cell_contents(&mut child_ui);
245
246 child_ui
247 }
248
249 pub fn allocate_rect(&mut self) -> Response {
251 let mut rect = self.rect;
252 rect.set_right(self.max.x);
253 rect.set_bottom(self.max.y);
254
255 self.ui.allocate_rect(rect, Sense::hover())
256 }
257}