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