1use super::*;
2
3#[derive(Debug, Clone, Copy)]
21pub struct ModalOptions {
22 pub tab_trap: bool,
32}
33
34impl Default for ModalOptions {
35 fn default() -> Self {
36 Self { tab_trap: true }
37 }
38}
39
40#[must_use = "ContainerBuilder does nothing until .col(), .row(), .line(), or .draw() is called"]
61pub struct ContainerBuilder<'a> {
62 pub(crate) ctx: &'a mut Context,
63 pub(crate) gap: u32,
64 pub(crate) row_gap: Option<u32>,
65 pub(crate) col_gap: Option<u32>,
66 pub(crate) align: Align,
67 pub(crate) align_self_value: Option<Align>,
68 pub(crate) justify: Justify,
69 pub(crate) border: Option<Border>,
70 pub(crate) border_sides: BorderSides,
71 pub(crate) border_style: Style,
72 pub(crate) bg: Option<Color>,
73 pub(crate) text_color: Option<Color>,
74 pub(crate) dark_bg: Option<Color>,
75 pub(crate) dark_border_style: Option<Style>,
76 pub(crate) group_hover_bg: Option<Color>,
77 pub(crate) group_hover_border_style: Option<Style>,
78 pub(crate) group_name: Option<std::sync::Arc<str>>,
79 pub(crate) padding: Padding,
80 pub(crate) margin: Margin,
81 pub(crate) constraints: Constraints,
82 pub(crate) title: Option<(String, Style)>,
83 pub(crate) grow: u16,
84 pub(crate) shrink_flag: bool,
90 pub(crate) scroll_offset: Option<u32>,
91 pub(crate) theme_override: Option<Theme>,
92}
93
94#[derive(Debug, Clone, Copy)]
101struct CanvasPixel {
102 bits: u32,
103 color: Color,
104}
105
106#[derive(Debug, Clone)]
108struct CanvasLabel {
109 x: usize,
110 y: usize,
111 text: String,
112 color: Color,
113}
114
115#[derive(Debug, Clone)]
117struct CanvasLayer {
118 grid: Vec<Vec<CanvasPixel>>,
119 labels: Vec<CanvasLabel>,
120}
121
122pub struct CanvasContext {
124 layers: Vec<CanvasLayer>,
125 cols: usize,
126 rows: usize,
127 px_w: usize,
128 px_h: usize,
129 current_color: Color,
130 scratch_pixels: Vec<CanvasPixel>,
133 scratch_labels: Vec<Option<(char, Color)>>,
136}
137
138#[inline]
145fn isqrt_i64(n: i64) -> isize {
146 if n <= 0 {
147 return 0;
148 }
149 let mut x = (n as f64).sqrt() as i64;
150 while x > 0 && x.saturating_mul(x) > n {
152 x -= 1;
153 }
154 while (x + 1).saturating_mul(x + 1) <= n {
155 x += 1;
156 }
157 x as isize
158}
159
160impl CanvasContext {
161 pub(crate) fn new(cols: usize, rows: usize) -> Self {
162 let cell_count = cols.saturating_mul(rows);
163 Self {
164 layers: vec![Self::new_layer(cols, rows)],
165 cols,
166 rows,
167 px_w: cols * 2,
168 px_h: rows * 4,
169 current_color: Color::Reset,
170 scratch_pixels: vec![
171 CanvasPixel {
172 bits: 0,
173 color: Color::Reset,
174 };
175 cell_count
176 ],
177 scratch_labels: vec![None; cell_count],
178 }
179 }
180
181 fn new_layer(cols: usize, rows: usize) -> CanvasLayer {
182 CanvasLayer {
183 grid: vec![
184 vec![
185 CanvasPixel {
186 bits: 0,
187 color: Color::Reset,
188 };
189 cols
190 ];
191 rows
192 ],
193 labels: Vec::new(),
194 }
195 }
196
197 fn current_layer_mut(&mut self) -> Option<&mut CanvasLayer> {
198 self.layers.last_mut()
199 }
200
201 fn dot_with_color(&mut self, x: usize, y: usize, color: Color) {
202 if x >= self.px_w || y >= self.px_h {
203 return;
204 }
205
206 let char_col = x / 2;
207 let char_row = y / 4;
208 let sub_col = x % 2;
209 let sub_row = y % 4;
210 const LEFT_BITS: [u32; 4] = [0x01, 0x02, 0x04, 0x40];
211 const RIGHT_BITS: [u32; 4] = [0x08, 0x10, 0x20, 0x80];
212
213 let bit = if sub_col == 0 {
214 LEFT_BITS[sub_row]
215 } else {
216 RIGHT_BITS[sub_row]
217 };
218
219 if let Some(layer) = self.current_layer_mut() {
220 let cell = &mut layer.grid[char_row][char_col];
221 let new_bits = cell.bits | bit;
222 if new_bits != cell.bits {
223 cell.bits = new_bits;
224 cell.color = color;
225 }
226 }
227 }
228
229 fn dot_isize(&mut self, x: isize, y: isize) {
230 if x >= 0 && y >= 0 {
231 self.dot(x as usize, y as usize);
232 }
233 }
234
235 pub fn width(&self) -> usize {
237 self.px_w
238 }
239
240 pub fn height(&self) -> usize {
242 self.px_h
243 }
244
245 pub fn dot(&mut self, x: usize, y: usize) {
247 self.dot_with_color(x, y, self.current_color);
248 }
249
250 pub fn line(&mut self, x0: usize, y0: usize, x1: usize, y1: usize) {
252 let (mut x, mut y) = (x0 as isize, y0 as isize);
253 let (x1, y1) = (x1 as isize, y1 as isize);
254 let dx = (x1 - x).abs();
255 let dy = -(y1 - y).abs();
256 let sx = if x < x1 { 1 } else { -1 };
257 let sy = if y < y1 { 1 } else { -1 };
258 let mut err = dx + dy;
259
260 loop {
261 self.dot_isize(x, y);
262 if x == x1 && y == y1 {
263 break;
264 }
265 let e2 = 2 * err;
266 if e2 >= dy {
267 err += dy;
268 x += sx;
269 }
270 if e2 <= dx {
271 err += dx;
272 y += sy;
273 }
274 }
275 }
276
277 pub fn rect(&mut self, x: usize, y: usize, w: usize, h: usize) {
279 if w == 0 || h == 0 {
280 return;
281 }
282
283 self.line(x, y, x + w.saturating_sub(1), y);
284 self.line(
285 x + w.saturating_sub(1),
286 y,
287 x + w.saturating_sub(1),
288 y + h.saturating_sub(1),
289 );
290 self.line(
291 x + w.saturating_sub(1),
292 y + h.saturating_sub(1),
293 x,
294 y + h.saturating_sub(1),
295 );
296 self.line(x, y + h.saturating_sub(1), x, y);
297 }
298
299 pub fn circle(&mut self, cx: usize, cy: usize, r: usize) {
301 let mut x = r as isize;
302 let mut y: isize = 0;
303 let mut err: isize = 1 - x;
304 let (cx, cy) = (cx as isize, cy as isize);
305
306 while x >= y {
307 for &(dx, dy) in &[
308 (x, y),
309 (y, x),
310 (-x, y),
311 (-y, x),
312 (x, -y),
313 (y, -x),
314 (-x, -y),
315 (-y, -x),
316 ] {
317 let px = cx + dx;
318 let py = cy + dy;
319 self.dot_isize(px, py);
320 }
321
322 y += 1;
323 if err < 0 {
324 err += 2 * y + 1;
325 } else {
326 x -= 1;
327 err += 2 * (y - x) + 1;
328 }
329 }
330 }
331
332 pub fn set_color(&mut self, color: Color) {
334 self.current_color = color;
335 }
336
337 pub fn color(&self) -> Color {
339 self.current_color
340 }
341
342 pub fn filled_rect(&mut self, x: usize, y: usize, w: usize, h: usize) {
344 if w == 0 || h == 0 {
345 return;
346 }
347
348 let x_end = x.saturating_add(w).min(self.px_w);
349 let y_end = y.saturating_add(h).min(self.px_h);
350 if x >= x_end || y >= y_end {
351 return;
352 }
353
354 for yy in y..y_end {
355 self.line(x, yy, x_end.saturating_sub(1), yy);
356 }
357 }
358
359 pub fn filled_circle(&mut self, cx: usize, cy: usize, r: usize) {
361 let (cx, cy, r) = (cx as isize, cy as isize, r as isize);
362 for y in (cy - r)..=(cy + r) {
363 let dy = y - cy;
364 let span_sq = (r * r - dy * dy).max(0);
365 let dx = isqrt_i64(span_sq as i64);
367 for x in (cx - dx)..=(cx + dx) {
368 self.dot_isize(x, y);
369 }
370 }
371 }
372
373 pub fn triangle(&mut self, x0: usize, y0: usize, x1: usize, y1: usize, x2: usize, y2: usize) {
375 self.line(x0, y0, x1, y1);
376 self.line(x1, y1, x2, y2);
377 self.line(x2, y2, x0, y0);
378 }
379
380 pub fn filled_triangle(
382 &mut self,
383 x0: usize,
384 y0: usize,
385 x1: usize,
386 y1: usize,
387 x2: usize,
388 y2: usize,
389 ) {
390 let vertices = [
391 (x0 as isize, y0 as isize),
392 (x1 as isize, y1 as isize),
393 (x2 as isize, y2 as isize),
394 ];
395 let min_y = vertices.iter().map(|(_, y)| *y).min().unwrap_or(0);
396 let max_y = vertices.iter().map(|(_, y)| *y).max().unwrap_or(-1);
397
398 for y in min_y..=max_y {
399 let mut intersections = [0.0f64; 4];
403 let mut isect_count = 0usize;
404
405 for edge in [(0usize, 1usize), (1usize, 2usize), (2usize, 0usize)] {
406 let (x_a, y_a) = vertices[edge.0];
407 let (x_b, y_b) = vertices[edge.1];
408 if y_a == y_b {
409 continue;
410 }
411
412 let (x_start, y_start, x_end, y_end) = if y_a < y_b {
413 (x_a, y_a, x_b, y_b)
414 } else {
415 (x_b, y_b, x_a, y_a)
416 };
417
418 if y < y_start || y >= y_end {
419 continue;
420 }
421
422 let t = (y - y_start) as f64 / (y_end - y_start) as f64;
423 if isect_count < intersections.len() {
424 intersections[isect_count] = x_start as f64 + t * (x_end - x_start) as f64;
425 isect_count += 1;
426 }
427 }
428
429 intersections[..isect_count].sort_by(|a, b| a.total_cmp(b));
430 let mut i = 0usize;
431 while i + 1 < isect_count {
432 let x_start = intersections[i].ceil() as isize;
433 let x_end = intersections[i + 1].floor() as isize;
434 for x in x_start..=x_end {
435 self.dot_isize(x, y);
436 }
437 i += 2;
438 }
439 }
440
441 self.triangle(x0, y0, x1, y1, x2, y2);
442 }
443
444 pub fn points(&mut self, pts: &[(usize, usize)]) {
446 for &(x, y) in pts {
447 self.dot(x, y);
448 }
449 }
450
451 pub fn polyline(&mut self, pts: &[(usize, usize)]) {
453 for window in pts.windows(2) {
454 if let [(x0, y0), (x1, y1)] = window {
455 self.line(*x0, *y0, *x1, *y1);
456 }
457 }
458 }
459
460 pub fn print(&mut self, x: usize, y: usize, text: &str) {
463 if text.is_empty() {
464 return;
465 }
466
467 let color = self.current_color;
468 if let Some(layer) = self.current_layer_mut() {
469 layer.labels.push(CanvasLabel {
470 x,
471 y,
472 text: text.to_string(),
473 color,
474 });
475 }
476 }
477
478 pub fn layer(&mut self) {
480 self.layers.push(Self::new_layer(self.cols, self.rows));
481 }
482
483 pub(crate) fn render(&mut self) -> Vec<Vec<(String, Color)>> {
484 let cell_count = self.cols.saturating_mul(self.rows);
485
486 if self.scratch_pixels.len() < cell_count {
489 self.scratch_pixels.resize(
490 cell_count,
491 CanvasPixel {
492 bits: 0,
493 color: Color::Reset,
494 },
495 );
496 }
497 if self.scratch_labels.len() < cell_count {
498 self.scratch_labels.resize(cell_count, None);
499 }
500 for px in &mut self.scratch_pixels[..cell_count] {
501 *px = CanvasPixel {
502 bits: 0,
503 color: Color::Reset,
504 };
505 }
506 for slot in &mut self.scratch_labels[..cell_count] {
507 *slot = None;
508 }
509
510 let cols = self.cols;
511 let rows = self.rows;
512
513 for layer in &self.layers {
514 for (row, src_row) in layer.grid.iter().enumerate().take(rows) {
515 let row_offset = row * cols;
516 for (col, src) in src_row.iter().enumerate().take(cols) {
517 if src.bits == 0 {
518 continue;
519 }
520 let dst = &mut self.scratch_pixels[row_offset + col];
521 let merged = dst.bits | src.bits;
522 if merged != dst.bits {
523 dst.bits = merged;
524 dst.color = src.color;
525 }
526 }
527 }
528
529 for label in &layer.labels {
530 let row = label.y / 4;
531 if row >= rows {
532 continue;
533 }
534 let start_col = label.x / 2;
535 let row_offset = row * cols;
536 for (offset, ch) in label.text.chars().enumerate() {
537 let col = start_col + offset;
538 if col >= cols {
539 break;
540 }
541 self.scratch_labels[row_offset + col] = Some((ch, label.color));
542 }
543 }
544 }
545
546 let mut lines: Vec<Vec<(String, Color)>> = Vec::with_capacity(rows);
547 for row in 0..rows {
548 let row_offset = row * cols;
549 let mut segments: Vec<(String, Color)> = Vec::new();
550 let mut current_color: Option<Color> = None;
551 let mut current_text = String::new();
552
553 for col in 0..cols {
554 let idx = row_offset + col;
555 let (ch, color) = if let Some((label_ch, label_color)) = self.scratch_labels[idx] {
556 (label_ch, label_color)
557 } else {
558 let pixel = self.scratch_pixels[idx];
559 let ch = char::from_u32(0x2800 + pixel.bits).unwrap_or(' ');
560 (ch, pixel.color)
561 };
562
563 match current_color {
564 Some(c) if c == color => {
565 current_text.push(ch);
566 }
567 Some(c) => {
568 segments.push((std::mem::take(&mut current_text), c));
569 current_text.push(ch);
570 current_color = Some(color);
571 }
572 None => {
573 current_text.push(ch);
574 current_color = Some(color);
575 }
576 }
577 }
578
579 if let Some(color) = current_color {
580 segments.push((current_text, color));
581 }
582 lines.push(segments);
583 }
584
585 lines
586 }
587}
588
589macro_rules! define_breakpoint_methods {
590 (
591 base = $base:ident,
592 arg = $arg:ident : $arg_ty:ty,
593 xs = $xs_fn:ident => [$( $xs_doc:literal ),* $(,)?],
594 sm = $sm_fn:ident => [$( $sm_doc:literal ),* $(,)?],
595 md = $md_fn:ident => [$( $md_doc:literal ),* $(,)?],
596 lg = $lg_fn:ident => [$( $lg_doc:literal ),* $(,)?],
597 xl = $xl_fn:ident => [$( $xl_doc:literal ),* $(,)?],
598 at = $at_fn:ident => [$( $at_doc:literal ),* $(,)?]
599 ) => {
600 $(#[doc = $xs_doc])*
601 pub fn $xs_fn(self, $arg: $arg_ty) -> Self {
602 if self.ctx.breakpoint() == Breakpoint::Xs {
603 self.$base($arg)
604 } else {
605 self
606 }
607 }
608
609 $(#[doc = $sm_doc])*
610 pub fn $sm_fn(self, $arg: $arg_ty) -> Self {
611 if self.ctx.breakpoint() == Breakpoint::Sm {
612 self.$base($arg)
613 } else {
614 self
615 }
616 }
617
618 $(#[doc = $md_doc])*
619 pub fn $md_fn(self, $arg: $arg_ty) -> Self {
620 if self.ctx.breakpoint() == Breakpoint::Md {
621 self.$base($arg)
622 } else {
623 self
624 }
625 }
626
627 $(#[doc = $lg_doc])*
628 pub fn $lg_fn(self, $arg: $arg_ty) -> Self {
629 if self.ctx.breakpoint() == Breakpoint::Lg {
630 self.$base($arg)
631 } else {
632 self
633 }
634 }
635
636 $(#[doc = $xl_doc])*
637 pub fn $xl_fn(self, $arg: $arg_ty) -> Self {
638 if self.ctx.breakpoint() == Breakpoint::Xl {
639 self.$base($arg)
640 } else {
641 self
642 }
643 }
644
645 $(#[doc = $at_doc])*
646 pub fn $at_fn(self, bp: Breakpoint, $arg: $arg_ty) -> Self {
647 if self.ctx.breakpoint() == bp {
648 self.$base($arg)
649 } else {
650 self
651 }
652 }
653 };
654}
655
656impl<'a> ContainerBuilder<'a> {
657 pub fn apply(mut self, style: &ContainerStyle) -> Self {
668 if let Some(base) = style.extends {
670 self = self.apply(base);
671 }
672 if let Some(v) = style.border {
673 self.border = Some(v);
674 }
675 if let Some(v) = style.border_sides {
676 self.border_sides = v;
677 }
678 if let Some(v) = style.border_style {
679 self.border_style = v;
680 }
681 if let Some(v) = style.bg {
682 self.bg = Some(v);
683 }
684 if let Some(v) = style.dark_bg {
685 self.dark_bg = Some(v);
686 }
687 if let Some(v) = style.dark_border_style {
688 self.dark_border_style = Some(v);
689 }
690 if let Some(v) = style.padding {
691 self.padding = v;
692 }
693 if let Some(v) = style.margin {
694 self.margin = v;
695 }
696 if let Some(v) = style.gap {
697 self.gap = v;
698 }
699 if let Some(v) = style.row_gap {
700 self.row_gap = Some(v);
701 }
702 if let Some(v) = style.col_gap {
703 self.col_gap = Some(v);
704 }
705 if let Some(v) = style.grow {
706 self.grow = v;
707 }
708 if let Some(v) = style.align {
709 self.align = v;
710 }
711 if let Some(v) = style.align_self {
712 self.align_self_value = Some(v);
713 }
714 if let Some(v) = style.justify {
715 self.justify = v;
716 }
717 if let Some(v) = style.text_color {
718 self.text_color = Some(v);
719 }
720 if let Some(w) = style.w {
721 self.constraints = self.constraints.w(w);
722 }
723 if let Some(h) = style.h {
724 self.constraints = self.constraints.h(h);
725 }
726 if let Some(v) = style.min_w {
727 self.constraints.set_min_width(Some(v));
728 }
729 if let Some(v) = style.max_w {
730 self.constraints.set_max_width(Some(v));
731 }
732 if let Some(v) = style.min_h {
733 self.constraints.set_min_height(Some(v));
734 }
735 if let Some(v) = style.max_h {
736 self.constraints.set_max_height(Some(v));
737 }
738 if let Some(v) = style.w_pct {
739 self.constraints.set_width_pct(Some(v));
740 }
741 if let Some(v) = style.h_pct {
742 self.constraints.set_height_pct(Some(v));
743 }
744 if let Some(tc) = style.theme_bg {
746 self.bg = Some(self.ctx.theme.resolve(tc));
747 }
748 if let Some(tc) = style.theme_text_color {
749 self.text_color = Some(self.ctx.theme.resolve(tc));
750 }
751 if let Some(tc) = style.theme_border_fg {
752 let color = self.ctx.theme.resolve(tc);
753 self.border_style = Style::new().fg(color);
754 }
755 self
756 }
757
758 pub fn border(mut self, border: Border) -> Self {
760 self.border = Some(border);
761 self
762 }
763
764 pub fn border_top(mut self, show: bool) -> Self {
766 self.border_sides.top = show;
767 self
768 }
769
770 pub fn border_right(mut self, show: bool) -> Self {
772 self.border_sides.right = show;
773 self
774 }
775
776 pub fn border_bottom(mut self, show: bool) -> Self {
778 self.border_sides.bottom = show;
779 self
780 }
781
782 pub fn border_left(mut self, show: bool) -> Self {
784 self.border_sides.left = show;
785 self
786 }
787
788 pub fn border_sides(mut self, sides: BorderSides) -> Self {
790 self.border_sides = sides;
791 self
792 }
793
794 pub fn border_x(self) -> Self {
796 self.border_sides(BorderSides {
797 top: false,
798 right: true,
799 bottom: false,
800 left: true,
801 })
802 }
803
804 pub fn border_y(self) -> Self {
806 self.border_sides(BorderSides {
807 top: true,
808 right: false,
809 bottom: true,
810 left: false,
811 })
812 }
813
814 pub fn rounded(self) -> Self {
816 self.border(Border::Rounded)
817 }
818
819 pub fn border_style(mut self, style: Style) -> Self {
821 self.border_style = style;
822 self
823 }
824
825 pub fn border_fg(mut self, color: Color) -> Self {
827 self.border_style = self.border_style.fg(color);
828 self
829 }
830
831 pub fn dark_border_style(mut self, style: Style) -> Self {
833 self.dark_border_style = Some(style);
834 self
835 }
836
837 pub fn bg(mut self, color: Color) -> Self {
839 self.bg = Some(color);
840 self
841 }
842
843 pub fn text_color(mut self, color: Color) -> Self {
846 self.text_color = Some(color);
847 self
848 }
849
850 pub fn dark_bg(mut self, color: Color) -> Self {
852 self.dark_bg = Some(color);
853 self
854 }
855
856 pub fn group_hover_bg(mut self, color: Color) -> Self {
858 self.group_hover_bg = Some(color);
859 self
860 }
861
862 pub fn group_hover_border_style(mut self, style: Style) -> Self {
864 self.group_hover_border_style = Some(style);
865 self
866 }
867
868 pub fn p(mut self, value: u32) -> Self {
872 self.padding = Padding::all(value);
873 self
874 }
875
876 #[deprecated(since = "0.20.0", note = "Use `p()` instead")]
878 pub fn pad(self, value: u32) -> Self {
879 self.p(value)
880 }
881
882 pub fn px(mut self, value: u32) -> Self {
884 self.padding.left = value;
885 self.padding.right = value;
886 self
887 }
888
889 pub fn py(mut self, value: u32) -> Self {
891 self.padding.top = value;
892 self.padding.bottom = value;
893 self
894 }
895
896 pub fn pt(mut self, value: u32) -> Self {
898 self.padding.top = value;
899 self
900 }
901
902 pub fn pr(mut self, value: u32) -> Self {
904 self.padding.right = value;
905 self
906 }
907
908 pub fn pb(mut self, value: u32) -> Self {
910 self.padding.bottom = value;
911 self
912 }
913
914 pub fn pl(mut self, value: u32) -> Self {
916 self.padding.left = value;
917 self
918 }
919
920 pub fn padding(mut self, padding: Padding) -> Self {
922 self.padding = padding;
923 self
924 }
925
926 pub fn m(mut self, value: u32) -> Self {
930 self.margin = Margin::all(value);
931 self
932 }
933
934 pub fn mx(mut self, value: u32) -> Self {
936 self.margin.left = value;
937 self.margin.right = value;
938 self
939 }
940
941 pub fn my(mut self, value: u32) -> Self {
943 self.margin.top = value;
944 self.margin.bottom = value;
945 self
946 }
947
948 pub fn mt(mut self, value: u32) -> Self {
950 self.margin.top = value;
951 self
952 }
953
954 pub fn mr(mut self, value: u32) -> Self {
956 self.margin.right = value;
957 self
958 }
959
960 pub fn mb(mut self, value: u32) -> Self {
962 self.margin.bottom = value;
963 self
964 }
965
966 pub fn ml(mut self, value: u32) -> Self {
968 self.margin.left = value;
969 self
970 }
971
972 pub fn margin(mut self, margin: Margin) -> Self {
974 self.margin = margin;
975 self
976 }
977
978 pub fn w(mut self, value: u32) -> Self {
982 self.constraints = self.constraints.w(value);
983 self
984 }
985
986 define_breakpoint_methods!(
987 base = w,
988 arg = value: u32,
989 xs = xs_w => [
990 "Width applied only at Xs breakpoint (< 40 cols).",
991 "",
992 "# Example",
993 "```ignore",
994 "ui.container().w(20).md_w(40).lg_w(60).col(|ui| { ... });",
995 "```"
996 ],
997 sm = sm_w => ["Width applied only at Sm breakpoint (40-79 cols)."],
998 md = md_w => ["Width applied only at Md breakpoint (80-119 cols)."],
999 lg = lg_w => ["Width applied only at Lg breakpoint (120-159 cols)."],
1000 xl = xl_w => ["Width applied only at Xl breakpoint (>= 160 cols)."],
1001 at = w_at => ["Width applied only at the given breakpoint."]
1002 );
1003
1004 pub fn h(mut self, value: u32) -> Self {
1006 self.constraints = self.constraints.h(value);
1007 self
1008 }
1009
1010 define_breakpoint_methods!(
1011 base = h,
1012 arg = value: u32,
1013 xs = xs_h => ["Height applied only at Xs breakpoint (< 40 cols)."],
1014 sm = sm_h => ["Height applied only at Sm breakpoint (40-79 cols)."],
1015 md = md_h => ["Height applied only at Md breakpoint (80-119 cols)."],
1016 lg = lg_h => ["Height applied only at Lg breakpoint (120-159 cols)."],
1017 xl = xl_h => ["Height applied only at Xl breakpoint (>= 160 cols)."],
1018 at = h_at => ["Height applied only at the given breakpoint."]
1019 );
1020
1021 pub fn min_w(mut self, value: u32) -> Self {
1023 self.constraints.set_min_width(Some(value));
1024 self
1025 }
1026
1027 define_breakpoint_methods!(
1028 base = min_w,
1029 arg = value: u32,
1030 xs = xs_min_w => ["Minimum width applied only at Xs breakpoint (< 40 cols)."],
1031 sm = sm_min_w => ["Minimum width applied only at Sm breakpoint (40-79 cols)."],
1032 md = md_min_w => ["Minimum width applied only at Md breakpoint (80-119 cols)."],
1033 lg = lg_min_w => ["Minimum width applied only at Lg breakpoint (120-159 cols)."],
1034 xl = xl_min_w => ["Minimum width applied only at Xl breakpoint (>= 160 cols)."],
1035 at = min_w_at => ["Minimum width applied only at the given breakpoint."]
1036 );
1037
1038 pub fn max_w(mut self, value: u32) -> Self {
1040 self.constraints.set_max_width(Some(value));
1041 self
1042 }
1043
1044 define_breakpoint_methods!(
1045 base = max_w,
1046 arg = value: u32,
1047 xs = xs_max_w => ["Maximum width applied only at Xs breakpoint (< 40 cols)."],
1048 sm = sm_max_w => ["Maximum width applied only at Sm breakpoint (40-79 cols)."],
1049 md = md_max_w => ["Maximum width applied only at Md breakpoint (80-119 cols)."],
1050 lg = lg_max_w => ["Maximum width applied only at Lg breakpoint (120-159 cols)."],
1051 xl = xl_max_w => ["Maximum width applied only at Xl breakpoint (>= 160 cols)."],
1052 at = max_w_at => ["Maximum width applied only at the given breakpoint."]
1053 );
1054
1055 pub fn min_h(mut self, value: u32) -> Self {
1057 self.constraints.set_min_height(Some(value));
1058 self
1059 }
1060
1061 define_breakpoint_methods!(
1062 base = min_h,
1063 arg = value: u32,
1064 xs = xs_min_h => ["Minimum height applied only at Xs breakpoint (< 40 cols)."],
1065 sm = sm_min_h => ["Minimum height applied only at Sm breakpoint (40-79 cols)."],
1066 md = md_min_h => ["Minimum height applied only at Md breakpoint (80-119 cols)."],
1067 lg = lg_min_h => ["Minimum height applied only at Lg breakpoint (120-159 cols)."],
1068 xl = xl_min_h => ["Minimum height applied only at Xl breakpoint (>= 160 cols)."],
1069 at = min_h_at => ["Minimum height applied only at the given breakpoint."]
1070 );
1071
1072 pub fn max_h(mut self, value: u32) -> Self {
1074 self.constraints.set_max_height(Some(value));
1075 self
1076 }
1077
1078 define_breakpoint_methods!(
1079 base = max_h,
1080 arg = value: u32,
1081 xs = xs_max_h => ["Maximum height applied only at Xs breakpoint (< 40 cols)."],
1082 sm = sm_max_h => ["Maximum height applied only at Sm breakpoint (40-79 cols)."],
1083 md = md_max_h => ["Maximum height applied only at Md breakpoint (80-119 cols)."],
1084 lg = lg_max_h => ["Maximum height applied only at Lg breakpoint (120-159 cols)."],
1085 xl = xl_max_h => ["Maximum height applied only at Xl breakpoint (>= 160 cols)."],
1086 at = max_h_at => ["Maximum height applied only at the given breakpoint."]
1087 );
1088
1089 #[deprecated(since = "0.20.0", note = "Use `min_w()` instead")]
1091 pub fn min_width(self, value: u32) -> Self {
1092 self.min_w(value)
1093 }
1094
1095 #[deprecated(since = "0.20.0", note = "Use `max_w()` instead")]
1097 pub fn max_width(self, value: u32) -> Self {
1098 self.max_w(value)
1099 }
1100
1101 #[deprecated(since = "0.20.0", note = "Use `min_h()` instead")]
1103 pub fn min_height(self, value: u32) -> Self {
1104 self.min_h(value)
1105 }
1106
1107 #[deprecated(since = "0.20.0", note = "Use `max_h()` instead")]
1109 pub fn max_height(self, value: u32) -> Self {
1110 self.max_h(value)
1111 }
1112
1113 pub fn w_pct(mut self, pct: u8) -> Self {
1115 self.constraints.set_width_pct(Some(pct.min(100)));
1116 self
1117 }
1118
1119 pub fn h_pct(mut self, pct: u8) -> Self {
1121 self.constraints.set_height_pct(Some(pct.min(100)));
1122 self
1123 }
1124
1125 pub fn constraints(mut self, constraints: Constraints) -> Self {
1127 self.constraints = constraints;
1128 self
1129 }
1130
1131 pub fn gap(mut self, gap: u32) -> Self {
1135 self.gap = gap;
1136 self
1137 }
1138
1139 pub fn row_gap(mut self, value: u32) -> Self {
1142 self.row_gap = Some(value);
1143 self
1144 }
1145
1146 pub fn col_gap(mut self, value: u32) -> Self {
1149 self.col_gap = Some(value);
1150 self
1151 }
1152
1153 define_breakpoint_methods!(
1154 base = gap,
1155 arg = value: u32,
1156 xs = xs_gap => ["Gap applied only at Xs breakpoint (< 40 cols)."],
1157 sm = sm_gap => ["Gap applied only at Sm breakpoint (40-79 cols)."],
1158 md = md_gap => [
1159 "Gap applied only at Md breakpoint (80-119 cols).",
1160 "",
1161 "# Example",
1162 "```ignore",
1163 "ui.container().gap(0).md_gap(2).col(|ui| { ... });",
1164 "```"
1165 ],
1166 lg = lg_gap => ["Gap applied only at Lg breakpoint (120-159 cols)."],
1167 xl = xl_gap => ["Gap applied only at Xl breakpoint (>= 160 cols)."],
1168 at = gap_at => ["Gap applied only at the given breakpoint."]
1169 );
1170
1171 pub fn grow(mut self, grow: u16) -> Self {
1173 self.grow = grow;
1174 self
1175 }
1176
1177 pub fn fill(self) -> Self {
1194 self.grow(1)
1195 }
1196
1197 pub fn shrink(mut self) -> Self {
1239 self.shrink_flag = true;
1240 self
1241 }
1242
1243 define_breakpoint_methods!(
1244 base = grow,
1245 arg = value: u16,
1246 xs = xs_grow => ["Grow factor applied only at Xs breakpoint (< 40 cols)."],
1247 sm = sm_grow => ["Grow factor applied only at Sm breakpoint (40-79 cols)."],
1248 md = md_grow => ["Grow factor applied only at Md breakpoint (80-119 cols)."],
1249 lg = lg_grow => ["Grow factor applied only at Lg breakpoint (120-159 cols)."],
1250 xl = xl_grow => ["Grow factor applied only at Xl breakpoint (>= 160 cols)."],
1251 at = grow_at => ["Grow factor applied only at the given breakpoint."]
1252 );
1253
1254 define_breakpoint_methods!(
1255 base = p,
1256 arg = value: u32,
1257 xs = xs_p => ["Uniform padding applied only at Xs breakpoint (< 40 cols)."],
1258 sm = sm_p => ["Uniform padding applied only at Sm breakpoint (40-79 cols)."],
1259 md = md_p => ["Uniform padding applied only at Md breakpoint (80-119 cols)."],
1260 lg = lg_p => ["Uniform padding applied only at Lg breakpoint (120-159 cols)."],
1261 xl = xl_p => ["Uniform padding applied only at Xl breakpoint (>= 160 cols)."],
1262 at = p_at => ["Padding applied only at the given breakpoint."]
1263 );
1264
1265 pub fn align(mut self, align: Align) -> Self {
1269 self.align = align;
1270 self
1271 }
1272
1273 pub fn center(self) -> Self {
1275 self.align(Align::Center)
1276 }
1277
1278 pub fn justify(mut self, justify: Justify) -> Self {
1280 self.justify = justify;
1281 self
1282 }
1283
1284 pub fn space_between(self) -> Self {
1286 self.justify(Justify::SpaceBetween)
1287 }
1288
1289 pub fn space_around(self) -> Self {
1291 self.justify(Justify::SpaceAround)
1292 }
1293
1294 pub fn space_evenly(self) -> Self {
1296 self.justify(Justify::SpaceEvenly)
1297 }
1298
1299 pub fn flex_center(self) -> Self {
1301 self.justify(Justify::Center).align(Align::Center)
1302 }
1303
1304 pub fn align_self(mut self, align: Align) -> Self {
1307 self.align_self_value = Some(align);
1308 self
1309 }
1310
1311 pub fn title(self, title: impl Into<String>) -> Self {
1315 self.title_styled(title, Style::new())
1316 }
1317
1318 pub fn title_styled(mut self, title: impl Into<String>, style: Style) -> Self {
1320 self.title = Some((title.into(), style));
1321 self
1322 }
1323
1324 pub fn with_if(self, cond: bool, f: impl FnOnce(Self) -> Self) -> Self {
1352 if cond {
1353 f(self)
1354 } else {
1355 self
1356 }
1357 }
1358
1359 pub fn theme(mut self, theme: Theme) -> Self {
1391 self.theme_override = Some(theme);
1392 self
1393 }
1394
1395 pub fn with(self, f: impl FnOnce(Self) -> Self) -> Self {
1413 f(self)
1414 }
1415
1416 #[doc(hidden)]
1430 pub fn scroll_offset(mut self, offset: u32) -> Self {
1431 self.scroll_offset = Some(offset);
1432 self
1433 }
1434
1435 pub(crate) fn group_name_arc(mut self, name: std::sync::Arc<str>) -> Self {
1443 self.group_name = Some(name);
1444 self
1445 }
1446
1447 pub fn col(self, f: impl FnOnce(&mut Context)) -> Response {
1452 self.finish(Direction::Column, f)
1453 }
1454
1455 pub fn row(self, f: impl FnOnce(&mut Context)) -> Response {
1460 self.finish(Direction::Row, f)
1461 }
1462
1463 pub fn line(mut self, f: impl FnOnce(&mut Context)) -> Response {
1468 self.gap = 0;
1469 self.finish(Direction::Row, f)
1470 }
1471
1472 pub fn draw(self, f: impl FnOnce(&mut crate::buffer::Buffer, Rect) + 'static) {
1487 let draw_id = self.ctx.deferred_draws.len();
1488 self.ctx.deferred_draws.push(Some(Box::new(f)));
1489 self.ctx.skip_interaction_slot();
1490 self.ctx.commands.push(Command::RawDraw {
1491 draw_id,
1492 constraints: self.constraints,
1493 grow: self.grow,
1494 margin: self.margin,
1495 });
1496 }
1497
1498 pub fn draw_with<D: 'static>(
1522 self,
1523 data: D,
1524 f: impl FnOnce(&mut crate::buffer::Buffer, Rect, &D) + 'static,
1525 ) {
1526 let draw_id = self.ctx.deferred_draws.len();
1527 self.ctx
1528 .deferred_draws
1529 .push(Some(Box::new(move |buf, rect| f(buf, rect, &data))));
1530 self.ctx.skip_interaction_slot();
1531 self.ctx.commands.push(Command::RawDraw {
1532 draw_id,
1533 constraints: self.constraints,
1534 grow: self.grow,
1535 margin: self.margin,
1536 });
1537 }
1538
1539 pub fn draw_interactive(
1560 self,
1561 f: impl FnOnce(&mut crate::buffer::Buffer, Rect) + 'static,
1562 ) -> Response {
1563 let draw_id = self.ctx.deferred_draws.len();
1564 self.ctx.deferred_draws.push(Some(Box::new(f)));
1565 let interaction_id = self.ctx.next_interaction_id();
1566 self.ctx.commands.push(Command::RawDraw {
1567 draw_id,
1568 constraints: self.constraints,
1569 grow: self.grow,
1570 margin: self.margin,
1571 });
1572 self.ctx.response_for(interaction_id)
1573 }
1574
1575 fn finish(mut self, direction: Direction, f: impl FnOnce(&mut Context)) -> Response {
1576 let interaction_id = self.ctx.next_interaction_id();
1577 let resolved_gap = match direction {
1578 Direction::Column => self.row_gap.unwrap_or(self.gap),
1579 Direction::Row => self.col_gap.unwrap_or(self.gap),
1580 };
1581
1582 let in_hovered_group = self
1583 .group_name
1584 .as_ref()
1585 .map(|name| self.ctx.is_group_hovered(name))
1586 .unwrap_or(false)
1587 || self
1588 .ctx
1589 .rollback
1590 .group_stack
1591 .last()
1592 .map(|name| self.ctx.is_group_hovered(name))
1593 .unwrap_or(false);
1594 let in_focused_group = self
1595 .group_name
1596 .as_ref()
1597 .map(|name| self.ctx.is_group_focused(name))
1598 .unwrap_or(false)
1599 || self
1600 .ctx
1601 .rollback
1602 .group_stack
1603 .last()
1604 .map(|name| self.ctx.is_group_focused(name))
1605 .unwrap_or(false);
1606
1607 let resolved_bg = if self.ctx.rollback.dark_mode {
1608 self.dark_bg.or(self.bg)
1609 } else {
1610 self.bg
1611 };
1612 let resolved_border_style = if self.ctx.rollback.dark_mode {
1613 self.dark_border_style.unwrap_or(self.border_style)
1614 } else {
1615 self.border_style
1616 };
1617 let bg_color = if in_hovered_group || in_focused_group {
1618 self.group_hover_bg.or(resolved_bg)
1619 } else {
1620 resolved_bg
1621 };
1622 let border_style = if in_hovered_group || in_focused_group {
1623 self.group_hover_border_style
1624 .unwrap_or(resolved_border_style)
1625 } else {
1626 resolved_border_style
1627 };
1628 let group_name = self.group_name.take();
1629 let is_group_container = group_name.is_some();
1630
1631 if self.shrink_flag {
1638 self.ctx.commands.push(Command::ShrinkMarker);
1639 }
1640
1641 if let Some(scroll_offset) = self.scroll_offset {
1642 self.ctx
1643 .commands
1644 .push(Command::BeginScrollable(Box::new(BeginScrollableArgs {
1645 grow: self.grow,
1646 border: self.border,
1647 border_sides: self.border_sides,
1648 border_style,
1649 bg_color,
1650 align: self.align,
1651 align_self: self.align_self_value,
1652 justify: self.justify,
1653 gap: resolved_gap,
1654 padding: self.padding,
1655 margin: self.margin,
1656 constraints: self.constraints,
1657 title: self.title,
1658 scroll_offset,
1659 group_name,
1660 })));
1661 } else {
1662 self.ctx
1663 .commands
1664 .push(Command::BeginContainer(Box::new(BeginContainerArgs {
1665 direction,
1666 gap: resolved_gap,
1667 align: self.align,
1668 align_self: self.align_self_value,
1669 justify: self.justify,
1670 border: self.border,
1671 border_sides: self.border_sides,
1672 border_style,
1673 bg_color,
1674 padding: self.padding,
1675 margin: self.margin,
1676 constraints: self.constraints,
1677 title: self.title,
1678 grow: self.grow,
1679 group_name,
1680 })));
1681 }
1682 self.ctx.rollback.text_color_stack.push(self.text_color);
1683 let theme_save = self.theme_override.map(|t| {
1687 let prev = self.ctx.theme;
1688 self.ctx.theme = t;
1689 self.ctx.rollback.dark_mode = t.is_dark;
1692 (prev, prev.is_dark)
1693 });
1694 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| f(self.ctx)));
1698 if let Some((prev, prev_dark)) = theme_save {
1699 self.ctx.theme = prev;
1700 self.ctx.rollback.dark_mode = prev_dark;
1701 }
1702 self.ctx.rollback.text_color_stack.pop();
1703 self.ctx.commands.push(Command::EndContainer);
1704 self.ctx.rollback.last_text_idx = None;
1705 if let Err(panic) = result {
1706 std::panic::resume_unwind(panic);
1707 }
1708
1709 if is_group_container {
1710 self.ctx.rollback.group_stack.pop();
1711 self.ctx.rollback.group_count = self.ctx.rollback.group_count.saturating_sub(1);
1712 }
1713
1714 self.ctx.response_for(interaction_id)
1715 }
1716}
1717
1718#[cfg(test)]
1719mod hotfix_tests {
1720 use super::*;
1723
1724 #[test]
1729 fn filled_triangle_paints_expected_interior() {
1730 let mut canvas = CanvasContext::new(20, 20);
1731 canvas.filled_triangle(2, 2, 18, 4, 6, 18);
1732
1733 let lines = canvas.render();
1736 let inside_row = 8 / 4;
1738 let outside_row = 0;
1739 assert!(lines.len() > inside_row);
1741 assert!(lines.len() > outside_row);
1742
1743 let inside: String = lines[inside_row].iter().map(|(s, _)| s.as_str()).collect();
1745 assert!(
1746 inside.chars().any(|c| c != '\u{2800}' && c != ' '),
1747 "expected filled glyphs inside triangle, got: {inside:?}"
1748 );
1749 }
1750
1751 #[test]
1754 fn filled_triangle_handles_tall_triangle_without_panic() {
1755 let mut canvas = CanvasContext::new(8, 50);
1756 canvas.filled_triangle(0, 0, 15, 0, 8, 199);
1757 let lines = canvas.render();
1758 assert_eq!(lines.len(), 50);
1759 }
1760
1761 #[test]
1764 fn filled_triangle_degenerate_horizontal_is_safe() {
1765 let mut canvas = CanvasContext::new(20, 20);
1766 canvas.filled_triangle(0, 0, 10, 0, 19, 0);
1767 let _ = canvas.render();
1768 }
1769
1770 #[test]
1773 fn isqrt_i64_matches_floor_sqrt_for_small_values() {
1774 for n in 0i64..=10_000 {
1775 let expected = (n as f64).sqrt().floor() as isize;
1776 assert_eq!(isqrt_i64(n), expected, "mismatch at n={n}");
1777 }
1778 }
1779
1780 #[test]
1781 fn isqrt_i64_handles_perfect_squares_and_boundaries() {
1782 for k in 0i64..=4096 {
1783 assert_eq!(isqrt_i64(k * k), k as isize);
1784 if k > 0 {
1785 assert_eq!(isqrt_i64(k * k - 1), (k - 1) as isize);
1786 }
1787 }
1788 }
1789
1790 #[test]
1791 fn isqrt_i64_clamps_non_positive_to_zero() {
1792 assert_eq!(isqrt_i64(0), 0);
1793 assert_eq!(isqrt_i64(-1), 0);
1794 assert_eq!(isqrt_i64(i64::MIN), 0);
1795 }
1796
1797 #[test]
1800 fn filled_circle_renders_without_panic_and_is_non_empty() {
1801 let mut canvas = CanvasContext::new(20, 20);
1802 canvas.filled_circle(10, 10, 6);
1803 let lines = canvas.render();
1804 let any_filled = lines
1805 .iter()
1806 .flatten()
1807 .any(|(s, _)| s.chars().any(|c| c != '\u{2800}' && c != ' '));
1808 assert!(any_filled, "filled_circle produced empty output");
1809 }
1810
1811 #[test]
1818 fn scroll_offset_is_crate_internal_api() {
1819 let _ = ContainerBuilder::scroll_offset;
1820 }
1821}