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