1use egui::{Id, Pos2, Rect, Sense, Ui, UiBuilder};
20
21use crate::tokens::shadcn;
22
23pub const DATA_SPLIT_DEFAULT: f32 = 0.5;
25const FRAC_MIN: f32 = 0.15;
27const FRAC_MAX: f32 = 0.85;
28const HANDLE: f32 = 6.0;
30
31pub fn clamp_frac(f: f32) -> f32 {
33 f.clamp(FRAC_MIN, FRAC_MAX)
34}
35
36#[allow(clippy::too_many_arguments)]
47pub fn quad(
48 ui: &mut Ui,
49 id: Id,
50 row_frac: f32,
51 col_frac: f32,
52 on_row: impl FnOnce(f32),
53 on_col: impl FnOnce(f32),
54 top_left: impl FnOnce(&mut Ui),
55 top_right: impl FnOnce(&mut Ui),
56 bottom_left: impl FnOnce(&mut Ui),
57 bottom_right: impl FnOnce(&mut Ui),
58) {
59 let total = ui.available_rect_before_wrap();
60 if total.width() <= 0.0 || total.height() <= 0.0 {
61 return; }
63 let row = clamp_frac(row_frac);
64 let col = clamp_frac(col_frac);
65 ui.allocate_rect(total, Sense::hover());
67
68 let half = HANDLE * 0.5;
69 let y = total.top() + total.height() * row;
70 let x = total.left() + total.width() * col;
71 let quads = [
72 Rect::from_min_max(total.left_top(), Pos2::new(x - half, y - half)),
73 Rect::from_min_max(
74 Pos2::new(x + half, total.top()),
75 Pos2::new(total.right(), y - half),
76 ),
77 Rect::from_min_max(
78 Pos2::new(total.left(), y + half),
79 Pos2::new(x - half, total.bottom()),
80 ),
81 Rect::from_min_max(Pos2::new(x + half, y + half), total.right_bottom()),
82 ];
83 ui.scope_builder(UiBuilder::new().max_rect(quads[0]), top_left);
84 ui.scope_builder(UiBuilder::new().max_rect(quads[1]), top_right);
85 ui.scope_builder(UiBuilder::new().max_rect(quads[2]), bottom_left);
86 ui.scope_builder(UiBuilder::new().max_rect(quads[3]), bottom_right);
87
88 let p = ui.painter();
90 let h_band = Rect::from_min_max(
91 Pos2::new(total.left(), y - half),
92 Pos2::new(total.right(), y + half),
93 );
94 let v_band = Rect::from_min_max(
95 Pos2::new(x - half, total.top()),
96 Pos2::new(x + half, total.bottom()),
97 );
98 p.rect_filled(h_band, 0.0, shadcn::BG1);
99 p.rect_filled(v_band, 0.0, shadcn::BG1);
100
101 let h_resp = ui.interact(h_band, id.with("handle_h"), Sense::drag());
102 let v_resp = ui.interact(v_band, id.with("handle_v"), Sense::drag());
103 let mut h_color = shadcn::BORDER;
104 let mut v_color = shadcn::BORDER;
105 if h_resp.dragged() {
106 h_color = shadcn::ACCENT4;
107 if total.height() > 1.0 {
108 let moved = clamp_frac(row + h_resp.drag_delta().y / total.height());
109 if moved != row {
110 on_row(moved);
111 }
112 }
113 } else if h_resp.hovered() {
114 h_color = shadcn::ACCENT4;
115 }
116 if v_resp.dragged() {
117 v_color = shadcn::ACCENT4;
118 if total.width() > 1.0 {
119 let moved = clamp_frac(col + v_resp.drag_delta().x / total.width());
120 if moved != col {
121 on_col(moved);
122 }
123 }
124 } else if v_resp.hovered() {
125 v_color = shadcn::ACCENT4;
126 }
127 p.line_segment(
128 [Pos2::new(total.left(), y), Pos2::new(total.right(), y)],
129 egui::Stroke::new(1.0, h_color),
130 );
131 p.line_segment(
132 [Pos2::new(x, total.top()), Pos2::new(x, total.bottom())],
133 egui::Stroke::new(1.0, v_color),
134 );
135}