1use crate::widgets::{self, Layout, SplitStyle};
4use gpui::{
5 AnyElement, App, Axis, DragMoveEvent, ElementId, Empty, IntoElement, MouseButton, Pixels,
6 Refineable, RenderOnce, StyleRefinement, Window, div, prelude::*, px,
7};
8use theme::Theme;
9
10#[derive(Clone)]
11struct Drag(ElementId);
12
13#[derive(Default)]
14struct Gesture {
15 held: bool,
16 offset: Pixels,
17}
18
19#[derive(IntoElement)]
23pub struct Split {
24 id: ElementId,
25 axis: Axis,
26 first: AnyElement,
27 second: AnyElement,
28 ratio: f32,
29 min: Pixels,
30 resized: Option<Box<Resize>>,
31 style: StyleRefinement,
32}
33
34type Resize = dyn Fn(&f32, &mut Window, &mut App);
35
36impl Split {
37 pub fn horizontal(
38 id: impl Into<ElementId>,
39 first: impl IntoElement,
40 second: impl IntoElement,
41 ) -> Self {
42 Self {
43 id: id.into(),
44 axis: Axis::Horizontal,
45 first: first.into_any_element(),
46 second: second.into_any_element(),
47 ratio: 0.5,
48 min: px(0.0),
49 resized: None,
50 style: StyleRefinement::default(),
51 }
52 }
53
54 pub fn vertical(
55 id: impl Into<ElementId>,
56 first: impl IntoElement,
57 second: impl IntoElement,
58 ) -> Self {
59 Self {
60 axis: Axis::Vertical,
61 ..Self::horizontal(id, first, second)
62 }
63 }
64
65 pub fn ratio(mut self, ratio: f32) -> Self {
67 self.ratio = ratio.clamp(0.0, 1.0);
68 self
69 }
70
71 pub fn min(mut self, min: Pixels) -> Self {
72 self.min = min.max(px(0.0));
73 self
74 }
75
76 pub fn on_resize(mut self, resized: impl Fn(&f32, &mut Window, &mut App) + 'static) -> Self {
77 self.resized = Some(Box::new(resized));
78 self
79 }
80}
81
82impl Styled for Split {
83 fn style(&mut self) -> &mut StyleRefinement {
84 &mut self.style
85 }
86}
87
88impl RenderOnce for Split {
89 fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
90 let gesture = window.use_keyed_state(self.id.clone(), cx, |_, _| Gesture::default());
91 let held = gesture.read(cx).held;
92 let pressed = gesture.clone();
93 let released = gesture.clone();
94 let axis = self.axis;
95 let min = self.min;
96 let id = self.id.clone();
97 let pane = |child, weight| {
98 div()
99 .flex_grow(weight)
100 .flex_shrink(1.0)
101 .flex_basis(px(0.0))
102 .min_w_0()
103 .min_h_0()
104 .overflow_hidden()
105 .map(|pane| match axis {
106 Axis::Horizontal => pane.min_w(min).h_full(),
107 Axis::Vertical => pane.min_h(min).w_full(),
108 })
109 .child(child)
110 };
111 let handle = Theme::of(cx)
112 .split_handle(axis, SplitStyle::Line { dragging: held })
113 .id("divider")
114 .on_drag(Drag(id.clone()), move |_, offset, _, cx| {
115 pressed.update(cx, |gesture, cx| {
116 gesture.held = true;
117 gesture.offset = match axis {
118 Axis::Horizontal => offset.x,
119 Axis::Vertical => offset.y,
120 } - px(widgets::SPLIT_HANDLE_HIT / 2.0);
121 cx.notify();
122 });
123 cx.new(|_| Empty)
124 });
125 let mut frame = div()
126 .id(self.id)
127 .size_full()
128 .flex()
129 .map(|frame| match axis {
130 Axis::Horizontal => frame.flex_row(),
131 Axis::Vertical => frame.flex_col(),
132 })
133 .when_some(self.resized, |frame, resized| {
134 frame.on_drag_move(move |event: &DragMoveEvent<Drag>, window, cx| {
135 if event.drag(cx).0 != id {
136 return;
137 }
138 let mut bounds = event.bounds;
139 let half = px(widgets::SPLIT_HANDLE_HIT / 2.0);
140 let extent = match axis {
141 Axis::Horizontal => {
142 bounds.origin.x += half + gesture.read(cx).offset;
143 bounds.size.width -= half * 2.0;
144 bounds.size.width
145 }
146 Axis::Vertical => {
147 bounds.origin.y += half + gesture.read(cx).offset;
148 bounds.size.height -= half * 2.0;
149 bounds.size.height
150 }
151 };
152 if extent <= px(0.0) {
153 return;
154 }
155 let ratio =
156 widgets::axis_fraction(event.event.position, bounds, axis, min / extent);
157 resized(&ratio, window, cx);
158 })
159 })
160 .on_mouse_up(MouseButton::Left, {
161 let released = released.clone();
162 move |_, _, cx| {
163 released.update(cx, |gesture, cx| {
164 if std::mem::take(&mut gesture.held) {
165 cx.notify();
166 }
167 })
168 }
169 })
170 .on_mouse_up_out(MouseButton::Left, move |_, _, cx| {
171 released.update(cx, |gesture, cx| {
172 if std::mem::take(&mut gesture.held) {
173 cx.notify();
174 }
175 })
176 })
177 .child(pane(self.first, self.ratio))
178 .child(handle)
179 .child(pane(self.second, 1.0 - self.ratio));
180 frame.style().refine(&self.style);
181 frame
182 }
183}