pane_group.rs

  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}