1use anyhow::{anyhow, Result};
2use gpui::{elements::*, Axis, ViewHandle};
3use theme::Theme;
4
5use crate::Pane;
6
7#[derive(Clone, Debug, Eq, PartialEq)]
8pub struct PaneGroup {
9 root: Member,
10}
11
12impl PaneGroup {
13 pub fn new(pane: ViewHandle<Pane>) -> Self {
14 Self {
15 root: Member::Pane(pane),
16 }
17 }
18
19 pub fn split(
20 &mut self,
21 old_pane: &ViewHandle<Pane>,
22 new_pane: &ViewHandle<Pane>,
23 direction: SplitDirection,
24 ) -> Result<()> {
25 match &mut self.root {
26 Member::Pane(pane) => {
27 if pane == old_pane {
28 self.root = Member::new_axis(old_pane.clone(), new_pane.clone(), direction);
29 Ok(())
30 } else {
31 Err(anyhow!("Pane not found"))
32 }
33 }
34 Member::Axis(axis) => axis.split(old_pane, new_pane, direction),
35 }
36 }
37
38 pub fn remove(&mut self, pane: &ViewHandle<Pane>) -> Result<bool> {
39 match &mut self.root {
40 Member::Pane(_) => Ok(false),
41 Member::Axis(axis) => {
42 if let Some(last_pane) = axis.remove(pane)? {
43 self.root = last_pane;
44 }
45 Ok(true)
46 }
47 }
48 }
49
50 pub fn render<'a>(&self, theme: &Theme) -> ElementBox {
51 self.root.render(theme)
52 }
53}
54
55#[derive(Clone, Debug, Eq, PartialEq)]
56enum Member {
57 Axis(PaneAxis),
58 Pane(ViewHandle<Pane>),
59}
60
61impl Member {
62 fn new_axis(
63 old_pane: ViewHandle<Pane>,
64 new_pane: ViewHandle<Pane>,
65 direction: SplitDirection,
66 ) -> Self {
67 use Axis::*;
68 use SplitDirection::*;
69
70 let axis = match direction {
71 Up | Down => Vertical,
72 Left | Right => Horizontal,
73 };
74
75 let members = match direction {
76 Up | Left => vec![Member::Pane(new_pane), Member::Pane(old_pane)],
77 Down | Right => vec![Member::Pane(old_pane), Member::Pane(new_pane)],
78 };
79
80 Member::Axis(PaneAxis { axis, members })
81 }
82
83 pub fn render(&self, theme: &Theme) -> ElementBox {
84 match self {
85 Member::Pane(pane) => ChildView::new(pane).boxed(),
86 Member::Axis(axis) => axis.render(theme),
87 }
88 }
89}
90
91#[derive(Clone, Debug, Eq, PartialEq)]
92struct PaneAxis {
93 axis: Axis,
94 members: Vec<Member>,
95}
96
97impl PaneAxis {
98 fn split(
99 &mut self,
100 old_pane: &ViewHandle<Pane>,
101 new_pane: &ViewHandle<Pane>,
102 direction: SplitDirection,
103 ) -> Result<()> {
104 use SplitDirection::*;
105
106 for (idx, member) in self.members.iter_mut().enumerate() {
107 match member {
108 Member::Axis(axis) => {
109 if axis.split(old_pane, new_pane, direction).is_ok() {
110 return Ok(());
111 }
112 }
113 Member::Pane(pane) => {
114 if pane == old_pane {
115 if direction.matches_axis(self.axis) {
116 match direction {
117 Up | Left => {
118 self.members.insert(idx, Member::Pane(new_pane.clone()));
119 }
120 Down | Right => {
121 self.members.insert(idx + 1, Member::Pane(new_pane.clone()));
122 }
123 }
124 } else {
125 *member =
126 Member::new_axis(old_pane.clone(), new_pane.clone(), direction);
127 }
128 return Ok(());
129 }
130 }
131 }
132 }
133 Err(anyhow!("Pane not found"))
134 }
135
136 fn remove(&mut self, pane_to_remove: &ViewHandle<Pane>) -> Result<Option<Member>> {
137 let mut found_pane = false;
138 let mut remove_member = None;
139 for (idx, member) in self.members.iter_mut().enumerate() {
140 match member {
141 Member::Axis(axis) => {
142 if let Ok(last_pane) = axis.remove(pane_to_remove) {
143 if let Some(last_pane) = last_pane {
144 *member = last_pane;
145 }
146 found_pane = true;
147 break;
148 }
149 }
150 Member::Pane(pane) => {
151 if pane == pane_to_remove {
152 found_pane = true;
153 remove_member = Some(idx);
154 break;
155 }
156 }
157 }
158 }
159
160 if found_pane {
161 if let Some(idx) = remove_member {
162 self.members.remove(idx);
163 }
164
165 if self.members.len() == 1 {
166 Ok(self.members.pop())
167 } else {
168 Ok(None)
169 }
170 } else {
171 Err(anyhow!("Pane not found"))
172 }
173 }
174
175 fn render<'a>(&self, theme: &Theme) -> ElementBox {
176 let last_member_ix = self.members.len() - 1;
177 Flex::new(self.axis)
178 .with_children(self.members.iter().enumerate().map(|(ix, member)| {
179 let mut member = member.render(theme);
180 if ix < last_member_ix {
181 let mut border = theme.workspace.pane_divider;
182 border.left = false;
183 border.right = false;
184 border.top = false;
185 border.bottom = false;
186 match self.axis {
187 Axis::Vertical => border.bottom = true,
188 Axis::Horizontal => border.right = true,
189 }
190 member = Container::new(member).with_border(border).boxed();
191 }
192
193 Flexible::new(1.0, true, member).boxed()
194 }))
195 .boxed()
196 }
197}
198
199#[derive(Clone, Copy, Debug)]
200pub enum SplitDirection {
201 Up,
202 Down,
203 Left,
204 Right,
205}
206
207impl SplitDirection {
208 fn matches_axis(self, orientation: Axis) -> bool {
209 use Axis::*;
210 use SplitDirection::*;
211
212 match self {
213 Up | Down => match orientation {
214 Vertical => true,
215 Horizontal => false,
216 },
217 Left | Right => match orientation {
218 Vertical => false,
219 Horizontal => true,
220 },
221 }
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 // use super::*;
228 // use serde_json::json;
229
230 // #[test]
231 // fn test_split_and_remove() -> Result<()> {
232 // let mut group = PaneGroup::new(1);
233 // assert_eq!(
234 // serde_json::to_value(&group)?,
235 // json!({
236 // "type": "pane",
237 // "paneId": 1,
238 // })
239 // );
240
241 // group.split(1, 2, SplitDirection::Right)?;
242 // assert_eq!(
243 // serde_json::to_value(&group)?,
244 // json!({
245 // "type": "axis",
246 // "orientation": "horizontal",
247 // "members": [
248 // {"type": "pane", "paneId": 1},
249 // {"type": "pane", "paneId": 2},
250 // ]
251 // })
252 // );
253
254 // group.split(2, 3, SplitDirection::Up)?;
255 // assert_eq!(
256 // serde_json::to_value(&group)?,
257 // json!({
258 // "type": "axis",
259 // "orientation": "horizontal",
260 // "members": [
261 // {"type": "pane", "paneId": 1},
262 // {
263 // "type": "axis",
264 // "orientation": "vertical",
265 // "members": [
266 // {"type": "pane", "paneId": 3},
267 // {"type": "pane", "paneId": 2},
268 // ]
269 // },
270 // ]
271 // })
272 // );
273
274 // group.split(1, 4, SplitDirection::Right)?;
275 // assert_eq!(
276 // serde_json::to_value(&group)?,
277 // json!({
278 // "type": "axis",
279 // "orientation": "horizontal",
280 // "members": [
281 // {"type": "pane", "paneId": 1},
282 // {"type": "pane", "paneId": 4},
283 // {
284 // "type": "axis",
285 // "orientation": "vertical",
286 // "members": [
287 // {"type": "pane", "paneId": 3},
288 // {"type": "pane", "paneId": 2},
289 // ]
290 // },
291 // ]
292 // })
293 // );
294
295 // group.split(2, 5, SplitDirection::Up)?;
296 // assert_eq!(
297 // serde_json::to_value(&group)?,
298 // json!({
299 // "type": "axis",
300 // "orientation": "horizontal",
301 // "members": [
302 // {"type": "pane", "paneId": 1},
303 // {"type": "pane", "paneId": 4},
304 // {
305 // "type": "axis",
306 // "orientation": "vertical",
307 // "members": [
308 // {"type": "pane", "paneId": 3},
309 // {"type": "pane", "paneId": 5},
310 // {"type": "pane", "paneId": 2},
311 // ]
312 // },
313 // ]
314 // })
315 // );
316
317 // assert_eq!(true, group.remove(5)?);
318 // assert_eq!(
319 // serde_json::to_value(&group)?,
320 // json!({
321 // "type": "axis",
322 // "orientation": "horizontal",
323 // "members": [
324 // {"type": "pane", "paneId": 1},
325 // {"type": "pane", "paneId": 4},
326 // {
327 // "type": "axis",
328 // "orientation": "vertical",
329 // "members": [
330 // {"type": "pane", "paneId": 3},
331 // {"type": "pane", "paneId": 2},
332 // ]
333 // },
334 // ]
335 // })
336 // );
337
338 // assert_eq!(true, group.remove(4)?);
339 // assert_eq!(
340 // serde_json::to_value(&group)?,
341 // json!({
342 // "type": "axis",
343 // "orientation": "horizontal",
344 // "members": [
345 // {"type": "pane", "paneId": 1},
346 // {
347 // "type": "axis",
348 // "orientation": "vertical",
349 // "members": [
350 // {"type": "pane", "paneId": 3},
351 // {"type": "pane", "paneId": 2},
352 // ]
353 // },
354 // ]
355 // })
356 // );
357
358 // assert_eq!(true, group.remove(3)?);
359 // assert_eq!(
360 // serde_json::to_value(&group)?,
361 // json!({
362 // "type": "axis",
363 // "orientation": "horizontal",
364 // "members": [
365 // {"type": "pane", "paneId": 1},
366 // {"type": "pane", "paneId": 2},
367 // ]
368 // })
369 // );
370
371 // assert_eq!(true, group.remove(2)?);
372 // assert_eq!(
373 // serde_json::to_value(&group)?,
374 // json!({
375 // "type": "pane",
376 // "paneId": 1,
377 // })
378 // );
379
380 // assert_eq!(false, group.remove(1)?);
381 // assert_eq!(
382 // serde_json::to_value(&group)?,
383 // json!({
384 // "type": "pane",
385 // "paneId": 1,
386 // })
387 // );
388
389 // Ok(())
390 // }
391}