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