1357 lines · 45.6 KB · Rust
RawDownload
use crate::{    AppState, CollaboratorId, FollowerState, Pane, Workspace, WorkspaceSettings,    pane_group::element::pane_axis,    workspace_settings::{PaneSplitDirectionHorizontal, PaneSplitDirectionVertical},};use anyhow::Result;use call::{ActiveCall, ParticipantLocation};use collections::HashMap;use gpui::{    Along, AnyView, AnyWeakView, Axis, Bounds, Entity, Hsla, IntoElement, MouseButton, Pixels,    Point, StyleRefinement, WeakEntity, Window, point, size,};use parking_lot::Mutex;use project::Project;use schemars::JsonSchema;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,} pub struct PaneRenderResult {    pub element: gpui::AnyElement,    pub contains_active_pane: bool,} impl PaneGroup {    pub fn with_root(root: Member) -> Self {        Self { root }    }     pub fn new(pane: Entity<Pane>) -> Self {        Self {            root: Member::Pane(pane),        }    }     pub fn split(        &mut self,        old_pane: &Entity<Pane>,        new_pane: &Entity<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 {                    anyhow::bail!("Pane not found");                }            }            Member::Axis(axis) => axis.split(old_pane, new_pane, direction),        }    }     pub fn bounding_box_for_pane(&self, pane: &Entity<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<&Entity<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: &Entity<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: &Entity<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: &Entity<Pane>, to: &Entity<Pane>) {        match &mut self.root {            Member::Pane(_) => {}            Member::Axis(axis) => axis.swap(from, to),        };    }     pub fn render(        &self,        zoomed: Option<&AnyWeakView>,        render_cx: &dyn PaneLeaderDecorator,        window: &mut Window,        cx: &mut App,    ) -> impl IntoElement {        self.root.render(0, zoomed, render_cx, window, cx).element    }     pub fn panes(&self) -> Vec<&Entity<Pane>> {        let mut panes = Vec::new();        self.root.collect_panes(&mut panes);        panes    }     pub fn first_pane(&self) -> Entity<Pane> {        self.root.first_pane()    }     pub fn last_pane(&self) -> Entity<Pane> {        self.root.last_pane()    }     pub fn find_pane_in_direction(        &mut self,        active_pane: &Entity<Pane>,        direction: SplitDirection,        cx: &App,    ) -> Option<&Entity<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)    }     pub fn invert_axies(&mut self) {        self.root.invert_pane_axies();    }} #[derive(Debug, Clone)]pub enum Member {    Axis(PaneAxis),    Pane(Entity<Pane>),} #[derive(Clone, Copy)]pub struct PaneRenderContext<'a> {    pub project: &'a Entity<Project>,    pub follower_states: &'a HashMap<CollaboratorId, FollowerState>,    pub active_call: Option<&'a Entity<ActiveCall>>,    pub active_pane: &'a Entity<Pane>,    pub app_state: &'a Arc<AppState>,    pub workspace: &'a WeakEntity<Workspace>,} #[derive(Default)]pub struct LeaderDecoration {    border: Option<Hsla>,    status_box: Option<AnyElement>,} pub trait PaneLeaderDecorator {    fn decorate(&self, pane: &Entity<Pane>, cx: &App) -> LeaderDecoration;    fn active_pane(&self) -> &Entity<Pane>;    fn workspace(&self) -> &WeakEntity<Workspace>;} pub struct ActivePaneDecorator<'a> {    active_pane: &'a Entity<Pane>,    workspace: &'a WeakEntity<Workspace>,} impl<'a> ActivePaneDecorator<'a> {    pub fn new(active_pane: &'a Entity<Pane>, workspace: &'a WeakEntity<Workspace>) -> Self {        Self {            active_pane,            workspace,        }    }} impl PaneLeaderDecorator for ActivePaneDecorator<'_> {    fn decorate(&self, _: &Entity<Pane>, _: &App) -> LeaderDecoration {        LeaderDecoration::default()    }    fn active_pane(&self) -> &Entity<Pane> {        self.active_pane    }     fn workspace(&self) -> &WeakEntity<Workspace> {        self.workspace    }} impl PaneLeaderDecorator for PaneRenderContext<'_> {    fn decorate(&self, pane: &Entity<Pane>, cx: &App) -> LeaderDecoration {        let follower_state = self.follower_states.iter().find_map(|(leader_id, state)| {            if state.center_pane == *pane {                Some((*leader_id, state))            } else {                None            }        });        let Some((leader_id, follower_state)) = follower_state else {            return LeaderDecoration::default();        };         let mut leader_color;        let status_box;        match leader_id {            CollaboratorId::PeerId(peer_id) => {                let Some(leader) = self.active_call.as_ref().and_then(|call| {                    let room = call.read(cx).room()?.read(cx);                    room.remote_participant_for_peer_id(peer_id)                }) else {                    return LeaderDecoration::default();                };                 let is_in_unshared_view = follower_state.active_view_id.is_some_and(|view_id| {                    !follower_state                        .items_by_leader_view_id                        .contains_key(&view_id)                });                 let mut leader_join_data = None;                let leader_status_box = match leader.location {                    ParticipantLocation::SharedProject {                        project_id: leader_project_id,                    } => {                        if Some(leader_project_id) == self.project.read(cx).remote_id() {                            is_in_unshared_view.then(|| {                                Label::new(format!(                                    "{} is in an unshared pane",                                    leader.user.github_login                                ))                            })                        } 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                    ))),                };                status_box = leader_status_box.map(|status| {                    div()                        .absolute()                        .w_96()                        .bottom_3()                        .right_3()                        .elevation_2(cx)                        .p_1()                        .child(status)                        .when_some(                            leader_join_data,                            |this, (leader_project_id, leader_user_id)| {                                let app_state = self.app_state.clone();                                this.cursor_pointer().on_mouse_down(                                    MouseButton::Left,                                    move |_, _, cx| {                                        crate::join_in_room_project(                                            leader_project_id,                                            leader_user_id,                                            app_state.clone(),                                            cx,                                        )                                        .detach_and_log_err(cx);                                    },                                )                            },                        )                        .into_any_element()                });                leader_color = cx                    .theme()                    .players()                    .color_for_participant(leader.participant_index.0)                    .cursor;            }            CollaboratorId::Agent => {                status_box = None;                leader_color = cx.theme().players().agent().cursor;            }        }         let is_in_panel = follower_state.dock_pane.is_some();        if is_in_panel {            leader_color.fade_out(0.75);        } else {            leader_color.fade_out(0.3);        }         LeaderDecoration {            status_box,            border: Some(leader_color),        }    }     fn active_pane(&self) -> &Entity<Pane> {        self.active_pane    }     fn workspace(&self) -> &WeakEntity<Workspace> {        self.workspace    }} impl Member {    fn new_axis(old_pane: Entity<Pane>, new_pane: Entity<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 first_pane(&self) -> Entity<Pane> {        match self {            Member::Axis(axis) => axis.members[0].first_pane(),            Member::Pane(pane) => pane.clone(),        }    }     fn last_pane(&self) -> Entity<Pane> {        match self {            Member::Axis(axis) => axis.members.last().unwrap().last_pane(),            Member::Pane(pane) => pane.clone(),        }    }     pub fn render(        &self,        basis: usize,        zoomed: Option<&AnyWeakView>,        render_cx: &dyn PaneLeaderDecorator,        window: &mut Window,        cx: &mut App,    ) -> PaneRenderResult {        match self {            Member::Pane(pane) => {                if zoomed == Some(&pane.downgrade().into()) {                    return PaneRenderResult {                        element: div().into_any(),                        contains_active_pane: false,                    };                }                 let decoration = render_cx.decorate(pane, cx);                let is_active = pane == render_cx.active_pane();                 PaneRenderResult {                    element: div()                        .relative()                        .flex_1()                        .size_full()                        .child(                            AnyView::from(pane.clone())                                .cached(StyleRefinement::default().v_flex().size_full()),                        )                        .when_some(decoration.border, |this, color| {                            this.child(                                div()                                    .absolute()                                    .size_full()                                    .left_0()                                    .top_0()                                    .border_2()                                    .border_color(color),                            )                        })                        .children(decoration.status_box)                        .into_any(),                    contains_active_pane: is_active,                }            }            Member::Axis(axis) => axis.render(basis + 1, zoomed, render_cx, window, cx),        }    }     fn collect_panes<'a>(&'a self, panes: &mut Vec<&'a Entity<Pane>>) {        match self {            Member::Axis(axis) => {                for member in &axis.members {                    member.collect_panes(panes);                }            }            Member::Pane(pane) => panes.push(pane),        }    }     fn invert_pane_axies(&mut self) {        match self {            Self::Axis(axis) => {                axis.axis = axis.axis.invert();                for member in axis.members.iter_mut() {                    member.invert_pane_axies();                }            }            Self::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 mut flexes = flexes.unwrap_or_else(|| vec![1.; members.len()]);        if flexes.len() != members.len()            || (flexes.iter().copied().sum::<f32>() - flexes.len() as f32).abs() >= 0.001        {            flexes = vec![1.; members.len()];        }         let flexes = Arc::new(Mutex::new(flexes));

Lines 1–500 of 1357.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t