use crate::{ pane_group::element::pane_axis, workspace_settings::{PaneSplitDirectionHorizontal, PaneSplitDirectionVertical}, AppState, FollowerState, Pane, Workspace, WorkspaceSettings,};use anyhow::{anyhow, Result};use call::{ActiveCall, ParticipantLocation};use client::proto::PeerId;use collections::HashMap;use gpui::{ point, size, Along, AnyView, AnyWeakView, Axis, Bounds, IntoElement, Model, MouseButton, Pixels, Point, StyleRefinement, View, ViewContext,};use parking_lot::Mutex;use project::Project;use serde::Deserialize;use settings::Settings;use std::sync::Arc;use ui::prelude::*; pub const HANDLE_HITBOX_SIZE: f32 = 4.0;const HORIZONTAL_MIN_SIZE: f32 = 80.;const VERTICAL_MIN_SIZE: f32 = 100.; /// One or many panes, arranged in a horizontal or vertical axis due to a split./// Panes have all their tabs and capabilities preserved, and can be split again or resized./// Single-pane group is a regular pane.#[derive(Clone)]pub struct PaneGroup { pub root: Member,} impl PaneGroup { pub fn with_root(root: Member) -> Self { Self { root } } pub fn new(pane: View<Pane>) -> Self { Self { root: Member::Pane(pane), } } pub fn split( &mut self, old_pane: &View<Pane>, new_pane: &View<Pane>, direction: SplitDirection, ) -> Result<()> { match &mut self.root { Member::Pane(pane) => { if pane == old_pane { self.root = Member::new_axis(old_pane.clone(), new_pane.clone(), direction); Ok(()) } else { Err(anyhow!("Pane not found")) } } Member::Axis(axis) => axis.split(old_pane, new_pane, direction), } } pub fn bounding_box_for_pane(&self, pane: &View<Pane>) -> Option<Bounds<Pixels>> { match &self.root { Member::Pane(_) => None, Member::Axis(axis) => axis.bounding_box_for_pane(pane), } } pub fn pane_at_pixel_position(&self, coordinate: Point<Pixels>) -> Option<&View<Pane>> { match &self.root { Member::Pane(pane) => Some(pane), Member::Axis(axis) => axis.pane_at_pixel_position(coordinate), } } /// Returns: /// - Ok(true) if it found and removed a pane /// - Ok(false) if it found but did not remove the pane /// - Err(_) if it did not find the pane pub fn remove(&mut self, pane: &View<Pane>) -> Result<bool> { match &mut self.root { Member::Pane(_) => Ok(false), Member::Axis(axis) => { if let Some(last_pane) = axis.remove(pane)? { self.root = last_pane; } Ok(true) } } } pub fn resize( &mut self, pane: &View<Pane>, direction: Axis, amount: Pixels, bounds: &Bounds<Pixels>, ) { match &mut self.root { Member::Pane(_) => {} Member::Axis(axis) => { let _ = axis.resize(pane, direction, amount, bounds); } }; } pub fn reset_pane_sizes(&mut self) { match &mut self.root { Member::Pane(_) => {} Member::Axis(axis) => { let _ = axis.reset_pane_sizes(); } }; } pub fn swap(&mut self, from: &View<Pane>, to: &View<Pane>) { match &mut self.root { Member::Pane(_) => {} Member::Axis(axis) => axis.swap(from, to), }; } #[allow(clippy::too_many_arguments)] pub fn render( &self, project: &Model<Project>, follower_states: &HashMap<PeerId, FollowerState>, active_call: Option<&Model<ActiveCall>>, active_pane: &View<Pane>, zoomed: Option<&AnyWeakView>, app_state: &Arc<AppState>, cx: &mut ViewContext<Workspace>, ) -> impl IntoElement { self.root.render( project, 0, follower_states, active_call, active_pane, zoomed, app_state, cx, ) } pub fn panes(&self) -> Vec<&View<Pane>> { let mut panes = Vec::new(); self.root.collect_panes(&mut panes); panes } pub fn first_pane(&self) -> View<Pane> { self.root.first_pane() } pub fn find_pane_in_direction( &mut self, active_pane: &View<Pane>, direction: SplitDirection, cx: &WindowContext, ) -> Option<&View<Pane>> { let bounding_box = self.bounding_box_for_pane(active_pane)?; let cursor = active_pane.read(cx).pixel_position_of_cursor(cx); let center = match cursor { Some(cursor) if bounding_box.contains(&cursor) => cursor, _ => bounding_box.center(), }; let distance_to_next = crate::HANDLE_HITBOX_SIZE; let target = match direction { SplitDirection::Left => { Point::new(bounding_box.left() - distance_to_next.into(), center.y) } SplitDirection::Right => { Point::new(bounding_box.right() + distance_to_next.into(), center.y) } SplitDirection::Up => { Point::new(center.x, bounding_box.top() - distance_to_next.into()) } SplitDirection::Down => { Point::new(center.x, bounding_box.bottom() + distance_to_next.into()) } }; self.pane_at_pixel_position(target) }} #[derive(Debug, Clone)]pub enum Member { Axis(PaneAxis), Pane(View<Pane>),} impl Member { fn new_axis(old_pane: View<Pane>, new_pane: View<Pane>, direction: SplitDirection) -> Self { use Axis::*; use SplitDirection::*; let axis = match direction { Up | Down => Vertical, Left | Right => Horizontal, }; let members = match direction { Up | Left => vec![Member::Pane(new_pane), Member::Pane(old_pane)], Down | Right => vec![Member::Pane(old_pane), Member::Pane(new_pane)], }; Member::Axis(PaneAxis::new(axis, members)) } fn contains(&self, needle: &View<Pane>) -> bool { match self { Member::Axis(axis) => axis.members.iter().any(|member| member.contains(needle)), Member::Pane(pane) => pane == needle, } } fn first_pane(&self) -> View<Pane> { match self { Member::Axis(axis) => axis.members[0].first_pane(), Member::Pane(pane) => pane.clone(), } } #[allow(clippy::too_many_arguments)] pub fn render( &self, project: &Model<Project>, basis: usize, follower_states: &HashMap<PeerId, FollowerState>, active_call: Option<&Model<ActiveCall>>, active_pane: &View<Pane>, zoomed: Option<&AnyWeakView>, app_state: &Arc<AppState>, cx: &mut ViewContext<Workspace>, ) -> impl IntoElement { match self { Member::Pane(pane) => { if zoomed == Some(&pane.downgrade().into()) { return div().into_any(); } let follower_state = follower_states.iter().find_map(|(leader_id, state)| { if state.center_pane == *pane { Some((*leader_id, state)) } else { None } }); let leader = follower_state.as_ref().and_then(|(leader_id, _)| { let room = active_call?.read(cx).room()?.read(cx); room.remote_participant_for_peer_id(*leader_id) }); let is_in_unshared_view = follower_state.as_ref().map_or(false, |(_, state)| { state.active_view_id.is_some_and(|view_id| { !state.items_by_leader_view_id.contains_key(&view_id) }) }); let is_in_panel = follower_state .as_ref() .map_or(false, |(_, state)| state.dock_pane.is_some()); let mut leader_border = None; let mut leader_status_box = None; let mut leader_join_data = None; if let Some(leader) = &leader { let mut leader_color = cx .theme() .players() .color_for_participant(leader.participant_index.0) .cursor; if is_in_panel { leader_color.fade_out(0.75); } else { leader_color.fade_out(0.3); } leader_border = Some(leader_color); leader_status_box = match leader.location { ParticipantLocation::SharedProject { project_id: leader_project_id, } => { if Some(leader_project_id) == project.read(cx).remote_id() { if is_in_unshared_view { Some(Label::new(format!( "{} is in an unshared pane", leader.user.github_login ))) } else { None } } else { leader_join_data = Some((leader_project_id, leader.user.id)); Some(Label::new(format!( "Follow {} to their active project", leader.user.github_login, ))) } } ParticipantLocation::UnsharedProject => Some(Label::new(format!( "{} is viewing an unshared Zed project", leader.user.github_login ))), ParticipantLocation::External => Some(Label::new(format!( "{} is viewing a window outside of Zed", leader.user.github_login ))), }; } div() .relative() .flex_1() .size_full() .child( AnyView::from(pane.clone()) .cached(StyleRefinement::default().v_flex().size_full()), ) .when_some(leader_border, |this, color| { this.child( div() .absolute() .size_full() .left_0() .top_0() .border_2() .border_color(color), ) }) .when_some(leader_status_box, |this, status_box| { this.child( div() .absolute() .w_96() .bottom_3() .right_3() .elevation_2(cx) .p_1() .child(status_box) .when_some( leader_join_data, |this, (leader_project_id, leader_user_id)| { this.cursor_pointer().on_mouse_down( MouseButton::Left, cx.listener(move |this, _, cx| { crate::join_in_room_project( leader_project_id, leader_user_id, this.app_state().clone(), cx, ) .detach_and_log_err(cx); }), ) }, ), ) }) .into_any() } Member::Axis(axis) => axis .render( project, basis + 1, follower_states, active_call, active_pane, zoomed, app_state, cx, ) .into_any(), } } fn collect_panes<'a>(&'a self, panes: &mut Vec<&'a View<Pane>>) { match self { Member::Axis(axis) => { for member in &axis.members { member.collect_panes(panes); } } Member::Pane(pane) => panes.push(pane), } }} #[derive(Debug, Clone)]pub struct PaneAxis { pub axis: Axis, pub members: Vec<Member>, pub flexes: Arc<Mutex<Vec<f32>>>, pub bounding_boxes: Arc<Mutex<Vec<Option<Bounds<Pixels>>>>>,} impl PaneAxis { pub fn new(axis: Axis, members: Vec<Member>) -> Self { let flexes = Arc::new(Mutex::new(vec![1.; members.len()])); let bounding_boxes = Arc::new(Mutex::new(vec![None; members.len()])); Self { axis, members, flexes, bounding_boxes, } } pub fn load(axis: Axis, members: Vec<Member>, flexes: Option<Vec<f32>>) -> Self { let flexes = flexes.unwrap_or_else(|| vec![1.; members.len()]); // debug_assert!(members.len() == flexes.len()); let flexes = Arc::new(Mutex::new(flexes)); let bounding_boxes = Arc::new(Mutex::new(vec![None; members.len()])); Self { axis, members, flexes, bounding_boxes, } } fn split( &mut self, old_pane: &View<Pane>, new_pane: &View<Pane>, direction: SplitDirection, ) -> Result<()> { for (mut idx, member) in self.members.iter_mut().enumerate() { match member { Member::Axis(axis) => { if axis.split(old_pane, new_pane, direction).is_ok() { return Ok(()); } } Member::Pane(pane) => { if pane == old_pane { if direction.axis() == self.axis { if direction.increasing() { idx += 1; } self.members.insert(idx, Member::Pane(new_pane.clone())); *self.flexes.lock() = vec![1.; self.members.len()]; } else { *member = Member::new_axis(old_pane.clone(), new_pane.clone(), direction); } return Ok(()); } } } } Err(anyhow!("Pane not found")) } fn remove(&mut self, pane_to_remove: &View<Pane>) -> Result<Option<Member>> { let mut found_pane = false; let mut remove_member = None; for (idx, member) in self.members.iter_mut().enumerate() { match member { Member::Axis(axis) => { if let Ok(last_pane) = axis.remove(pane_to_remove) { if let Some(last_pane) = last_pane { *member = last_pane; } found_pane = true; break; } } Member::Pane(pane) => { if pane == pane_to_remove { found_pane = true; remove_member = Some(idx); break; } } } } if found_pane { if let Some(idx) = remove_member { self.members.remove(idx); *self.flexes.lock() = vec![1.; self.members.len()]; } if self.members.len() == 1 { let result = self.members.pop(); *self.flexes.lock() = vec![1.; self.members.len()]; Ok(result) } else { Ok(None) } } else { Err(anyhow!("Pane not found"))Lines 1–500 of 1253. Load more
use crate::{ pane_group::element::pane_axis, workspace_settings::{PaneSplitDirectionHorizontal, PaneSplitDirectionVertical}, AppState, FollowerState, Pane, Workspace, WorkspaceSettings, }; use anyhow::{anyhow, Result}; use call::{ActiveCall, ParticipantLocation}; use client::proto::PeerId; use collections::HashMap; use gpui::{ point, size, Along, AnyView, AnyWeakView, Axis, Bounds, IntoElement, Model, MouseButton, Pixels, Point, StyleRefinement, View, ViewContext, }; use parking_lot::Mutex; use project::Project; use serde::Deserialize; use settings::Settings; use std::sync::Arc; use ui::prelude::*; pub const HANDLE_HITBOX_SIZE: f32 = 4.0; const HORIZONTAL_MIN_SIZE: f32 = 80.; const VERTICAL_MIN_SIZE: f32 = 100.; /// One or many panes, arranged in a horizontal or vertical axis due to a split. /// Panes have all their tabs and capabilities preserved, and can be split again or resized. /// Single-pane group is a regular pane. #[derive(Clone)] pub struct PaneGroup { pub root: Member, } impl PaneGroup { pub fn with_root(root: Member) -> Self { Self { root } } pub fn new(pane: View<Pane>) -> Self { Self { root: Member::Pane(pane), } } pub fn split( &mut self, old_pane: &View<Pane>, new_pane: &View<Pane>, direction: SplitDirection, ) -> Result<()> { match &mut self.root { Member::Pane(pane) => { if pane == old_pane { self.root = Member::new_axis(old_pane.clone(), new_pane.clone(), direction); Ok(()) } else { Err(anyhow!("Pane not found")) } } Member::Axis(axis) => axis.split(old_pane, new_pane, direction), } } pub fn bounding_box_for_pane(&self, pane: &View<Pane>) -> Option<Bounds<Pixels>> { match &self.root { Member::Pane(_) => None, Member::Axis(axis) => axis.bounding_box_for_pane(pane), } } pub fn pane_at_pixel_position(&self, coordinate: Point<Pixels>) -> Option<&View<Pane>> { match &self.root { Member::Pane(pane) => Some(pane), Member::Axis(axis) => axis.pane_at_pixel_position(coordinate), } } /// Returns: /// - Ok(true) if it found and removed a pane /// - Ok(false) if it found but did not remove the pane /// - Err(_) if it did not find the pane pub fn remove(&mut self, pane: &View<Pane>) -> Result<bool> { match &mut self.root { Member::Pane(_) => Ok(false), Member::Axis(axis) => { if let Some(last_pane) = axis.remove(pane)? { self.root = last_pane; } Ok(true) } } } pub fn resize( &mut self, pane: &View<Pane>, direction: Axis, amount: Pixels, bounds: &Bounds<Pixels>, ) { match &mut self.root { Member::Pane(_) => {} Member::Axis(axis) => { let _ = axis.resize(pane, direction, amount, bounds); } }; } pub fn reset_pane_sizes(&mut self) { match &mut self.root { Member::Pane(_) => {} Member::Axis(axis) => { let _ = axis.reset_pane_sizes(); } }; } pub fn swap(&mut self, from: &View<Pane>, to: &View<Pane>) { match &mut self.root { Member::Pane(_) => {} Member::Axis(axis) => axis.swap(from, to), }; } #[allow(clippy::too_many_arguments)] pub fn render( &self, project: &Model<Project>, follower_states: &HashMap<PeerId, FollowerState>, active_call: Option<&Model<ActiveCall>>, active_pane: &View<Pane>, zoomed: Option<&AnyWeakView>, app_state: &Arc<AppState>, cx: &mut ViewContext<Workspace>, ) -> impl IntoElement { self.root.render( project, 0, follower_states, active_call, active_pane, zoomed, app_state, cx, ) } pub fn panes(&self) -> Vec<&View<Pane>> { let mut panes = Vec::new(); self.root.collect_panes(&mut panes); panes } pub fn first_pane(&self) -> View<Pane> { self.root.first_pane() } pub fn find_pane_in_direction( &mut self, active_pane: &View<Pane>, direction: SplitDirection, cx: &WindowContext, ) -> Option<&View<Pane>> { let bounding_box = self.bounding_box_for_pane(active_pane)?; let cursor = active_pane.read(cx).pixel_position_of_cursor(cx); let center = match cursor { Some(cursor) if bounding_box.contains(&cursor) => cursor, _ => bounding_box.center(), }; let distance_to_next = crate::HANDLE_HITBOX_SIZE; let target = match direction { SplitDirection::Left => { Point::new(bounding_box.left() - distance_to_next.into(), center.y) } SplitDirection::Right => { Point::new(bounding_box.right() + distance_to_next.into(), center.y) } SplitDirection::Up => { Point::new(center.x, bounding_box.top() - distance_to_next.into()) } SplitDirection::Down => { Point::new(center.x, bounding_box.bottom() + distance_to_next.into()) } }; self.pane_at_pixel_position(target) } } #[derive(Debug, Clone)] pub enum Member { Axis(PaneAxis), Pane(View<Pane>), } impl Member { fn new_axis(old_pane: View<Pane>, new_pane: View<Pane>, direction: SplitDirection) -> Self { use Axis::*; use SplitDirection::*; let axis = match direction { Up | Down => Vertical, Left | Right => Horizontal, }; let members = match direction { Up | Left => vec![Member::Pane(new_pane), Member::Pane(old_pane)], Down | Right => vec![Member::Pane(old_pane), Member::Pane(new_pane)], }; Member::Axis(PaneAxis::new(axis, members)) } fn contains(&self, needle: &View<Pane>) -> bool { match self { Member::Axis(axis) => axis.members.iter().any(|member| member.contains(needle)), Member::Pane(pane) => pane == needle, } } fn first_pane(&self) -> View<Pane> { match self { Member::Axis(axis) => axis.members[0].first_pane(), Member::Pane(pane) => pane.clone(), } } #[allow(clippy::too_many_arguments)] pub fn render( &self, project: &Model<Project>, basis: usize, follower_states: &HashMap<PeerId, FollowerState>, active_call: Option<&Model<ActiveCall>>, active_pane: &View<Pane>, zoomed: Option<&AnyWeakView>, app_state: &Arc<AppState>, cx: &mut ViewContext<Workspace>, ) -> impl IntoElement { match self { Member::Pane(pane) => { if zoomed == Some(&pane.downgrade().into()) { return div().into_any(); } let follower_state = follower_states.iter().find_map(|(leader_id, state)| { if state.center_pane == *pane { Some((*leader_id, state)) } else { None } }); let leader = follower_state.as_ref().and_then(|(leader_id, _)| { let room = active_call?.read(cx).room()?.read(cx); room.remote_participant_for_peer_id(*leader_id) }); let is_in_unshared_view = follower_state.as_ref().map_or(false, |(_, state)| { state.active_view_id.is_some_and(|view_id| { !state.items_by_leader_view_id.contains_key(&view_id) }) }); let is_in_panel = follower_state .as_ref() .map_or(false, |(_, state)| state.dock_pane.is_some()); let mut leader_border = None; let mut leader_status_box = None; let mut leader_join_data = None; if let Some(leader) = &leader { let mut leader_color = cx .theme() .players() .color_for_participant(leader.participant_index.0) .cursor; if is_in_panel { leader_color.fade_out(0.75); } else { leader_color.fade_out(0.3); } leader_border = Some(leader_color); leader_status_box = match leader.location { ParticipantLocation::SharedProject { project_id: leader_project_id, } => { if Some(leader_project_id) == project.read(cx).remote_id() { if is_in_unshared_view { Some(Label::new(format!( "{} is in an unshared pane", leader.user.github_login ))) } else { None } } else { leader_join_data = Some((leader_project_id, leader.user.id)); Some(Label::new(format!( "Follow {} to their active project", leader.user.github_login, ))) } } ParticipantLocation::UnsharedProject => Some(Label::new(format!( "{} is viewing an unshared Zed project", leader.user.github_login ))), ParticipantLocation::External => Some(Label::new(format!( "{} is viewing a window outside of Zed", leader.user.github_login ))), }; } div() .relative() .flex_1() .size_full() .child( AnyView::from(pane.clone()) .cached(StyleRefinement::default().v_flex().size_full()), ) .when_some(leader_border, |this, color| { this.child( div() .absolute() .size_full() .left_0() .top_0() .border_2() .border_color(color), ) }) .when_some(leader_status_box, |this, status_box| { this.child( div() .absolute() .w_96() .bottom_3() .right_3() .elevation_2(cx) .p_1() .child(status_box) .when_some( leader_join_data, |this, (leader_project_id, leader_user_id)| { this.cursor_pointer().on_mouse_down( MouseButton::Left, cx.listener(move |this, _, cx| { crate::join_in_room_project( leader_project_id, leader_user_id, this.app_state().clone(), cx, ) .detach_and_log_err(cx); }), ) }, ), ) }) .into_any() } Member::Axis(axis) => axis .render( project, basis + 1, follower_states, active_call, active_pane, zoomed, app_state, cx, ) .into_any(), } } fn collect_panes<'a>(&'a self, panes: &mut Vec<&'a View<Pane>>) { match self { Member::Axis(axis) => { for member in &axis.members { member.collect_panes(panes); } } Member::Pane(pane) => panes.push(pane), } } } #[derive(Debug, Clone)] pub struct PaneAxis { pub axis: Axis, pub members: Vec<Member>, pub flexes: Arc<Mutex<Vec<f32>>>, pub bounding_boxes: Arc<Mutex<Vec<Option<Bounds<Pixels>>>>>, } impl PaneAxis { pub fn new(axis: Axis, members: Vec<Member>) -> Self { let flexes = Arc::new(Mutex::new(vec![1.; members.len()])); let bounding_boxes = Arc::new(Mutex::new(vec![None; members.len()])); Self { axis, members, flexes, bounding_boxes, } } pub fn load(axis: Axis, members: Vec<Member>, flexes: Option<Vec<f32>>) -> Self { let flexes = flexes.unwrap_or_else(|| vec![1.; members.len()]); // debug_assert!(members.len() == flexes.len()); let flexes = Arc::new(Mutex::new(flexes)); let bounding_boxes = Arc::new(Mutex::new(vec![None; members.len()])); Self { axis, members, flexes, bounding_boxes, } } fn split( &mut self, old_pane: &View<Pane>, new_pane: &View<Pane>, direction: SplitDirection, ) -> Result<()> { for (mut idx, member) in self.members.iter_mut().enumerate() { match member { Member::Axis(axis) => { if axis.split(old_pane, new_pane, direction).is_ok() { return Ok(()); } } Member::Pane(pane) => { if pane == old_pane { if direction.axis() == self.axis { if direction.increasing() { idx += 1; } self.members.insert(idx, Member::Pane(new_pane.clone())); *self.flexes.lock() = vec![1.; self.members.len()]; } else { *member = Member::new_axis(old_pane.clone(), new_pane.clone(), direction); } return Ok(()); } } } } Err(anyhow!("Pane not found")) } fn remove(&mut self, pane_to_remove: &View<Pane>) -> Result<Option<Member>> { let mut found_pane = false; let mut remove_member = None; for (idx, member) in self.members.iter_mut().enumerate() { match member { Member::Axis(axis) => { if let Ok(last_pane) = axis.remove(pane_to_remove) { if let Some(last_pane) = last_pane { *member = last_pane; } found_pane = true; break; } } Member::Pane(pane) => { if pane == pane_to_remove { found_pane = true; remove_member = Some(idx); break; } } } } if found_pane { if let Some(idx) = remove_member { self.members.remove(idx); *self.flexes.lock() = vec![1.; self.members.len()]; } if self.members.len() == 1 { let result = self.members.pop(); *self.flexes.lock() = vec![1.; self.members.len()]; Ok(result) } else { Ok(None) } } else { Err(anyhow!("Pane not found"))