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: 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}
74
75impl<'u, 'a> ContainerBuilder<'u, 'a> {
76 pub fn new(ui: &'u mut Ui<'a>) -> Self {
77 let id = ui.id();
78 Self {
79 ui,
80 direction: Direction::Column,
81 halign: Align::Left,
82 valign: Align::Top,
83 wrap: Wrap::NoWrap,
84 children_draws: Vec::new(),
85 child_sizes: Vec::new(),
86 child_ranges: Vec::new(),
87 child_origins: Vec::new(),
88 start_x: 0.0,
89 start_y: 0.0,
90 pos_x: None,
91 pos_y: None,
92 width: None,
93 height: None,
94 fill_x: false,
95 fill_y: false,
96 padding_top: 0.0,
97 padding_bottom: 0.0,
98 padding_left: 0.0,
99 padding_right: 0.0,
100 gap: 0.0,
101 bg: None,
102 border_color: None,
103 border_width: 0.0,
104 shadow_blur: 0.0,
105 shadow_color: None,
106 shadow_offset: [0.0, 0.0],
107 shadow_opacity: 1.0,
108 opacity: 1.0,
109 render_mode: None,
110 max_width: None,
111 max_height: None,
112 min_width: None,
113 min_height: None,
114 scroll_x: false,
115 scroll_y: false,
116 clip: false,
117 id,
118 radius: [0.0; 4],
119 border_alignment: BorderAlignment::Inside,
120 is_absolute: false,
121 is_overlay: false,
122 hover_bg: None,
123 hover_border_color: None,
124 hover_border_width: None,
125 hover_scale: 1.0,
126 active_bg: None,
127 active_border_color: None,
128 active_border_width: None,
129 active_scale: 1.0,
130 }
131 }
132
133 pub fn absolute(mut self, x: f32, y: f32) -> Self {
134 self.pos_x = Some(x);
135 self.pos_y = Some(y);
136 self.is_absolute = true;
137 self
138 }
139 pub fn pos(mut self, x: f32, y: f32) -> Self {
140 self.pos_x = Some(x);
141 self.pos_y = Some(y);
142 self
143 }
144 pub fn width(mut self, w: f32) -> Self {
145 self.width = Some(w);
146 self
147 }
148 pub fn height(mut self, h: f32) -> Self {
149 self.height = Some(h);
150 self
151 }
152 pub fn fill_x(mut self) -> Self {
153 self.fill_x = true;
154 self
155 }
156 pub fn fill_y(mut self) -> Self {
157 self.fill_y = true;
158 self
159 }
160 pub fn fill(mut self) -> Self {
161 self.fill_x = true;
162 self.fill_y = true;
163 self
164 }
165 pub fn full_width(mut self) -> Self {
166 self.fill_x = true;
167 self
168 }
169 pub fn full_height(mut self) -> Self {
170 self.fill_y = true;
171 self
172 }
173 pub fn max_width(mut self, w: f32) -> Self {
174 self.max_width = Some(w);
175 self
176 }
177 pub fn max_height(mut self, h: f32) -> Self {
178 self.max_height = Some(h);
179 self
180 }
181 pub fn min_width(mut self, w: f32) -> Self {
182 self.min_width = Some(w);
183 self
184 }
185 pub fn min_height(mut self, h: f32) -> Self {
186 self.min_height = Some(h);
187 self
188 }
189 pub fn scrollable(mut self, x: bool, y: bool) -> Self {
190 self.scroll_x = x;
191 self.scroll_y = y;
192 self
193 }
194 pub fn scroll_x(mut self, e: bool) -> Self {
195 self.scroll_x = e;
196 if e { self.clip = true; }
197 self
198 }
199 pub fn scroll_y(mut self, e: bool) -> Self {
200 self.scroll_y = e;
201 if e { self.clip = true; }
202 self
203 }
204 pub fn clip(mut self, enabled: bool) -> Self {
206 self.clip = enabled;
207 self
208 }
209 pub fn padding(mut self, t: f32, r: f32, b: f32, l: f32) -> Self {
210 self.padding_top = t;
211 self.padding_bottom = b;
212 self.padding_left = l;
213 self.padding_right = r;
214 self
215 }
216 pub fn padding_all(mut self, p: f32) -> Self {
217 self.padding_top = p;
218 self.padding_bottom = p;
219 self.padding_left = p;
220 self.padding_right = p;
221 self
222 }
223 pub fn padding_x(mut self, p: f32) -> Self {
224 self.padding_left = p;
225 self.padding_right = p;
226 self
227 }
228 pub fn padding_y(mut self, p: f32) -> Self {
229 self.padding_top = p;
230 self.padding_bottom = p;
231 self
232 }
233 pub fn padding_top(mut self, p: f32) -> Self {
234 self.padding_top = p;
235 self
236 }
237 pub fn padding_bottom(mut self, p: f32) -> Self {
238 self.padding_bottom = p;
239 self
240 }
241 pub fn padding_left(mut self, p: f32) -> Self {
242 self.padding_left = p;
243 self
244 }
245 pub fn padding_right(mut self, p: f32) -> Self {
246 self.padding_right = p;
247 self
248 }
249
250
251 pub fn row(mut self) -> Self {
252 self.direction = Direction::Row;
253 self
254 }
255
256 pub fn column(mut self) -> Self {
257 self.direction = Direction::Column;
258 self
259 }
260
261 pub fn wrap(mut self, strategy: Wrap) -> Self {
262 self.wrap = strategy;
263 self
264 }
265
266 pub fn no_wrap(mut self) -> Self {
267 self.wrap = Wrap::NoWrap;
268 self
269 }
270
271 pub fn align(mut self, align: Align) -> Self {
272 self.halign = align;
273 self.valign = align;
274 self
275 }
276
277 pub fn radius(mut self, tl: f32, tr: f32, br: f32, bl: f32) -> Self {
278 self.radius = [tl, tr, br, bl];
279 self
280 }
281
282 pub fn radius_all(mut self, r: f32) -> Self {
283 self.radius = [r, r, r, r];
284 self
285 }
286
287 pub fn halign(mut self, align: Align) -> Self {
288 self.halign = align;
289 self
290 }
291
292 pub fn valign(mut self, align: Align) -> Self {
293 self.valign = align;
294 self
295 }
296
297 pub fn radius_top_left(mut self, r: f32) -> Self {
298 self.radius[0] = r;
299 self
300 }
301 pub fn radius_top_right(mut self, r: f32) -> Self {
302 self.radius[1] = r;
303 self
304 }
305 pub fn radius_bottom_right(mut self, r: f32) -> Self {
306 self.radius[2] = r;
307 self
308 }
309 pub fn radius_bottom_left(mut self, r: f32) -> Self {
310 self.radius[3] = r;
311 self
312 }
313
314 pub fn radius_top(mut self, r: f32) -> Self {
315 self.radius[0] = r;
316 self.radius[1] = r;
317 self
318 }
319 pub fn radius_bottom(mut self, r: f32) -> Self {
320 self.radius[2] = r;
321 self.radius[3] = r;
322 self
323 }
324 pub fn radius_left(mut self, r: f32) -> Self {
325 self.radius[0] = r;
326 self.radius[3] = r;
327 self
328 }
329 pub fn radius_right(mut self, r: f32) -> Self {
330 self.radius[1] = r;
331 self.radius[2] = r;
332 self
333 }
334 pub fn radius_x(mut self, r: f32) -> Self {
335 self.radius = [r, r, r, r];
336 self
337 }
338 pub fn radius_y(mut self, r: f32) -> Self {
339 self.radius = [r, r, r, r];
340 self
341 }
342
343 pub fn active_bg(mut self, color: Color) -> Self {
344 self.active_bg = Some(color);
345 self
346 }
347
348 pub fn active_border(mut self, color: Color, width: f32) -> Self {
349 self.active_border_color = Some(color);
350 self.active_border_width = Some(width);
351 self
352 }
353
354 pub fn active_scale(mut self, scale: f32) -> Self {
355 self.active_scale = scale;
356 self
357 }
358
359 pub fn gap(mut self, g: f32) -> Self {
360 self.gap = g;
361 self
362 }
363 pub fn bg(mut self, c: Color) -> Self {
364 self.bg = Some(c);
365 self
366 }
367 pub fn border(mut self, c: Color, w: f32) -> Self {
368 self.border_color = Some(c);
369 self.border_width = w;
370 self
371 }
372 pub fn shadow(mut self, color: Color, x: f32, y: f32, blur: f32) -> Self {
373 self.shadow_color = Some(color);
374 self.shadow_offset = [x, y];
375 self.shadow_blur = blur;
376 self
377 }
378
379 pub fn shadow_opacity(mut self, opacity: f32) -> Self {
380 self.shadow_opacity = opacity;
381 self
382 }
383 pub fn opacity(mut self, o: f32) -> Self {
384 self.opacity = o;
385 self
386 }
387
388 pub fn id(mut self, id: impl std::hash::Hash) -> Self {
389 let mut hasher = std::collections::hash_map::DefaultHasher::new();
390 use std::hash::{Hash, Hasher};
391 id.hash(&mut hasher);
392 if let Some(parent) = self.ui.semantic_stack.last() {
393 parent.hash(&mut hasher);
394 }
395 self.id = zenthra_core::Id::from_u64(hasher.finish());
396 self
397 }
398
399 pub fn hover_bg(mut self, color: Color) -> Self {
400 self.hover_bg = Some(color);
401 self
402 }
403
404 pub fn hover_border(mut self, color: Color, width: f32) -> Self {
405 self.hover_border_color = Some(color);
406 self.hover_border_width = Some(width);
407 self
408 }
409
410 pub fn hover_scale(mut self, scale: f32) -> Self {
411 self.hover_scale = scale;
412 self
413 }
414
415 pub fn border_alignment(mut self, alignment: BorderAlignment) -> Self {
416 self.border_alignment = alignment;
417 self
418 }
419
420 pub fn render_mode(mut self, mode: zenthra_core::RenderMode) -> Self {
421 self.render_mode = Some(mode);
422 self
423 }
424
425 pub fn overlay(mut self) -> Self {
426 self.is_overlay = true;
427 self
428 }
429
430 pub fn on_click<F>(self, mut f: F) -> Self
431 where
432 F: FnMut() + 'a,
433 {
434 self.ui.add_listener(self.id, crate::ui::EventPhase::Bubble, move |_, event| {
435 if let crate::ui::WidgetEvent::Click = event {
436 f();
437 }
438 });
439 self
440 }
441
442 pub fn on_hover<F>(self, mut f: F) -> Self
443 where
444 F: FnMut(bool) + 'a,
445 {
446 self.ui.add_listener(self.id, crate::ui::EventPhase::Bubble, move |_, event| {
447 if let crate::ui::WidgetEvent::Hover(hovered) = event {
448 f(*hovered);
449 }
450 });
451 self
452 }
453
454 pub fn on_scroll<F>(self, mut f: F) -> Self
455 where
456 F: FnMut(f32, f32) + 'a,
457 {
458 self.ui.add_listener(self.id, crate::ui::EventPhase::Bubble, move |_, event| {
459 if let crate::ui::WidgetEvent::Scroll(dx, dy) = event {
460 f(*dx, *dy);
461 }
462 });
463 self
464 }
465
466 pub fn on_event<F>(self, phase: crate::ui::EventPhase, f: F) -> Self
467 where
468 F: FnMut(&mut crate::ui::EventContext, &crate::ui::WidgetEvent) + 'a,
469 {
470 self.ui.add_listener(self.id, phase, f);
471 self
472 }
473
474 pub fn show<F>(mut self, f: F) -> zenthra_core::Response
475 where
476 F: FnOnce(&mut Ui),
477 {
478 let id = self.id;
480
481 self.start_x = self.ui.cursor_x;
482 self.start_y = self.ui.cursor_y;
483 let ox = self.pos_x.unwrap_or(self.start_x);
484 let oy = self.pos_y.unwrap_or(self.start_y);
485
486 let prev_dir = self.ui.direction;
488 let prev_line_h = self.ui.line_height;
489 let prev_base_x = self.ui.base_x;
490 let prev_base_y = self.ui.base_y;
491 let prev_offset_x = self.ui.offset_x;
492 let prev_offset_y = self.ui.offset_y;
493 let prev_viewport = self.ui.current_viewport;
494
495 let prev_child_sizes = std::mem::take(&mut self.ui.child_sizes);
497 let prev_child_ranges = std::mem::take(&mut self.ui.child_draw_ranges);
498 let prev_child_origins = std::mem::take(&mut self.ui.child_origins);
499 let prev_id_ranges = std::mem::take(&mut self.ui.id_ranges);
500 let prev_id_log = std::mem::take(&mut self.ui.id_log);
501
502 let prev_max_x = self.ui.max_x;
503 let prev_max_y = self.ui.max_y;
504
505 let _id_log_start_idx = self.ui.id_log.len();
506
507 if let Some(w) = self.width {
509 self.ui.max_x = (ox + w - self.padding_right).min(prev_max_x);
510 }
511 if let Some(h) = self.height {
512 self.ui.max_y = (oy + h - self.padding_bottom).min(prev_max_y);
513 }
514
515 self.ui.direction = self.direction;
516 self.ui.line_height = 0.0;
517
518 let mode = self.render_mode.unwrap_or(self.ui.current_render_mode());
519 self.ui.render_mode_stack.push(mode);
520 if mode == zenthra_core::RenderMode::Continuous {
521 self.ui.needs_redraw = true;
522 }
523
524 let avail_w = if let Some(w) = self.width {
525 w - self.padding_left - self.padding_right - 2.0 * self.border_width
526 } else {
527 (prev_max_x - ox - self.padding_left - self.padding_right - 2.0 * self.border_width).max(0.0)
528 };
529 let avail_h = if let Some(h) = self.height {
530 h - self.padding_top - self.padding_bottom - 2.0 * self.border_width
531 } else {
532 (prev_max_y - oy - self.padding_top - self.padding_bottom - 2.0 * self.border_width).max(0.0)
533 };
534
535 self.ui.max_x = ox + self.padding_left + self.border_width + avail_w;
538 self.ui.max_y = oy + self.padding_top + self.border_width + avail_h;
539
540 self.ui.semantic_stack.push(id);
541 self.ui.register_semantic(zenthra_core::SemanticNode::new(id, zenthra_core::Role::Container, zenthra_core::Rect::new(ox, oy, 0.0, 0.0)));
542
543 let parent_draws = std::mem::take(&mut self.ui.draws);
544
545 let prev_global_ox = self.ui.offset_x;
547 let prev_global_oy = self.ui.offset_y;
548 let prev_avail_w = self.ui.available_width;
549
550 let (sx, sy) = if self.scroll_x || self.scroll_y {
551 *self.ui.scroll_state.get(&id).unwrap_or(&(0.0, 0.0))
552 } else {
553 (0.0, 0.0)
554 };
555
556 self.ui.offset_x -= sx;
557 self.ui.offset_y -= sy;
558 self.ui.available_width = avail_w;
559
560 self.ui.base_x = ox + self.padding_left + self.border_width;
561 self.ui.base_y = oy + self.padding_top + self.border_width;
562 self.ui.cursor_x = self.ui.base_x;
563 self.ui.cursor_y = self.ui.base_y;
564
565 if self.clip {
567 if let Some((rect, _)) = self.ui.get_recorded_layout(id) {
568 let my_screen_rect = [rect.origin.x + prev_global_ox, rect.origin.y + prev_global_oy, rect.size.width, rect.size.height];
569 let parent_rect = [prev_viewport.origin.x, prev_viewport.origin.y, prev_viewport.size.width, prev_viewport.size.height];
570 let intersected = intersect_rects(my_screen_rect, parent_rect);
571 self.ui.current_viewport = zenthra_core::Rect::new(intersected[0], intersected[1], intersected[2], intersected[3]);
572 }
573 }
574
575 f(self.ui);
577
578 self.ui.offset_x = prev_global_ox;
580 self.ui.offset_y = prev_global_oy;
581 self.ui.available_width = prev_avail_w;
582
583 self.ui.render_mode_stack.pop();
585 self.ui.semantic_stack.pop();
586 self.ui.current_viewport = prev_viewport;
587
588 let child_ids_only = std::mem::replace(&mut self.ui.id_log, prev_id_log);
590 let child_id_ranges = std::mem::replace(&mut self.ui.id_ranges, prev_id_ranges);
591 self.children_draws = std::mem::replace(&mut self.ui.draws, parent_draws);
592 self.child_sizes = std::mem::replace(&mut self.ui.child_sizes, prev_child_sizes);
593 self.child_ranges = std::mem::replace(&mut self.ui.child_draw_ranges, prev_child_ranges);
594 self.child_origins = std::mem::replace(&mut self.ui.child_origins, prev_child_origins);
595
596 let _id_log_end_idx = child_ids_only.len();
597
598 self.ui.direction = prev_dir;
600 self.ui.line_height = prev_line_h;
601 self.ui.base_x = prev_base_x;
602 self.ui.base_y = prev_base_y;
603 self.ui.offset_x = prev_offset_x;
604 self.ui.offset_y = prev_offset_y;
605 self.ui.cursor_x = self.start_x;
606 self.ui.cursor_y = self.start_y;
607 self.ui.max_x = prev_max_x;
608 self.ui.max_y = prev_max_y;
609
610 let n = self.child_sizes.len();
612 let mut target_positions: Vec<(f32, f32)> = vec![(0.0, 0.0); n];
613 let (content_w, content_h) = match self.wrap {
614 Wrap::NoWrap => self.layout_no_wrap(ox, oy, avail_w, avail_h, &mut target_positions),
615 _ => self.layout_wrap(ox, oy, avail_w, avail_h, &mut target_positions),
616 };
617
618 let mut w = if self.fill_x {
619 self.ui.max_x - ox } else {
621 self.width.unwrap_or(content_w + self.padding_left + self.padding_right + 2.0 * self.border_width)
622 };
623
624 if let Some(mw) = self.max_width { w = w.min(mw); }
625 if let Some(mw) = self.min_width { w = w.max(mw); }
626
627 let mut h = if self.fill_y {
628 self.ui.max_y - oy } else {
630 self.height.unwrap_or(content_h + self.padding_top + self.padding_bottom + 2.0 * self.border_width)
631 };
632
633 if let Some(mh) = self.max_height { h = h.min(mh); }
634 if let Some(mh) = self.min_height { h = h.max(mh); }
635
636 let draw_start = self.ui.draws.len();
637 self.ui.record_layout(id, zenthra_core::Rect::new(ox, oy, w, h));
638
639 let max_sx = (content_w + self.padding_left + self.padding_right - w).max(0.0);
640 let max_sy = (content_h + self.padding_top + self.padding_bottom - h).max(0.0);
641
642 let (actual_ox, actual_oy) = if let Some((rect, _)) = self.ui.get_recorded_layout(id) {
643 (rect.origin.x + prev_global_ox, rect.origin.y + prev_global_oy)
644 } else {
645 (ox + prev_global_ox, oy + prev_global_oy)
646 };
647
648 let mouse_in_parent = if self.ui.skip_clip_stack.last().cloned().unwrap_or(false) {
649 true
650 } else {
651 prev_viewport.contains(zenthra_core::Point::new(self.ui.mouse_x, self.ui.mouse_y))
652 };
653
654 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);
655 let container_active = container_hover && self.ui.mouse_down;
656 let mut final_scale = 1.0;
657
658 let (scroll_x, scroll_y) = if self.scroll_x || self.scroll_y {
659 let (mut sx, mut sy) = *self.ui.scroll_state.get(&id).unwrap_or(&(0.0, 0.0));
660
661 if container_hover {
662 let mut events = std::mem::take(&mut self.ui.input_events);
663 events.retain(|event| {
664 let mut keep = true;
665 if let zenthra_platform::event::PlatformEvent::MouseWheel { delta_y, delta_x, .. } = event {
666 let mut consumed = false;
667 if self.scroll_y && *delta_y != 0.0 {
668 let can_up = sy > 0.0 && *delta_y > 0.0;
669 let can_down = sy < max_sy && *delta_y < 0.0;
670 if can_up || can_down {
671 sy -= delta_y * 15.0;
672 consumed = true;
673 }
674 }
675 if self.scroll_x && *delta_x != 0.0 {
676 let can_left = sx > 0.0 && *delta_x > 0.0;
677 let can_right = sx < max_sx && *delta_x < 0.0;
678 if can_left || can_right {
679 sx -= delta_x * 15.0;
680 consumed = true;
681 }
682 }
683 if consumed {
684 keep = false;
685 self.ui.needs_redraw = true;
686 }
687 }
688 keep
689 });
690 self.ui.input_events = events;
691 }
692
693 sx = sx.clamp(0.0, max_sx);
694 sy = sy.clamp(0.0, max_sy);
695
696 self.ui.scroll_state.insert(id, (sx, sy));
697 (sx, sy)
698 } else {
699 (0.0, 0.0)
700 };
701
702 let clip = [ox, oy, w, h];
703
704 if let Some(mut bg) = self.bg {
706 let mut bw = self.border_width;
707 let mut bc = self.border_color.unwrap_or(Color::TRANSPARENT);
708
709 if container_active {
710 bg = self.active_bg.or(self.hover_bg).unwrap_or(bg);
711 bw = self.active_border_width.or(self.hover_border_width).unwrap_or(bw);
712 bc = self.active_border_color.or(self.hover_border_color).unwrap_or(bc);
713 final_scale = self.active_scale;
714 } else if container_hover {
715 bg = self.hover_bg.unwrap_or(bg);
716 bw = self.hover_border_width.unwrap_or(bw);
717 bc = self.hover_border_color.unwrap_or(bc);
718 final_scale = self.hover_scale;
719 }
720
721 let visual_w = w * final_scale;
722 let visual_h = h * final_scale;
723 let visual_ox = ox - (visual_w - w) / 2.0;
724 let visual_oy = oy - (visual_h - h) / 2.0;
725
726 if self.is_overlay {
727 self.ui.overlays.push(DrawCommand::Rect(RectDraw {
728 instance: RectInstance {
729 pos: [visual_ox, visual_oy],
730 size: [visual_w, visual_h],
731 color: bg.to_array(),
732 radius: [
733 self.radius[3], self.radius[2], self.radius[1], self.radius[0], ],
738 border_width: bw,
739 border_color: bc.to_array(),
740 shadow_color: self.shadow_color.map(|c| {
741 let mut a = c.to_array();
742 a[3] *= self.shadow_opacity;
743 a
744 }).unwrap_or([0.0, 0.0, 0.0, 0.0]),
745 shadow_offset: self.shadow_offset,
746 shadow_blur: self.shadow_blur,
747 clip_rect: [-100000.0, -100000.0, 2000000.0, 2000000.0],
748 grayscale: 0.0,
749 brightness: 1.0,
750 opacity: self.opacity,
751 border_alignment: match self.border_alignment {
752 BorderAlignment::Inside => 0.0,
753 BorderAlignment::Center => 0.5,
754 BorderAlignment::Outside => 1.0,
755 },
756 }
757 }));
758 } else {
759 self.ui.draws.push(DrawCommand::Rect(RectDraw {
760 instance: RectInstance {
761 pos: [visual_ox, visual_oy],
762 size: [visual_w, visual_h],
763 color: bg.to_array(),
764 radius: [
765 self.radius[3], self.radius[2], self.radius[1], self.radius[0], ],
770 border_width: bw,
771 border_color: bc.to_array(),
772 shadow_color: self.shadow_color.map(|c| {
773 let mut a = c.to_array();
774 a[3] *= self.shadow_opacity;
775 a
776 }).unwrap_or([0.0, 0.0, 0.0, 0.0]),
777 shadow_offset: self.shadow_offset,
778 shadow_blur: self.shadow_blur,
779 clip_rect: [-100000.0, -100000.0, 2000000.0, 2000000.0],
780 grayscale: 0.0,
781 brightness: 1.0,
782 opacity: self.opacity,
783 border_alignment: match self.border_alignment {
784 BorderAlignment::Inside => 0.0,
785 BorderAlignment::Center => 0.5,
786 BorderAlignment::Outside => 1.0,
787 },
788 }
789 }));
790 }
791 }
792
793 for (i, (start, end)) in self.child_ranges.iter().enumerate() {
795 if i >= target_positions.len() { break; }
796 let (tx, ty) = target_positions[i];
797
798 let (origin_x, origin_y) = self.child_origins.get(i).copied().unwrap_or_else(|| {
799 self.children_draws
800 .get(*start)
801 .map(|d| draw_origin(d))
802 .unwrap_or((ox + self.padding_left + self.border_width, oy + self.padding_top + self.border_width))
803 });
804
805 let dx = tx - origin_x;
806 let dy = ty - origin_y;
807
808 let final_dx = dx - scroll_x;
809 let final_dy = dy - scroll_y;
810
811 for draw in &mut self.children_draws[*start..*end] {
813 offset_draw(draw, final_dx, final_dy);
814 if self.scroll_x || self.scroll_y || self.clip {
815 set_clip(draw, clip);
816 }
817 }
818
819 if let Some(&(ids_start, ids_end)) = child_id_ranges.get(i) {
821 for j in ids_start..ids_end {
822 let cid = child_ids_only[j];
823 if let Some((rect, _)) = self.ui.next_layout_cache.get_mut(&cid) {
824 rect.origin.x += dx;
825 rect.origin.y += dy;
826 }
827 }
828 }
829 }
830
831
832 for draw in self.children_draws.drain(..) {
834 if self.is_overlay {
835 self.ui.overlays.push(draw);
836 } else {
837 self.ui.draws.push(draw);
838 }
839 }
840
841 if self.scroll_x || self.scroll_y {
843 let bar_thickness = 4.0;
844 let bar_margin = 0.0;
845
846 if self.scroll_y && max_sy > 0.0 {
848 let thumb_h = (h / (content_h + self.padding_top + self.padding_bottom)) * h;
849 let thumb_h = thumb_h.max(20.0);
850 let scroll_ratio = scroll_y / max_sy;
851 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);
852 let is_dragging = self.ui.active_drag.as_ref().map(|d| d.id == id && d.start_mouse <= -1000.0).unwrap_or(false);
853
854 if self.ui.clicked && is_hover {
856 self.ui.active_drag = Some(crate::ui::ScrollDrag {
857 id,
858 start_mouse: -1000.0 - self.ui.mouse_y,
859 start_scroll: scroll_y,
860 });
861 }
862
863 if let Some(drag) = &self.ui.active_drag {
864 if drag.id == id && drag.start_mouse <= -1000.0 {
865 let current_marker = -1000.0 - self.ui.mouse_y;
866 let delta = drag.start_mouse - current_marker;
867 let scroll_range = h - thumb_h;
868 if scroll_range > 0.0 {
869 let new_sy = (drag.start_scroll + (delta / scroll_range) * max_sy).clamp(0.0, max_sy);
870 self.ui.scroll_state.insert(id, (scroll_x, new_sy));
871 self.ui.needs_redraw = true;
872 }
873 }
874 }
875
876 {
877 let color = if is_hover || is_dragging {
878 Color::rgba(1.0, 1.0, 1.0, 0.5)
879 } else if container_hover {
880 Color::rgba(1.0, 1.0, 1.0, 0.2)
881 } else {
882 Color::rgba(1.0, 1.0, 1.0, 0.1)
883 };
884
885 let visual_thumb_x = ox + w - bar_thickness - bar_margin - self.border_width;
888 let visual_thumb_y = oy + (h - thumb_h) * scroll_ratio;
889
890 let draw = crate::ui::DrawCommand::OverlayRect(crate::ui::OverlayRectDraw {
891 x: visual_thumb_x,
892 y: visual_thumb_y,
893 width: bar_thickness,
894 height: thumb_h,
895 color,
896 clip: [ox, oy, w, h],
897 });
898 if self.is_overlay { self.ui.overlays.push(draw); } else { self.ui.draws.push(draw); }
899 }
900 }
901
902 if self.scroll_x && max_sx > 0.0 {
904 let thumb_w = (w / (content_w + self.padding_left + self.padding_right)) * w;
905 let thumb_w = thumb_w.max(20.0);
906 let scroll_ratio = scroll_x / max_sx;
907 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);
908 let is_dragging = self.ui.active_drag.as_ref().map(|d| d.id == id && d.start_mouse > -1000.0).unwrap_or(false);
909
910 if self.ui.clicked && is_hover {
911 self.ui.active_drag = Some(crate::ui::ScrollDrag {
912 id,
913 start_mouse: self.ui.mouse_x,
914 start_scroll: scroll_x,
915 });
916 }
917
918 if let Some(drag) = &self.ui.active_drag {
919 if drag.id == id && drag.start_mouse > -1000.0 {
920 let delta = self.ui.mouse_x - drag.start_mouse;
921 let scroll_range = w - thumb_w;
922 if scroll_range > 0.0 {
923 let new_sx = (drag.start_scroll + (delta / scroll_range) * max_sx).clamp(0.0, max_sx);
924 let (_, current_sy) = *self.ui.scroll_state.get(&id).unwrap_or(&(0.0, 0.0));
925 self.ui.scroll_state.insert(id, (new_sx, current_sy));
926 self.ui.needs_redraw = true;
927 }
928 }
929 }
930
931 {
932 let color = if is_hover || is_dragging {
933 Color::rgba(1.0, 1.0, 1.0, 0.5)
934 } else if container_hover {
935 Color::rgba(1.0, 1.0, 1.0, 0.2)
936 } else {
937 Color::rgba(1.0, 1.0, 1.0, 0.1)
938 };
939
940 let visual_thumb_x = ox + (w - thumb_w) * scroll_ratio;
941 let visual_thumb_y = oy + h - bar_thickness - bar_margin - self.border_width;
942
943 let draw = crate::ui::DrawCommand::OverlayRect(crate::ui::OverlayRectDraw {
944 x: visual_thumb_x,
945 y: visual_thumb_y,
946 width: thumb_w,
947 height: bar_thickness,
948 color,
949 clip: [ox, oy, w, h],
950 });
951 if self.is_overlay { self.ui.overlays.push(draw); } else { self.ui.draws.push(draw); }
952 }
953 }
954 }
955
956 self.ui.id_log.extend(child_ids_only);
958
959 if !self.is_absolute {
960 self.ui.advance(w, h, draw_start);
961 }
962
963 self.ui.dispatch_event(id, crate::ui::WidgetEvent::Hover(container_hover));
965
966 if self.ui.clicked && container_hover {
967 self.ui.dispatch_event(id, crate::ui::WidgetEvent::Click);
968 }
969
970 let events = std::mem::take(&mut self.ui.input_events);
971 for event in &events {
972 if let zenthra_platform::event::PlatformEvent::MouseWheel { delta_x, delta_y } = event {
973 if container_hover {
974 self.ui.dispatch_event(id, crate::ui::WidgetEvent::Scroll(*delta_x, *delta_y));
975 }
976 }
977 }
978 self.ui.input_events = events;
979
980 zenthra_core::Response {
981 clicked: self.ui.clicked && container_hover,
982 hovered: container_hover,
983 pressed: container_active,
984 }
985 }
986
987 fn layout_no_wrap(
988 &self,
989 ox: f32,
990 oy: f32,
991 inner_w: f32,
992 inner_h: f32,
993 targets: &mut [(f32, f32)],
994 ) -> (f32, f32) {
995 let n = self.child_sizes.len();
996 if n == 0 { return (0.0, 0.0); }
997
998 let (content_w, content_h) = match self.direction {
999 Direction::Row => {
1000 let w = self.child_sizes.iter().map(|(w, _)| w).sum::<f32>()
1001 + self.gap * (n.saturating_sub(1)) as f32;
1002 let h = self.child_sizes.iter().map(|(_, h)| *h).fold(0.0f32, f32::max);
1003 (w, h)
1004 }
1005 Direction::Column => {
1006 let w = self.child_sizes.iter().map(|(w, _)| *w).fold(0.0f32, f32::max);
1007 let h = self.child_sizes.iter().map(|(_, h)| h).sum::<f32>()
1008 + self.gap * (n.saturating_sub(1)) as f32;
1009 (w, h)
1010 }
1011 Direction::Stack => {
1012 let w = self.child_sizes.iter().map(|(w, _)| *w).fold(0.0f32, f32::max);
1013 let h = self.child_sizes.iter().map(|(_, h)| *h).fold(0.0f32, f32::max);
1014 (w, h)
1015 }
1016 };
1017
1018 let real_w = if self.width.is_some() || self.fill_x { inner_w } else { content_w };
1019 let real_h = if self.height.is_some() || self.fill_y { inner_h } else { content_h };
1020
1021 match self.target_halign_valign(ox, oy, real_w, real_h, content_w, content_h, targets) {
1022 (w, h) => (w, h),
1023 }
1024 }
1025
1026 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) {
1027 let n = self.child_sizes.len();
1028 match self.direction {
1029 Direction::Row => {
1030 let extra = (real_w - content_w).max(0.0);
1031 let mut cx = ox + self.padding_left + self.border_width + match self.halign {
1032 Align::Left => 0.0,
1033 Align::Center => extra / 2.0,
1034 Align::Right => extra,
1035 Align::SpaceBetween => 0.0,
1036 Align::SpaceAround => extra / (n as f32 * 2.0),
1037 _ => 0.0,
1038 };
1039 let gap = match self.halign {
1040 Align::SpaceBetween if n > 1 => extra / (n - 1) as f32,
1041 Align::SpaceAround => extra / n as f32,
1042 _ => self.gap,
1043 };
1044 for (i, (cw, ch)) in self.child_sizes.iter().enumerate() {
1045 let cy = oy + self.padding_top + self.border_width + match self.valign {
1046 Align::Top => 0.0,
1047 Align::Center => (real_h - ch) / 2.0,
1048 Align::Bottom => real_h - ch,
1049 _ => 0.0,
1050 };
1051 targets[i] = (cx, cy);
1052 cx += cw + gap;
1053 }
1054 }
1055 Direction::Column => {
1056 let extra = (real_h - content_h).max(0.0);
1057 let mut cy = oy + self.padding_top + self.border_width + match self.valign {
1058 Align::Top => 0.0,
1059 Align::Center => extra / 2.0,
1060 Align::Bottom => extra,
1061 Align::SpaceBetween => 0.0,
1062 Align::SpaceAround => extra / (n as f32 * 2.0),
1063 _ => 0.0,
1064 };
1065 let gap = match self.valign {
1066 Align::SpaceBetween if n > 1 => extra / (n - 1) as f32,
1067 Align::SpaceAround => extra / n as f32,
1068 _ => self.gap,
1069 };
1070 for (i, (cw, ch)) in self.child_sizes.iter().enumerate() {
1071 let cx = ox + self.padding_left + self.border_width + match self.halign {
1072 Align::Left => 0.0,
1073 Align::Center => (real_w - cw) / 2.0,
1074 Align::Right => real_w - cw,
1075 _ => 0.0,
1076 };
1077 targets[i] = (cx, cy);
1078 cy += ch + gap;
1079 }
1080 }
1081 Direction::Stack => {
1082 for (i, (cw, ch)) in self.child_sizes.iter().enumerate() {
1083 let cx = ox + self.padding_left + self.border_width + match self.halign {
1084 Align::Center => (real_w - cw).max(0.0) / 2.0,
1085 Align::Right => real_w - cw,
1086 _ => 0.0,
1087 };
1088 let cy = oy + self.padding_top + self.border_width + match self.valign {
1089 Align::Center => (real_h - ch).max(0.0) / 2.0,
1090 Align::Bottom => real_h - ch,
1091 _ => 0.0,
1092 };
1093 targets[i] = (cx, cy);
1094 }
1095 }
1096 }
1097 (content_w, content_h)
1098 }
1099
1100 fn layout_wrap(
1101 &self,
1102 ox: f32,
1103 oy: f32,
1104 inner_w: f32,
1105 inner_h: f32,
1106 targets: &mut [(f32, f32)],
1107 ) -> (f32, f32) {
1108 let n = self.child_sizes.len();
1109 if n == 0 { return (0.0, 0.0); }
1110
1111 let (main_reversed, cross_reversed) = match self.wrap {
1112 Wrap::Wrap => (false, false),
1113 Wrap::WrapReverse => (false, true),
1114 Wrap::RightToLeft => (true, false),
1115 Wrap::RightToLeftReverse => (true, true),
1116 _ => (false, false),
1117 };
1118
1119 match self.direction {
1120 Direction::Row => {
1121 let mut rows: Vec<Vec<usize>> = Vec::new();
1122 let mut current_row = Vec::new();
1123 let mut row_w = 0.0f32;
1124
1125 for (i, (cw, _)) in self.child_sizes.iter().enumerate() {
1126 let needed = if current_row.is_empty() { *cw } else { row_w + self.gap + cw };
1127 if needed > inner_w && !current_row.is_empty() {
1128 rows.push(std::mem::take(&mut current_row));
1129 current_row.push(i);
1130 row_w = *cw;
1131 } else {
1132 current_row.push(i);
1133 row_w = needed;
1134 }
1135 }
1136 if !current_row.is_empty() { rows.push(current_row); }
1137
1138 let row_heights: Vec<f32> = rows.iter().map(|row| {
1139 row.iter().map(|&i| self.child_sizes[i].1).fold(0.0f32, f32::max)
1140 }).collect();
1141
1142 let total_h = row_heights.iter().sum::<f32>() + self.gap * (rows.len().saturating_sub(1)) as f32;
1143 let max_row_w = rows.iter().map(|row| {
1144 row.iter().map(|&i| self.child_sizes[i].0).sum::<f32>() + self.gap * (row.len().saturating_sub(1)) as f32
1145 }).fold(0.0f32, f32::max);
1146
1147 let real_h = if self.height.is_some() || self.fill_y { inner_h } else { total_h };
1148 let real_w = if self.width.is_some() || self.fill_x { inner_w } else { max_row_w };
1149
1150 let mut cy = if cross_reversed {
1151 oy + self.padding_top + self.border_width + match self.valign {
1152 Align::Top => total_h,
1153 Align::Center => (real_h + total_h) / 2.0,
1154 Align::Bottom => real_h,
1155 _ => total_h,
1156 }
1157 } else {
1158 oy + self.padding_top + self.border_width + match self.valign {
1159 Align::Center => (real_h - total_h).max(0.0) / 2.0,
1160 Align::Bottom => (real_h - total_h).max(0.0),
1161 _ => 0.0,
1162 }
1163 };
1164
1165 for (ri, row) in rows.iter().enumerate() {
1166 let row_h = row_heights[ri];
1167 let row_content_w = row.iter().map(|&i| self.child_sizes[i].0).sum::<f32>() + self.gap * (row.len().saturating_sub(1)) as f32;
1168 let extra = (real_w - row_content_w).max(0.0);
1169 if cross_reversed { cy -= row_h; }
1170
1171 let mut cx = if main_reversed {
1172 ox + self.padding_left + self.border_width + match self.halign {
1173 Align::Left => row_content_w,
1174 Align::Center => (real_w + row_content_w) / 2.0,
1175 Align::Right => real_w,
1176 _ => row_content_w,
1177 }
1178 } else {
1179 ox + self.padding_left + self.border_width + match self.halign {
1180 Align::Left => 0.0,
1181 Align::Center => extra / 2.0,
1182 Align::Right => extra,
1183 Align::SpaceBetween => 0.0,
1184 Align::SpaceAround => extra / (row.len() as f32 * 2.0),
1185 _ => 0.0,
1186 }
1187 };
1188
1189 let gap = match self.halign {
1190 Align::SpaceBetween if row.len() > 1 && !main_reversed => extra / (row.len() - 1) as f32,
1191 Align::SpaceAround if !main_reversed => extra / row.len() as f32,
1192 _ => self.gap,
1193 };
1194
1195 for &ci in row {
1196 let (cw, ch) = self.child_sizes[ci];
1197 if main_reversed { cx -= cw; }
1198 let child_y = cy + match self.valign {
1199 Align::Center => (row_h - ch) / 2.0,
1200 Align::Bottom => row_h - ch,
1201 _ => 0.0,
1202 };
1203 targets[ci] = (cx, child_y);
1204 if main_reversed { cx -= gap; } else { cx += cw + gap; }
1205 }
1206 if cross_reversed { cy -= self.gap; } else { cy += row_h + self.gap; }
1207 }
1208 (max_row_w, total_h)
1209 }
1210 Direction::Column => {
1211 let mut cols: Vec<Vec<usize>> = Vec::new();
1212 let mut current_col = Vec::new();
1213 let mut col_h = 0.0f32;
1214
1215 for (i, (_, ch)) in self.child_sizes.iter().enumerate() {
1216 let needed = if current_col.is_empty() { *ch } else { col_h + self.gap + ch };
1217 if needed > inner_h && !current_col.is_empty() {
1218 cols.push(std::mem::take(&mut current_col));
1219 current_col.push(i);
1220 col_h = *ch;
1221 } else {
1222 current_col.push(i);
1223 col_h = needed;
1224 }
1225 }
1226 if !current_col.is_empty() { cols.push(current_col); }
1227
1228 let col_widths: Vec<f32> = cols.iter().map(|col| {
1229 col.iter().map(|&i| self.child_sizes[i].0).fold(0.0f32, f32::max)
1230 }).collect();
1231
1232 let total_w = col_widths.iter().sum::<f32>() + self.gap * (cols.len().saturating_sub(1)) as f32;
1233 let max_col_h = cols.iter().map(|col| {
1234 col.iter().map(|&i| self.child_sizes[i].1).sum::<f32>() + self.gap * (col.len().saturating_sub(1)) as f32
1235 }).fold(0.0f32, f32::max);
1236
1237 let real_w = if self.width.is_some() || self.fill_x { inner_w } else { total_w };
1238 let real_h = if self.height.is_some() || self.fill_y { inner_h } else { max_col_h };
1239
1240 let mut cx = if cross_reversed {
1241 ox + self.padding_left + self.border_width + match self.halign {
1242 Align::Left => total_w,
1243 Align::Center => (real_w + total_w) / 2.0,
1244 Align::Right => real_w,
1245 _ => total_w,
1246 }
1247 } else {
1248 ox + self.padding_left + self.border_width + match self.halign {
1249 Align::Center => (real_w - total_w).max(0.0) / 2.0,
1250 Align::Right => (real_w - total_w).max(0.0),
1251 _ => 0.0,
1252 }
1253 };
1254
1255 for (ci_idx, col) in cols.iter().enumerate() {
1256 let col_w = col_widths[ci_idx];
1257 let col_content_h = col.iter().map(|&i| self.child_sizes[i].1).sum::<f32>() + self.gap * (col.len().saturating_sub(1)) as f32;
1258 let extra = (real_h - col_content_h).max(0.0);
1259 if cross_reversed { cx -= col_w; }
1260
1261 let mut cy = if main_reversed {
1262 oy + self.padding_top + self.border_width + match self.valign {
1263 Align::Top => col_content_h,
1264 Align::Center => (real_h + col_content_h) / 2.0,
1265 Align::Bottom => real_h,
1266 _ => col_content_h,
1267 }
1268 } else {
1269 oy + self.padding_top + self.border_width + match self.valign {
1270 Align::Top => 0.0,
1271 Align::Center => extra / 2.0,
1272 Align::Bottom => extra,
1273 Align::SpaceBetween => 0.0,
1274 Align::SpaceAround => extra / (col.len() as f32 * 2.0),
1275 _ => 0.0,
1276 }
1277 };
1278
1279 let gap = match self.valign {
1280 Align::SpaceBetween if col.len() > 1 && !main_reversed => extra / (col.len() - 1) as f32,
1281 Align::SpaceAround if !main_reversed => extra / col.len() as f32,
1282 _ => self.gap,
1283 };
1284
1285 for &ci in col {
1286 let (cw, ch) = self.child_sizes[ci];
1287 if main_reversed { cy -= ch; }
1288 let child_x = cx + match self.halign {
1289 Align::Center => (col_w - cw) / 2.0,
1290 Align::Right => col_w - cw,
1291 _ => 0.0,
1292 };
1293 targets[ci] = (child_x, cy);
1294 if main_reversed { cy -= gap; } else { cy += ch + gap; }
1295 }
1296 if cross_reversed { cx -= self.gap; } else { cx += col_w + self.gap; }
1297 }
1298 (total_w, max_col_h)
1299 }
1300 Direction::Stack => {
1301 let w = self.child_sizes.iter().map(|(w, _)| *w).fold(0.0f32, f32::max);
1302 let h = self.child_sizes.iter().map(|(_, h)| *h).fold(0.0f32, f32::max);
1303 for (i, (cw, ch)) in self.child_sizes.iter().enumerate() {
1304 let cx = ox + self.padding_left + self.border_width + match self.halign {
1305 Align::Center => (inner_w - cw).max(0.0) / 2.0,
1306 Align::Right => inner_w - cw,
1307 _ => 0.0,
1308 };
1309 let cy = oy + self.padding_top + self.border_width + match self.valign {
1310 Align::Center => (inner_h - ch).max(0.0) / 2.0,
1311 Align::Bottom => inner_h - ch,
1312 _ => 0.0,
1313 };
1314 targets[i] = (cx, cy);
1315 }
1316 (w, h)
1317 }
1318 }
1319 }
1320}
1321
1322fn offset_draw(cmd: &mut DrawCommand, dx: f32, dy: f32) {
1324 match cmd {
1325 DrawCommand::Rect(r) => {
1326 r.instance.pos[0] += dx;
1327 r.instance.pos[1] += dy;
1328 r.instance.clip_rect[0] += dx;
1330 r.instance.clip_rect[1] += dy;
1331 }
1332 DrawCommand::Text(t) => {
1333 t.pos[0] += dx;
1334 t.pos[1] += dy;
1335 t.clip[0] += dx;
1336 t.clip[1] += dy;
1337 }
1338 DrawCommand::OverlayRect(c) => {
1339 c.x += dx;
1340 c.y += dy;
1341 c.clip[0] += dx;
1342 c.clip[1] += dy;
1343 }
1344 DrawCommand::Image(i) => {
1345 i.instance.pos[0] += dx;
1346 i.instance.pos[1] += dy;
1347 i.instance.clip_rect[0] += dx;
1348 i.instance.clip_rect[1] += dy;
1349 }
1350 }
1351}
1352
1353fn draw_origin(cmd: &DrawCommand) -> (f32, f32) {
1354 match cmd {
1355 DrawCommand::Rect(r) => (r.instance.pos[0], r.instance.pos[1]),
1356 DrawCommand::Text(t) => (t.pos[0], t.pos[1]),
1357 DrawCommand::OverlayRect(c) => (c.x, c.y),
1358 DrawCommand::Image(i) => (i.instance.pos[0], i.instance.pos[1]),
1359 }
1360}
1361
1362fn set_clip(cmd: &mut DrawCommand, clip: [f32; 4]) {
1363 match cmd {
1364 DrawCommand::Rect(r) => r.instance.clip_rect = intersect_rects(r.instance.clip_rect, clip),
1365 DrawCommand::Text(t) => t.clip = intersect_rects(t.clip, clip),
1366 DrawCommand::OverlayRect(c) => c.clip = intersect_rects(c.clip, clip),
1367 DrawCommand::Image(i) => i.instance.clip_rect = intersect_rects(i.instance.clip_rect, clip),
1368 }
1369}
1370
1371fn intersect_rects(a: [f32; 4], b: [f32; 4]) -> [f32; 4] {
1372 let x1 = a[0].max(b[0]);
1373 let y1 = a[1].max(b[1]);
1374 let x2 = (a[0] + a[2]).min(b[0] + b[2]);
1375 let y2 = (a[1] + a[3]).min(b[1] + b[3]);
1376
1377 let w = (x2 - x1).max(0.0);
1378 let h = (y2 - y1).max(0.0);
1379
1380 [x1, y1, w, h]
1381}