2022-03-21 22:12:15 +00:00
|
|
|
use crate::{FollowerStatesByLeader, Pane};
|
2021-03-18 21:52:46 +00:00
|
|
|
use anyhow::{anyhow, Result};
|
2022-03-21 22:12:15 +00:00
|
|
|
use client::PeerId;
|
|
|
|
use collections::HashMap;
|
|
|
|
use gpui::{elements::*, Axis, Border, ViewHandle};
|
|
|
|
use project::Collaborator;
|
2021-10-05 09:14:30 +00:00
|
|
|
use theme::Theme;
|
2021-03-18 21:52:46 +00:00
|
|
|
|
|
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
|
|
pub struct PaneGroup {
|
|
|
|
root: Member,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl PaneGroup {
|
2022-01-27 16:52:36 +00:00
|
|
|
pub fn new(pane: ViewHandle<Pane>) -> Self {
|
2021-03-18 21:52:46 +00:00
|
|
|
Self {
|
2022-01-27 16:52:36 +00:00
|
|
|
root: Member::Pane(pane),
|
2021-03-18 21:52:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn split(
|
|
|
|
&mut self,
|
2022-01-27 16:52:36 +00:00
|
|
|
old_pane: &ViewHandle<Pane>,
|
|
|
|
new_pane: &ViewHandle<Pane>,
|
2021-03-18 21:52:46 +00:00
|
|
|
direction: SplitDirection,
|
|
|
|
) -> Result<()> {
|
|
|
|
match &mut self.root {
|
2022-01-27 16:52:36 +00:00
|
|
|
Member::Pane(pane) => {
|
|
|
|
if pane == old_pane {
|
|
|
|
self.root = Member::new_axis(old_pane.clone(), new_pane.clone(), direction);
|
2021-03-18 21:52:46 +00:00
|
|
|
Ok(())
|
|
|
|
} else {
|
|
|
|
Err(anyhow!("Pane not found"))
|
|
|
|
}
|
|
|
|
}
|
2022-01-27 16:52:36 +00:00
|
|
|
Member::Axis(axis) => axis.split(old_pane, new_pane, direction),
|
2021-03-18 21:52:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-27 16:52:36 +00:00
|
|
|
pub fn remove(&mut self, pane: &ViewHandle<Pane>) -> Result<bool> {
|
2021-03-18 21:52:46 +00:00
|
|
|
match &mut self.root {
|
|
|
|
Member::Pane(_) => Ok(false),
|
|
|
|
Member::Axis(axis) => {
|
2022-01-27 16:52:36 +00:00
|
|
|
if let Some(last_pane) = axis.remove(pane)? {
|
2021-03-18 21:52:46 +00:00
|
|
|
self.root = last_pane;
|
|
|
|
}
|
|
|
|
Ok(true)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-21 22:12:15 +00:00
|
|
|
pub(crate) fn render<'a>(
|
|
|
|
&self,
|
|
|
|
theme: &Theme,
|
|
|
|
follower_states: &FollowerStatesByLeader,
|
|
|
|
collaborators: &HashMap<PeerId, Collaborator>,
|
|
|
|
) -> ElementBox {
|
|
|
|
self.root.render(theme, follower_states, collaborators)
|
2021-03-18 21:52:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
|
|
enum Member {
|
|
|
|
Axis(PaneAxis),
|
2022-01-27 16:52:36 +00:00
|
|
|
Pane(ViewHandle<Pane>),
|
2021-03-18 21:52:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Member {
|
2022-01-27 16:52:36 +00:00
|
|
|
fn new_axis(
|
|
|
|
old_pane: ViewHandle<Pane>,
|
|
|
|
new_pane: ViewHandle<Pane>,
|
|
|
|
direction: SplitDirection,
|
|
|
|
) -> Self {
|
2021-03-18 21:52:46 +00:00
|
|
|
use Axis::*;
|
|
|
|
use SplitDirection::*;
|
|
|
|
|
|
|
|
let axis = match direction {
|
|
|
|
Up | Down => Vertical,
|
|
|
|
Left | Right => Horizontal,
|
|
|
|
};
|
|
|
|
|
|
|
|
let members = match direction {
|
2022-01-27 16:52:36 +00:00
|
|
|
Up | Left => vec![Member::Pane(new_pane), Member::Pane(old_pane)],
|
|
|
|
Down | Right => vec![Member::Pane(old_pane), Member::Pane(new_pane)],
|
2021-03-18 21:52:46 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
Member::Axis(PaneAxis { axis, members })
|
|
|
|
}
|
|
|
|
|
2022-03-21 22:12:15 +00:00
|
|
|
pub fn render(
|
|
|
|
&self,
|
|
|
|
theme: &Theme,
|
|
|
|
follower_states: &FollowerStatesByLeader,
|
|
|
|
collaborators: &HashMap<PeerId, Collaborator>,
|
|
|
|
) -> ElementBox {
|
2021-03-18 21:52:46 +00:00
|
|
|
match self {
|
2022-03-21 22:12:15 +00:00
|
|
|
Member::Pane(pane) => {
|
|
|
|
let mut border = Border::default();
|
|
|
|
let leader = follower_states
|
|
|
|
.iter()
|
|
|
|
.find_map(|(leader_id, follower_states)| {
|
|
|
|
if follower_states.contains_key(pane) {
|
|
|
|
Some(leader_id)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.and_then(|leader_id| collaborators.get(leader_id));
|
|
|
|
if let Some(leader) = leader {
|
|
|
|
let leader_color = theme
|
|
|
|
.editor
|
|
|
|
.replica_selection_style(leader.replica_id)
|
|
|
|
.cursor;
|
|
|
|
border = Border::all(1.0, leader_color);
|
|
|
|
border
|
|
|
|
.color
|
|
|
|
.fade_out(1. - theme.workspace.leader_border_opacity);
|
|
|
|
border.overlay = true;
|
|
|
|
}
|
|
|
|
ChildView::new(pane).contained().with_border(border).boxed()
|
|
|
|
}
|
|
|
|
Member::Axis(axis) => axis.render(theme, follower_states, collaborators),
|
2021-03-18 21:52:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
|
|
struct PaneAxis {
|
|
|
|
axis: Axis,
|
|
|
|
members: Vec<Member>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl PaneAxis {
|
|
|
|
fn split(
|
|
|
|
&mut self,
|
2022-01-27 16:52:36 +00:00
|
|
|
old_pane: &ViewHandle<Pane>,
|
|
|
|
new_pane: &ViewHandle<Pane>,
|
2021-03-18 21:52:46 +00:00
|
|
|
direction: SplitDirection,
|
|
|
|
) -> Result<()> {
|
|
|
|
use SplitDirection::*;
|
|
|
|
|
|
|
|
for (idx, member) in self.members.iter_mut().enumerate() {
|
|
|
|
match member {
|
|
|
|
Member::Axis(axis) => {
|
2022-01-27 16:52:36 +00:00
|
|
|
if axis.split(old_pane, new_pane, direction).is_ok() {
|
2021-03-18 21:52:46 +00:00
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
}
|
2022-01-27 16:52:36 +00:00
|
|
|
Member::Pane(pane) => {
|
|
|
|
if pane == old_pane {
|
2021-03-18 21:52:46 +00:00
|
|
|
if direction.matches_axis(self.axis) {
|
|
|
|
match direction {
|
|
|
|
Up | Left => {
|
2022-01-27 16:52:36 +00:00
|
|
|
self.members.insert(idx, Member::Pane(new_pane.clone()));
|
2021-03-18 21:52:46 +00:00
|
|
|
}
|
|
|
|
Down | Right => {
|
2022-01-27 16:52:36 +00:00
|
|
|
self.members.insert(idx + 1, Member::Pane(new_pane.clone()));
|
2021-03-18 21:52:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2022-01-27 16:52:36 +00:00
|
|
|
*member =
|
|
|
|
Member::new_axis(old_pane.clone(), new_pane.clone(), direction);
|
2021-03-18 21:52:46 +00:00
|
|
|
}
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Err(anyhow!("Pane not found"))
|
|
|
|
}
|
|
|
|
|
2022-01-27 16:52:36 +00:00
|
|
|
fn remove(&mut self, pane_to_remove: &ViewHandle<Pane>) -> Result<Option<Member>> {
|
2021-03-18 21:52:46 +00:00
|
|
|
let mut found_pane = false;
|
|
|
|
let mut remove_member = None;
|
|
|
|
for (idx, member) in self.members.iter_mut().enumerate() {
|
|
|
|
match member {
|
|
|
|
Member::Axis(axis) => {
|
2022-01-27 16:52:36 +00:00
|
|
|
if let Ok(last_pane) = axis.remove(pane_to_remove) {
|
2021-03-18 21:52:46 +00:00
|
|
|
if let Some(last_pane) = last_pane {
|
|
|
|
*member = last_pane;
|
|
|
|
}
|
|
|
|
found_pane = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2022-01-27 16:52:36 +00:00
|
|
|
Member::Pane(pane) => {
|
|
|
|
if pane == pane_to_remove {
|
2021-03-18 21:52:46 +00:00
|
|
|
found_pane = true;
|
|
|
|
remove_member = Some(idx);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if found_pane {
|
|
|
|
if let Some(idx) = remove_member {
|
|
|
|
self.members.remove(idx);
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.members.len() == 1 {
|
|
|
|
Ok(self.members.pop())
|
|
|
|
} else {
|
|
|
|
Ok(None)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Err(anyhow!("Pane not found"))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-21 22:12:15 +00:00
|
|
|
fn render(
|
|
|
|
&self,
|
|
|
|
theme: &Theme,
|
|
|
|
follower_state: &FollowerStatesByLeader,
|
|
|
|
collaborators: &HashMap<PeerId, Collaborator>,
|
|
|
|
) -> ElementBox {
|
2021-03-18 21:52:46 +00:00
|
|
|
let last_member_ix = self.members.len() - 1;
|
|
|
|
Flex::new(self.axis)
|
|
|
|
.with_children(self.members.iter().enumerate().map(|(ix, member)| {
|
2022-03-21 22:12:15 +00:00
|
|
|
let mut member = member.render(theme, follower_state, collaborators);
|
2021-03-18 21:52:46 +00:00
|
|
|
if ix < last_member_ix {
|
2021-09-07 22:04:29 +00:00
|
|
|
let mut border = theme.workspace.pane_divider;
|
|
|
|
border.left = false;
|
|
|
|
border.right = false;
|
|
|
|
border.top = false;
|
|
|
|
border.bottom = false;
|
2021-03-18 21:52:46 +00:00
|
|
|
match self.axis {
|
|
|
|
Axis::Vertical => border.bottom = true,
|
|
|
|
Axis::Horizontal => border.right = true,
|
|
|
|
}
|
|
|
|
member = Container::new(member).with_border(border).boxed();
|
|
|
|
}
|
|
|
|
|
2022-01-07 01:29:34 +00:00
|
|
|
Flexible::new(1.0, true, member).boxed()
|
2021-03-18 21:52:46 +00:00
|
|
|
}))
|
|
|
|
.boxed()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-23 03:02:48 +00:00
|
|
|
#[derive(Clone, Copy, Debug)]
|
2021-03-18 21:52:46 +00:00
|
|
|
pub enum SplitDirection {
|
|
|
|
Up,
|
|
|
|
Down,
|
|
|
|
Left,
|
|
|
|
Right,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl SplitDirection {
|
|
|
|
fn matches_axis(self, orientation: Axis) -> bool {
|
|
|
|
use Axis::*;
|
|
|
|
use SplitDirection::*;
|
|
|
|
|
|
|
|
match self {
|
|
|
|
Up | Down => match orientation {
|
|
|
|
Vertical => true,
|
|
|
|
Horizontal => false,
|
|
|
|
},
|
|
|
|
Left | Right => match orientation {
|
|
|
|
Vertical => false,
|
|
|
|
Horizontal => true,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
// use super::*;
|
|
|
|
// use serde_json::json;
|
|
|
|
|
|
|
|
// #[test]
|
|
|
|
// fn test_split_and_remove() -> Result<()> {
|
|
|
|
// let mut group = PaneGroup::new(1);
|
|
|
|
// assert_eq!(
|
|
|
|
// serde_json::to_value(&group)?,
|
|
|
|
// json!({
|
|
|
|
// "type": "pane",
|
|
|
|
// "paneId": 1,
|
|
|
|
// })
|
|
|
|
// );
|
|
|
|
|
|
|
|
// group.split(1, 2, SplitDirection::Right)?;
|
|
|
|
// assert_eq!(
|
|
|
|
// serde_json::to_value(&group)?,
|
|
|
|
// json!({
|
|
|
|
// "type": "axis",
|
|
|
|
// "orientation": "horizontal",
|
|
|
|
// "members": [
|
|
|
|
// {"type": "pane", "paneId": 1},
|
|
|
|
// {"type": "pane", "paneId": 2},
|
|
|
|
// ]
|
|
|
|
// })
|
|
|
|
// );
|
|
|
|
|
|
|
|
// group.split(2, 3, SplitDirection::Up)?;
|
|
|
|
// assert_eq!(
|
|
|
|
// serde_json::to_value(&group)?,
|
|
|
|
// json!({
|
|
|
|
// "type": "axis",
|
|
|
|
// "orientation": "horizontal",
|
|
|
|
// "members": [
|
|
|
|
// {"type": "pane", "paneId": 1},
|
|
|
|
// {
|
|
|
|
// "type": "axis",
|
|
|
|
// "orientation": "vertical",
|
|
|
|
// "members": [
|
|
|
|
// {"type": "pane", "paneId": 3},
|
|
|
|
// {"type": "pane", "paneId": 2},
|
|
|
|
// ]
|
|
|
|
// },
|
|
|
|
// ]
|
|
|
|
// })
|
|
|
|
// );
|
|
|
|
|
|
|
|
// group.split(1, 4, SplitDirection::Right)?;
|
|
|
|
// assert_eq!(
|
|
|
|
// serde_json::to_value(&group)?,
|
|
|
|
// json!({
|
|
|
|
// "type": "axis",
|
|
|
|
// "orientation": "horizontal",
|
|
|
|
// "members": [
|
|
|
|
// {"type": "pane", "paneId": 1},
|
|
|
|
// {"type": "pane", "paneId": 4},
|
|
|
|
// {
|
|
|
|
// "type": "axis",
|
|
|
|
// "orientation": "vertical",
|
|
|
|
// "members": [
|
|
|
|
// {"type": "pane", "paneId": 3},
|
|
|
|
// {"type": "pane", "paneId": 2},
|
|
|
|
// ]
|
|
|
|
// },
|
|
|
|
// ]
|
|
|
|
// })
|
|
|
|
// );
|
|
|
|
|
|
|
|
// group.split(2, 5, SplitDirection::Up)?;
|
|
|
|
// assert_eq!(
|
|
|
|
// serde_json::to_value(&group)?,
|
|
|
|
// json!({
|
|
|
|
// "type": "axis",
|
|
|
|
// "orientation": "horizontal",
|
|
|
|
// "members": [
|
|
|
|
// {"type": "pane", "paneId": 1},
|
|
|
|
// {"type": "pane", "paneId": 4},
|
|
|
|
// {
|
|
|
|
// "type": "axis",
|
|
|
|
// "orientation": "vertical",
|
|
|
|
// "members": [
|
|
|
|
// {"type": "pane", "paneId": 3},
|
|
|
|
// {"type": "pane", "paneId": 5},
|
|
|
|
// {"type": "pane", "paneId": 2},
|
|
|
|
// ]
|
|
|
|
// },
|
|
|
|
// ]
|
|
|
|
// })
|
|
|
|
// );
|
|
|
|
|
|
|
|
// assert_eq!(true, group.remove(5)?);
|
|
|
|
// assert_eq!(
|
|
|
|
// serde_json::to_value(&group)?,
|
|
|
|
// json!({
|
|
|
|
// "type": "axis",
|
|
|
|
// "orientation": "horizontal",
|
|
|
|
// "members": [
|
|
|
|
// {"type": "pane", "paneId": 1},
|
|
|
|
// {"type": "pane", "paneId": 4},
|
|
|
|
// {
|
|
|
|
// "type": "axis",
|
|
|
|
// "orientation": "vertical",
|
|
|
|
// "members": [
|
|
|
|
// {"type": "pane", "paneId": 3},
|
|
|
|
// {"type": "pane", "paneId": 2},
|
|
|
|
// ]
|
|
|
|
// },
|
|
|
|
// ]
|
|
|
|
// })
|
|
|
|
// );
|
|
|
|
|
|
|
|
// assert_eq!(true, group.remove(4)?);
|
|
|
|
// assert_eq!(
|
|
|
|
// serde_json::to_value(&group)?,
|
|
|
|
// json!({
|
|
|
|
// "type": "axis",
|
|
|
|
// "orientation": "horizontal",
|
|
|
|
// "members": [
|
|
|
|
// {"type": "pane", "paneId": 1},
|
|
|
|
// {
|
|
|
|
// "type": "axis",
|
|
|
|
// "orientation": "vertical",
|
|
|
|
// "members": [
|
|
|
|
// {"type": "pane", "paneId": 3},
|
|
|
|
// {"type": "pane", "paneId": 2},
|
|
|
|
// ]
|
|
|
|
// },
|
|
|
|
// ]
|
|
|
|
// })
|
|
|
|
// );
|
|
|
|
|
|
|
|
// assert_eq!(true, group.remove(3)?);
|
|
|
|
// assert_eq!(
|
|
|
|
// serde_json::to_value(&group)?,
|
|
|
|
// json!({
|
|
|
|
// "type": "axis",
|
|
|
|
// "orientation": "horizontal",
|
|
|
|
// "members": [
|
|
|
|
// {"type": "pane", "paneId": 1},
|
|
|
|
// {"type": "pane", "paneId": 2},
|
|
|
|
// ]
|
|
|
|
// })
|
|
|
|
// );
|
|
|
|
|
|
|
|
// assert_eq!(true, group.remove(2)?);
|
|
|
|
// assert_eq!(
|
|
|
|
// serde_json::to_value(&group)?,
|
|
|
|
// json!({
|
|
|
|
// "type": "pane",
|
|
|
|
// "paneId": 1,
|
|
|
|
// })
|
|
|
|
// );
|
|
|
|
|
|
|
|
// assert_eq!(false, group.remove(1)?);
|
|
|
|
// assert_eq!(
|
|
|
|
// serde_json::to_value(&group)?,
|
|
|
|
// json!({
|
|
|
|
// "type": "pane",
|
|
|
|
// "paneId": 1,
|
|
|
|
// })
|
|
|
|
// );
|
|
|
|
|
|
|
|
// Ok(())
|
|
|
|
// }
|
|
|
|
}
|