gpui_component/resizable/
mod.rs1use std::ops::Range;
2
3use gpui::{px, Along, App, Axis, Bounds, Context, ElementId, EventEmitter, Pixels, Window};
4
5use crate::PixelsExt;
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(crate) fn insert_panel(
59 &mut self,
60 size: Option<Pixels>,
61 ix: Option<usize>,
62 cx: &mut Context<Self>,
63 ) {
64 let panel_state = ResizablePanelState {
65 size,
66 ..Default::default()
67 };
68
69 if let Some(ix) = ix {
70 self.panels.insert(ix, panel_state);
71 self.sizes.insert(ix, size.unwrap_or(PANEL_MIN_SIZE));
72 } else {
73 self.panels.push(panel_state);
74 self.sizes.push(size.unwrap_or(PANEL_MIN_SIZE));
75 };
76 cx.notify();
77 }
78
79 pub(crate) fn sync_panels_count(&mut self, axis: Axis, panels_count: usize) {
80 self.axis = axis;
81 if panels_count > self.panels.len() {
82 let diff = panels_count - self.panels.len();
83 self.panels
84 .extend(vec![ResizablePanelState::default(); diff]);
85 self.sizes.extend(vec![PANEL_MIN_SIZE; diff]);
86 }
87 }
88
89 pub(crate) fn update_panel_size(
90 &mut self,
91 panel_ix: usize,
92 bounds: Bounds<Pixels>,
93 size_range: Range<Pixels>,
94 cx: &mut Context<Self>,
95 ) {
96 let size = bounds.size.along(self.axis);
97 self.sizes[panel_ix] = size;
98 self.panels[panel_ix].size = Some(size);
99 self.panels[panel_ix].bounds = bounds;
100 self.panels[panel_ix].size_range = size_range;
101 cx.notify();
102 }
103
104 pub(crate) fn remove_panel(&mut self, panel_ix: usize, cx: &mut Context<Self>) {
105 self.panels.remove(panel_ix);
106 self.sizes.remove(panel_ix);
107 if let Some(resizing_panel_ix) = self.resizing_panel_ix {
108 if resizing_panel_ix > panel_ix {
109 self.resizing_panel_ix = Some(resizing_panel_ix - 1);
110 }
111 }
112 cx.notify();
113 }
114
115 pub(crate) fn replace_panel(
116 &mut self,
117 panel_ix: usize,
118 panel: ResizablePanelState,
119 cx: &mut Context<Self>,
120 ) {
121 let old_size = self.sizes[panel_ix];
122
123 self.panels[panel_ix] = panel;
124 self.sizes[panel_ix] = old_size;
125 cx.notify();
126 }
127
128 pub(crate) fn clear(&mut self) {
129 self.panels.clear();
130 self.sizes.clear();
131 }
132
133 pub(crate) fn total_size(&self) -> Pixels {
134 self.sizes.iter().map(|s| s.as_f32()).sum::<f32>().into()
135 }
136
137 pub(crate) fn done_resizing(&mut self, cx: &mut Context<Self>) {
138 self.resizing_panel_ix = None;
139 cx.emit(ResizablePanelEvent::Resized);
140 }
141
142 fn panel_size_range(&self, ix: usize) -> Range<Pixels> {
143 let Some(panel) = self.panels.get(ix) else {
144 return PANEL_MIN_SIZE..Pixels::MAX;
145 };
146
147 panel.size_range.clone()
148 }
149
150 fn sync_real_panel_sizes(&mut self, _: &App) {
151 for (i, panel) in self.panels.iter().enumerate() {
152 self.sizes[i] = panel.bounds.size.along(self.axis).floor();
153 }
154 }
155
156 fn resize_panel(&mut self, ix: usize, size: Pixels, _: &mut Window, cx: &mut Context<Self>) {
159 let old_sizes = self.sizes.clone();
160
161 let mut ix = ix;
162 if ix >= old_sizes.len() - 1 {
164 return;
165 }
166 let size = size.floor();
167 let container_size = self.bounds.size.along(self.axis);
168 self.sync_real_panel_sizes(cx);
169
170 let move_changed = size - old_sizes[ix];
171 if move_changed == px(0.) {
172 return;
173 }
174
175 let size_range = self.panel_size_range(ix);
176 let new_size = size.clamp(size_range.start, size_range.end);
177 let is_expand = move_changed > px(0.);
178
179 let main_ix = ix;
180 let mut new_sizes = old_sizes.clone();
181
182 if is_expand {
183 let mut changed = new_size - old_sizes[ix];
184 new_sizes[ix] = new_size;
185
186 while changed > px(0.) && ix < old_sizes.len() - 1 {
187 ix += 1;
188 let size_range = self.panel_size_range(ix);
189 let available_size = (new_sizes[ix] - size_range.start).max(px(0.));
190 let to_reduce = changed.min(available_size);
191 new_sizes[ix] -= to_reduce;
192 changed -= to_reduce;
193 }
194 } else {
195 let mut changed = new_size - size;
196 new_sizes[ix + 1] += old_sizes[ix] - new_size;
197 new_sizes[ix] = new_size;
198
199 while changed > px(0.) && ix > 0 {
200 ix -= 1;
201 let size_range = self.panel_size_range(ix);
202 let available_size = (new_sizes[ix] - size_range.start).max(px(0.));
203 let to_reduce = changed.min(available_size);
204 changed -= to_reduce;
205 new_sizes[ix] -= to_reduce;
206 }
207 }
208
209 let total_size: Pixels = new_sizes.iter().map(|s| s.as_f32()).sum::<f32>().into();
211 if total_size > container_size {
212 let overflow = total_size - container_size;
213 new_sizes[main_ix] = (new_sizes[main_ix] - overflow).max(size_range.start);
214 }
215
216 for (i, _) in old_sizes.iter().enumerate() {
217 let size = new_sizes[i];
218 self.panels[i].size = Some(size);
219 }
220
221 self.sizes = new_sizes;
222 cx.notify();
223 }
224}
225
226impl EventEmitter<ResizablePanelEvent> for ResizableState {}
227
228#[derive(Debug, Clone, Default)]
229pub(crate) struct ResizablePanelState {
230 pub size: Option<Pixels>,
231 pub size_range: Range<Pixels>,
232 bounds: Bounds<Pixels>,
233}