1use super::*;
2
3#[must_use = "ContainerBuilder does nothing until .col(), .row(), .line(), or .draw() is called"]
24pub struct ContainerBuilder<'a> {
25 pub(crate) ctx: &'a mut Context,
26 pub(crate) gap: u32,
27 pub(crate) row_gap: Option<u32>,
28 pub(crate) col_gap: Option<u32>,
29 pub(crate) align: Align,
30 pub(crate) align_self_value: Option<Align>,
31 pub(crate) justify: Justify,
32 pub(crate) border: Option<Border>,
33 pub(crate) border_sides: BorderSides,
34 pub(crate) border_style: Style,
35 pub(crate) bg: Option<Color>,
36 pub(crate) text_color: Option<Color>,
37 pub(crate) dark_bg: Option<Color>,
38 pub(crate) dark_border_style: Option<Style>,
39 pub(crate) group_hover_bg: Option<Color>,
40 pub(crate) group_hover_border_style: Option<Style>,
41 pub(crate) group_name: Option<std::sync::Arc<str>>,
42 pub(crate) padding: Padding,
43 pub(crate) margin: Margin,
44 pub(crate) constraints: Constraints,
45 pub(crate) title: Option<(String, Style)>,
46 pub(crate) grow: u16,
47 pub(crate) scroll_offset: Option<u32>,
48}
49
50#[derive(Debug, Clone, Copy)]
57struct CanvasPixel {
58 bits: u32,
59 color: Color,
60}
61
62#[derive(Debug, Clone)]
64struct CanvasLabel {
65 x: usize,
66 y: usize,
67 text: String,
68 color: Color,
69}
70
71#[derive(Debug, Clone)]
73struct CanvasLayer {
74 grid: Vec<Vec<CanvasPixel>>,
75 labels: Vec<CanvasLabel>,
76}
77
78pub struct CanvasContext {
80 layers: Vec<CanvasLayer>,
81 cols: usize,
82 rows: usize,
83 px_w: usize,
84 px_h: usize,
85 current_color: Color,
86 scratch_pixels: Vec<CanvasPixel>,
89 scratch_labels: Vec<Option<(char, Color)>>,
92}
93
94#[inline]
101fn isqrt_i64(n: i64) -> isize {
102 if n <= 0 {
103 return 0;
104 }
105 let mut x = (n as f64).sqrt() as i64;
106 while x > 0 && x.saturating_mul(x) > n {
108 x -= 1;
109 }
110 while (x + 1).saturating_mul(x + 1) <= n {
111 x += 1;
112 }
113 x as isize
114}
115
116impl CanvasContext {
117 pub(crate) fn new(cols: usize, rows: usize) -> Self {
118 let cell_count = cols.saturating_mul(rows);
119 Self {
120 layers: vec![Self::new_layer(cols, rows)],
121 cols,
122 rows,
123 px_w: cols * 2,
124 px_h: rows * 4,
125 current_color: Color::Reset,
126 scratch_pixels: vec![
127 CanvasPixel {
128 bits: 0,
129 color: Color::Reset,
130 };
131 cell_count
132 ],
133 scratch_labels: vec![None; cell_count],
134 }
135 }
136
137 fn new_layer(cols: usize, rows: usize) -> CanvasLayer {
138 CanvasLayer {
139 grid: vec![
140 vec![
141 CanvasPixel {
142 bits: 0,
143 color: Color::Reset,
144 };
145 cols
146 ];
147 rows
148 ],
149 labels: Vec::new(),
150 }
151 }
152
153 fn current_layer_mut(&mut self) -> Option<&mut CanvasLayer> {
154 self.layers.last_mut()
155 }
156
157 fn dot_with_color(&mut self, x: usize, y: usize, color: Color) {
158 if x >= self.px_w || y >= self.px_h {
159 return;
160 }
161
162 let char_col = x / 2;
163 let char_row = y / 4;
164 let sub_col = x % 2;
165 let sub_row = y % 4;
166 const LEFT_BITS: [u32; 4] = [0x01, 0x02, 0x04, 0x40];
167 const RIGHT_BITS: [u32; 4] = [0x08, 0x10, 0x20, 0x80];
168
169 let bit = if sub_col == 0 {
170 LEFT_BITS[sub_row]
171 } else {
172 RIGHT_BITS[sub_row]
173 };
174
175 if let Some(layer) = self.current_layer_mut() {
176 let cell = &mut layer.grid[char_row][char_col];
177 let new_bits = cell.bits | bit;
178 if new_bits != cell.bits {
179 cell.bits = new_bits;
180 cell.color = color;
181 }
182 }
183 }
184
185 fn dot_isize(&mut self, x: isize, y: isize) {
186 if x >= 0 && y >= 0 {
187 self.dot(x as usize, y as usize);
188 }
189 }
190
191 pub fn width(&self) -> usize {
193 self.px_w
194 }
195
196 pub fn height(&self) -> usize {
198 self.px_h
199 }
200
201 pub fn dot(&mut self, x: usize, y: usize) {
203 self.dot_with_color(x, y, self.current_color);
204 }
205
206 pub fn line(&mut self, x0: usize, y0: usize, x1: usize, y1: usize) {
208 let (mut x, mut y) = (x0 as isize, y0 as isize);
209 let (x1, y1) = (x1 as isize, y1 as isize);
210 let dx = (x1 - x).abs();
211 let dy = -(y1 - y).abs();
212 let sx = if x < x1 { 1 } else { -1 };
213 let sy = if y < y1 { 1 } else { -1 };
214 let mut err = dx + dy;
215
216 loop {
217 self.dot_isize(x, y);
218 if x == x1 && y == y1 {
219 break;
220 }
221 let e2 = 2 * err;
222 if e2 >= dy {
223 err += dy;
224 x += sx;
225 }
226 if e2 <= dx {
227 err += dx;
228 y += sy;
229 }
230 }
231 }
232
233 pub fn rect(&mut self, x: usize, y: usize, w: usize, h: usize) {
235 if w == 0 || h == 0 {
236 return;
237 }
238
239 self.line(x, y, x + w.saturating_sub(1), y);
240 self.line(
241 x + w.saturating_sub(1),
242 y,
243 x + w.saturating_sub(1),
244 y + h.saturating_sub(1),
245 );
246 self.line(
247 x + w.saturating_sub(1),
248 y + h.saturating_sub(1),
249 x,
250 y + h.saturating_sub(1),
251 );
252 self.line(x, y + h.saturating_sub(1), x, y);
253 }
254
255 pub fn circle(&mut self, cx: usize, cy: usize, r: usize) {
257 let mut x = r as isize;
258 let mut y: isize = 0;
259 let mut err: isize = 1 - x;
260 let (cx, cy) = (cx as isize, cy as isize);
261
262 while x >= y {
263 for &(dx, dy) in &[
264 (x, y),
265 (y, x),
266 (-x, y),
267 (-y, x),
268 (x, -y),
269 (y, -x),
270 (-x, -y),
271 (-y, -x),
272 ] {
273 let px = cx + dx;
274 let py = cy + dy;
275 self.dot_isize(px, py);
276 }
277
278 y += 1;
279 if err < 0 {
280 err += 2 * y + 1;
281 } else {
282 x -= 1;
283 err += 2 * (y - x) + 1;
284 }
285 }
286 }
287
288 pub fn set_color(&mut self, color: Color) {
290 self.current_color = color;
291 }
292
293 pub fn color(&self) -> Color {
295 self.current_color
296 }
297
298 pub fn filled_rect(&mut self, x: usize, y: usize, w: usize, h: usize) {
300 if w == 0 || h == 0 {
301 return;
302 }
303
304 let x_end = x.saturating_add(w).min(self.px_w);
305 let y_end = y.saturating_add(h).min(self.px_h);
306 if x >= x_end || y >= y_end {
307 return;
308 }
309
310 for yy in y..y_end {
311 self.line(x, yy, x_end.saturating_sub(1), yy);
312 }
313 }
314
315 pub fn filled_circle(&mut self, cx: usize, cy: usize, r: usize) {
317 let (cx, cy, r) = (cx as isize, cy as isize, r as isize);
318 for y in (cy - r)..=(cy + r) {
319 let dy = y - cy;
320 let span_sq = (r * r - dy * dy).max(0);
321 let dx = isqrt_i64(span_sq as i64);
323 for x in (cx - dx)..=(cx + dx) {
324 self.dot_isize(x, y);
325 }
326 }
327 }
328
329 pub fn triangle(&mut self, x0: usize, y0: usize, x1: usize, y1: usize, x2: usize, y2: usize) {
331 self.line(x0, y0, x1, y1);
332 self.line(x1, y1, x2, y2);
333 self.line(x2, y2, x0, y0);
334 }
335
336 pub fn filled_triangle(
338 &mut self,
339 x0: usize,
340 y0: usize,
341 x1: usize,
342 y1: usize,
343 x2: usize,
344 y2: usize,
345 ) {
346 let vertices = [
347 (x0 as isize, y0 as isize),
348 (x1 as isize, y1 as isize),
349 (x2 as isize, y2 as isize),
350 ];
351 let min_y = vertices.iter().map(|(_, y)| *y).min().unwrap_or(0);
352 let max_y = vertices.iter().map(|(_, y)| *y).max().unwrap_or(-1);
353
354 for y in min_y..=max_y {
355 let mut intersections = [0.0f64; 4];
359 let mut isect_count = 0usize;
360
361 for edge in [(0usize, 1usize), (1usize, 2usize), (2usize, 0usize)] {
362 let (x_a, y_a) = vertices[edge.0];
363 let (x_b, y_b) = vertices[edge.1];
364 if y_a == y_b {
365 continue;
366 }
367
368 let (x_start, y_start, x_end, y_end) = if y_a < y_b {
369 (x_a, y_a, x_b, y_b)
370 } else {
371 (x_b, y_b, x_a, y_a)
372 };
373
374 if y < y_start || y >= y_end {
375 continue;
376 }
377
378 let t = (y - y_start) as f64 / (y_end - y_start) as f64;
379 if isect_count < intersections.len() {
380 intersections[isect_count] = x_start as f64 + t * (x_end - x_start) as f64;
381 isect_count += 1;
382 }
383 }
384
385 intersections[..isect_count].sort_by(|a, b| a.total_cmp(b));
386 let mut i = 0usize;
387 while i + 1 < isect_count {
388 let x_start = intersections[i].ceil() as isize;
389 let x_end = intersections[i + 1].floor() as isize;
390 for x in x_start..=x_end {
391 self.dot_isize(x, y);
392 }
393 i += 2;
394 }
395 }
396
397 self.triangle(x0, y0, x1, y1, x2, y2);
398 }
399
400 pub fn points(&mut self, pts: &[(usize, usize)]) {
402 for &(x, y) in pts {
403 self.dot(x, y);
404 }
405 }
406
407 pub fn polyline(&mut self, pts: &[(usize, usize)]) {
409 for window in pts.windows(2) {
410 if let [(x0, y0), (x1, y1)] = window {
411 self.line(*x0, *y0, *x1, *y1);
412 }
413 }
414 }
415
416 pub fn print(&mut self, x: usize, y: usize, text: &str) {
419 if text.is_empty() {
420 return;
421 }
422
423 let color = self.current_color;
424 if let Some(layer) = self.current_layer_mut() {
425 layer.labels.push(CanvasLabel {
426 x,
427 y,
428 text: text.to_string(),
429 color,
430 });
431 }
432 }
433
434 pub fn layer(&mut self) {
436 self.layers.push(Self::new_layer(self.cols, self.rows));
437 }
438
439 pub(crate) fn render(&mut self) -> Vec<Vec<(String, Color)>> {
440 let cell_count = self.cols.saturating_mul(self.rows);
441
442 if self.scratch_pixels.len() < cell_count {
445 self.scratch_pixels.resize(
446 cell_count,
447 CanvasPixel {
448 bits: 0,
449 color: Color::Reset,
450 },
451 );
452 }
453 if self.scratch_labels.len() < cell_count {
454 self.scratch_labels.resize(cell_count, None);
455 }
456 for px in &mut self.scratch_pixels[..cell_count] {
457 *px = CanvasPixel {
458 bits: 0,
459 color: Color::Reset,
460 };
461 }
462 for slot in &mut self.scratch_labels[..cell_count] {
463 *slot = None;
464 }
465
466 let cols = self.cols;
467 let rows = self.rows;
468
469 for layer in &self.layers {
470 for (row, src_row) in layer.grid.iter().enumerate().take(rows) {
471 let row_offset = row * cols;
472 for (col, src) in src_row.iter().enumerate().take(cols) {
473 if src.bits == 0 {
474 continue;
475 }
476 let dst = &mut self.scratch_pixels[row_offset + col];
477 let merged = dst.bits | src.bits;
478 if merged != dst.bits {
479 dst.bits = merged;
480 dst.color = src.color;
481 }
482 }
483 }
484
485 for label in &layer.labels {
486 let row = label.y / 4;
487 if row >= rows {
488 continue;
489 }
490 let start_col = label.x / 2;
491 let row_offset = row * cols;
492 for (offset, ch) in label.text.chars().enumerate() {
493 let col = start_col + offset;
494 if col >= cols {
495 break;
496 }
497 self.scratch_labels[row_offset + col] = Some((ch, label.color));
498 }
499 }
500 }
501
502 let mut lines: Vec<Vec<(String, Color)>> = Vec::with_capacity(rows);
503 for row in 0..rows {
504 let row_offset = row * cols;
505 let mut segments: Vec<(String, Color)> = Vec::new();
506 let mut current_color: Option<Color> = None;
507 let mut current_text = String::new();
508
509 for col in 0..cols {
510 let idx = row_offset + col;
511 let (ch, color) = if let Some((label_ch, label_color)) = self.scratch_labels[idx] {
512 (label_ch, label_color)
513 } else {
514 let pixel = self.scratch_pixels[idx];
515 let ch = char::from_u32(0x2800 + pixel.bits).unwrap_or(' ');
516 (ch, pixel.color)
517 };
518
519 match current_color {
520 Some(c) if c == color => {
521 current_text.push(ch);
522 }
523 Some(c) => {
524 segments.push((std::mem::take(&mut current_text), c));
525 current_text.push(ch);
526 current_color = Some(color);
527 }
528 None => {
529 current_text.push(ch);
530 current_color = Some(color);
531 }
532 }
533 }
534
535 if let Some(color) = current_color {
536 segments.push((current_text, color));
537 }
538 lines.push(segments);
539 }
540
541 lines
542 }
543}
544
545macro_rules! define_breakpoint_methods {
546 (
547 base = $base:ident,
548 arg = $arg:ident : $arg_ty:ty,
549 xs = $xs_fn:ident => [$( $xs_doc:literal ),* $(,)?],
550 sm = $sm_fn:ident => [$( $sm_doc:literal ),* $(,)?],
551 md = $md_fn:ident => [$( $md_doc:literal ),* $(,)?],
552 lg = $lg_fn:ident => [$( $lg_doc:literal ),* $(,)?],
553 xl = $xl_fn:ident => [$( $xl_doc:literal ),* $(,)?],
554 at = $at_fn:ident => [$( $at_doc:literal ),* $(,)?]
555 ) => {
556 $(#[doc = $xs_doc])*
557 pub fn $xs_fn(self, $arg: $arg_ty) -> Self {
558 if self.ctx.breakpoint() == Breakpoint::Xs {
559 self.$base($arg)
560 } else {
561 self
562 }
563 }
564
565 $(#[doc = $sm_doc])*
566 pub fn $sm_fn(self, $arg: $arg_ty) -> Self {
567 if self.ctx.breakpoint() == Breakpoint::Sm {
568 self.$base($arg)
569 } else {
570 self
571 }
572 }
573
574 $(#[doc = $md_doc])*
575 pub fn $md_fn(self, $arg: $arg_ty) -> Self {
576 if self.ctx.breakpoint() == Breakpoint::Md {
577 self.$base($arg)
578 } else {
579 self
580 }
581 }
582
583 $(#[doc = $lg_doc])*
584 pub fn $lg_fn(self, $arg: $arg_ty) -> Self {
585 if self.ctx.breakpoint() == Breakpoint::Lg {
586 self.$base($arg)
587 } else {
588 self
589 }
590 }
591
592 $(#[doc = $xl_doc])*
593 pub fn $xl_fn(self, $arg: $arg_ty) -> Self {
594 if self.ctx.breakpoint() == Breakpoint::Xl {
595 self.$base($arg)
596 } else {
597 self
598 }
599 }
600
601 $(#[doc = $at_doc])*
602 pub fn $at_fn(self, bp: Breakpoint, $arg: $arg_ty) -> Self {
603 if self.ctx.breakpoint() == bp {
604 self.$base($arg)
605 } else {
606 self
607 }
608 }
609 };
610}
611
612impl<'a> ContainerBuilder<'a> {
613 pub fn apply(mut self, style: &ContainerStyle) -> Self {
624 if let Some(base) = style.extends {
626 self = self.apply(base);
627 }
628 if let Some(v) = style.border {
629 self.border = Some(v);
630 }
631 if let Some(v) = style.border_sides {
632 self.border_sides = v;
633 }
634 if let Some(v) = style.border_style {
635 self.border_style = v;
636 }
637 if let Some(v) = style.bg {
638 self.bg = Some(v);
639 }
640 if let Some(v) = style.dark_bg {
641 self.dark_bg = Some(v);
642 }
643 if let Some(v) = style.dark_border_style {
644 self.dark_border_style = Some(v);
645 }
646 if let Some(v) = style.padding {
647 self.padding = v;
648 }
649 if let Some(v) = style.margin {
650 self.margin = v;
651 }
652 if let Some(v) = style.gap {
653 self.gap = v;
654 }
655 if let Some(v) = style.row_gap {
656 self.row_gap = Some(v);
657 }
658 if let Some(v) = style.col_gap {
659 self.col_gap = Some(v);
660 }
661 if let Some(v) = style.grow {
662 self.grow = v;
663 }
664 if let Some(v) = style.align {
665 self.align = v;
666 }
667 if let Some(v) = style.align_self {
668 self.align_self_value = Some(v);
669 }
670 if let Some(v) = style.justify {
671 self.justify = v;
672 }
673 if let Some(v) = style.text_color {
674 self.text_color = Some(v);
675 }
676 if let Some(w) = style.w {
677 self.constraints.min_width = Some(w);
678 self.constraints.max_width = Some(w);
679 }
680 if let Some(h) = style.h {
681 self.constraints.min_height = Some(h);
682 self.constraints.max_height = Some(h);
683 }
684 if let Some(v) = style.min_w {
685 self.constraints.min_width = Some(v);
686 }
687 if let Some(v) = style.max_w {
688 self.constraints.max_width = Some(v);
689 }
690 if let Some(v) = style.min_h {
691 self.constraints.min_height = Some(v);
692 }
693 if let Some(v) = style.max_h {
694 self.constraints.max_height = Some(v);
695 }
696 if let Some(v) = style.w_pct {
697 self.constraints.width_pct = Some(v);
698 }
699 if let Some(v) = style.h_pct {
700 self.constraints.height_pct = Some(v);
701 }
702 if let Some(tc) = style.theme_bg {
704 self.bg = Some(self.ctx.theme.resolve(tc));
705 }
706 if let Some(tc) = style.theme_text_color {
707 self.text_color = Some(self.ctx.theme.resolve(tc));
708 }
709 if let Some(tc) = style.theme_border_fg {
710 let color = self.ctx.theme.resolve(tc);
711 self.border_style = Style::new().fg(color);
712 }
713 self
714 }
715
716 pub fn border(mut self, border: Border) -> Self {
718 self.border = Some(border);
719 self
720 }
721
722 pub fn border_top(mut self, show: bool) -> Self {
724 self.border_sides.top = show;
725 self
726 }
727
728 pub fn border_right(mut self, show: bool) -> Self {
730 self.border_sides.right = show;
731 self
732 }
733
734 pub fn border_bottom(mut self, show: bool) -> Self {
736 self.border_sides.bottom = show;
737 self
738 }
739
740 pub fn border_left(mut self, show: bool) -> Self {
742 self.border_sides.left = show;
743 self
744 }
745
746 pub fn border_sides(mut self, sides: BorderSides) -> Self {
748 self.border_sides = sides;
749 self
750 }
751
752 pub fn border_x(self) -> Self {
754 self.border_sides(BorderSides {
755 top: false,
756 right: true,
757 bottom: false,
758 left: true,
759 })
760 }
761
762 pub fn border_y(self) -> Self {
764 self.border_sides(BorderSides {
765 top: true,
766 right: false,
767 bottom: true,
768 left: false,
769 })
770 }
771
772 pub fn rounded(self) -> Self {
774 self.border(Border::Rounded)
775 }
776
777 pub fn border_style(mut self, style: Style) -> Self {
779 self.border_style = style;
780 self
781 }
782
783 pub fn border_fg(mut self, color: Color) -> Self {
785 self.border_style = self.border_style.fg(color);
786 self
787 }
788
789 pub fn dark_border_style(mut self, style: Style) -> Self {
791 self.dark_border_style = Some(style);
792 self
793 }
794
795 pub fn bg(mut self, color: Color) -> Self {
797 self.bg = Some(color);
798 self
799 }
800
801 pub fn text_color(mut self, color: Color) -> Self {
804 self.text_color = Some(color);
805 self
806 }
807
808 pub fn dark_bg(mut self, color: Color) -> Self {
810 self.dark_bg = Some(color);
811 self
812 }
813
814 pub fn group_hover_bg(mut self, color: Color) -> Self {
816 self.group_hover_bg = Some(color);
817 self
818 }
819
820 pub fn group_hover_border_style(mut self, style: Style) -> Self {
822 self.group_hover_border_style = Some(style);
823 self
824 }
825
826 pub fn p(self, value: u32) -> Self {
830 self.pad(value)
831 }
832
833 pub fn pad(mut self, value: u32) -> Self {
835 self.padding = Padding::all(value);
836 self
837 }
838
839 pub fn px(mut self, value: u32) -> Self {
841 self.padding.left = value;
842 self.padding.right = value;
843 self
844 }
845
846 pub fn py(mut self, value: u32) -> Self {
848 self.padding.top = value;
849 self.padding.bottom = value;
850 self
851 }
852
853 pub fn pt(mut self, value: u32) -> Self {
855 self.padding.top = value;
856 self
857 }
858
859 pub fn pr(mut self, value: u32) -> Self {
861 self.padding.right = value;
862 self
863 }
864
865 pub fn pb(mut self, value: u32) -> Self {
867 self.padding.bottom = value;
868 self
869 }
870
871 pub fn pl(mut self, value: u32) -> Self {
873 self.padding.left = value;
874 self
875 }
876
877 pub fn padding(mut self, padding: Padding) -> Self {
879 self.padding = padding;
880 self
881 }
882
883 pub fn m(mut self, value: u32) -> Self {
887 self.margin = Margin::all(value);
888 self
889 }
890
891 pub fn mx(mut self, value: u32) -> Self {
893 self.margin.left = value;
894 self.margin.right = value;
895 self
896 }
897
898 pub fn my(mut self, value: u32) -> Self {
900 self.margin.top = value;
901 self.margin.bottom = value;
902 self
903 }
904
905 pub fn mt(mut self, value: u32) -> Self {
907 self.margin.top = value;
908 self
909 }
910
911 pub fn mr(mut self, value: u32) -> Self {
913 self.margin.right = value;
914 self
915 }
916
917 pub fn mb(mut self, value: u32) -> Self {
919 self.margin.bottom = value;
920 self
921 }
922
923 pub fn ml(mut self, value: u32) -> Self {
925 self.margin.left = value;
926 self
927 }
928
929 pub fn margin(mut self, margin: Margin) -> Self {
931 self.margin = margin;
932 self
933 }
934
935 pub fn w(mut self, value: u32) -> Self {
939 self.constraints.min_width = Some(value);
940 self.constraints.max_width = Some(value);
941 self
942 }
943
944 define_breakpoint_methods!(
945 base = w,
946 arg = value: u32,
947 xs = xs_w => [
948 "Width applied only at Xs breakpoint (< 40 cols).",
949 "",
950 "# Example",
951 "```ignore",
952 "ui.container().w(20).md_w(40).lg_w(60).col(|ui| { ... });",
953 "```"
954 ],
955 sm = sm_w => ["Width applied only at Sm breakpoint (40-79 cols)."],
956 md = md_w => ["Width applied only at Md breakpoint (80-119 cols)."],
957 lg = lg_w => ["Width applied only at Lg breakpoint (120-159 cols)."],
958 xl = xl_w => ["Width applied only at Xl breakpoint (>= 160 cols)."],
959 at = w_at => ["Width applied only at the given breakpoint."]
960 );
961
962 pub fn h(mut self, value: u32) -> Self {
964 self.constraints.min_height = Some(value);
965 self.constraints.max_height = Some(value);
966 self
967 }
968
969 define_breakpoint_methods!(
970 base = h,
971 arg = value: u32,
972 xs = xs_h => ["Height applied only at Xs breakpoint (< 40 cols)."],
973 sm = sm_h => ["Height applied only at Sm breakpoint (40-79 cols)."],
974 md = md_h => ["Height applied only at Md breakpoint (80-119 cols)."],
975 lg = lg_h => ["Height applied only at Lg breakpoint (120-159 cols)."],
976 xl = xl_h => ["Height applied only at Xl breakpoint (>= 160 cols)."],
977 at = h_at => ["Height applied only at the given breakpoint."]
978 );
979
980 pub fn min_w(mut self, value: u32) -> Self {
982 self.constraints.min_width = Some(value);
983 self
984 }
985
986 define_breakpoint_methods!(
987 base = min_w,
988 arg = value: u32,
989 xs = xs_min_w => ["Minimum width applied only at Xs breakpoint (< 40 cols)."],
990 sm = sm_min_w => ["Minimum width applied only at Sm breakpoint (40-79 cols)."],
991 md = md_min_w => ["Minimum width applied only at Md breakpoint (80-119 cols)."],
992 lg = lg_min_w => ["Minimum width applied only at Lg breakpoint (120-159 cols)."],
993 xl = xl_min_w => ["Minimum width applied only at Xl breakpoint (>= 160 cols)."],
994 at = min_w_at => ["Minimum width applied only at the given breakpoint."]
995 );
996
997 pub fn max_w(mut self, value: u32) -> Self {
999 self.constraints.max_width = Some(value);
1000 self
1001 }
1002
1003 define_breakpoint_methods!(
1004 base = max_w,
1005 arg = value: u32,
1006 xs = xs_max_w => ["Maximum width applied only at Xs breakpoint (< 40 cols)."],
1007 sm = sm_max_w => ["Maximum width applied only at Sm breakpoint (40-79 cols)."],
1008 md = md_max_w => ["Maximum width applied only at Md breakpoint (80-119 cols)."],
1009 lg = lg_max_w => ["Maximum width applied only at Lg breakpoint (120-159 cols)."],
1010 xl = xl_max_w => ["Maximum width applied only at Xl breakpoint (>= 160 cols)."],
1011 at = max_w_at => ["Maximum width applied only at the given breakpoint."]
1012 );
1013
1014 pub fn min_h(mut self, value: u32) -> Self {
1016 self.constraints.min_height = Some(value);
1017 self
1018 }
1019
1020 pub fn max_h(mut self, value: u32) -> Self {
1022 self.constraints.max_height = Some(value);
1023 self
1024 }
1025
1026 pub fn min_width(mut self, value: u32) -> Self {
1028 self.constraints.min_width = Some(value);
1029 self
1030 }
1031
1032 pub fn max_width(mut self, value: u32) -> Self {
1034 self.constraints.max_width = Some(value);
1035 self
1036 }
1037
1038 pub fn min_height(mut self, value: u32) -> Self {
1040 self.constraints.min_height = Some(value);
1041 self
1042 }
1043
1044 pub fn max_height(mut self, value: u32) -> Self {
1046 self.constraints.max_height = Some(value);
1047 self
1048 }
1049
1050 pub fn w_pct(mut self, pct: u8) -> Self {
1052 self.constraints.width_pct = Some(pct.min(100));
1053 self
1054 }
1055
1056 pub fn h_pct(mut self, pct: u8) -> Self {
1058 self.constraints.height_pct = Some(pct.min(100));
1059 self
1060 }
1061
1062 pub fn constraints(mut self, constraints: Constraints) -> Self {
1064 self.constraints = constraints;
1065 self
1066 }
1067
1068 pub fn gap(mut self, gap: u32) -> Self {
1072 self.gap = gap;
1073 self
1074 }
1075
1076 pub fn row_gap(mut self, value: u32) -> Self {
1079 self.row_gap = Some(value);
1080 self
1081 }
1082
1083 pub fn col_gap(mut self, value: u32) -> Self {
1086 self.col_gap = Some(value);
1087 self
1088 }
1089
1090 define_breakpoint_methods!(
1091 base = gap,
1092 arg = value: u32,
1093 xs = xs_gap => ["Gap applied only at Xs breakpoint (< 40 cols)."],
1094 sm = sm_gap => ["Gap applied only at Sm breakpoint (40-79 cols)."],
1095 md = md_gap => [
1096 "Gap applied only at Md breakpoint (80-119 cols).",
1097 "",
1098 "# Example",
1099 "```ignore",
1100 "ui.container().gap(0).md_gap(2).col(|ui| { ... });",
1101 "```"
1102 ],
1103 lg = lg_gap => ["Gap applied only at Lg breakpoint (120-159 cols)."],
1104 xl = xl_gap => ["Gap applied only at Xl breakpoint (>= 160 cols)."],
1105 at = gap_at => ["Gap applied only at the given breakpoint."]
1106 );
1107
1108 pub fn grow(mut self, grow: u16) -> Self {
1110 self.grow = grow;
1111 self
1112 }
1113
1114 define_breakpoint_methods!(
1115 base = grow,
1116 arg = value: u16,
1117 xs = xs_grow => ["Grow factor applied only at Xs breakpoint (< 40 cols)."],
1118 sm = sm_grow => ["Grow factor applied only at Sm breakpoint (40-79 cols)."],
1119 md = md_grow => ["Grow factor applied only at Md breakpoint (80-119 cols)."],
1120 lg = lg_grow => ["Grow factor applied only at Lg breakpoint (120-159 cols)."],
1121 xl = xl_grow => ["Grow factor applied only at Xl breakpoint (>= 160 cols)."],
1122 at = grow_at => ["Grow factor applied only at the given breakpoint."]
1123 );
1124
1125 define_breakpoint_methods!(
1126 base = p,
1127 arg = value: u32,
1128 xs = xs_p => ["Uniform padding applied only at Xs breakpoint (< 40 cols)."],
1129 sm = sm_p => ["Uniform padding applied only at Sm breakpoint (40-79 cols)."],
1130 md = md_p => ["Uniform padding applied only at Md breakpoint (80-119 cols)."],
1131 lg = lg_p => ["Uniform padding applied only at Lg breakpoint (120-159 cols)."],
1132 xl = xl_p => ["Uniform padding applied only at Xl breakpoint (>= 160 cols)."],
1133 at = p_at => ["Padding applied only at the given breakpoint."]
1134 );
1135
1136 pub fn align(mut self, align: Align) -> Self {
1140 self.align = align;
1141 self
1142 }
1143
1144 pub fn center(self) -> Self {
1146 self.align(Align::Center)
1147 }
1148
1149 pub fn justify(mut self, justify: Justify) -> Self {
1151 self.justify = justify;
1152 self
1153 }
1154
1155 pub fn space_between(self) -> Self {
1157 self.justify(Justify::SpaceBetween)
1158 }
1159
1160 pub fn space_around(self) -> Self {
1162 self.justify(Justify::SpaceAround)
1163 }
1164
1165 pub fn space_evenly(self) -> Self {
1167 self.justify(Justify::SpaceEvenly)
1168 }
1169
1170 pub fn flex_center(self) -> Self {
1172 self.justify(Justify::Center).align(Align::Center)
1173 }
1174
1175 pub fn align_self(mut self, align: Align) -> Self {
1178 self.align_self_value = Some(align);
1179 self
1180 }
1181
1182 pub fn title(self, title: impl Into<String>) -> Self {
1186 self.title_styled(title, Style::new())
1187 }
1188
1189 pub fn title_styled(mut self, title: impl Into<String>, style: Style) -> Self {
1191 self.title = Some((title.into(), style));
1192 self
1193 }
1194
1195 pub fn with_if(self, cond: bool, f: impl FnOnce(Self) -> Self) -> Self {
1223 if cond {
1224 f(self)
1225 } else {
1226 self
1227 }
1228 }
1229
1230 pub fn with(self, f: impl FnOnce(Self) -> Self) -> Self {
1248 f(self)
1249 }
1250
1251 pub fn scroll_offset(mut self, offset: u32) -> Self {
1260 self.scroll_offset = Some(offset);
1261 self
1262 }
1263
1264 pub(crate) fn group_name_arc(mut self, name: std::sync::Arc<str>) -> Self {
1272 self.group_name = Some(name);
1273 self
1274 }
1275
1276 pub fn col(self, f: impl FnOnce(&mut Context)) -> Response {
1281 self.finish(Direction::Column, f)
1282 }
1283
1284 pub fn row(self, f: impl FnOnce(&mut Context)) -> Response {
1289 self.finish(Direction::Row, f)
1290 }
1291
1292 pub fn line(mut self, f: impl FnOnce(&mut Context)) -> Response {
1297 self.gap = 0;
1298 self.finish(Direction::Row, f)
1299 }
1300
1301 pub fn draw(self, f: impl FnOnce(&mut crate::buffer::Buffer, Rect) + 'static) {
1316 let draw_id = self.ctx.deferred_draws.len();
1317 self.ctx.deferred_draws.push(Some(Box::new(f)));
1318 self.ctx.skip_interaction_slot();
1319 self.ctx.commands.push(Command::RawDraw {
1320 draw_id,
1321 constraints: self.constraints,
1322 grow: self.grow,
1323 margin: self.margin,
1324 });
1325 }
1326
1327 pub fn draw_with<D: 'static>(
1351 self,
1352 data: D,
1353 f: impl FnOnce(&mut crate::buffer::Buffer, Rect, &D) + 'static,
1354 ) {
1355 let draw_id = self.ctx.deferred_draws.len();
1356 self.ctx
1357 .deferred_draws
1358 .push(Some(Box::new(move |buf, rect| f(buf, rect, &data))));
1359 self.ctx.skip_interaction_slot();
1360 self.ctx.commands.push(Command::RawDraw {
1361 draw_id,
1362 constraints: self.constraints,
1363 grow: self.grow,
1364 margin: self.margin,
1365 });
1366 }
1367
1368 pub fn draw_interactive(
1389 self,
1390 f: impl FnOnce(&mut crate::buffer::Buffer, Rect) + 'static,
1391 ) -> Response {
1392 let draw_id = self.ctx.deferred_draws.len();
1393 self.ctx.deferred_draws.push(Some(Box::new(f)));
1394 let interaction_id = self.ctx.next_interaction_id();
1395 self.ctx.commands.push(Command::RawDraw {
1396 draw_id,
1397 constraints: self.constraints,
1398 grow: self.grow,
1399 margin: self.margin,
1400 });
1401 self.ctx.response_for(interaction_id)
1402 }
1403
1404 fn finish(mut self, direction: Direction, f: impl FnOnce(&mut Context)) -> Response {
1405 let interaction_id = self.ctx.next_interaction_id();
1406 let resolved_gap = match direction {
1407 Direction::Column => self.row_gap.unwrap_or(self.gap),
1408 Direction::Row => self.col_gap.unwrap_or(self.gap),
1409 };
1410
1411 let in_hovered_group = self
1412 .group_name
1413 .as_ref()
1414 .map(|name| self.ctx.is_group_hovered(name))
1415 .unwrap_or(false)
1416 || self
1417 .ctx
1418 .rollback
1419 .group_stack
1420 .last()
1421 .map(|name| self.ctx.is_group_hovered(name))
1422 .unwrap_or(false);
1423 let in_focused_group = self
1424 .group_name
1425 .as_ref()
1426 .map(|name| self.ctx.is_group_focused(name))
1427 .unwrap_or(false)
1428 || self
1429 .ctx
1430 .rollback
1431 .group_stack
1432 .last()
1433 .map(|name| self.ctx.is_group_focused(name))
1434 .unwrap_or(false);
1435
1436 let resolved_bg = if self.ctx.rollback.dark_mode {
1437 self.dark_bg.or(self.bg)
1438 } else {
1439 self.bg
1440 };
1441 let resolved_border_style = if self.ctx.rollback.dark_mode {
1442 self.dark_border_style.unwrap_or(self.border_style)
1443 } else {
1444 self.border_style
1445 };
1446 let bg_color = if in_hovered_group || in_focused_group {
1447 self.group_hover_bg.or(resolved_bg)
1448 } else {
1449 resolved_bg
1450 };
1451 let border_style = if in_hovered_group || in_focused_group {
1452 self.group_hover_border_style
1453 .unwrap_or(resolved_border_style)
1454 } else {
1455 resolved_border_style
1456 };
1457 let group_name = self.group_name.take();
1458 let is_group_container = group_name.is_some();
1459
1460 if let Some(scroll_offset) = self.scroll_offset {
1461 self.ctx
1462 .commands
1463 .push(Command::BeginScrollable(Box::new(BeginScrollableArgs {
1464 grow: self.grow,
1465 border: self.border,
1466 border_sides: self.border_sides,
1467 border_style,
1468 bg_color,
1469 align: self.align,
1470 align_self: self.align_self_value,
1471 justify: self.justify,
1472 gap: resolved_gap,
1473 padding: self.padding,
1474 margin: self.margin,
1475 constraints: self.constraints,
1476 title: self.title,
1477 scroll_offset,
1478 group_name,
1479 })));
1480 } else {
1481 self.ctx
1482 .commands
1483 .push(Command::BeginContainer(Box::new(BeginContainerArgs {
1484 direction,
1485 gap: resolved_gap,
1486 align: self.align,
1487 align_self: self.align_self_value,
1488 justify: self.justify,
1489 border: self.border,
1490 border_sides: self.border_sides,
1491 border_style,
1492 bg_color,
1493 padding: self.padding,
1494 margin: self.margin,
1495 constraints: self.constraints,
1496 title: self.title,
1497 grow: self.grow,
1498 group_name,
1499 })));
1500 }
1501 self.ctx.rollback.text_color_stack.push(self.text_color);
1502 f(self.ctx);
1503 self.ctx.rollback.text_color_stack.pop();
1504 self.ctx.commands.push(Command::EndContainer);
1505 self.ctx.rollback.last_text_idx = None;
1506
1507 if is_group_container {
1508 self.ctx.rollback.group_stack.pop();
1509 self.ctx.rollback.group_count = self.ctx.rollback.group_count.saturating_sub(1);
1510 }
1511
1512 self.ctx.response_for(interaction_id)
1513 }
1514}
1515
1516#[cfg(test)]
1517mod hotfix_tests {
1518 use super::*;
1521
1522 #[test]
1527 fn filled_triangle_paints_expected_interior() {
1528 let mut canvas = CanvasContext::new(20, 20);
1529 canvas.filled_triangle(2, 2, 18, 4, 6, 18);
1530
1531 let lines = canvas.render();
1534 let inside_row = 8 / 4;
1536 let outside_row = 0;
1537 assert!(lines.len() > inside_row);
1539 assert!(lines.len() > outside_row);
1540
1541 let inside: String = lines[inside_row].iter().map(|(s, _)| s.as_str()).collect();
1543 assert!(
1544 inside.chars().any(|c| c != '\u{2800}' && c != ' '),
1545 "expected filled glyphs inside triangle, got: {inside:?}"
1546 );
1547 }
1548
1549 #[test]
1552 fn filled_triangle_handles_tall_triangle_without_panic() {
1553 let mut canvas = CanvasContext::new(8, 50);
1554 canvas.filled_triangle(0, 0, 15, 0, 8, 199);
1555 let lines = canvas.render();
1556 assert_eq!(lines.len(), 50);
1557 }
1558
1559 #[test]
1562 fn filled_triangle_degenerate_horizontal_is_safe() {
1563 let mut canvas = CanvasContext::new(20, 20);
1564 canvas.filled_triangle(0, 0, 10, 0, 19, 0);
1565 let _ = canvas.render();
1566 }
1567
1568 #[test]
1571 fn isqrt_i64_matches_floor_sqrt_for_small_values() {
1572 for n in 0i64..=10_000 {
1573 let expected = (n as f64).sqrt().floor() as isize;
1574 assert_eq!(isqrt_i64(n), expected, "mismatch at n={n}");
1575 }
1576 }
1577
1578 #[test]
1579 fn isqrt_i64_handles_perfect_squares_and_boundaries() {
1580 for k in 0i64..=4096 {
1581 assert_eq!(isqrt_i64(k * k), k as isize);
1582 if k > 0 {
1583 assert_eq!(isqrt_i64(k * k - 1), (k - 1) as isize);
1584 }
1585 }
1586 }
1587
1588 #[test]
1589 fn isqrt_i64_clamps_non_positive_to_zero() {
1590 assert_eq!(isqrt_i64(0), 0);
1591 assert_eq!(isqrt_i64(-1), 0);
1592 assert_eq!(isqrt_i64(i64::MIN), 0);
1593 }
1594
1595 #[test]
1598 fn filled_circle_renders_without_panic_and_is_non_empty() {
1599 let mut canvas = CanvasContext::new(20, 20);
1600 canvas.filled_circle(10, 10, 6);
1601 let lines = canvas.render();
1602 let any_filled = lines
1603 .iter()
1604 .flatten()
1605 .any(|(s, _)| s.chars().any(|c| c != '\u{2800}' && c != ' '));
1606 assert!(any_filled, "filled_circle produced empty output");
1607 }
1608
1609 #[test]
1616 fn scroll_offset_is_crate_internal_api() {
1617 let _ = ContainerBuilder::scroll_offset;
1618 }
1619}