pane_group.rs

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