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