pane_group.rs

  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}