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