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