rgpui_component/resizable/
mod.rs1use std::ops::Range;
2
3use rgpui::{
4 Along, App, Axis, Bounds, Context, ElementId, EventEmitter, IsZero, Pixels, Window, px,
5};
6
7mod panel;
8mod resize_handle;
9pub use panel::*;
10pub(crate) use resize_handle::*;
11
12pub(crate) const PANEL_MIN_SIZE: Pixels = px(100.);
13
14pub fn h_resizable(id: impl Into<ElementId>) -> ResizablePanelGroup {
16 ResizablePanelGroup::new(id).axis(Axis::Horizontal)
17}
18
19pub fn v_resizable(id: impl Into<ElementId>) -> ResizablePanelGroup {
21 ResizablePanelGroup::new(id).axis(Axis::Vertical)
22}
23
24pub fn resizable_panel() -> ResizablePanel {
26 ResizablePanel::new()
27}
28
29#[derive(Debug, Clone)]
31pub struct ResizableState {
32 axis: Axis,
34 panels: Vec<ResizablePanelState>,
35 sizes: Vec<Pixels>,
36 pub(crate) resizing_panel_ix: Option<usize>,
37 bounds: Bounds<Pixels>,
38}
39
40impl Default for ResizableState {
41 fn default() -> Self {
42 Self {
43 axis: Axis::Horizontal,
44 panels: vec![],
45 sizes: vec![],
46 resizing_panel_ix: None,
47 bounds: Bounds::default(),
48 }
49 }
50}
51
52impl ResizableState {
53 pub fn sizes(&self) -> &Vec<Pixels> {
55 &self.sizes
56 }
57
58 pub fn resize_panel(
68 &mut self,
69 ix: usize,
70 size: Pixels,
71 window: &mut Window,
72 cx: &mut Context<Self>,
73 ) {
74 if ix >= self.sizes.len() {
75 return;
76 }
77 if ix + 1 < self.sizes.len() {
78 self.resize_panel_at_handle(ix, size, window, cx);
79 } else if ix > 0 {
80 let delta = self.sizes[ix] - size;
83 let prev = self.sizes[ix - 1];
84 self.resize_panel_at_handle(ix - 1, prev + delta, window, cx);
85 }
86 self.done_resizing(cx);
87 }
88
89 pub(crate) fn insert_panel(
90 &mut self,
91 size: Option<Pixels>,
92 ix: Option<usize>,
93 cx: &mut Context<Self>,
94 ) {
95 let panel_state = ResizablePanelState {
96 size,
97 ..Default::default()
98 };
99
100 let size = size.unwrap_or(PANEL_MIN_SIZE);
101
102 let container_size = self.container_size().max(px(1.));
105 let total_leftover_size = (container_size - size).max(px(1.));
106
107 for (i, panel) in self.panels.iter_mut().enumerate() {
108 let ratio = self.sizes[i] / container_size;
109 self.sizes[i] = total_leftover_size * ratio;
110 panel.size = Some(self.sizes[i]);
111 }
112
113 if let Some(ix) = ix {
114 self.panels.insert(ix, panel_state);
115 self.sizes.insert(ix, size);
116 } else {
117 self.panels.push(panel_state);
118 self.sizes.push(size);
119 };
120
121 cx.notify();
122 }
123
124 pub(crate) fn sync_panels_count(
125 &mut self,
126 axis: Axis,
127 panels_count: usize,
128 cx: &mut Context<Self>,
129 ) {
130 let mut changed = self.axis != axis;
131 self.axis = axis;
132
133 if panels_count > self.panels.len() {
134 let diff = panels_count - self.panels.len();
135 self.panels
136 .extend(vec![ResizablePanelState::default(); diff]);
137 self.sizes.extend(vec![PANEL_MIN_SIZE; diff]);
138 changed = true;
139 }
140
141 if panels_count < self.panels.len() {
142 self.panels.truncate(panels_count);
143 self.sizes.truncate(panels_count);
144 changed = true;
145 }
146
147 if changed {
148 self.adjust_to_container_size(cx);
150 }
151 }
152
153 pub(crate) fn update_panel_size(
154 &mut self,
155 panel_ix: usize,
156 bounds: Bounds<Pixels>,
157 size_range: Range<Pixels>,
158 cx: &mut Context<Self>,
159 ) {
160 let size = bounds.size.along(self.axis);
161 if self.sizes[panel_ix].as_f32() == PANEL_MIN_SIZE.as_f32() {
165 self.sizes[panel_ix] = size;
166 self.panels[panel_ix].size = Some(size);
167 }
168 self.panels[panel_ix].bounds = bounds;
169 self.panels[panel_ix].size_range = size_range;
170 cx.notify();
171 }
172
173 pub(crate) fn remove_panel(&mut self, panel_ix: usize, cx: &mut Context<Self>) {
174 self.panels.remove(panel_ix);
175 self.sizes.remove(panel_ix);
176 if let Some(resizing_panel_ix) = self.resizing_panel_ix {
177 if resizing_panel_ix > panel_ix {
178 self.resizing_panel_ix = Some(resizing_panel_ix - 1);
179 }
180 }
181 self.adjust_to_container_size(cx);
182 }
183
184 pub(crate) fn replace_panel(
185 &mut self,
186 panel_ix: usize,
187 panel: ResizablePanelState,
188 cx: &mut Context<Self>,
189 ) {
190 let old_size = self.sizes[panel_ix];
191
192 self.panels[panel_ix] = panel;
193 self.sizes[panel_ix] = old_size;
194 self.adjust_to_container_size(cx);
195 }
196
197 pub(crate) fn clear(&mut self) {
198 self.panels.clear();
199 self.sizes.clear();
200 }
201
202 #[inline]
203 pub(crate) fn container_size(&self) -> Pixels {
204 self.bounds.size.along(self.axis)
205 }
206
207 pub(crate) fn done_resizing(&mut self, cx: &mut Context<Self>) {
208 self.resizing_panel_ix = None;
209 cx.emit(ResizablePanelEvent::Resized);
210 }
211
212 fn panel_size_range(&self, ix: usize) -> Range<Pixels> {
213 let Some(panel) = self.panels.get(ix) else {
214 return PANEL_MIN_SIZE..Pixels::MAX;
215 };
216
217 panel.size_range.clone()
218 }
219
220 fn sync_real_panel_sizes(&mut self, _: &App) {
221 for (i, panel) in self.panels.iter().enumerate() {
222 self.sizes[i] = panel.bounds.size.along(self.axis);
223 }
224 }
225
226 fn resize_panel_at_handle(
233 &mut self,
234 ix: usize,
235 size: Pixels,
236 _: &mut Window,
237 cx: &mut Context<Self>,
238 ) {
239 let old_sizes = self.sizes.clone();
240
241 let mut ix = ix;
242 if ix >= old_sizes.len() - 1 {
244 return;
245 }
246 let container_size = self.container_size();
247 self.sync_real_panel_sizes(cx);
248
249 let move_changed = size - old_sizes[ix];
250 if move_changed == px(0.) {
251 return;
252 }
253
254 let size_range = self.panel_size_range(ix);
255 let new_size = size.clamp(size_range.start, size_range.end);
256 let is_expand = move_changed > px(0.);
257
258 let main_ix = ix;
259 let mut new_sizes = old_sizes.clone();
260
261 if is_expand {
262 let mut changed = new_size - old_sizes[ix];
263 new_sizes[ix] = new_size;
264
265 while changed > px(0.) && ix < old_sizes.len() - 1 {
266 ix += 1;
267 let size_range = self.panel_size_range(ix);
268 let available_size = (new_sizes[ix] - size_range.start).max(px(0.));
269 let to_reduce = changed.min(available_size);
270 new_sizes[ix] -= to_reduce;
271 changed -= to_reduce;
272 }
273 } else {
274 let mut changed = new_size - size;
275 new_sizes[ix] = new_size;
276
277 while changed > px(0.) && ix > 0 {
278 ix -= 1;
279 let size_range = self.panel_size_range(ix);
280 let available_size = (new_sizes[ix] - size_range.start).max(px(0.));
281 let to_reduce = changed.min(available_size);
282 changed -= to_reduce;
283 new_sizes[ix] -= to_reduce;
284 }
285
286 new_sizes[main_ix + 1] += old_sizes[main_ix] - size - changed;
287 }
288
289 let total_size: Pixels = new_sizes.iter().map(|s| s.as_f32()).sum::<f32>().into();
291 if total_size > container_size {
292 let overflow = total_size - container_size;
293 new_sizes[main_ix] = (new_sizes[main_ix] - overflow).max(size_range.start);
294 }
295
296 for (i, _) in old_sizes.iter().enumerate() {
297 let size = new_sizes[i];
298 self.panels[i].size = Some(size);
299 }
300 self.sizes = new_sizes;
301 cx.notify();
302 }
303
304 fn adjust_to_container_size(&mut self, cx: &mut Context<Self>) {
308 if self.container_size().is_zero() {
309 return;
310 }
311
312 let container_size = self.container_size();
313 let total_size = px(self.sizes.iter().map(|s| s.as_f32()).sum::<f32>());
314
315 for i in 0..self.panels.len() {
316 let size = self.sizes[i];
317 let ratio = size / total_size;
318 let new_size = container_size * ratio;
319
320 self.sizes[i] = new_size;
321 self.panels[i].size = Some(new_size);
322 }
323 cx.notify();
324 }
325}
326
327impl EventEmitter<ResizablePanelEvent> for ResizableState {}
328
329#[derive(Debug, Clone, Default)]
330pub(crate) struct ResizablePanelState {
331 pub size: Option<Pixels>,
332 pub size_range: Range<Pixels>,
333 bounds: Bounds<Pixels>,
334}