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 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 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 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 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 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 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 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 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 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 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 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 self.ui.render_mode_stack.pop();
626 self.ui.semantic_stack.pop();
627 self.ui.current_viewport = prev_viewport;
628
629 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 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 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 } 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 } 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 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 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], self.radius[2], self.radius[1], self.radius[0], ],
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], self.radius[2], self.radius[1], self.radius[0], ],
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, })
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], self.radius[2], self.radius[1], self.radius[0], ],
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 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], self.radius[2], self.radius[1], self.radius[0], ],
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], self.radius[2], self.radius[1], self.radius[0], ],
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 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 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 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 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 if self.scroll_x || self.scroll_y {
1014 let bar_thickness = 4.0;
1015 let bar_margin = 0.0;
1016
1017 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 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 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 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 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 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
1493fn 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 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}