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