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