1use std::{
2 ops::{Deref, Range},
3 rc::Rc,
4};
5
6use gpui::{
7 canvas, div, prelude::FluentBuilder, Along, AnyElement, App, AppContext, Axis, Bounds, Context,
8 Element, ElementId, Empty, Entity, EventEmitter, InteractiveElement as _, IntoElement,
9 IsZero as _, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Render, RenderOnce, Style,
10 Styled, Window,
11};
12
13use crate::{h_flex, resizable::PANEL_MIN_SIZE, v_flex, AxisExt};
14
15use super::{resizable_panel, resize_handle, ResizableState};
16
17pub enum ResizablePanelEvent {
18 Resized,
19}
20
21#[derive(Clone)]
22pub(crate) struct DragPanel;
23impl Render for DragPanel {
24 fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
25 Empty
26 }
27}
28
29#[derive(IntoElement)]
31pub struct ResizablePanelGroup {
32 id: ElementId,
33 state: Option<Entity<ResizableState>>,
34 axis: Axis,
35 size: Option<Pixels>,
36 children: Vec<ResizablePanel>,
37 on_resize: Rc<dyn Fn(&Entity<ResizableState>, &mut Window, &mut App)>,
38}
39
40impl ResizablePanelGroup {
41 pub fn new(id: impl Into<ElementId>) -> Self {
43 Self {
44 id: id.into(),
45 axis: Axis::Horizontal,
46 children: vec![],
47 state: None,
48 size: None,
49 on_resize: Rc::new(|_, _, _| {}),
50 }
51 }
52
53 pub fn with_state(mut self, state: &Entity<ResizableState>) -> Self {
57 self.state = Some(state.clone());
58 self
59 }
60
61 pub fn axis(mut self, axis: Axis) -> Self {
63 self.axis = axis;
64 self
65 }
66
67 pub fn child(mut self, panel: impl Into<ResizablePanel>) -> Self {
73 self.children.push(panel.into());
74 self
75 }
76
77 pub fn children<I>(mut self, panels: impl IntoIterator<Item = I>) -> Self
79 where
80 I: Into<ResizablePanel>,
81 {
82 self.children = panels.into_iter().map(|panel| panel.into()).collect();
83 self
84 }
85
86 pub fn size(mut self, size: Pixels) -> Self {
91 self.size = Some(size);
92 self
93 }
94
95 pub fn on_resize(
101 mut self,
102 on_resize: impl Fn(&Entity<ResizableState>, &mut Window, &mut App) + 'static,
103 ) -> Self {
104 self.on_resize = Rc::new(on_resize);
105 self
106 }
107}
108
109impl<T> From<T> for ResizablePanel
110where
111 T: Into<AnyElement>,
112{
113 fn from(value: T) -> Self {
114 resizable_panel().child(value.into())
115 }
116}
117
118impl From<ResizablePanelGroup> for ResizablePanel {
119 fn from(value: ResizablePanelGroup) -> Self {
120 resizable_panel().child(value)
121 }
122}
123
124impl EventEmitter<ResizablePanelEvent> for ResizablePanelGroup {}
125
126impl RenderOnce for ResizablePanelGroup {
127 fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
128 let state = self.state.unwrap_or(
129 window.use_keyed_state(self.id.clone(), cx, |_, _| ResizableState::default()),
130 );
131 let container = if self.axis.is_horizontal() {
132 h_flex()
133 } else {
134 v_flex()
135 };
136
137 let panels_count = self.children.len();
139 state.update(cx, |state, cx| {
140 state.sync_panels_count(self.axis, panels_count, cx);
141 });
142
143 container
144 .id(self.id)
145 .size_full()
146 .children(
147 self.children
148 .into_iter()
149 .enumerate()
150 .map(|(ix, mut panel)| {
151 panel.panel_ix = ix;
152 panel.axis = self.axis;
153 panel.state = Some(state.clone());
154 panel
155 }),
156 )
157 .child({
158 canvas(
159 {
160 let state = state.clone();
161 move |bounds, _, cx| {
162 state.update(cx, |state, cx| {
163 let size_changed = state.bounds.size.along(self.axis)
164 != bounds.size.along(self.axis);
165
166 state.bounds = bounds;
167
168 if size_changed {
169 state.adjust_to_container_size(cx);
170 }
171 })
172 }
173 },
174 |_, _, _, _| {},
175 )
176 .absolute()
177 .size_full()
178 })
179 .child(ResizePanelGroupElement {
180 state: state.clone(),
181 axis: self.axis,
182 on_resize: self.on_resize.clone(),
183 })
184 }
185}
186
187#[derive(IntoElement)]
189pub struct ResizablePanel {
190 axis: Axis,
191 panel_ix: usize,
192 state: Option<Entity<ResizableState>>,
193 initial_size: Option<Pixels>,
195 size_range: Range<Pixels>,
197 children: Vec<AnyElement>,
198 visible: bool,
199}
200
201impl ResizablePanel {
202 pub(super) fn new() -> Self {
204 Self {
205 panel_ix: 0,
206 initial_size: None,
207 state: None,
208 size_range: (PANEL_MIN_SIZE..Pixels::MAX),
209 axis: Axis::Horizontal,
210 children: vec![],
211 visible: true,
212 }
213 }
214
215 pub fn visible(mut self, visible: bool) -> Self {
217 self.visible = visible;
218 self
219 }
220
221 pub fn size(mut self, size: impl Into<Pixels>) -> Self {
223 self.initial_size = Some(size.into());
224 self
225 }
226
227 pub fn size_range(mut self, range: impl Into<Range<Pixels>>) -> Self {
231 self.size_range = range.into();
232 self
233 }
234}
235
236impl ParentElement for ResizablePanel {
237 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
238 self.children.extend(elements);
239 }
240}
241
242impl RenderOnce for ResizablePanel {
243 fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
244 if !self.visible {
245 return div().id(("resizable-panel", self.panel_ix));
246 }
247
248 let state = self
249 .state
250 .expect("BUG: The `state` in ResizablePanel should be present.");
251 let panel_state = state
252 .read(cx)
253 .panels
254 .get(self.panel_ix)
255 .expect("BUG: The `index` of ResizablePanel should be one of in `state`.");
256 let size_range = self.size_range.clone();
257
258 div()
259 .id(("resizable-panel", self.panel_ix))
260 .flex()
261 .flex_grow()
262 .size_full()
263 .relative()
264 .when(self.axis.is_vertical(), |this| {
265 this.min_h(size_range.start).max_h(size_range.end)
266 })
267 .when(self.axis.is_horizontal(), |this| {
268 this.min_w(size_range.start).max_w(size_range.end)
269 })
270 .when(self.initial_size.is_none(), |this| this.flex_shrink())
274 .when_some(self.initial_size, |this, initial_size| {
275 this.when(
278 panel_state.size.is_none() && !initial_size.is_zero(),
279 |this| this.flex_none(),
280 )
281 .flex_basis(initial_size)
282 })
283 .map(|this| match panel_state.size {
284 Some(size) => this.flex_basis(size.min(size_range.end).max(size_range.start)),
285 None => this,
286 })
287 .child({
288 canvas(
289 {
290 let state = state.clone();
291 move |bounds, _, cx| {
292 state.update(cx, |state, cx| {
293 state.update_panel_size(self.panel_ix, bounds, self.size_range, cx)
294 })
295 }
296 },
297 |_, _, _, _| {},
298 )
299 .absolute()
300 .size_full()
301 })
302 .children(self.children)
303 .when(self.panel_ix > 0, |this| {
304 let ix = self.panel_ix - 1;
305 this.child(resize_handle(("resizable-handle", ix), self.axis).on_drag(
306 DragPanel,
307 move |drag_panel, _, _, cx| {
308 cx.stop_propagation();
309 state.update(cx, |state, _| {
311 state.resizing_panel_ix = Some(ix);
312 });
313 cx.new(|_| drag_panel.deref().clone())
314 },
315 ))
316 })
317 }
318}
319
320struct ResizePanelGroupElement {
321 state: Entity<ResizableState>,
322 on_resize: Rc<dyn Fn(&Entity<ResizableState>, &mut Window, &mut App)>,
323 axis: Axis,
324}
325
326impl IntoElement for ResizePanelGroupElement {
327 type Element = Self;
328
329 fn into_element(self) -> Self::Element {
330 self
331 }
332}
333
334impl Element for ResizePanelGroupElement {
335 type RequestLayoutState = ();
336 type PrepaintState = ();
337
338 fn id(&self) -> Option<gpui::ElementId> {
339 None
340 }
341
342 fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
343 None
344 }
345
346 fn request_layout(
347 &mut self,
348 _: Option<&gpui::GlobalElementId>,
349 _: Option<&gpui::InspectorElementId>,
350 window: &mut Window,
351 cx: &mut App,
352 ) -> (gpui::LayoutId, Self::RequestLayoutState) {
353 (window.request_layout(Style::default(), None, cx), ())
354 }
355
356 fn prepaint(
357 &mut self,
358 _: Option<&gpui::GlobalElementId>,
359 _: Option<&gpui::InspectorElementId>,
360 _: Bounds<Pixels>,
361 _: &mut Self::RequestLayoutState,
362 _window: &mut Window,
363 _cx: &mut App,
364 ) -> Self::PrepaintState {
365 ()
366 }
367
368 fn paint(
369 &mut self,
370 _: Option<&gpui::GlobalElementId>,
371 _: Option<&gpui::InspectorElementId>,
372 _: Bounds<Pixels>,
373 _: &mut Self::RequestLayoutState,
374 _: &mut Self::PrepaintState,
375 window: &mut Window,
376 cx: &mut App,
377 ) {
378 window.on_mouse_event({
379 let state = self.state.clone();
380 let axis = self.axis;
381 let current_ix = state.read(cx).resizing_panel_ix;
382 move |e: &MouseMoveEvent, phase, window, cx| {
383 if !phase.bubble() {
384 return;
385 }
386 let Some(ix) = current_ix else { return };
387
388 state.update(cx, |state, cx| {
389 let panel = state.panels.get(ix).expect("BUG: invalid panel index");
390
391 match axis {
392 Axis::Horizontal => {
393 state.resize_panel(ix, e.position.x - panel.bounds.left(), window, cx)
394 }
395 Axis::Vertical => {
396 state.resize_panel(ix, e.position.y - panel.bounds.top(), window, cx);
397 }
398 }
399 cx.notify();
400 })
401 }
402 });
403
404 window.on_mouse_event({
406 let state = self.state.clone();
407 let current_ix = state.read(cx).resizing_panel_ix;
408 let on_resize = self.on_resize.clone();
409 move |_: &MouseUpEvent, phase, window, cx| {
410 if current_ix.is_none() {
411 return;
412 }
413 if phase.bubble() {
414 state.update(cx, |state, cx| state.done_resizing(cx));
415 on_resize(&state, window, cx);
416 }
417 }
418 })
419 }
420}