Skip to main content

zenthra_widgets/
container.rs

1use crate::ui::{DrawCommand, RectDraw, Ui};
2use zenthra_core::{Color, Align, BorderAlignment};
3use zenthra_render::RectInstance;
4
5#[derive(Debug, Clone, Copy, PartialEq)]
6pub enum Direction {
7    Row,
8    Column,
9    Stack,
10}
11
12#[derive(Debug, Clone, Copy, PartialEq)]
13pub enum Wrap {
14    NoWrap,
15    Wrap,
16    WrapReverse,
17    RightToLeft,
18    RightToLeftReverse,
19}
20
21pub struct ContainerBuilder<'u, 'a> {
22    ui: &'u mut Ui<'a>,
23    direction: Direction,
24    halign: Align,
25    valign: Align,
26    wrap: Wrap,
27    children_draws: Vec<DrawCommand>,
28    child_sizes: Vec<(f32, f32)>,
29    child_ranges: Vec<(usize, usize)>,
30    child_origins: Vec<(f32, f32)>,
31    start_x: f32,
32    start_y: f32,
33    pos_x: Option<f32>,
34    pos_y: Option<f32>,
35    width: Option<f32>,
36    height: Option<f32>,
37    fill_x: bool,
38    fill_y: bool,
39    padding_top: f32,
40    padding_bottom: f32,
41    padding_left: f32,
42    padding_right: f32,
43    gap: f32,
44    bg: Option<Color>,
45    border_color: Option<Color>,
46    border_width: f32,
47    shadow_blur: f32,
48    shadow_color: Option<Color>,
49    shadow_offset: [f32; 2],
50    shadow_opacity: f32,
51    opacity: f32,
52    render_mode: Option<zenthra_core::RenderMode>,
53    max_width: Option<f32>,
54    max_height: Option<f32>,
55    min_width: Option<f32>,
56    min_height: Option<f32>,
57    scroll_x: bool,
58    scroll_y: bool,
59    clip: Option<bool>,
60    pub id: zenthra_core::Id,
61    radius: [f32; 4],
62    border_alignment: BorderAlignment,
63    is_absolute: bool,
64    is_overlay: bool,
65    hover_bg: Option<Color>,
66    hover_border_color: Option<Color>,
67    hover_border_width: Option<f32>,
68    hover_scale: f32,
69    active_bg: Option<Color>,
70    active_border_color: Option<Color>,
71    active_border_width: Option<f32>,
72    active_scale: f32,
73    backdrop_blur: Option<f32>,
74    post_process_shader: Option<&'static str>,
75    backdrop_filter: Option<zenthra_core::BackdropFilter>,
76}
77
78impl<'u, 'a> ContainerBuilder<'u, 'a> {
79    pub fn new(ui: &'u mut Ui<'a>) -> Self {
80        let id = ui.id();
81        Self {
82            ui,
83            direction: Direction::Column,
84            halign: Align::Left,
85            valign: Align::Top,
86            wrap: Wrap::NoWrap,
87            children_draws: Vec::new(),
88            child_sizes: Vec::new(),
89            child_ranges: Vec::new(),
90            child_origins: Vec::new(),
91            start_x: 0.0,
92            start_y: 0.0,
93            pos_x: None,
94            pos_y: None,
95            width: None,
96            height: None,
97            fill_x: false,
98            fill_y: false,
99            padding_top: 0.0,
100            padding_bottom: 0.0,
101            padding_left: 0.0,
102            padding_right: 0.0,
103            gap: 0.0,
104            bg: None,
105            border_color: None,
106            border_width: 0.0,
107            shadow_blur: 0.0,
108            shadow_color: None,
109            shadow_offset: [0.0, 0.0],
110            shadow_opacity: 1.0,
111            opacity: 1.0,
112            render_mode: None,
113            max_width: None,
114            max_height: None,
115            min_width: None,
116            min_height: None,
117            scroll_x: false,
118            scroll_y: false,
119            clip: None,
120            id,
121            radius: [0.0; 4],
122            border_alignment: BorderAlignment::Inside,
123            is_absolute: false,
124            is_overlay: false,
125            hover_bg: None,
126            hover_border_color: None,
127            hover_border_width: None,
128            hover_scale: 1.0,
129            active_bg: None,
130            active_border_color: None,
131            active_border_width: None,
132            active_scale: 1.0,
133            backdrop_blur: None,
134            post_process_shader: None,
135            backdrop_filter: None,
136        }
137    }
138
139    pub fn absolute(mut self, x: f32, y: f32) -> Self {
140        self.pos_x = Some(x);
141        self.pos_y = Some(y);
142        self.is_absolute = true;
143        self
144    }
145    pub fn pos(mut self, x: f32, y: f32) -> Self {
146        self.pos_x = Some(x);
147        self.pos_y = Some(y);
148        self
149    }
150    pub fn width(mut self, w: f32) -> Self {
151        self.width = Some(w);
152        self
153    }
154    pub fn height(mut self, h: f32) -> Self {
155        self.height = Some(h);
156        self
157    }
158    pub fn fill_x(mut self) -> Self {
159        self.fill_x = true;
160        self
161    }
162    pub fn fill_y(mut self) -> Self {
163        self.fill_y = true;
164        self
165    }
166    pub fn fill(mut self) -> Self {
167        self.fill_x = true;
168        self.fill_y = true;
169        self
170    }
171    pub fn full_width(mut self) -> Self {
172        self.fill_x = true;
173        self
174    }
175    pub fn full_height(mut self) -> Self {
176        self.fill_y = true;
177        self
178    }
179    pub fn max_width(mut self, w: f32) -> Self {
180        self.max_width = Some(w);
181        self
182    }
183    pub fn max_height(mut self, h: f32) -> Self {
184        self.max_height = Some(h);
185        self
186    }
187    pub fn min_width(mut self, w: f32) -> Self {
188        self.min_width = Some(w);
189        self
190    }
191    pub fn min_height(mut self, h: f32) -> Self {
192        self.min_height = Some(h);
193        self
194    }
195    pub fn scrollable(mut self, x: bool, y: bool) -> Self {
196        self.scroll_x = x;
197        self.scroll_y = y;
198        self
199    }
200    pub fn scroll_x(mut self, e: bool) -> Self {
201        self.scroll_x = e;
202        if e { self.clip = Some(true); }
203        self
204    }
205    pub fn scroll_y(mut self, e: bool) -> Self {
206        self.scroll_y = e;
207        if e { self.clip = Some(true); }
208        self
209    }
210    /// Force clip the overflowing content bounds of this container regardless of scrolling state
211    pub fn clip(mut self, enabled: bool) -> Self {
212        self.clip = Some(enabled);
213        self
214    }
215    pub fn padding(mut self, t: f32, r: f32, b: f32, l: f32) -> Self {
216        self.padding_top = t;
217        self.padding_bottom = b;
218        self.padding_left = l;
219        self.padding_right = r;
220        self
221    }
222    pub fn padding_all(mut self, p: f32) -> Self {
223        self.padding_top = p;
224        self.padding_bottom = p;
225        self.padding_left = p;
226        self.padding_right = p;
227        self
228    }
229    pub fn padding_x(mut self, p: f32) -> Self {
230        self.padding_left = p;
231        self.padding_right = p;
232        self
233    }
234    pub fn padding_y(mut self, p: f32) -> Self {
235        self.padding_top = p;
236        self.padding_bottom = p;
237        self
238    }
239    pub fn padding_top(mut self, p: f32) -> Self {
240        self.padding_top = p;
241        self
242    }
243    pub fn padding_bottom(mut self, p: f32) -> Self {
244        self.padding_bottom = p;
245        self
246    }
247    pub fn padding_left(mut self, p: f32) -> Self {
248        self.padding_left = p;
249        self
250    }
251    pub fn padding_right(mut self, p: f32) -> Self {
252        self.padding_right = p;
253        self
254    }
255
256
257    pub fn row(mut self) -> Self {
258        self.direction = Direction::Row;
259        self
260    }
261
262    pub fn column(mut self) -> Self {
263        self.direction = Direction::Column;
264        self
265    }
266
267    pub fn wrap(mut self, strategy: Wrap) -> Self {
268        self.wrap = strategy;
269        self
270    }
271
272    pub fn no_wrap(mut self) -> Self {
273        self.wrap = Wrap::NoWrap;
274        self
275    }
276
277    pub fn align(mut self, align: Align) -> Self {
278        self.halign = align;
279        self.valign = align;
280        self
281    }
282
283    pub fn radius(mut self, tl: f32, tr: f32, br: f32, bl: f32) -> Self {
284        self.radius = [tl, tr, br, bl];
285        self
286    }
287
288    pub fn radius_all(mut self, r: f32) -> Self {
289        self.radius = [r, r, r, r];
290        self
291    }
292
293    pub fn halign(mut self, align: Align) -> Self {
294        self.halign = align;
295        self
296    }
297
298    pub fn valign(mut self, align: Align) -> Self {
299        self.valign = align;
300        self
301    }
302
303    pub fn radius_top_left(mut self, r: f32) -> Self {
304        self.radius[0] = r;
305        self
306    }
307    pub fn radius_top_right(mut self, r: f32) -> Self {
308        self.radius[1] = r;
309        self
310    }
311    pub fn radius_bottom_right(mut self, r: f32) -> Self {
312        self.radius[2] = r;
313        self
314    }
315    pub fn radius_bottom_left(mut self, r: f32) -> Self {
316        self.radius[3] = r;
317        self
318    }
319
320    pub fn radius_top(mut self, r: f32) -> Self {
321        self.radius[0] = r;
322        self.radius[1] = r;
323        self
324    }
325    pub fn radius_bottom(mut self, r: f32) -> Self {
326        self.radius[2] = r;
327        self.radius[3] = r;
328        self
329    }
330    pub fn radius_left(mut self, r: f32) -> Self {
331        self.radius[0] = r;
332        self.radius[3] = r;
333        self
334    }
335    pub fn radius_right(mut self, r: f32) -> Self {
336        self.radius[1] = r;
337        self.radius[2] = r;
338        self
339    }
340    pub fn radius_x(mut self, r: f32) -> Self {
341        self.radius = [r, r, r, r];
342        self
343    }
344    pub fn radius_y(mut self, r: f32) -> Self {
345        self.radius = [r, r, r, r];
346        self
347    }
348
349    pub fn active_bg(mut self, color: Color) -> Self {
350        self.active_bg = Some(color);
351        self
352    }
353
354    pub fn active_border(mut self, color: Color, width: f32) -> Self {
355        self.active_border_color = Some(color);
356        self.active_border_width = Some(width);
357        self
358    }
359
360    pub fn active_scale(mut self, scale: f32) -> Self {
361        self.active_scale = scale;
362        self
363    }
364
365    pub fn gap(mut self, g: f32) -> Self {
366        self.gap = g;
367        self
368    }
369    pub fn bg(mut self, c: Color) -> Self {
370        self.bg = Some(c);
371        self
372    }
373    pub fn bg_opacity(mut self, opacity: f32) -> Self {
374        if let Some(ref mut bg) = self.bg {
375            bg.a = opacity;
376        }
377        self
378    }
379    pub fn border(mut self, c: Color, w: f32) -> Self {
380        self.border_color = Some(c);
381        self.border_width = w;
382        self
383    }
384    pub fn shadow(mut self, color: Color, x: f32, y: f32, blur: f32) -> Self {
385        self.shadow_color = Some(color);
386        self.shadow_offset = [x, y];
387        self.shadow_blur = blur;
388        self
389    }
390
391    pub fn shadow_opacity(mut self, opacity: f32) -> Self {
392        self.shadow_opacity = opacity;
393        self
394    }
395    pub fn opacity(mut self, o: f32) -> Self {
396        self.opacity = o;
397        self
398    }
399
400    pub fn id(mut self, id: impl std::hash::Hash) -> Self {
401        let mut hasher = std::collections::hash_map::DefaultHasher::new();
402        use std::hash::{Hash, Hasher};
403        id.hash(&mut hasher);
404        if let Some(parent) = self.ui.semantic_stack.last() {
405            parent.hash(&mut hasher);
406        }
407        self.id = zenthra_core::Id::from_u64(hasher.finish());
408        self
409    }
410
411    pub fn hover_bg(mut self, color: Color) -> Self {
412        self.hover_bg = Some(color);
413        self
414    }
415
416    pub fn hover_border(mut self, color: Color, width: f32) -> Self {
417        self.hover_border_color = Some(color);
418        self.hover_border_width = Some(width);
419        self
420    }
421
422    pub fn hover_scale(mut self, scale: f32) -> Self {
423        self.hover_scale = scale;
424        self
425    }
426
427    pub fn border_alignment(mut self, alignment: BorderAlignment) -> Self {
428        self.border_alignment = alignment;
429        self
430    }
431
432    /// Enable glassmorphism: blurs the scene content behind this container.
433    /// `radius` is in logical pixels — higher values produce a softer, deeper blur.
434    /// Typically 10.0–40.0. Combine with a semi-transparent `.bg()` for the frosted glass look.
435    pub fn backdrop_blur(mut self, radius: f32) -> Self {
436        self.backdrop_blur = Some(radius);
437        self
438    }
439
440    pub fn post_process_shader(mut self, shader_id: &'static str) -> Self {
441        self.post_process_shader = Some(shader_id);
442        self
443    }
444
445    pub fn backdrop_filter(mut self, filter: zenthra_core::BackdropFilter) -> Self {
446        self.backdrop_filter = Some(filter);
447        self
448    }
449
450    pub fn render_mode(mut self, mode: zenthra_core::RenderMode) -> Self {
451        self.render_mode = Some(mode);
452        self
453    }
454
455    pub fn overlay(mut self) -> Self {
456        self.is_overlay = true;
457        self
458    }
459
460    pub fn on_click<F>(self, mut f: F) -> Self
461    where
462        F: FnMut() + 'a,
463    {
464        self.ui.add_listener(self.id, crate::ui::EventPhase::Bubble, move |_, event| {
465            if let crate::ui::WidgetEvent::Click = event {
466                f();
467            }
468        });
469        self
470    }
471
472    pub fn on_hover<F>(self, mut f: F) -> Self
473    where
474        F: FnMut(bool) + 'a,
475    {
476        self.ui.add_listener(self.id, crate::ui::EventPhase::Bubble, move |_, event| {
477            if let crate::ui::WidgetEvent::Hover(hovered) = event {
478                f(*hovered);
479            }
480        });
481        self
482    }
483
484    pub fn on_scroll<F>(self, mut f: F) -> Self
485    where
486        F: FnMut(f32, f32) + 'a,
487    {
488        self.ui.add_listener(self.id, crate::ui::EventPhase::Bubble, move |_, event| {
489            if let crate::ui::WidgetEvent::Scroll(dx, dy) = event {
490                f(*dx, *dy);
491            }
492        });
493        self
494    }
495
496    pub fn on_event<F>(self, phase: crate::ui::EventPhase, f: F) -> Self
497    where
498        F: FnMut(&mut crate::ui::EventContext, &crate::ui::WidgetEvent) + 'a,
499    {
500        self.ui.add_listener(self.id, phase, f);
501        self
502    }
503
504    pub fn show<F>(mut self, f: F) -> zenthra_core::Response
505    where
506        F: FnOnce(&mut Ui),
507    {
508        // 1. Capture the start position and own ID
509        let id = self.id;
510        let actual_clip = self.clip.unwrap_or(self.width.is_some() || self.height.is_some() || self.scroll_x || self.scroll_y);
511
512        self.start_x = self.ui.cursor_x;
513        self.start_y = self.ui.cursor_y;
514        let ox = self.pos_x.unwrap_or(self.start_x);
515        let oy = self.pos_y.unwrap_or(self.start_y);
516
517        // -- Save current Ui environment --
518        let prev_dir = self.ui.direction;
519        let prev_line_h = self.ui.line_height;
520        let prev_base_x = self.ui.base_x;
521        let prev_base_y = self.ui.base_y;
522        let prev_offset_x = self.ui.offset_x;
523        let prev_offset_y = self.ui.offset_y;
524        let prev_viewport = self.ui.current_viewport;
525        
526        // Take and isolate state maps for children
527        let prev_child_sizes = std::mem::take(&mut self.ui.child_sizes);
528        let prev_child_ranges = std::mem::take(&mut self.ui.child_draw_ranges);
529        let prev_child_origins = std::mem::take(&mut self.ui.child_origins);
530        let prev_id_ranges = std::mem::take(&mut self.ui.id_ranges);
531        let prev_id_log = std::mem::take(&mut self.ui.id_log);
532        
533        let prev_max_x = self.ui.max_x;
534        let prev_max_y = self.ui.max_y;
535        
536        let _id_log_start_idx = self.ui.id_log.len();
537
538        // Set child environment
539        if let Some(w) = self.width {
540            self.ui.max_x = (ox + w - self.padding_right).min(prev_max_x);
541        }
542        if let Some(h) = self.height {
543            self.ui.max_y = (oy + h - self.padding_bottom).min(prev_max_y);
544        }
545
546        self.ui.direction = self.direction;
547        self.ui.line_height = 0.0;
548
549        let mode = self.render_mode.unwrap_or(self.ui.current_render_mode());
550        self.ui.render_mode_stack.push(mode);
551        if mode == zenthra_core::RenderMode::Continuous {
552            self.ui.needs_redraw = true;
553        }
554
555        let avail_w = if let Some(w) = self.width {
556            w - self.padding_left - self.padding_right - 2.0 * self.border_width
557        } else {
558            (prev_max_x - ox - self.padding_left - self.padding_right - 2.0 * self.border_width).max(0.0)
559        };
560        let avail_h = if let Some(h) = self.height {
561            h - self.padding_top - self.padding_bottom - 2.0 * self.border_width
562        } else {
563            (prev_max_y - oy - self.padding_top - self.padding_bottom - 2.0 * self.border_width).max(0.0)
564        };
565
566        // Important: If we are absolute, our internal boundary is NOT capped by prev_max_x.
567        // This allows us to have a full width even at the edge of the screen.
568        self.ui.max_x = ox + self.padding_left + self.border_width + avail_w;
569        self.ui.max_y = oy + self.padding_top + self.border_width + avail_h;
570
571        self.ui.semantic_stack.push(id);
572        self.ui.register_semantic(zenthra_core::SemanticNode::new(id, zenthra_core::Role::Container, zenthra_core::Rect::new(ox, oy, 0.0, 0.0)));
573
574        let parent_draws = std::mem::take(&mut self.ui.draws);
575
576        // Update global offsets for children (for visibility/culling logic)
577        let prev_global_ox = self.ui.offset_x;
578        let prev_global_oy = self.ui.offset_y;
579        let prev_avail_w = self.ui.available_width;
580        
581        let (sx, sy) = if self.scroll_x || self.scroll_y {
582            *self.ui.scroll_state.get(&id).unwrap_or(&(0.0, 0.0))
583        } else {
584            (0.0, 0.0)
585        };
586
587        self.ui.offset_x -= sx;
588        self.ui.offset_y -= sy;
589        self.ui.available_width = avail_w;
590
591        self.ui.base_x = ox + self.padding_left + self.border_width;
592        self.ui.base_y = oy + self.padding_top + self.border_width;
593        self.ui.cursor_x = self.ui.base_x;
594        self.ui.cursor_y = self.ui.base_y;
595
596        // Update viewport for children if clipping is enabled
597        if actual_clip {
598            if let Some((rect, _)) = self.ui.get_recorded_layout(id) {
599                let my_screen_rect = [rect.origin.x + prev_global_ox, rect.origin.y + prev_global_oy, rect.size.width, rect.size.height];
600                let parent_rect = [prev_viewport.origin.x, prev_viewport.origin.y, prev_viewport.size.width, prev_viewport.size.height];
601                let intersected = intersect_rects(my_screen_rect, parent_rect);
602                self.ui.current_viewport = zenthra_core::Rect::new(intersected[0], intersected[1], intersected[2], intersected[3]);
603            }
604        }
605
606        // -- Run Children --
607        if self.is_overlay {
608            let key = zenthra_core::Id::from_u64((id.raw() << 8) | 99);
609            self.ui.interaction_state.insert(key, 1.0);
610            self.ui.active_overlay_stack.push(id);
611        }
612
613        f(self.ui);
614
615        if self.is_overlay {
616            self.ui.active_overlay_stack.pop();
617        }
618
619        // Restore global offsets
620        self.ui.offset_x = prev_global_ox;
621        self.ui.offset_y = prev_global_oy;
622        self.ui.available_width = prev_avail_w;
623
624        // -- Restore Stacks and Environment --
625        self.ui.render_mode_stack.pop();
626        self.ui.semantic_stack.pop();
627        self.ui.current_viewport = prev_viewport;
628        
629        // Capture everything the children created
630        let child_ids_only = std::mem::replace(&mut self.ui.id_log, prev_id_log);
631        let child_id_ranges = std::mem::replace(&mut self.ui.id_ranges, prev_id_ranges);
632        self.children_draws = std::mem::replace(&mut self.ui.draws, parent_draws);
633        self.child_sizes = std::mem::replace(&mut self.ui.child_sizes, prev_child_sizes);
634        self.child_ranges = std::mem::replace(&mut self.ui.child_draw_ranges, prev_child_ranges);
635        self.child_origins = std::mem::replace(&mut self.ui.child_origins, prev_child_origins);
636        
637        let _id_log_end_idx = child_ids_only.len();
638
639        // Restore cursor/base
640        self.ui.direction = prev_dir;
641        self.ui.line_height = prev_line_h;
642        self.ui.base_x = prev_base_x;
643        self.ui.base_y = prev_base_y;
644        self.ui.offset_x = prev_offset_x;
645        self.ui.offset_y = prev_offset_y;
646        self.ui.cursor_x = self.start_x;
647        self.ui.cursor_y = self.start_y;
648        self.ui.max_x = prev_max_x;
649        self.ui.max_y = prev_max_y;
650
651        // -- Layout Engine --
652        let n = self.child_sizes.len();
653        let mut target_positions: Vec<(f32, f32)> = vec![(0.0, 0.0); n];
654        let (content_w, content_h) = match self.wrap {
655            Wrap::NoWrap => self.layout_no_wrap(ox, oy, avail_w, avail_h, &mut target_positions),
656            _ => self.layout_wrap(ox, oy, avail_w, avail_h, &mut target_positions),
657        };
658
659        let mut w = if self.fill_x {
660            self.ui.max_x - ox  // use parent's right boundary, not global window width
661        } else {
662            self.width.unwrap_or(content_w + self.padding_left + self.padding_right + 2.0 * self.border_width)
663        };
664        
665        if let Some(mw) = self.max_width { w = w.min(mw); }
666        if let Some(mw) = self.min_width { w = w.max(mw); }
667
668        let mut h = if self.fill_y {
669            self.ui.max_y - oy  // use parent's bottom boundary, not global window height
670        } else {
671            self.height.unwrap_or(content_h + self.padding_top + self.padding_bottom + 2.0 * self.border_width)
672        };
673
674        if let Some(mh) = self.max_height { h = h.min(mh); }
675        if let Some(mh) = self.min_height { h = h.max(mh); }
676
677        let draw_start = self.ui.draws.len();
678        self.ui.record_layout(id, zenthra_core::Rect::new(ox, oy, w, h));
679
680        let max_sx = (content_w + self.padding_left + self.padding_right - w).max(0.0);
681        let max_sy = (content_h + self.padding_top + self.padding_bottom - h).max(0.0);
682
683        let (actual_ox, actual_oy) = if let Some((rect, _)) = self.ui.get_recorded_layout(id) {
684            (rect.origin.x + prev_global_ox, rect.origin.y + prev_global_oy)
685        } else {
686            (ox + prev_global_ox, oy + prev_global_oy)
687        };
688
689        let mouse_in_parent = if self.ui.skip_clip_stack.last().cloned().unwrap_or(false) {
690            true
691        } else {
692            prev_viewport.contains(zenthra_core::Point::new(self.ui.mouse_x, self.ui.mouse_y))
693        };
694
695        let container_hover = mouse_in_parent && self.ui.mouse_in_rect(actual_ox, actual_oy, w, h) && !self.ui.is_occluded(id, self.ui.mouse_x, self.ui.mouse_y);
696        let container_active = container_hover && self.ui.mouse_down;
697        let mut final_scale = 1.0;
698
699        let (scroll_x, scroll_y) = if self.scroll_x || self.scroll_y {
700            let (mut sx, mut sy) = *self.ui.scroll_state.get(&id).unwrap_or(&(0.0, 0.0));
701            
702            if container_hover {
703                let mut events = std::mem::take(&mut self.ui.input_events);
704                events.retain(|event| {
705                    let mut keep = true;
706                    if let zenthra_platform::event::PlatformEvent::MouseWheel { delta_y, delta_x, .. } = event {
707                        let mut consumed = false;
708                        if self.scroll_y && *delta_y != 0.0 {
709                            let can_up = sy > 0.0 && *delta_y > 0.0;
710                            let can_down = sy < max_sy && *delta_y < 0.0;
711                            if can_up || can_down {
712                                sy -= delta_y * 15.0;
713                                consumed = true;
714                            }
715                        }
716                        if self.scroll_x && *delta_x != 0.0 {
717                            let can_left = sx > 0.0 && *delta_x > 0.0;
718                            let can_right = sx < max_sx && *delta_x < 0.0;
719                            if can_left || can_right {
720                                sx -= delta_x * 15.0;
721                                consumed = true;
722                            }
723                        }
724                        // Fallback: standard vertical wheel scrolls horizontal-only viewports
725                        if self.scroll_x && !self.scroll_y && *delta_y != 0.0 && *delta_x == 0.0 {
726                            let val = *delta_y;
727                            let can_left = sx > 0.0 && val > 0.0;
728                            let can_right = sx < max_sx && val < 0.0;
729                            if can_left || can_right {
730                                sx -= val * 15.0;
731                                consumed = true;
732                            }
733                        }
734                        if consumed {
735                            keep = false;
736                            self.ui.needs_redraw = true;
737                        }
738                    }
739                    keep
740                });
741                self.ui.input_events = events;
742            }
743            
744            sx = sx.clamp(0.0, max_sx);
745            sy = sy.clamp(0.0, max_sy);
746            
747            self.ui.scroll_state.insert(id, (sx, sy));
748            (sx, sy)
749        } else {
750            (0.0, 0.0)
751        };
752
753        let clip = [ox, oy, w, h];
754
755        // Glassmorphism/Custom Shaders: emit draw command before background
756        let filter_draw = if let Some(ref filter) = self.backdrop_filter {
757            let mut blur_radius = 0.0;
758            let mut blur_type = zenthra_core::style::blur::Type::Normal;
759            let mut brightness = 1.0;
760            let mut saturation = 1.0;
761            let mut contrast = 1.0;
762            let mut opacity = 1.0;
763
764            for f in &filter.filters {
765                match f {
766                    zenthra_core::Filter::Blur(r, t) => {
767                        blur_radius = *r;
768                        blur_type = *t;
769                    }
770                    zenthra_core::Filter::Opacity(o) => {
771                        opacity = *o;
772                    }
773                    zenthra_core::Filter::Saturate(s) => {
774                        saturation = *s;
775                    }
776                    zenthra_core::Filter::Brightness(b) => {
777                        brightness = *b;
778                    }
779                    zenthra_core::Filter::Contrast(c) => {
780                        contrast = *c;
781                    }
782                }
783            }
784
785            if blur_radius > 0.0 {
786                Some(crate::ui::BackdropBlurDraw {
787                    x:           ox,
788                    y:           oy,
789                    width:       w,
790                    height:      h,
791                    radius:      [
792                        self.radius[3], // TL
793                        self.radius[2], // TR
794                        self.radius[1], // BR
795                        self.radius[0], // BL
796                    ],
797                    blur_radius,
798                    clip_rect: clip,
799                    brightness,
800                    saturation,
801                    contrast,
802                    opacity,
803                    blur_type,
804                })
805            } else {
806                None
807            }
808        } else if let Some(blur_radius) = self.backdrop_blur {
809            Some(crate::ui::BackdropBlurDraw {
810                x:           ox,
811                y:           oy,
812                width:       w,
813                height:      h,
814                radius:      [
815                    self.radius[3], // TL
816                    self.radius[2], // TR
817                    self.radius[1], // BR
818                    self.radius[0], // BL
819                ],
820                blur_radius,
821                clip_rect: clip,
822                brightness: 1.12,
823                saturation: 1.20,
824                contrast: 1.0,
825                opacity: 1.0,
826                blur_type: zenthra_core::style::blur::Type::Glassmorphism, // fallback
827            })
828        } else {
829            None
830        };
831
832        let push_overlay = self.is_overlay && !self.ui.skip_clip_stack.last().cloned().unwrap_or(false);
833
834        if let Some(shader_id) = self.post_process_shader {
835            let blur_radius = if let Some(ref filter) = self.backdrop_filter {
836                let mut r = 0.0;
837                for f in &filter.filters {
838                    if let zenthra_core::Filter::Blur(radius, _) = f {
839                        r = *radius;
840                    }
841                }
842                Some(r)
843            } else {
844                None
845            }.or(self.backdrop_blur).unwrap_or(0.0);
846            let cp = crate::ui::CustomPostProcessDraw {
847                x:           ox,
848                y:           oy,
849                width:       w,
850                height:      h,
851                radius:      [
852                    self.radius[3], // TL
853                    self.radius[2], // TR
854                    self.radius[1], // BR
855                    self.radius[0], // BL
856                ],
857                blur_radius,
858                shader_id,
859                clip_rect: clip,
860            };
861            if push_overlay {
862                self.ui.overlays.push(DrawCommand::CustomPostProcess(cp));
863            } else {
864                self.ui.draws.push(DrawCommand::CustomPostProcess(cp));
865            }
866        } else if let Some(bd) = filter_draw {
867            if push_overlay {
868                self.ui.overlays.push(DrawCommand::BackdropBlur(bd));
869            } else {
870                self.ui.draws.push(DrawCommand::BackdropBlur(bd));
871            }
872        }
873
874        // Background
875        if let Some(mut bg) = self.bg {
876            let mut bw = self.border_width;
877            let mut bc = self.border_color.unwrap_or(Color::TRANSPARENT);
878
879            if container_active {
880                bg = self.active_bg.or(self.hover_bg).unwrap_or(bg);
881                bw = self.active_border_width.or(self.hover_border_width).unwrap_or(bw);
882                bc = self.active_border_color.or(self.hover_border_color).unwrap_or(bc);
883                final_scale = self.active_scale;
884            } else if container_hover {
885                bg = self.hover_bg.unwrap_or(bg);
886                bw = self.hover_border_width.unwrap_or(bw);
887                bc = self.hover_border_color.unwrap_or(bc);
888                final_scale = self.hover_scale;
889            }
890
891            let visual_w = w * final_scale;
892            let visual_h = h * final_scale;
893            let visual_ox = ox - (visual_w - w) / 2.0;
894            let visual_oy = oy - (visual_h - h) / 2.0;
895
896            if push_overlay {
897                self.ui.overlays.push(DrawCommand::Rect(RectDraw {
898                    instance: RectInstance {
899                        pos: [visual_ox, visual_oy],
900                        size: [visual_w, visual_h],
901                        color: bg.to_array(),
902                        radius: [
903                            self.radius[3], // Bottom-Left -> Top-Left
904                            self.radius[2], // Bottom-Right -> Top-Right
905                            self.radius[1], // Top-Right -> Bottom-Right
906                            self.radius[0], // Top-Left -> Bottom-Left
907                        ],
908                        border_width: bw,
909                        border_color: bc.to_array(),
910                        shadow_color: self.shadow_color.map(|c| {
911                            let mut a = c.to_array();
912                            a[3] *= self.shadow_opacity;
913                            a
914                        }).unwrap_or([0.0, 0.0, 0.0, 0.0]),
915                        shadow_offset: self.shadow_offset,
916                        shadow_blur: self.shadow_blur,
917                        clip_rect: [-100000.0, -100000.0, 2000000.0, 2000000.0],
918                        grayscale: 0.0,
919                        brightness: 1.0,
920                        opacity: self.opacity,
921                        border_alignment: match self.border_alignment {
922                            BorderAlignment::Inside => 0.0,
923                            BorderAlignment::Center => 0.5,
924                            BorderAlignment::Outside => 1.0,
925                        },
926                    }
927                }));
928            } else {
929                self.ui.draws.push(DrawCommand::Rect(RectDraw {
930                    instance: RectInstance {
931                        pos: [visual_ox, visual_oy],
932                        size: [visual_w, visual_h],
933                        color: bg.to_array(),
934                        radius: [
935                            self.radius[3], // Bottom-Left -> Top-Left
936                            self.radius[2], // Bottom-Right -> Top-Right
937                            self.radius[1], // Top-Right -> Bottom-Right
938                            self.radius[0], // Top-Left -> Bottom-Left
939                        ],
940                        border_width: bw,
941                        border_color: bc.to_array(),
942                        shadow_color: self.shadow_color.map(|c| {
943                            let mut a = c.to_array();
944                            a[3] *= self.shadow_opacity;
945                            a
946                        }).unwrap_or([0.0, 0.0, 0.0, 0.0]),
947                        shadow_offset: self.shadow_offset,
948                        shadow_blur: self.shadow_blur,
949                        clip_rect: [-100000.0, -100000.0, 2000000.0, 2000000.0],
950                        grayscale: 0.0,
951                        brightness: 1.0,
952                        opacity: self.opacity,
953                        border_alignment: match self.border_alignment {
954                            BorderAlignment::Inside => 0.0,
955                            BorderAlignment::Center => 0.5,
956                            BorderAlignment::Outside => 1.0,
957                        },
958                    }
959                }));
960            }
961        }
962
963        // Final Translation pass (Always run to keep cache in sync)
964        for (i, (start, end)) in self.child_ranges.iter().enumerate() {
965            if i >= target_positions.len() { break; }
966            let (tx, ty) = target_positions[i];
967            
968            let (origin_x, origin_y) = self.child_origins.get(i).copied().unwrap_or_else(|| {
969                self.children_draws
970                    .get(*start)
971                    .map(|d| draw_origin(d))
972                    .unwrap_or((ox + self.padding_left + self.border_width, oy + self.padding_top + self.border_width))
973            });
974
975            let dx = tx - origin_x;
976            let dy = ty - origin_y;
977            
978            let final_dx = dx - scroll_x;
979            let final_dy = dy - scroll_y;
980
981            // Shift visual commands
982            for draw in &mut self.children_draws[*start..*end] {
983                offset_draw(draw, final_dx, final_dy);
984                if self.scroll_x || self.scroll_y || actual_clip {
985                    set_clip(draw, clip);
986                }
987            }
988            
989            // Shift IDs RECURSIVELY (Layout shift only, scrolling is handled live)
990            if let Some(&(ids_start, ids_end)) = child_id_ranges.get(i) {
991                for j in ids_start..ids_end {
992                    let cid = child_ids_only[j];
993                    if let Some((rect, _)) = self.ui.next_layout_cache.get_mut(&cid) {
994                        rect.origin.x += dx;
995                        rect.origin.y += dy;
996                    }
997                }
998            }
999        }
1000
1001
1002        // Flush children draws to parent
1003        let push_overlay = self.is_overlay && !self.ui.skip_clip_stack.last().cloned().unwrap_or(false);
1004        for draw in self.children_draws.drain(..) {
1005            if push_overlay {
1006                self.ui.overlays.push(draw);
1007            } else {
1008                self.ui.draws.push(draw);
1009            }
1010        }
1011        
1012        // --- Visual Scrollbars & Dragging ---
1013        if self.scroll_x || self.scroll_y {
1014            let bar_thickness = 4.0;
1015            let bar_margin = 0.0;
1016
1017            // Vertical Scrollbar
1018            if self.scroll_y && max_sy > 0.0 {
1019                let thumb_h = (h / (content_h + self.padding_top + self.padding_bottom)) * h;
1020                let thumb_h = thumb_h.max(20.0);
1021                let scroll_ratio = scroll_y / max_sy;
1022                let is_hover = self.ui.mouse_in_rect(actual_ox + w - bar_thickness - bar_margin - 2.0, actual_oy + (h - thumb_h) * scroll_ratio, bar_thickness + 4.0, thumb_h) && !self.ui.is_occluded(id, self.ui.mouse_x, self.ui.mouse_y);
1023                let is_dragging = self.ui.active_drag.as_ref().map(|d| d.id == id && d.start_mouse <= -1000.0).unwrap_or(false); 
1024
1025                // Handle Drag Y
1026                if self.ui.clicked && is_hover {
1027                    self.ui.active_drag = Some(crate::ui::ScrollDrag {
1028                        id,
1029                        start_mouse: -1000.0 - self.ui.mouse_y, 
1030                        start_scroll: scroll_y,
1031                    });
1032                }
1033                
1034                if let Some(drag) = &self.ui.active_drag {
1035                    if drag.id == id && drag.start_mouse <= -1000.0 {
1036                        let current_marker = -1000.0 - self.ui.mouse_y;
1037                        let delta = drag.start_mouse - current_marker;
1038                        let scroll_range = h - thumb_h;
1039                        if scroll_range > 0.0 {
1040                            let new_sy = (drag.start_scroll + (delta / scroll_range) * max_sy).clamp(0.0, max_sy);
1041                            self.ui.scroll_state.insert(id, (scroll_x, new_sy));
1042                            self.ui.needs_redraw = true;
1043                        }
1044                    }
1045                }
1046
1047                {
1048                    let color = if is_hover || is_dragging { 
1049                        Color::rgba(1.0, 1.0, 1.0, 0.5) 
1050                    } else if container_hover {
1051                        Color::rgba(1.0, 1.0, 1.0, 0.2)
1052                    } else {
1053                        Color::rgba(1.0, 1.0, 1.0, 0.1)
1054                    };
1055                    
1056                    // Use layout coordinates (ox, oy) for drawing so they are shifted by parents correctly.
1057                    // But interaction uses actual_ox/actual_oy (screen space).
1058                    let visual_thumb_x = ox + w - bar_thickness - bar_margin - self.border_width;
1059                    let visual_thumb_y = oy + (h - thumb_h) * scroll_ratio;
1060
1061                    let draw = crate::ui::DrawCommand::OverlayRect(crate::ui::OverlayRectDraw {
1062                        x: visual_thumb_x,
1063                        y: visual_thumb_y,
1064                        width: bar_thickness,
1065                        height: thumb_h,
1066                        color,
1067                        clip: [ox, oy, w, h], 
1068                    });
1069                    if push_overlay { self.ui.overlays.push(draw); } else { self.ui.draws.push(draw); }
1070                }
1071            }
1072
1073            // Horizontal Scrollbar
1074            if self.scroll_x && max_sx > 0.0 {
1075                let thumb_w = (w / (content_w + self.padding_left + self.padding_right)) * w;
1076                let thumb_w = thumb_w.max(20.0);
1077                let scroll_ratio = scroll_x / max_sx;
1078                let is_hover = self.ui.mouse_in_rect(actual_ox + (w - thumb_w) * scroll_ratio, actual_oy + h - bar_thickness - bar_margin - 2.0, thumb_w, bar_thickness + 4.0) && !self.ui.is_occluded(id, self.ui.mouse_x, self.ui.mouse_y);
1079                let is_dragging = self.ui.active_drag.as_ref().map(|d| d.id == id && d.start_mouse > -1000.0).unwrap_or(false);
1080
1081                if self.ui.clicked && is_hover {
1082                    self.ui.active_drag = Some(crate::ui::ScrollDrag {
1083                        id,
1084                        start_mouse: self.ui.mouse_x,
1085                        start_scroll: scroll_x,
1086                    });
1087                }
1088
1089                if let Some(drag) = &self.ui.active_drag {
1090                    if drag.id == id && drag.start_mouse > -1000.0 {
1091                        let delta = self.ui.mouse_x - drag.start_mouse;
1092                        let scroll_range = w - thumb_w;
1093                        if scroll_range > 0.0 {
1094                            let new_sx = (drag.start_scroll + (delta / scroll_range) * max_sx).clamp(0.0, max_sx);
1095                            let (_, current_sy) = *self.ui.scroll_state.get(&id).unwrap_or(&(0.0, 0.0));
1096                            self.ui.scroll_state.insert(id, (new_sx, current_sy));
1097                            self.ui.needs_redraw = true;
1098                        }
1099                    }
1100                }
1101
1102                {
1103                    let color = if is_hover || is_dragging { 
1104                        Color::rgba(1.0, 1.0, 1.0, 0.5) 
1105                    } else if container_hover {
1106                        Color::rgba(1.0, 1.0, 1.0, 0.2)
1107                    } else {
1108                        Color::rgba(1.0, 1.0, 1.0, 0.1)
1109                    };
1110                    
1111                    let visual_thumb_x = ox + (w - thumb_w) * scroll_ratio;
1112                    let visual_thumb_y = oy + h - bar_thickness - bar_margin - self.border_width;
1113
1114                    let draw = crate::ui::DrawCommand::OverlayRect(crate::ui::OverlayRectDraw {
1115                        x: visual_thumb_x,
1116                        y: visual_thumb_y,
1117                        width: thumb_w,
1118                        height: bar_thickness,
1119                        color,
1120                        clip: [ox, oy, w, h],
1121                    });
1122                    if push_overlay { self.ui.overlays.push(draw); } else { self.ui.draws.push(draw); }
1123                }
1124            }
1125        }
1126
1127        // Bubble IDs up to parent's scope
1128        self.ui.id_log.extend(child_ids_only);
1129        
1130        if !self.is_absolute {
1131            self.ui.advance(w, h, draw_start);
1132        }
1133
1134        // Dispatch events
1135        self.ui.dispatch_event(id, crate::ui::WidgetEvent::Hover(container_hover));
1136
1137        if self.ui.clicked && container_hover {
1138            self.ui.dispatch_event(id, crate::ui::WidgetEvent::Click);
1139        }
1140
1141        let events = std::mem::take(&mut self.ui.input_events);
1142        for event in &events {
1143            if let zenthra_platform::event::PlatformEvent::MouseWheel { delta_x, delta_y } = event {
1144                if container_hover {
1145                    self.ui.dispatch_event(id, crate::ui::WidgetEvent::Scroll(*delta_x, *delta_y));
1146                }
1147            }
1148        }
1149        self.ui.input_events = events;
1150
1151        zenthra_core::Response {
1152            clicked: self.ui.clicked && container_hover,
1153            hovered: container_hover,
1154            pressed: container_active,
1155        }
1156    }
1157
1158    fn layout_no_wrap(
1159        &self,
1160        ox: f32,
1161        oy: f32,
1162        inner_w: f32,
1163        inner_h: f32,
1164        targets: &mut [(f32, f32)],
1165    ) -> (f32, f32) {
1166        let n = self.child_sizes.len();
1167        if n == 0 { return (0.0, 0.0); }
1168
1169        let (content_w, content_h) = match self.direction {
1170            Direction::Row => {
1171                let w = self.child_sizes.iter().map(|(w, _)| w).sum::<f32>()
1172                    + self.gap * (n.saturating_sub(1)) as f32;
1173                let h = self.child_sizes.iter().map(|(_, h)| *h).fold(0.0f32, f32::max);
1174                (w, h)
1175            }
1176            Direction::Column => {
1177                let w = self.child_sizes.iter().map(|(w, _)| *w).fold(0.0f32, f32::max);
1178                let h = self.child_sizes.iter().map(|(_, h)| h).sum::<f32>()
1179                    + self.gap * (n.saturating_sub(1)) as f32;
1180                (w, h)
1181            }
1182            Direction::Stack => {
1183                let w = self.child_sizes.iter().map(|(w, _)| *w).fold(0.0f32, f32::max);
1184                let h = self.child_sizes.iter().map(|(_, h)| *h).fold(0.0f32, f32::max);
1185                (w, h)
1186            }
1187        };
1188
1189        let real_w = if self.width.is_some() || self.fill_x { inner_w } else { content_w };
1190        let real_h = if self.height.is_some() || self.fill_y { inner_h } else { content_h };
1191
1192        match self.target_halign_valign(ox, oy, real_w, real_h, content_w, content_h, targets) {
1193            (w, h) => (w, h),
1194        }
1195    }
1196    
1197    fn target_halign_valign(&self, ox: f32, oy: f32, real_w: f32, real_h: f32, content_w: f32, content_h: f32, targets: &mut [(f32, f32)]) -> (f32, f32) {
1198        let n = self.child_sizes.len();
1199        match self.direction {
1200            Direction::Row => {
1201                let extra = (real_w - content_w).max(0.0);
1202                let mut cx = ox + self.padding_left + self.border_width + match self.halign {
1203                    Align::Left => 0.0,
1204                    Align::Center => extra / 2.0,
1205                    Align::Right => extra,
1206                    Align::SpaceBetween => 0.0,
1207                    Align::SpaceAround => extra / (n as f32 * 2.0),
1208                    _ => 0.0,
1209                };
1210                let gap = match self.halign {
1211                    Align::SpaceBetween if n > 1 => extra / (n - 1) as f32,
1212                    Align::SpaceAround => extra / n as f32,
1213                    _ => self.gap,
1214                };
1215                for (i, (cw, ch)) in self.child_sizes.iter().enumerate() {
1216                    let cy = oy + self.padding_top + self.border_width + match self.valign {
1217                        Align::Top => 0.0,
1218                        Align::Center => (real_h - ch) / 2.0,
1219                        Align::Bottom => real_h - ch,
1220                        _ => 0.0,
1221                    };
1222                    targets[i] = (cx, cy);
1223                    cx += cw + gap;
1224                }
1225            }
1226            Direction::Column => {
1227                let extra = (real_h - content_h).max(0.0);
1228                let mut cy = oy + self.padding_top + self.border_width + match self.valign {
1229                    Align::Top => 0.0,
1230                    Align::Center => extra / 2.0,
1231                    Align::Bottom => extra,
1232                    Align::SpaceBetween => 0.0,
1233                    Align::SpaceAround => extra / (n as f32 * 2.0),
1234                    _ => 0.0,
1235                };
1236                let gap = match self.valign {
1237                    Align::SpaceBetween if n > 1 => extra / (n - 1) as f32,
1238                    Align::SpaceAround => extra / n as f32,
1239                    _ => self.gap,
1240                };
1241                for (i, (cw, ch)) in self.child_sizes.iter().enumerate() {
1242                    let cx = ox + self.padding_left + self.border_width + match self.halign {
1243                        Align::Left => 0.0,
1244                        Align::Center => (real_w - cw) / 2.0,
1245                        Align::Right => real_w - cw,
1246                        _ => 0.0,
1247                    };
1248                    targets[i] = (cx, cy);
1249                    cy += ch + gap;
1250                }
1251            }
1252            Direction::Stack => {
1253                for (i, (cw, ch)) in self.child_sizes.iter().enumerate() {
1254                    let cx = ox + self.padding_left + self.border_width + match self.halign {
1255                        Align::Center => (real_w - cw).max(0.0) / 2.0,
1256                        Align::Right => real_w - cw,
1257                        _ => 0.0,
1258                    };
1259                    let cy = oy + self.padding_top + self.border_width + match self.valign {
1260                        Align::Center => (real_h - ch).max(0.0) / 2.0,
1261                        Align::Bottom => real_h - ch,
1262                        _ => 0.0,
1263                    };
1264                    targets[i] = (cx, cy);
1265                }
1266            }
1267        }
1268        (content_w, content_h)
1269    }
1270
1271    fn layout_wrap(
1272        &self,
1273        ox: f32,
1274        oy: f32,
1275        inner_w: f32,
1276        inner_h: f32,
1277        targets: &mut [(f32, f32)],
1278    ) -> (f32, f32) {
1279        let n = self.child_sizes.len();
1280        if n == 0 { return (0.0, 0.0); }
1281
1282        let (main_reversed, cross_reversed) = match self.wrap {
1283            Wrap::Wrap => (false, false),
1284            Wrap::WrapReverse => (false, true),
1285            Wrap::RightToLeft => (true, false),
1286            Wrap::RightToLeftReverse => (true, true),
1287            _ => (false, false),
1288        };
1289
1290        match self.direction {
1291            Direction::Row => {
1292                let mut rows: Vec<Vec<usize>> = Vec::new();
1293                let mut current_row = Vec::new();
1294                let mut row_w = 0.0f32;
1295
1296                for (i, (cw, _)) in self.child_sizes.iter().enumerate() {
1297                    let needed = if current_row.is_empty() { *cw } else { row_w + self.gap + cw };
1298                    if needed > inner_w && !current_row.is_empty() {
1299                        rows.push(std::mem::take(&mut current_row));
1300                        current_row.push(i);
1301                        row_w = *cw;
1302                    } else {
1303                        current_row.push(i);
1304                        row_w = needed;
1305                    }
1306                }
1307                if !current_row.is_empty() { rows.push(current_row); }
1308
1309                let row_heights: Vec<f32> = rows.iter().map(|row| {
1310                    row.iter().map(|&i| self.child_sizes[i].1).fold(0.0f32, f32::max)
1311                }).collect();
1312
1313                let total_h = row_heights.iter().sum::<f32>() + self.gap * (rows.len().saturating_sub(1)) as f32;
1314                let max_row_w = rows.iter().map(|row| {
1315                    row.iter().map(|&i| self.child_sizes[i].0).sum::<f32>() + self.gap * (row.len().saturating_sub(1)) as f32
1316                }).fold(0.0f32, f32::max);
1317
1318                let real_h = if self.height.is_some() || self.fill_y { inner_h } else { total_h };
1319                let real_w = if self.width.is_some() || self.fill_x { inner_w } else { max_row_w };
1320
1321                let mut cy = if cross_reversed {
1322                    oy + self.padding_top + self.border_width + match self.valign {
1323                        Align::Top => total_h,
1324                        Align::Center => (real_h + total_h) / 2.0,
1325                        Align::Bottom => real_h,
1326                        _ => total_h,
1327                    }
1328                } else {
1329                    oy + self.padding_top + self.border_width + match self.valign {
1330                        Align::Center => (real_h - total_h).max(0.0) / 2.0,
1331                        Align::Bottom => (real_h - total_h).max(0.0),
1332                        _ => 0.0,
1333                    }
1334                };
1335
1336                for (ri, row) in rows.iter().enumerate() {
1337                    let row_h = row_heights[ri];
1338                    let row_content_w = row.iter().map(|&i| self.child_sizes[i].0).sum::<f32>() + self.gap * (row.len().saturating_sub(1)) as f32;
1339                    let extra = (real_w - row_content_w).max(0.0);
1340                    if cross_reversed { cy -= row_h; }
1341
1342                    let mut cx = if main_reversed {
1343                        ox + self.padding_left + self.border_width + match self.halign {
1344                            Align::Left => row_content_w,
1345                            Align::Center => (real_w + row_content_w) / 2.0,
1346                            Align::Right => real_w,
1347                            _ => row_content_w,
1348                        }
1349                    } else {
1350                        ox + self.padding_left + self.border_width + match self.halign {
1351                            Align::Left => 0.0,
1352                            Align::Center => extra / 2.0,
1353                            Align::Right => extra,
1354                            Align::SpaceBetween => 0.0,
1355                            Align::SpaceAround => extra / (row.len() as f32 * 2.0),
1356                            _ => 0.0,
1357                        }
1358                    };
1359
1360                    let gap = match self.halign {
1361                        Align::SpaceBetween if row.len() > 1 && !main_reversed => extra / (row.len() - 1) as f32,
1362                        Align::SpaceAround if !main_reversed => extra / row.len() as f32,
1363                        _ => self.gap,
1364                    };
1365
1366                    for &ci in row {
1367                        let (cw, ch) = self.child_sizes[ci];
1368                        if main_reversed { cx -= cw; }
1369                        let child_y = cy + match self.valign {
1370                            Align::Center => (row_h - ch) / 2.0,
1371                            Align::Bottom => row_h - ch,
1372                            _ => 0.0,
1373                        };
1374                        targets[ci] = (cx, child_y);
1375                        if main_reversed { cx -= gap; } else { cx += cw + gap; }
1376                    }
1377                    if cross_reversed { cy -= self.gap; } else { cy += row_h + self.gap; }
1378                }
1379                (max_row_w, total_h)
1380            }
1381            Direction::Column => {
1382                let mut cols: Vec<Vec<usize>> = Vec::new();
1383                let mut current_col = Vec::new();
1384                let mut col_h = 0.0f32;
1385
1386                for (i, (_, ch)) in self.child_sizes.iter().enumerate() {
1387                    let needed = if current_col.is_empty() { *ch } else { col_h + self.gap + ch };
1388                    if needed > inner_h && !current_col.is_empty() {
1389                        cols.push(std::mem::take(&mut current_col));
1390                        current_col.push(i);
1391                        col_h = *ch;
1392                    } else {
1393                        current_col.push(i);
1394                        col_h = needed;
1395                    }
1396                }
1397                if !current_col.is_empty() { cols.push(current_col); }
1398
1399                let col_widths: Vec<f32> = cols.iter().map(|col| {
1400                    col.iter().map(|&i| self.child_sizes[i].0).fold(0.0f32, f32::max)
1401                }).collect();
1402
1403                let total_w = col_widths.iter().sum::<f32>() + self.gap * (cols.len().saturating_sub(1)) as f32;
1404                let max_col_h = cols.iter().map(|col| {
1405                    col.iter().map(|&i| self.child_sizes[i].1).sum::<f32>() + self.gap * (col.len().saturating_sub(1)) as f32
1406                }).fold(0.0f32, f32::max);
1407
1408                let real_w = if self.width.is_some() || self.fill_x { inner_w } else { total_w };
1409                let real_h = if self.height.is_some() || self.fill_y { inner_h } else { max_col_h };
1410
1411                let mut cx = if cross_reversed {
1412                    ox + self.padding_left + self.border_width + match self.halign {
1413                        Align::Left => total_w,
1414                        Align::Center => (real_w + total_w) / 2.0,
1415                        Align::Right => real_w,
1416                        _ => total_w,
1417                    }
1418                } else {
1419                    ox + self.padding_left + self.border_width + match self.halign {
1420                        Align::Center => (real_w - total_w).max(0.0) / 2.0,
1421                        Align::Right => (real_w - total_w).max(0.0),
1422                        _ => 0.0,
1423                    }
1424                };
1425
1426                for (ci_idx, col) in cols.iter().enumerate() {
1427                    let col_w = col_widths[ci_idx];
1428                    let col_content_h = col.iter().map(|&i| self.child_sizes[i].1).sum::<f32>() + self.gap * (col.len().saturating_sub(1)) as f32;
1429                    let extra = (real_h - col_content_h).max(0.0);
1430                    if cross_reversed { cx -= col_w; }
1431
1432                    let mut cy = if main_reversed {
1433                        oy + self.padding_top + self.border_width + match self.valign {
1434                            Align::Top => col_content_h,
1435                            Align::Center => (real_h + col_content_h) / 2.0,
1436                            Align::Bottom => real_h,
1437                            _ => col_content_h,
1438                        }
1439                    } else {
1440                        oy + self.padding_top + self.border_width + match self.valign {
1441                            Align::Top => 0.0,
1442                            Align::Center => extra / 2.0,
1443                            Align::Bottom => extra,
1444                            Align::SpaceBetween => 0.0,
1445                            Align::SpaceAround => extra / (col.len() as f32 * 2.0),
1446                            _ => 0.0,
1447                        }
1448                    };
1449
1450                    let gap = match self.valign {
1451                        Align::SpaceBetween if col.len() > 1 && !main_reversed => extra / (col.len() - 1) as f32,
1452                        Align::SpaceAround if !main_reversed => extra / col.len() as f32,
1453                        _ => self.gap,
1454                    };
1455
1456                    for &ci in col {
1457                        let (cw, ch) = self.child_sizes[ci];
1458                        if main_reversed { cy -= ch; }
1459                    let child_x = cx + match self.halign {
1460                        Align::Center => (col_w - cw) / 2.0,
1461                        Align::Right => col_w - cw,
1462                        _ => 0.0,
1463                    };
1464                        targets[ci] = (child_x, cy);
1465                        if main_reversed { cy -= gap; } else { cy += ch + gap; }
1466                    }
1467                    if cross_reversed { cx -= self.gap; } else { cx += col_w + self.gap; }
1468                }
1469                (total_w, max_col_h)
1470            }
1471            Direction::Stack => {
1472                let w = self.child_sizes.iter().map(|(w, _)| *w).fold(0.0f32, f32::max);
1473                let h = self.child_sizes.iter().map(|(_, h)| *h).fold(0.0f32, f32::max);
1474                for (i, (cw, ch)) in self.child_sizes.iter().enumerate() {
1475                    let cx = ox + self.padding_left + self.border_width + match self.halign {
1476                        Align::Center => (inner_w - cw).max(0.0) / 2.0,
1477                        Align::Right => inner_w - cw,
1478                        _ => 0.0,
1479                    };
1480                    let cy = oy + self.padding_top + self.border_width + match self.valign {
1481                        Align::Center => (inner_h - ch).max(0.0) / 2.0,
1482                        Align::Bottom => inner_h - ch,
1483                        _ => 0.0,
1484                    };
1485                    targets[i] = (cx, cy);
1486                }
1487                (w, h)
1488            }
1489        }
1490    }
1491}
1492
1493/// Offset a draw command's position and its clipping rect by (dx, dy).
1494fn offset_draw(cmd: &mut DrawCommand, dx: f32, dy: f32) {
1495    match cmd {
1496        DrawCommand::Rect(r) => {
1497            r.instance.pos[0] += dx;
1498            r.instance.pos[1] += dy;
1499            // Translate the widget's internal clip rect so it stays with the widget
1500            r.instance.clip_rect[0] += dx;
1501            r.instance.clip_rect[1] += dy;
1502        }
1503        DrawCommand::Text(t) => {
1504            t.pos[0] += dx;
1505            t.pos[1] += dy;
1506            t.clip[0] += dx;
1507            t.clip[1] += dy;
1508        }
1509        DrawCommand::OverlayRect(c) => {
1510            c.x += dx;
1511            c.y += dy;
1512            c.clip[0] += dx;
1513            c.clip[1] += dy;
1514        }
1515        DrawCommand::Image(i) => {
1516            i.instance.pos[0] += dx;
1517            i.instance.pos[1] += dy;
1518            i.instance.clip_rect[0] += dx;
1519            i.instance.clip_rect[1] += dy;
1520        }
1521        DrawCommand::BackdropBlur(b) => {
1522            b.x += dx;
1523            b.y += dy;
1524            b.clip_rect[0] += dx;
1525            b.clip_rect[1] += dy;
1526        }
1527        DrawCommand::CustomPostProcess(b) => {
1528            b.x += dx;
1529            b.y += dy;
1530            b.clip_rect[0] += dx;
1531            b.clip_rect[1] += dy;
1532        }
1533    }
1534}
1535
1536fn draw_origin(cmd: &DrawCommand) -> (f32, f32) {
1537    match cmd {
1538        DrawCommand::Rect(r) => (r.instance.pos[0], r.instance.pos[1]),
1539        DrawCommand::Text(t) => (t.pos[0], t.pos[1]),
1540        DrawCommand::OverlayRect(c) => (c.x, c.y),
1541        DrawCommand::Image(i) => (i.instance.pos[0], i.instance.pos[1]),
1542        DrawCommand::BackdropBlur(b) => (b.x, b.y),
1543        DrawCommand::CustomPostProcess(b) => (b.x, b.y),
1544    }
1545}
1546
1547fn set_clip(cmd: &mut DrawCommand, clip: [f32; 4]) {
1548    match cmd {
1549        DrawCommand::Rect(r) => r.instance.clip_rect = intersect_rects(r.instance.clip_rect, clip),
1550        DrawCommand::Text(t) => t.clip = intersect_rects(t.clip, clip),
1551        DrawCommand::OverlayRect(c) => c.clip = intersect_rects(c.clip, clip),
1552        DrawCommand::Image(i) => i.instance.clip_rect = intersect_rects(i.instance.clip_rect, clip),
1553        DrawCommand::BackdropBlur(b) => b.clip_rect = intersect_rects(b.clip_rect, clip),
1554        DrawCommand::CustomPostProcess(b) => b.clip_rect = intersect_rects(b.clip_rect, clip),
1555    }
1556}
1557
1558fn intersect_rects(a: [f32; 4], b: [f32; 4]) -> [f32; 4] {
1559    let x1 = a[0].max(b[0]);
1560    let y1 = a[1].max(b[1]);
1561    let x2 = (a[0] + a[2]).min(b[0] + b[2]);
1562    let y2 = (a[1] + a[3]).min(b[1] + b[3]);
1563    
1564    let w = (x2 - x1).max(0.0);
1565    let h = (y2 - y1).max(0.0);
1566    
1567    [x1, y1, w, h]
1568}