egui_components/
resizable.rs1use egui::{pos2, Id, Rect, Sense, Ui, UiBuilder};
14use egui_components_theme::{mix, Theme};
15
16pub struct Resizable {
17 id: Id,
18 vertical: bool,
19 default_fraction: f32,
20 min_fraction: f32,
21 max_fraction: f32,
22 handle_thickness: f32,
23}
24
25impl Resizable {
26 pub fn new(id_salt: impl std::hash::Hash) -> Self {
27 Self {
28 id: Id::new(id_salt),
29 vertical: false,
30 default_fraction: 0.5,
31 min_fraction: 0.1,
32 max_fraction: 0.9,
33 handle_thickness: 6.0,
34 }
35 }
36 pub fn vertical(mut self) -> Self {
38 self.vertical = true;
39 self
40 }
41 pub fn default_fraction(mut self, f: f32) -> Self {
42 self.default_fraction = f.clamp(0.05, 0.95);
43 self
44 }
45 pub fn min_fraction(mut self, f: f32) -> Self {
46 self.min_fraction = f;
47 self
48 }
49 pub fn max_fraction(mut self, f: f32) -> Self {
50 self.max_fraction = f;
51 self
52 }
53
54 pub fn show(
55 self,
56 ui: &mut Ui,
57 first: impl FnOnce(&mut Ui),
58 second: impl FnOnce(&mut Ui),
59 ) {
60 let theme = Theme::get(ui.ctx());
61 let c = theme.colors;
62 let mem_id = ui.make_persistent_id(self.id);
63
64 let mut fraction = ui
65 .data(|d| d.get_temp::<f32>(mem_id))
66 .unwrap_or(self.default_fraction);
67
68 let avail = ui.available_size();
70 let total = if self.vertical { avail.y } else { avail.x };
71 let (rect, _) = ui.allocate_exact_size(avail, Sense::hover());
72 let half = self.handle_thickness * 0.5;
73
74 let split = (total * fraction).round();
75
76 let (first_rect, handle_rect, second_rect) = if self.vertical {
77 (
78 Rect::from_min_max(rect.min, pos2(rect.right(), rect.top() + split - half)),
79 Rect::from_min_max(
80 pos2(rect.left(), rect.top() + split - half),
81 pos2(rect.right(), rect.top() + split + half),
82 ),
83 Rect::from_min_max(pos2(rect.left(), rect.top() + split + half), rect.max),
84 )
85 } else {
86 (
87 Rect::from_min_max(rect.min, pos2(rect.left() + split - half, rect.bottom())),
88 Rect::from_min_max(
89 pos2(rect.left() + split - half, rect.top()),
90 pos2(rect.left() + split + half, rect.bottom()),
91 ),
92 Rect::from_min_max(pos2(rect.left() + split + half, rect.top()), rect.max),
93 )
94 };
95
96 let handle = ui.interact(handle_rect, mem_id.with("handle"), Sense::drag());
98 if handle.dragged() && total > 0.0 {
99 let delta = if self.vertical {
100 handle.drag_delta().y
101 } else {
102 handle.drag_delta().x
103 };
104 fraction = (fraction + delta / total).clamp(self.min_fraction, self.max_fraction);
105 ui.data_mut(|d| d.insert_temp(mem_id, fraction));
106 }
107 if handle.hovered() || handle.dragged() {
108 ui.ctx().set_cursor_icon(if self.vertical {
109 egui::CursorIcon::ResizeVertical
110 } else {
111 egui::CursorIcon::ResizeHorizontal
112 });
113 }
114
115 let line_color = if handle.hovered() || handle.dragged() {
117 c.ring
118 } else {
119 c.border
120 };
121 let painter = ui.painter();
122 if self.vertical {
123 let y = handle_rect.center().y;
124 painter.line_segment(
125 [pos2(rect.left(), y), pos2(rect.right(), y)],
126 egui::Stroke::new(1.0, line_color),
127 );
128 } else {
129 let x = handle_rect.center().x;
130 painter.line_segment(
131 [pos2(x, rect.top()), pos2(x, rect.bottom())],
132 egui::Stroke::new(1.0, line_color),
133 );
134 }
135 if handle.hovered() || handle.dragged() {
137 painter.rect_filled(
138 handle_rect,
139 theme.corner_sm(),
140 mix(c.ring, c.background, 0.85),
141 );
142 }
143
144 let mut first_ui = ui.new_child(
146 UiBuilder::new()
147 .max_rect(first_rect.shrink(2.0))
148 .layout(egui::Layout::top_down(egui::Align::Min)),
149 );
150 first(&mut first_ui);
151
152 let mut second_ui = ui.new_child(
153 UiBuilder::new()
154 .max_rect(second_rect.shrink(2.0))
155 .layout(egui::Layout::top_down(egui::Align::Min)),
156 );
157 second(&mut second_ui);
158 }
159}