1use crate::ui::{DrawCommand, RectDraw, Ui};
4use zenthra_core::{Color, Id, Rect, Align, BorderAlignment};
5use zenthra_render::RectInstance;
6use crate::container::Direction;
7
8pub struct StackBuilder<'u, 'a> {
9 ui: &'u mut Ui<'a>,
10 id: Id,
11 child_origins: Vec<(f32, f32)>,
12
13 width: Option<f32>,
15 height: Option<f32>,
16 fill_x: bool,
17 fill_y: bool,
18 padding_top: f32,
19 padding_bottom: f32,
20 padding_left: f32,
21 padding_right: f32,
22 halign: Align,
23 valign: Align,
24
25 bg: Option<Color>,
27 border_color: Option<Color>,
28 border_width: f32,
29 radius: [f32; 4],
30 shadow_color: Option<Color>,
31 shadow_offset: [f32; 2],
32 shadow_blur: f32,
33 shadow_opacity: f32,
34 opacity: f32,
35 clip: bool,
36 border_alignment: BorderAlignment,
37 is_overlay: bool,
38 is_absolute: bool,
39}
40
41impl<'u, 'a> StackBuilder<'u, 'a> {
42 pub fn new(ui: &'u mut Ui<'a>) -> Self {
43 let id = ui.id();
44 Self {
45 ui,
46 id,
47 child_origins: Vec::new(),
48 width: None,
49 height: None,
50 fill_x: false,
51 fill_y: false,
52 padding_top: 0.0,
53 padding_bottom: 0.0,
54 padding_left: 0.0,
55 padding_right: 0.0,
56 halign: Align::Left,
57 valign: Align::Top,
58 bg: None,
59 border_color: None,
60 border_width: 0.0,
61 radius: [0.0; 4],
62 shadow_color: None,
63 shadow_offset: [0.0, 0.0],
64 shadow_blur: 0.0,
65 shadow_opacity: 1.0,
66 opacity: 1.0,
67 clip: false,
68 border_alignment: BorderAlignment::Inside,
69 is_overlay: false,
70 is_absolute: false,
71 }
72 }
73
74 pub fn id(mut self, id: impl std::hash::Hash) -> Self {
75 let mut hasher = std::collections::hash_map::DefaultHasher::new();
76 use std::hash::Hasher;
77 id.hash(&mut hasher);
78 self.id = Id::from_u64(hasher.finish());
79 self
80 }
81
82 pub fn width(mut self, w: f32) -> Self {
83 self.width = Some(w);
84 self
85 }
86
87 pub fn height(mut self, h: f32) -> Self {
88 self.height = Some(h);
89 self
90 }
91
92 pub fn size(mut self, w: f32, h: f32) -> Self {
93 self.width = Some(w);
94 self.height = Some(h);
95 self
96 }
97
98 pub fn full_width(mut self) -> Self {
99 self.fill_x = true;
100 self
101 }
102
103 pub fn full_height(mut self) -> Self {
104 self.fill_y = true;
105 self
106 }
107
108 pub fn fill(mut self) -> Self {
109 self.fill_x = true;
110 self.fill_y = true;
111 self
112 }
113
114 pub fn padding(mut self, t: f32, r: f32, b: f32, l: f32) -> Self {
115 self.padding_top = t;
116 self.padding_bottom = b;
117 self.padding_left = l;
118 self.padding_right = r;
119 self
120 }
121
122 pub fn padding_all(mut self, p: f32) -> Self {
123 self.padding_top = p;
124 self.padding_bottom = p;
125 self.padding_left = p;
126 self.padding_right = p;
127 self
128 }
129
130 pub fn padding_x(mut self, p: f32) -> Self {
131 self.padding_left = p;
132 self.padding_right = p;
133 self
134 }
135
136 pub fn padding_y(mut self, p: f32) -> Self {
137 self.padding_top = p;
138 self.padding_bottom = p;
139 self
140 }
141
142 pub fn bg(mut self, bg: Color) -> Self {
143 self.bg = Some(bg);
144 self
145 }
146
147 pub fn border(mut self, color: Color, width: f32) -> Self {
148 self.border_color = Some(color);
149 self.border_width = width;
150 self
151 }
152
153 pub fn radius(mut self, tl: f32, tr: f32, br: f32, bl: f32) -> Self {
154 self.radius = [tl, tr, br, bl];
155 self
156 }
157
158 pub fn radius_all(mut self, r: f32) -> Self {
159 self.radius = [r, r, r, r];
160 self
161 }
162
163 pub fn shadow(mut self, color: Color, x: f32, y: f32, blur: f32) -> Self {
164 self.shadow_color = Some(color);
165 self.shadow_offset = [x, y];
166 self.shadow_blur = blur;
167 self
168 }
169
170 pub fn shadow_opacity(mut self, opacity: f32) -> Self {
171 self.shadow_opacity = opacity;
172 self
173 }
174
175 pub fn opacity(mut self, opacity: f32) -> Self {
176 self.opacity = opacity;
177 self
178 }
179
180 pub fn clip(mut self, clip: bool) -> Self {
181 self.clip = clip;
182 self
183 }
184
185 pub fn border_alignment(mut self, alignment: BorderAlignment) -> Self {
186 self.border_alignment = alignment;
187 self
188 }
189
190 pub fn halign(mut self, align: Align) -> Self {
191 self.halign = align;
192 self
193 }
194
195 pub fn valign(mut self, align: Align) -> Self {
196 self.valign = align;
197 self
198 }
199
200 pub fn overlay(mut self) -> Self {
201 self.is_overlay = true;
202 self
203 }
204
205 pub fn absolute(mut self) -> Self {
206 self.is_absolute = true;
207 self
208 }
209
210 pub fn show<F>(mut self, f: F)
211 where
212 F: FnOnce(&mut Ui),
213 {
214 let id = self.id;
215 let start_x = self.ui.cursor_x;
216 let start_y = self.ui.cursor_y;
217 let ox = start_x;
218 let oy = start_y;
219
220 let prev_dir = self.ui.direction;
222 let prev_line_h = self.ui.line_height;
223 let prev_base_x = self.ui.base_x;
224 let prev_base_y = self.ui.base_y;
225 let prev_offset_x = self.ui.offset_x;
226 let prev_offset_y = self.ui.offset_y;
227 let prev_viewport = self.ui.current_viewport;
228
229 let prev_child_sizes = std::mem::take(&mut self.ui.child_sizes);
230 let prev_child_ranges = std::mem::take(&mut self.ui.child_draw_ranges);
231 let prev_child_origins = std::mem::take(&mut self.ui.child_origins);
232 let prev_id_ranges = std::mem::take(&mut self.ui.id_ranges);
233 let prev_id_log = std::mem::take(&mut self.ui.id_log);
234
235 let prev_max_x = self.ui.max_x;
236 let prev_max_y = self.ui.max_y;
237
238 if let Some(w) = self.width {
240 self.ui.max_x = (ox + w - self.padding_right).min(prev_max_x);
241 }
242 if let Some(h) = self.height {
243 self.ui.max_y = (oy + h - self.padding_bottom).min(prev_max_y);
244 }
245
246 self.ui.direction = Direction::Stack;
247 self.ui.line_height = 0.0;
248
249 let avail_w = if let Some(w) = self.width {
250 w - self.padding_left - self.padding_right - 2.0 * self.border_width
251 } else {
252 (prev_max_x - ox - self.padding_left - self.padding_right - 2.0 * self.border_width).max(0.0)
253 };
254 let avail_h = if let Some(h) = self.height {
255 h - self.padding_top - self.padding_bottom - 2.0 * self.border_width
256 } else {
257 (prev_max_y - oy - self.padding_top - self.padding_bottom - 2.0 * self.border_width).max(0.0)
258 };
259
260 self.ui.max_x = ox + self.padding_left + self.border_width + avail_w;
261 self.ui.max_y = oy + self.padding_top + self.border_width + avail_h;
262
263 self.ui.semantic_stack.push(id);
264 self.ui.register_semantic(zenthra_core::SemanticNode::new(
265 id,
266 zenthra_core::Role::Container,
267 zenthra_core::Rect::new(ox, oy, 0.0, 0.0),
268 ));
269
270 let parent_draws = std::mem::take(&mut self.ui.draws);
271
272 self.ui.base_x = ox + self.padding_left + self.border_width;
273 self.ui.base_y = oy + self.padding_top + self.border_width;
274 self.ui.cursor_x = self.ui.base_x;
275 self.ui.cursor_y = self.ui.base_y;
276
277 if self.clip {
279 if let Some((rect, _)) = self.ui.get_recorded_layout(id) {
280 let my_screen_rect = [
281 rect.origin.x + prev_offset_x,
282 rect.origin.y + prev_offset_y,
283 rect.size.width,
284 rect.size.height,
285 ];
286 let parent_rect = [
287 prev_viewport.origin.x,
288 prev_viewport.origin.y,
289 prev_viewport.size.width,
290 prev_viewport.size.height,
291 ];
292 let intersected = intersect_rects(my_screen_rect, parent_rect);
293 self.ui.current_viewport = Rect::new(intersected[0], intersected[1], intersected[2], intersected[3]);
294 }
295 }
296
297 f(self.ui);
299
300 self.ui.semantic_stack.pop();
302 self.ui.current_viewport = prev_viewport;
303
304 let child_ids_only = std::mem::replace(&mut self.ui.id_log, prev_id_log);
306 let child_id_ranges = std::mem::replace(&mut self.ui.id_ranges, prev_id_ranges);
307 let children_draws = std::mem::replace(&mut self.ui.draws, parent_draws);
308 let child_sizes = std::mem::replace(&mut self.ui.child_sizes, prev_child_sizes);
309 let child_ranges = std::mem::replace(&mut self.ui.child_draw_ranges, prev_child_ranges);
310 self.child_origins = std::mem::replace(&mut self.ui.child_origins, prev_child_origins);
311
312 self.ui.direction = prev_dir;
314 self.ui.line_height = prev_line_h;
315 self.ui.base_x = prev_base_x;
316 self.ui.base_y = prev_base_y;
317 self.ui.cursor_x = start_x;
318 self.ui.cursor_y = start_y;
319 self.ui.max_x = prev_max_x;
320 self.ui.max_y = prev_max_y;
321
322 let content_w = child_sizes.iter().map(|(cw, _)| *cw).fold(0.0f32, f32::max);
324 let content_h = child_sizes.iter().map(|(_, ch)| *ch).fold(0.0f32, f32::max);
325
326 let w = if self.fill_x {
327 self.ui.max_x - ox
328 } else {
329 self.width.unwrap_or(content_w + self.padding_left + self.padding_right + 2.0 * self.border_width)
330 };
331 let h = if self.fill_y {
332 self.ui.max_y - oy
333 } else {
334 self.height.unwrap_or(content_h + self.padding_top + self.padding_bottom + 2.0 * self.border_width)
335 };
336
337 self.ui.record_layout(id, Rect::new(ox, oy, w, h));
338
339 let (adv_w, adv_h) = match prev_dir {
341 Direction::Column => (0.0, h),
342 Direction::Row => (w, 0.0),
343 Direction::Stack => (0.0, 0.0),
344 };
345
346 let mut target_draws = Vec::new();
348 if let Some(bg) = self.bg {
349 let bw = self.border_width;
350 let bc = self.border_color.unwrap_or(Color::TRANSPARENT);
351
352 target_draws.push(DrawCommand::Rect(RectDraw {
353 instance: RectInstance {
354 pos: [ox, oy],
355 size: [w, h],
356 color: bg.to_array(),
357 radius: [
358 self.radius[3], self.radius[2], self.radius[1], self.radius[0], ],
363 border_width: bw,
364 border_color: bc.to_array(),
365 shadow_color: self.shadow_color.map(|c| {
366 let mut a = c.to_array();
367 a[3] *= self.shadow_opacity;
368 a
369 }).unwrap_or([0.0, 0.0, 0.0, 0.0]),
370 shadow_offset: self.shadow_offset,
371 shadow_blur: self.shadow_blur,
372 clip_rect: [-100000.0, -100000.0, 2000000.0, 2000000.0],
373 grayscale: 0.0,
374 brightness: 1.0,
375 opacity: self.opacity,
376 border_alignment: match self.border_alignment {
377 BorderAlignment::Inside => 0.0,
378 BorderAlignment::Center => 0.5,
379 BorderAlignment::Outside => 1.0,
380 },
381 }
382 }));
383 }
384
385 let inner_w = w - self.padding_left - self.padding_right - 2.0 * self.border_width;
387 let inner_h = h - self.padding_top - self.padding_bottom - 2.0 * self.border_width;
388
389 let clip = [ox + prev_offset_x, oy + prev_offset_y, w, h];
390
391 let mut children_draws_mut = children_draws;
392
393 for (i, (start, end)) in child_ranges.iter().enumerate() {
394 if i >= child_sizes.len() { break; }
395 let (cw, ch) = child_sizes[i];
396
397 let tx = ox + self.padding_left + self.border_width + match self.halign {
398 Align::Center => (inner_w - cw).max(0.0) / 2.0,
399 Align::Right => inner_w - cw,
400 _ => 0.0,
401 };
402
403 let ty = oy + self.padding_top + self.border_width + match self.valign {
404 Align::Center => (inner_h - ch).max(0.0) / 2.0,
405 Align::Bottom => inner_h - ch,
406 _ => 0.0,
407 };
408
409 let (origin_x, origin_y) = self.child_origins.get(i).copied().unwrap_or_else(|| {
410 children_draws_mut
411 .get(*start)
412 .map(|d| draw_origin(d))
413 .unwrap_or((ox + self.padding_left + self.border_width, oy + self.padding_top + self.border_width))
414 });
415
416 let dx = tx - origin_x;
417 let dy = ty - origin_y;
418
419 for draw in &mut children_draws_mut[*start..*end] {
420 set_clip(draw, clip);
421 offset_draw(draw, dx, dy);
422 }
423
424 if let Some(&(ids_start, ids_end)) = child_id_ranges.get(i) {
426 for j in ids_start..ids_end {
427 let cid = child_ids_only[j];
428 if let Some((rect, _)) = self.ui.next_layout_cache.get_mut(&cid) {
429 rect.origin.x += dx;
430 rect.origin.y += dy;
431 }
432 }
433 }
434 }
435
436 for draw in target_draws {
438 if self.is_overlay {
439 self.ui.overlays.push(draw);
440 } else {
441 self.ui.draws.push(draw);
442 }
443 }
444
445 let advance_draw_start = self.ui.draws.len();
446
447 for draw in children_draws_mut {
448 if self.is_overlay {
449 self.ui.overlays.push(draw);
450 } else {
451 self.ui.draws.push(draw);
452 }
453 }
454
455 self.ui.id_log.extend(child_ids_only);
457
458 if !self.is_absolute {
459 self.ui.advance(adv_w, adv_h, advance_draw_start);
460 }
461 }
462}
463
464fn offset_draw(cmd: &mut DrawCommand, dx: f32, dy: f32) {
466 match cmd {
467 DrawCommand::Rect(r) => {
468 r.instance.pos[0] += dx;
469 r.instance.pos[1] += dy;
470 r.instance.clip_rect[0] += dx;
471 r.instance.clip_rect[1] += dy;
472 }
473 DrawCommand::Text(t) => {
474 t.pos[0] += dx;
475 t.pos[1] += dy;
476 t.clip[0] += dx;
477 t.clip[1] += dy;
478 }
479 DrawCommand::OverlayRect(c) => {
480 c.x += dx;
481 c.y += dy;
482 c.clip[0] += dx;
483 c.clip[1] += dy;
484 }
485 DrawCommand::Image(i) => {
486 i.instance.pos[0] += dx;
487 i.instance.pos[1] += dy;
488 i.instance.clip_rect[0] += dx;
489 i.instance.clip_rect[1] += dy;
490 }
491 DrawCommand::BackdropBlur(b) => {
492 b.x += dx;
493 b.y += dy;
494 b.clip_rect[0] += dx;
495 b.clip_rect[1] += dy;
496 }
497 DrawCommand::CustomPostProcess(b) => {
498 b.x += dx;
499 b.y += dy;
500 b.clip_rect[0] += dx;
501 b.clip_rect[1] += dy;
502 }
503 }
504}
505
506fn draw_origin(cmd: &DrawCommand) -> (f32, f32) {
507 match cmd {
508 DrawCommand::Rect(r) => (r.instance.pos[0], r.instance.pos[1]),
509 DrawCommand::Text(t) => (t.pos[0], t.pos[1]),
510 DrawCommand::OverlayRect(c) => (c.x, c.y),
511 DrawCommand::Image(i) => (i.instance.pos[0], i.instance.pos[1]),
512 DrawCommand::BackdropBlur(b) => (b.x, b.y),
513 DrawCommand::CustomPostProcess(b) => (b.x, b.y),
514 }
515}
516
517fn set_clip(cmd: &mut DrawCommand, clip: [f32; 4]) {
518 match cmd {
519 DrawCommand::Rect(r) => r.instance.clip_rect = intersect_rects(r.instance.clip_rect, clip),
520 DrawCommand::Text(t) => t.clip = intersect_rects(t.clip, clip),
521 DrawCommand::OverlayRect(c) => c.clip = intersect_rects(c.clip, clip),
522 DrawCommand::Image(i) => i.instance.clip_rect = intersect_rects(i.instance.clip_rect, clip),
523 DrawCommand::BackdropBlur(b) => b.clip_rect = intersect_rects(b.clip_rect, clip),
524 DrawCommand::CustomPostProcess(b) => b.clip_rect = intersect_rects(b.clip_rect, clip),
525 }
526}
527
528fn intersect_rects(a: [f32; 4], b: [f32; 4]) -> [f32; 4] {
529 let x1 = a[0].max(b[0]);
530 let y1 = a[1].max(b[1]);
531 let x2 = (a[0] + a[2]).min(b[0] + b[2]);
532 let y2 = (a[1] + a[3]).min(b[1] + b[3]);
533
534 let w = (x2 - x1).max(0.0);
535 let h = (y2 - y1).max(0.0);
536
537 [x1, y1, w, h]
538}