Skip to main content

zenthra_widgets/layout/
stack.rs

1// crates/zenthra-widgets/src/layout/stack.rs
2
3use 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    // Layout
14    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    // Styling
26    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        // -- Save environment --
221        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        // Isolate stack bounds limits
239        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        // Update viewport for children if clipping is enabled
278        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        // -- Run Children --
298        f(self.ui);
299
300        // -- Restore Stacks and Environment --
301        self.ui.semantic_stack.pop();
302        self.ui.current_viewport = prev_viewport;
303
304        // Capture child data
305        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        // Restore layout cursor
313        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        // Calculate Stack sizes
323        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        // Advance parent layout cursor
340        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        // Background / Border visual Rect
347        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], // Bottom-Left -> Top-Left
359                        self.radius[2], // Bottom-Right -> Top-Right
360                        self.radius[1], // Top-Right -> Bottom-Right
361                        self.radius[0], // Top-Left -> Bottom-Left
362                    ],
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        // Process children layout and shifting
386        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            // Shift IDs recursively in layout cache
425            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        // Flush target draws and shifted children draws to the parent list
437        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        // Bubble IDs up to parent's scope
456        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
464// Layout helper implementations
465fn 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}