1use std::{
2 ops::{Deref, Range},
3 rc::Rc,
4};
5
6use rgpui::{
7 Along, AnyElement, App, AppContext, Axis, Bounds, Context, Element, ElementId, Empty, Entity,
8 EventEmitter, InteractiveElement as _, IntoElement, IsZero as _, MouseMoveEvent, MouseUpEvent,
9 ParentElement, Pixels, Render, RenderOnce, Style, StyleRefinement, Styled, Window, div,
10 prelude::FluentBuilder,
11};
12
13use crate::{
14 AxisExt, ElementExt, h_flex, resizable::PANEL_MIN_SIZE, styled::StyledExt as _, v_flex,
15};
16
17use super::{ResizableState, resizable_panel, resize_handle};
18
19pub enum ResizablePanelEvent {
20 Resized,
21}
22
23#[derive(Clone)]
24pub(crate) struct DragPanel;
25impl Render for DragPanel {
26 fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
27 Empty
28 }
29}
30
31#[derive(IntoElement)]
33pub struct ResizablePanelGroup {
34 id: ElementId,
35 state: Option<Entity<ResizableState>>,
36 axis: Axis,
37 size: Option<Pixels>,
38 children: Vec<ResizablePanel>,
39 on_resize: Rc<dyn Fn(&Entity<ResizableState>, &mut Window, &mut App)>,
40}
41
42impl ResizablePanelGroup {
43 pub fn new(id: impl Into<ElementId>) -> Self {
45 Self {
46 id: id.into(),
47 axis: Axis::Horizontal,
48 children: vec![],
49 state: None,
50 size: None,
51 on_resize: Rc::new(|_, _, _| {}),
52 }
53 }
54
55 pub fn with_state(mut self, state: &Entity<ResizableState>) -> Self {
59 self.state = Some(state.clone());
60 self
61 }
62
63 pub fn axis(mut self, axis: Axis) -> Self {
65 self.axis = axis;
66 self
67 }
68
69 pub fn child(mut self, panel: impl Into<ResizablePanel>) -> Self {
75 self.children.push(panel.into());
76 self
77 }
78
79 pub fn children<I>(mut self, panels: impl IntoIterator<Item = I>) -> Self
81 where
82 I: Into<ResizablePanel>,
83 {
84 self.children = panels.into_iter().map(|panel| panel.into()).collect();
85 self
86 }
87
88 pub fn size(mut self, size: Pixels) -> Self {
93 self.size = Some(size);
94 self
95 }
96
97 pub fn on_resize(
103 mut self,
104 on_resize: impl Fn(&Entity<ResizableState>, &mut Window, &mut App) + 'static,
105 ) -> Self {
106 self.on_resize = Rc::new(on_resize);
107 self
108 }
109}
110
111impl<T> From<T> for ResizablePanel
112where
113 T: Into<AnyElement>,
114{
115 fn from(value: T) -> Self {
116 resizable_panel().child(value.into())
117 }
118}
119
120impl From<ResizablePanelGroup> for ResizablePanel {
121 fn from(value: ResizablePanelGroup) -> Self {
122 resizable_panel().child(value)
123 }
124}
125
126impl EventEmitter<ResizablePanelEvent> for ResizablePanelGroup {}
127
128impl RenderOnce for ResizablePanelGroup {
129 fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
130 let state = self.state.unwrap_or(
131 window.use_keyed_state(self.id.clone(), cx, |_, _| ResizableState::default()),
132 );
133 let container = if self.axis.is_horizontal() {
134 h_flex()
135 } else {
136 v_flex()
137 };
138
139 let panels_count = self.children.len();
141 state.update(cx, |state, cx| {
142 state.sync_panels_count(self.axis, panels_count, cx);
143 });
144
145 container
146 .id(self.id)
147 .size_full()
148 .children(
149 self.children
150 .into_iter()
151 .enumerate()
152 .map(|(ix, mut panel)| {
153 panel.panel_ix = ix;
154 panel.axis = self.axis;
155 panel.state = Some(state.clone());
156 panel
157 }),
158 )
159 .on_prepaint({
160 let state = state.clone();
161 move |bounds, _, cx| {
162 state.update(cx, |state, cx| {
163 let size_changed =
164 state.bounds.size.along(self.axis) != 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 .child(ResizePanelGroupElement {
175 state: state.clone(),
176 axis: self.axis,
177 on_resize: self.on_resize.clone(),
178 })
179 }
180}
181
182#[derive(IntoElement)]
210pub struct ResizablePanel {
211 axis: Axis,
212 panel_ix: usize,
213 state: Option<Entity<ResizableState>>,
214 initial_size: Option<Pixels>,
216 size_range: Range<Pixels>,
218 children: Vec<AnyElement>,
219 visible: bool,
220 style: StyleRefinement,
221}
222
223impl ResizablePanel {
224 pub(super) fn new() -> Self {
226 Self {
227 panel_ix: 0,
228 initial_size: None,
229 state: None,
230 size_range: (PANEL_MIN_SIZE..Pixels::MAX),
231 axis: Axis::Horizontal,
232 children: vec![],
233 visible: true,
234 style: StyleRefinement::default(),
235 }
236 }
237
238 pub fn visible(mut self, visible: bool) -> Self {
240 self.visible = visible;
241 self
242 }
243
244 pub fn size(mut self, size: impl Into<Pixels>) -> Self {
246 self.initial_size = Some(size.into());
247 self
248 }
249
250 pub fn size_range(mut self, range: impl Into<Range<Pixels>>) -> Self {
254 self.size_range = range.into();
255 self
256 }
257}
258
259impl Styled for ResizablePanel {
260 fn style(&mut self) -> &mut StyleRefinement {
261 &mut self.style
262 }
263}
264
265impl ParentElement for ResizablePanel {
266 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
267 self.children.extend(elements);
268 }
269}
270
271impl RenderOnce for ResizablePanel {
272 fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
273 if !self.visible {
274 return div().id(("resizable-panel", self.panel_ix));
275 }
276
277 let state = self
278 .state
279 .expect("BUG: The `state` in ResizablePanel should be present.");
280 let panel_state = state
281 .read(cx)
282 .panels
283 .get(self.panel_ix)
284 .expect("BUG: The `index` of ResizablePanel should be one of in `state`.");
285 let size_range = self.size_range.clone();
286
287 div()
288 .id(("resizable-panel", self.panel_ix))
289 .flex()
290 .flex_grow()
291 .size_full()
292 .relative()
293 .refine_style(&self.style)
301 .when(self.axis.is_vertical(), |this| {
302 this.min_h(size_range.start).max_h(size_range.end)
303 })
304 .when(self.axis.is_horizontal(), |this| {
305 this.min_w(size_range.start).max_w(size_range.end)
306 })
307 .when(self.initial_size.is_none(), |this| this.flex_shrink())
311 .when_some(self.initial_size, |this, initial_size| {
312 this.when(
315 panel_state.size.is_none() && !initial_size.is_zero(),
316 |this| this.flex_none(),
317 )
318 .flex_basis(initial_size)
319 })
320 .map(|this| match panel_state.size {
321 Some(size) => this.flex_basis(size.min(size_range.end).max(size_range.start)),
322 None => this,
323 })
324 .on_prepaint({
325 let state = state.clone();
326 move |bounds, _, cx| {
327 state.update(cx, |state, cx| {
328 state.update_panel_size(self.panel_ix, bounds, self.size_range, cx)
329 })
330 }
331 })
332 .children(self.children)
333 .when(self.panel_ix > 0, |this| {
334 let ix = self.panel_ix - 1;
335 this.child(resize_handle(("resizable-handle", ix), self.axis).on_drag(
336 DragPanel,
337 move |drag_panel, _, _, cx| {
338 cx.stop_propagation();
339 state.update(cx, |state, _| {
341 state.resizing_panel_ix = Some(ix);
342 });
343 cx.new(|_| drag_panel.deref().clone())
344 },
345 ))
346 })
347 }
348}
349
350struct ResizePanelGroupElement {
351 state: Entity<ResizableState>,
352 on_resize: Rc<dyn Fn(&Entity<ResizableState>, &mut Window, &mut App)>,
353 axis: Axis,
354}
355
356impl IntoElement for ResizePanelGroupElement {
357 type Element = Self;
358
359 fn into_element(self) -> Self::Element {
360 self
361 }
362}
363
364impl Element for ResizePanelGroupElement {
365 type RequestLayoutState = ();
366 type PrepaintState = ();
367
368 fn id(&self) -> Option<rgpui::ElementId> {
369 None
370 }
371
372 fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
373 None
374 }
375
376 fn request_layout(
377 &mut self,
378 _: Option<&rgpui::GlobalElementId>,
379 _: Option<&rgpui::InspectorElementId>,
380 window: &mut Window,
381 cx: &mut App,
382 ) -> (rgpui::LayoutId, Self::RequestLayoutState) {
383 (window.request_layout(Style::default(), None, cx), ())
384 }
385
386 fn prepaint(
387 &mut self,
388 _: Option<&rgpui::GlobalElementId>,
389 _: Option<&rgpui::InspectorElementId>,
390 _: Bounds<Pixels>,
391 _: &mut Self::RequestLayoutState,
392 _window: &mut Window,
393 _cx: &mut App,
394 ) -> Self::PrepaintState {
395 ()
396 }
397
398 fn paint(
399 &mut self,
400 _: Option<&rgpui::GlobalElementId>,
401 _: Option<&rgpui::InspectorElementId>,
402 _: Bounds<Pixels>,
403 _: &mut Self::RequestLayoutState,
404 _: &mut Self::PrepaintState,
405 window: &mut Window,
406 cx: &mut App,
407 ) {
408 window.on_mouse_event({
409 let state = self.state.clone();
410 let axis = self.axis;
411 let current_ix = state.read(cx).resizing_panel_ix;
412 move |e: &MouseMoveEvent, phase, window, cx| {
413 if !phase.bubble() {
414 return;
415 }
416 let Some(ix) = current_ix else { return };
417
418 state.update(cx, |state, cx| {
419 let panel = state.panels.get(ix).expect("BUG: invalid panel index");
420
421 match axis {
422 Axis::Horizontal => state.resize_panel_at_handle(
423 ix,
424 e.position.x - panel.bounds.left(),
425 window,
426 cx,
427 ),
428 Axis::Vertical => state.resize_panel_at_handle(
429 ix,
430 e.position.y - panel.bounds.top(),
431 window,
432 cx,
433 ),
434 }
435 cx.notify();
436 })
437 }
438 });
439
440 window.on_mouse_event({
442 let state = self.state.clone();
443 let current_ix = state.read(cx).resizing_panel_ix;
444 let on_resize = self.on_resize.clone();
445 move |_: &MouseUpEvent, phase, window, cx| {
446 if current_ix.is_none() {
447 return;
448 }
449 if phase.bubble() {
450 state.update(cx, |state, cx| state.done_resizing(cx));
451 on_resize(&state, window, cx);
452 }
453 }
454 })
455 }
456}