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