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