pane_group.rs

   1use crate::{
   2    AppState, CollaboratorId, FollowerState, Pane, Workspace, WorkspaceSettings,
   3    pane_group::element::pane_axis,
   4    workspace_settings::{PaneSplitDirectionHorizontal, PaneSplitDirectionVertical},
   5};
   6use anyhow::Result;
   7use call::{ActiveCall, ParticipantLocation};
   8use collections::HashMap;
   9use gpui::{
  10    Along, AnyView, AnyWeakView, Axis, Bounds, Entity, Hsla, IntoElement, MouseButton, Pixels,
  11    Point, StyleRefinement, WeakEntity, Window, point, size,
  12};
  13use parking_lot::Mutex;
  14use project::Project;
  15use schemars::JsonSchema;
  16use serde::Deserialize;
  17use settings::Settings;
  18use std::sync::Arc;
  19use ui::prelude::*;
  20
  21pub const HANDLE_HITBOX_SIZE: f32 = 4.0;
  22const HORIZONTAL_MIN_SIZE: f32 = 80.;
  23const VERTICAL_MIN_SIZE: f32 = 100.;
  24
  25/// One or many panes, arranged in a horizontal or vertical axis due to a split.
  26/// Panes have all their tabs and capabilities preserved, and can be split again or resized.
  27/// Single-pane group is a regular pane.
  28#[derive(Clone)]
  29pub struct PaneGroup {
  30    pub root: Member,
  31}
  32
  33pub struct PaneRenderResult {
  34    pub element: gpui::AnyElement,
  35    pub contains_active_pane: bool,
  36}
  37
  38impl PaneGroup {
  39    pub fn with_root(root: Member) -> Self {
  40        Self { root }
  41    }
  42
  43    pub fn new(pane: Entity<Pane>) -> Self {
  44        Self {
  45            root: Member::Pane(pane),
  46        }
  47    }
  48
  49    pub fn split(
  50        &mut self,
  51        old_pane: &Entity<Pane>,
  52        new_pane: &Entity<Pane>,
  53        direction: SplitDirection,
  54    ) -> Result<()> {
  55        match &mut self.root {
  56            Member::Pane(pane) => {
  57                if pane == old_pane {
  58                    self.root = Member::new_axis(old_pane.clone(), new_pane.clone(), direction);
  59                    Ok(())
  60                } else {
  61                    anyhow::bail!("Pane not found");
  62                }
  63            }
  64            Member::Axis(axis) => axis.split(old_pane, new_pane, direction),
  65        }
  66    }
  67
  68    pub fn bounding_box_for_pane(&self, pane: &Entity<Pane>) -> Option<Bounds<Pixels>> {
  69        match &self.root {
  70            Member::Pane(_) => None,
  71            Member::Axis(axis) => axis.bounding_box_for_pane(pane),
  72        }
  73    }
  74
  75    pub fn pane_at_pixel_position(&self, coordinate: Point<Pixels>) -> Option<&Entity<Pane>> {
  76        match &self.root {
  77            Member::Pane(pane) => Some(pane),
  78            Member::Axis(axis) => axis.pane_at_pixel_position(coordinate),
  79        }
  80    }
  81
  82    /// Returns:
  83    /// - Ok(true) if it found and removed a pane
  84    /// - Ok(false) if it found but did not remove the pane
  85    /// - Err(_) if it did not find the pane
  86    pub fn remove(&mut self, pane: &Entity<Pane>) -> Result<bool> {
  87        match &mut self.root {
  88            Member::Pane(_) => Ok(false),
  89            Member::Axis(axis) => {
  90                if let Some(last_pane) = axis.remove(pane)? {
  91                    self.root = last_pane;
  92                }
  93                Ok(true)
  94            }
  95        }
  96    }
  97
  98    pub fn resize(
  99        &mut self,
 100        pane: &Entity<Pane>,
 101        direction: Axis,
 102        amount: Pixels,
 103        bounds: &Bounds<Pixels>,
 104    ) {
 105        match &mut self.root {
 106            Member::Pane(_) => {}
 107            Member::Axis(axis) => {
 108                let _ = axis.resize(pane, direction, amount, bounds);
 109            }
 110        };
 111    }
 112
 113    pub fn reset_pane_sizes(&mut self) {
 114        match &mut self.root {
 115            Member::Pane(_) => {}
 116            Member::Axis(axis) => {
 117                let _ = axis.reset_pane_sizes();
 118            }
 119        };
 120    }
 121
 122    pub fn swap(&mut self, from: &Entity<Pane>, to: &Entity<Pane>) {
 123        match &mut self.root {
 124            Member::Pane(_) => {}
 125            Member::Axis(axis) => axis.swap(from, to),
 126        };
 127    }
 128
 129    pub fn render(
 130        &self,
 131        zoomed: Option<&AnyWeakView>,
 132        render_cx: &dyn PaneLeaderDecorator,
 133        window: &mut Window,
 134        cx: &mut App,
 135    ) -> impl IntoElement {
 136        self.root.render(0, zoomed, render_cx, window, cx).element
 137    }
 138
 139    pub fn panes(&self) -> Vec<&Entity<Pane>> {
 140        let mut panes = Vec::new();
 141        self.root.collect_panes(&mut panes);
 142        panes
 143    }
 144
 145    pub fn first_pane(&self) -> Entity<Pane> {
 146        self.root.first_pane()
 147    }
 148
 149    pub fn last_pane(&self) -> Entity<Pane> {
 150        self.root.last_pane()
 151    }
 152
 153    pub fn find_pane_in_direction(
 154        &mut self,
 155        active_pane: &Entity<Pane>,
 156        direction: SplitDirection,
 157        cx: &App,
 158    ) -> Option<&Entity<Pane>> {
 159        let bounding_box = self.bounding_box_for_pane(active_pane)?;
 160        let cursor = active_pane.read(cx).pixel_position_of_cursor(cx);
 161        let center = match cursor {
 162            Some(cursor) if bounding_box.contains(&cursor) => cursor,
 163            _ => bounding_box.center(),
 164        };
 165
 166        let distance_to_next = crate::HANDLE_HITBOX_SIZE;
 167
 168        let target = match direction {
 169            SplitDirection::Left => {
 170                Point::new(bounding_box.left() - distance_to_next.into(), center.y)
 171            }
 172            SplitDirection::Right => {
 173                Point::new(bounding_box.right() + distance_to_next.into(), center.y)
 174            }
 175            SplitDirection::Up => {
 176                Point::new(center.x, bounding_box.top() - distance_to_next.into())
 177            }
 178            SplitDirection::Down => {
 179                Point::new(center.x, bounding_box.bottom() + distance_to_next.into())
 180            }
 181        };
 182        self.pane_at_pixel_position(target)
 183    }
 184
 185    pub fn invert_axies(&mut self) {
 186        self.root.invert_pane_axies();
 187    }
 188}
 189
 190#[derive(Debug, Clone)]
 191pub enum Member {
 192    Axis(PaneAxis),
 193    Pane(Entity<Pane>),
 194}
 195
 196#[derive(Clone, Copy)]
 197pub struct PaneRenderContext<'a> {
 198    pub project: &'a Entity<Project>,
 199    pub follower_states: &'a HashMap<CollaboratorId, FollowerState>,
 200    pub active_call: Option<&'a Entity<ActiveCall>>,
 201    pub active_pane: &'a Entity<Pane>,
 202    pub app_state: &'a Arc<AppState>,
 203    pub workspace: &'a WeakEntity<Workspace>,
 204}
 205
 206#[derive(Default)]
 207pub struct LeaderDecoration {
 208    border: Option<Hsla>,
 209    status_box: Option<AnyElement>,
 210}
 211
 212pub trait PaneLeaderDecorator {
 213    fn decorate(&self, pane: &Entity<Pane>, cx: &App) -> LeaderDecoration;
 214    fn active_pane(&self) -> &Entity<Pane>;
 215    fn workspace(&self) -> &WeakEntity<Workspace>;
 216}
 217
 218pub struct ActivePaneDecorator<'a> {
 219    active_pane: &'a Entity<Pane>,
 220    workspace: &'a WeakEntity<Workspace>,
 221}
 222
 223impl<'a> ActivePaneDecorator<'a> {
 224    pub fn new(active_pane: &'a Entity<Pane>, workspace: &'a WeakEntity<Workspace>) -> Self {
 225        Self {
 226            active_pane,
 227            workspace,
 228        }
 229    }
 230}
 231
 232impl PaneLeaderDecorator for ActivePaneDecorator<'_> {
 233    fn decorate(&self, _: &Entity<Pane>, _: &App) -> LeaderDecoration {
 234        LeaderDecoration::default()
 235    }
 236    fn active_pane(&self) -> &Entity<Pane> {
 237        self.active_pane
 238    }
 239
 240    fn workspace(&self) -> &WeakEntity<Workspace> {
 241        self.workspace
 242    }
 243}
 244
 245impl PaneLeaderDecorator for PaneRenderContext<'_> {
 246    fn decorate(&self, pane: &Entity<Pane>, cx: &App) -> LeaderDecoration {
 247        let follower_state = self.follower_states.iter().find_map(|(leader_id, state)| {
 248            if state.center_pane == *pane {
 249                Some((*leader_id, state))
 250            } else {
 251                None
 252            }
 253        });
 254        let Some((leader_id, follower_state)) = follower_state else {
 255            return LeaderDecoration::default();
 256        };
 257
 258        let mut leader_color;
 259        let status_box;
 260        match leader_id {
 261            CollaboratorId::PeerId(peer_id) => {
 262                let Some(leader) = self.active_call.as_ref().and_then(|call| {
 263                    let room = call.read(cx).room()?.read(cx);
 264                    room.remote_participant_for_peer_id(peer_id)
 265                }) else {
 266                    return LeaderDecoration::default();
 267                };
 268
 269                let is_in_unshared_view = follower_state.active_view_id.is_some_and(|view_id| {
 270                    !follower_state
 271                        .items_by_leader_view_id
 272                        .contains_key(&view_id)
 273                });
 274
 275                let mut leader_join_data = None;
 276                let leader_status_box = match leader.location {
 277                    ParticipantLocation::SharedProject {
 278                        project_id: leader_project_id,
 279                    } => {
 280                        if Some(leader_project_id) == self.project.read(cx).remote_id() {
 281                            is_in_unshared_view.then(|| {
 282                                Label::new(format!(
 283                                    "{} is in an unshared pane",
 284                                    leader.user.github_login
 285                                ))
 286                            })
 287                        } else {
 288                            leader_join_data = Some((leader_project_id, leader.user.id));
 289                            Some(Label::new(format!(
 290                                "Follow {} to their active project",
 291                                leader.user.github_login,
 292                            )))
 293                        }
 294                    }
 295                    ParticipantLocation::UnsharedProject => Some(Label::new(format!(
 296                        "{} is viewing an unshared Zed project",
 297                        leader.user.github_login
 298                    ))),
 299                    ParticipantLocation::External => Some(Label::new(format!(
 300                        "{} is viewing a window outside of Zed",
 301                        leader.user.github_login
 302                    ))),
 303                };
 304                status_box = leader_status_box.map(|status| {
 305                    div()
 306                        .absolute()
 307                        .w_96()
 308                        .bottom_3()
 309                        .right_3()
 310                        .elevation_2(cx)
 311                        .p_1()
 312                        .child(status)
 313                        .when_some(
 314                            leader_join_data,
 315                            |this, (leader_project_id, leader_user_id)| {
 316                                let app_state = self.app_state.clone();
 317                                this.cursor_pointer().on_mouse_down(
 318                                    MouseButton::Left,
 319                                    move |_, _, cx| {
 320                                        crate::join_in_room_project(
 321                                            leader_project_id,
 322                                            leader_user_id,
 323                                            app_state.clone(),
 324                                            cx,
 325                                        )
 326                                        .detach_and_log_err(cx);
 327                                    },
 328                                )
 329                            },
 330                        )
 331                        .into_any_element()
 332                });
 333                leader_color = cx
 334                    .theme()
 335                    .players()
 336                    .color_for_participant(leader.participant_index.0)
 337                    .cursor;
 338            }
 339            CollaboratorId::Agent => {
 340                status_box = None;
 341                leader_color = cx.theme().players().agent().cursor;
 342            }
 343        }
 344
 345        let is_in_panel = follower_state.dock_pane.is_some();
 346        if is_in_panel {
 347            leader_color.fade_out(0.75);
 348        } else {
 349            leader_color.fade_out(0.3);
 350        }
 351
 352        LeaderDecoration {
 353            status_box,
 354            border: Some(leader_color),
 355        }
 356    }
 357
 358    fn active_pane(&self) -> &Entity<Pane> {
 359        self.active_pane
 360    }
 361
 362    fn workspace(&self) -> &WeakEntity<Workspace> {
 363        self.workspace
 364    }
 365}
 366
 367impl Member {
 368    fn new_axis(old_pane: Entity<Pane>, new_pane: Entity<Pane>, direction: SplitDirection) -> Self {
 369        use Axis::*;
 370        use SplitDirection::*;
 371
 372        let axis = match direction {
 373            Up | Down => Vertical,
 374            Left | Right => Horizontal,
 375        };
 376
 377        let members = match direction {
 378            Up | Left => vec![Member::Pane(new_pane), Member::Pane(old_pane)],
 379            Down | Right => vec![Member::Pane(old_pane), Member::Pane(new_pane)],
 380        };
 381
 382        Member::Axis(PaneAxis::new(axis, members))
 383    }
 384
 385    fn first_pane(&self) -> Entity<Pane> {
 386        match self {
 387            Member::Axis(axis) => axis.members[0].first_pane(),
 388            Member::Pane(pane) => pane.clone(),
 389        }
 390    }
 391
 392    fn last_pane(&self) -> Entity<Pane> {
 393        match self {
 394            Member::Axis(axis) => axis.members.last().unwrap().last_pane(),
 395            Member::Pane(pane) => pane.clone(),
 396        }
 397    }
 398
 399    pub fn render(
 400        &self,
 401        basis: usize,
 402        zoomed: Option<&AnyWeakView>,
 403        render_cx: &dyn PaneLeaderDecorator,
 404        window: &mut Window,
 405        cx: &mut App,
 406    ) -> PaneRenderResult {
 407        match self {
 408            Member::Pane(pane) => {
 409                if zoomed == Some(&pane.downgrade().into()) {
 410                    return PaneRenderResult {
 411                        element: div().into_any(),
 412                        contains_active_pane: false,
 413                    };
 414                }
 415
 416                let decoration = render_cx.decorate(pane, cx);
 417                let is_active = pane == render_cx.active_pane();
 418
 419                PaneRenderResult {
 420                    element: div()
 421                        .relative()
 422                        .flex_1()
 423                        .size_full()
 424                        .child(
 425                            AnyView::from(pane.clone())
 426                                .cached(StyleRefinement::default().v_flex().size_full()),
 427                        )
 428                        .when_some(decoration.border, |this, color| {
 429                            this.child(
 430                                div()
 431                                    .absolute()
 432                                    .size_full()
 433                                    .left_0()
 434                                    .top_0()
 435                                    .border_2()
 436                                    .border_color(color),
 437                            )
 438                        })
 439                        .children(decoration.status_box)
 440                        .into_any(),
 441                    contains_active_pane: is_active,
 442                }
 443            }
 444            Member::Axis(axis) => axis.render(basis + 1, zoomed, render_cx, window, cx),
 445        }
 446    }
 447
 448    fn collect_panes<'a>(&'a self, panes: &mut Vec<&'a Entity<Pane>>) {
 449        match self {
 450            Member::Axis(axis) => {
 451                for member in &axis.members {
 452                    member.collect_panes(panes);
 453                }
 454            }
 455            Member::Pane(pane) => panes.push(pane),
 456        }
 457    }
 458
 459    fn invert_pane_axies(&mut self) {
 460        match self {
 461            Self::Axis(axis) => {
 462                axis.axis = axis.axis.invert();
 463                for member in axis.members.iter_mut() {
 464                    member.invert_pane_axies();
 465                }
 466            }
 467            Self::Pane(_) => {}
 468        }
 469    }
 470}
 471
 472#[derive(Debug, Clone)]
 473pub struct PaneAxis {
 474    pub axis: Axis,
 475    pub members: Vec<Member>,
 476    pub flexes: Arc<Mutex<Vec<f32>>>,
 477    pub bounding_boxes: Arc<Mutex<Vec<Option<Bounds<Pixels>>>>>,
 478}
 479
 480impl PaneAxis {
 481    pub fn new(axis: Axis, members: Vec<Member>) -> Self {
 482        let flexes = Arc::new(Mutex::new(vec![1.; members.len()]));
 483        let bounding_boxes = Arc::new(Mutex::new(vec![None; members.len()]));
 484        Self {
 485            axis,
 486            members,
 487            flexes,
 488            bounding_boxes,
 489        }
 490    }
 491
 492    pub fn load(axis: Axis, members: Vec<Member>, flexes: Option<Vec<f32>>) -> Self {
 493        let mut flexes = flexes.unwrap_or_else(|| vec![1.; members.len()]);
 494        if flexes.len() != members.len()
 495            || (flexes.iter().copied().sum::<f32>() - flexes.len() as f32).abs() >= 0.001
 496        {
 497            flexes = vec![1.; members.len()];
 498        }
 499
 500        let flexes = Arc::new(Mutex::new(flexes));
 501        let bounding_boxes = Arc::new(Mutex::new(vec![None; members.len()]));
 502        Self {
 503            axis,
 504            members,
 505            flexes,
 506            bounding_boxes,
 507        }
 508    }
 509
 510    fn split(
 511        &mut self,
 512        old_pane: &Entity<Pane>,
 513        new_pane: &Entity<Pane>,
 514        direction: SplitDirection,
 515    ) -> Result<()> {
 516        for (mut idx, member) in self.members.iter_mut().enumerate() {
 517            match member {
 518                Member::Axis(axis) => {
 519                    if axis.split(old_pane, new_pane, direction).is_ok() {
 520                        return Ok(());
 521                    }
 522                }
 523                Member::Pane(pane) => {
 524                    if pane == old_pane {
 525                        if direction.axis() == self.axis {
 526                            if direction.increasing() {
 527                                idx += 1;
 528                            }
 529
 530                            self.members.insert(idx, Member::Pane(new_pane.clone()));
 531                            *self.flexes.lock() = vec![1.; self.members.len()];
 532                        } else {
 533                            *member =
 534                                Member::new_axis(old_pane.clone(), new_pane.clone(), direction);
 535                        }
 536                        return Ok(());
 537                    }
 538                }
 539            }
 540        }
 541        anyhow::bail!("Pane not found");
 542    }
 543
 544    fn remove(&mut self, pane_to_remove: &Entity<Pane>) -> Result<Option<Member>> {
 545        let mut found_pane = false;
 546        let mut remove_member = None;
 547        for (idx, member) in self.members.iter_mut().enumerate() {
 548            match member {
 549                Member::Axis(axis) => {
 550                    if let Ok(last_pane) = axis.remove(pane_to_remove) {
 551                        if let Some(last_pane) = last_pane {
 552                            *member = last_pane;
 553                        }
 554                        found_pane = true;
 555                        break;
 556                    }
 557                }
 558                Member::Pane(pane) => {
 559                    if pane == pane_to_remove {
 560                        found_pane = true;
 561                        remove_member = Some(idx);
 562                        break;
 563                    }
 564                }
 565            }
 566        }
 567
 568        if found_pane {
 569            if let Some(idx) = remove_member {
 570                self.members.remove(idx);
 571                *self.flexes.lock() = vec![1.; self.members.len()];
 572            }
 573
 574            if self.members.len() == 1 {
 575                let result = self.members.pop();
 576                *self.flexes.lock() = vec![1.; self.members.len()];
 577                Ok(result)
 578            } else {
 579                Ok(None)
 580            }
 581        } else {
 582            anyhow::bail!("Pane not found");
 583        }
 584    }
 585
 586    fn reset_pane_sizes(&self) {
 587        *self.flexes.lock() = vec![1.; self.members.len()];
 588        for member in self.members.iter() {
 589            if let Member::Axis(axis) = member {
 590                axis.reset_pane_sizes();
 591            }
 592        }
 593    }
 594
 595    fn resize(
 596        &mut self,
 597        pane: &Entity<Pane>,
 598        axis: Axis,
 599        amount: Pixels,
 600        bounds: &Bounds<Pixels>,
 601    ) -> Option<bool> {
 602        let container_size = self
 603            .bounding_boxes
 604            .lock()
 605            .iter()
 606            .filter_map(|e| *e)
 607            .reduce(|acc, e| acc.union(&e))
 608            .unwrap_or(*bounds)
 609            .size;
 610
 611        let found_pane = self
 612            .members
 613            .iter()
 614            .any(|member| matches!(member, Member::Pane(p) if p == pane));
 615
 616        if found_pane && self.axis != axis {
 617            return Some(false); // pane found but this is not the correct axis direction
 618        }
 619        let mut found_axis_index: Option<usize> = None;
 620        if !found_pane {
 621            for (i, pa) in self.members.iter_mut().enumerate() {
 622                if let Member::Axis(pa) = pa
 623                    && let Some(done) = pa.resize(pane, axis, amount, bounds) {
 624                        if done {
 625                            return Some(true); // pane found and operations already done
 626                        } else if self.axis != axis {
 627                            return Some(false); // pane found but this is not the correct axis direction
 628                        } else {
 629                            found_axis_index = Some(i); // pane found and this is correct direction
 630                        }
 631                    }
 632            }
 633            found_axis_index?; // no pane found
 634        }
 635
 636        let min_size = match axis {
 637            Axis::Horizontal => px(HORIZONTAL_MIN_SIZE),
 638            Axis::Vertical => px(VERTICAL_MIN_SIZE),
 639        };
 640        let mut flexes = self.flexes.lock();
 641
 642        let ix = if found_pane {
 643            self.members.iter().position(|m| {
 644                if let Member::Pane(p) = m {
 645                    p == pane
 646                } else {
 647                    false
 648                }
 649            })
 650        } else {
 651            found_axis_index
 652        };
 653
 654        if ix.is_none() {
 655            return Some(true);
 656        }
 657
 658        let ix = ix.unwrap_or(0);
 659
 660        let size = move |ix, flexes: &[f32]| {
 661            container_size.along(axis) * (flexes[ix] / flexes.len() as f32)
 662        };
 663
 664        // Don't allow resizing to less than the minimum size, if elements are already too small
 665        if min_size - px(1.) > size(ix, flexes.as_slice()) {
 666            return Some(true);
 667        }
 668
 669        let flex_changes = |pixel_dx, target_ix, next: isize, flexes: &[f32]| {
 670            let flex_change = flexes.len() as f32 * pixel_dx / container_size.along(axis);
 671            let current_target_flex = flexes[target_ix] + flex_change;
 672            let next_target_flex = flexes[(target_ix as isize + next) as usize] - flex_change;
 673            (current_target_flex, next_target_flex)
 674        };
 675
 676        let apply_changes =
 677            |current_ix: usize, proposed_current_pixel_change: Pixels, flexes: &mut [f32]| {
 678                let next_target_size = Pixels::max(
 679                    size(current_ix + 1, flexes) - proposed_current_pixel_change,
 680                    min_size,
 681                );
 682                let current_target_size = Pixels::max(
 683                    size(current_ix, flexes) + size(current_ix + 1, flexes) - next_target_size,
 684                    min_size,
 685                );
 686
 687                let current_pixel_change = current_target_size - size(current_ix, flexes);
 688
 689                let (current_target_flex, next_target_flex) =
 690                    flex_changes(current_pixel_change, current_ix, 1, flexes);
 691
 692                flexes[current_ix] = current_target_flex;
 693                flexes[current_ix + 1] = next_target_flex;
 694            };
 695
 696        if ix + 1 == flexes.len() {
 697            apply_changes(ix - 1, -1.0 * amount, flexes.as_mut_slice());
 698        } else {
 699            apply_changes(ix, amount, flexes.as_mut_slice());
 700        }
 701        Some(true)
 702    }
 703
 704    fn swap(&mut self, from: &Entity<Pane>, to: &Entity<Pane>) {
 705        for member in self.members.iter_mut() {
 706            match member {
 707                Member::Axis(axis) => axis.swap(from, to),
 708                Member::Pane(pane) => {
 709                    if pane == from {
 710                        *member = Member::Pane(to.clone());
 711                    } else if pane == to {
 712                        *member = Member::Pane(from.clone())
 713                    }
 714                }
 715            }
 716        }
 717    }
 718
 719    fn bounding_box_for_pane(&self, pane: &Entity<Pane>) -> Option<Bounds<Pixels>> {
 720        debug_assert!(self.members.len() == self.bounding_boxes.lock().len());
 721
 722        for (idx, member) in self.members.iter().enumerate() {
 723            match member {
 724                Member::Pane(found) => {
 725                    if pane == found {
 726                        return self.bounding_boxes.lock()[idx];
 727                    }
 728                }
 729                Member::Axis(axis) => {
 730                    if let Some(rect) = axis.bounding_box_for_pane(pane) {
 731                        return Some(rect);
 732                    }
 733                }
 734            }
 735        }
 736        None
 737    }
 738
 739    fn pane_at_pixel_position(&self, coordinate: Point<Pixels>) -> Option<&Entity<Pane>> {
 740        debug_assert!(self.members.len() == self.bounding_boxes.lock().len());
 741
 742        let bounding_boxes = self.bounding_boxes.lock();
 743
 744        for (idx, member) in self.members.iter().enumerate() {
 745            if let Some(coordinates) = bounding_boxes[idx]
 746                && coordinates.contains(&coordinate) {
 747                    return match member {
 748                        Member::Pane(found) => Some(found),
 749                        Member::Axis(axis) => axis.pane_at_pixel_position(coordinate),
 750                    };
 751                }
 752        }
 753        None
 754    }
 755
 756    fn render(
 757        &self,
 758        basis: usize,
 759        zoomed: Option<&AnyWeakView>,
 760        render_cx: &dyn PaneLeaderDecorator,
 761        window: &mut Window,
 762        cx: &mut App,
 763    ) -> PaneRenderResult {
 764        debug_assert!(self.members.len() == self.flexes.lock().len());
 765        let mut active_pane_ix = None;
 766        let mut contains_active_pane = false;
 767        let mut is_leaf_pane = vec![false; self.members.len()];
 768
 769        let rendered_children = self
 770            .members
 771            .iter()
 772            .enumerate()
 773            .map(|(ix, member)| {
 774                match member {
 775                    Member::Pane(pane) => {
 776                        is_leaf_pane[ix] = true;
 777                        if pane == render_cx.active_pane() {
 778                            active_pane_ix = Some(ix);
 779                            contains_active_pane = true;
 780                        }
 781                    }
 782                    Member::Axis(_) => {
 783                        is_leaf_pane[ix] = false;
 784                    }
 785                }
 786
 787                let result = member.render((basis + ix) * 10, zoomed, render_cx, window, cx);
 788                if result.contains_active_pane {
 789                    contains_active_pane = true;
 790                }
 791                result.element.into_any_element()
 792            })
 793            .collect::<Vec<_>>();
 794
 795        let element = pane_axis(
 796            self.axis,
 797            basis,
 798            self.flexes.clone(),
 799            self.bounding_boxes.clone(),
 800            render_cx.workspace().clone(),
 801        )
 802        .with_is_leaf_pane_mask(is_leaf_pane)
 803        .children(rendered_children)
 804        .with_active_pane(active_pane_ix)
 805        .into_any_element();
 806
 807        PaneRenderResult {
 808            element,
 809            contains_active_pane,
 810        }
 811    }
 812}
 813
 814#[derive(Clone, Copy, Debug, Deserialize, PartialEq, JsonSchema)]
 815#[serde(rename_all = "snake_case")]
 816pub enum SplitDirection {
 817    Up,
 818    Down,
 819    Left,
 820    Right,
 821}
 822
 823impl std::fmt::Display for SplitDirection {
 824    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 825        match self {
 826            SplitDirection::Up => write!(f, "up"),
 827            SplitDirection::Down => write!(f, "down"),
 828            SplitDirection::Left => write!(f, "left"),
 829            SplitDirection::Right => write!(f, "right"),
 830        }
 831    }
 832}
 833
 834impl SplitDirection {
 835    pub fn all() -> [Self; 4] {
 836        [Self::Up, Self::Down, Self::Left, Self::Right]
 837    }
 838
 839    pub fn vertical(cx: &mut App) -> Self {
 840        match WorkspaceSettings::get_global(cx).pane_split_direction_vertical {
 841            PaneSplitDirectionVertical::Left => SplitDirection::Left,
 842            PaneSplitDirectionVertical::Right => SplitDirection::Right,
 843        }
 844    }
 845
 846    pub fn horizontal(cx: &mut App) -> Self {
 847        match WorkspaceSettings::get_global(cx).pane_split_direction_horizontal {
 848            PaneSplitDirectionHorizontal::Down => SplitDirection::Down,
 849            PaneSplitDirectionHorizontal::Up => SplitDirection::Up,
 850        }
 851    }
 852
 853    pub fn edge(&self, rect: Bounds<Pixels>) -> Pixels {
 854        match self {
 855            Self::Up => rect.origin.y,
 856            Self::Down => rect.bottom_left().y,
 857            Self::Left => rect.bottom_left().x,
 858            Self::Right => rect.bottom_right().x,
 859        }
 860    }
 861
 862    pub fn along_edge(&self, bounds: Bounds<Pixels>, length: Pixels) -> Bounds<Pixels> {
 863        match self {
 864            Self::Up => Bounds {
 865                origin: bounds.origin,
 866                size: size(bounds.size.width, length),
 867            },
 868            Self::Down => Bounds {
 869                origin: point(bounds.bottom_left().x, bounds.bottom_left().y - length),
 870                size: size(bounds.size.width, length),
 871            },
 872            Self::Left => Bounds {
 873                origin: bounds.origin,
 874                size: size(length, bounds.size.height),
 875            },
 876            Self::Right => Bounds {
 877                origin: point(bounds.bottom_right().x - length, bounds.bottom_left().y),
 878                size: size(length, bounds.size.height),
 879            },
 880        }
 881    }
 882
 883    pub fn axis(&self) -> Axis {
 884        match self {
 885            Self::Up | Self::Down => Axis::Vertical,
 886            Self::Left | Self::Right => Axis::Horizontal,
 887        }
 888    }
 889
 890    pub fn increasing(&self) -> bool {
 891        match self {
 892            Self::Left | Self::Up => false,
 893            Self::Down | Self::Right => true,
 894        }
 895    }
 896}
 897
 898mod element {
 899    use std::mem;
 900    use std::{cell::RefCell, iter, rc::Rc, sync::Arc};
 901
 902    use gpui::{
 903        Along, AnyElement, App, Axis, BorderStyle, Bounds, Element, GlobalElementId,
 904        HitboxBehavior, IntoElement, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement,
 905        Pixels, Point, Size, Style, WeakEntity, Window, px, relative, size,
 906    };
 907    use gpui::{CursorStyle, Hitbox};
 908    use parking_lot::Mutex;
 909    use settings::Settings;
 910    use smallvec::SmallVec;
 911    use ui::prelude::*;
 912    use util::ResultExt;
 913
 914    use crate::Workspace;
 915
 916    use crate::WorkspaceSettings;
 917
 918    use super::{HANDLE_HITBOX_SIZE, HORIZONTAL_MIN_SIZE, VERTICAL_MIN_SIZE};
 919
 920    const DIVIDER_SIZE: f32 = 1.0;
 921
 922    pub(super) fn pane_axis(
 923        axis: Axis,
 924        basis: usize,
 925        flexes: Arc<Mutex<Vec<f32>>>,
 926        bounding_boxes: Arc<Mutex<Vec<Option<Bounds<Pixels>>>>>,
 927        workspace: WeakEntity<Workspace>,
 928    ) -> PaneAxisElement {
 929        PaneAxisElement {
 930            axis,
 931            basis,
 932            flexes,
 933            bounding_boxes,
 934            children: SmallVec::new(),
 935            active_pane_ix: None,
 936            workspace,
 937            is_leaf_pane_mask: Vec::new(),
 938        }
 939    }
 940
 941    pub struct PaneAxisElement {
 942        axis: Axis,
 943        basis: usize,
 944        /// Equivalent to ColumnWidths (but in terms of flexes instead of percentages)
 945        /// For example, flexes "1.33, 1, 1", instead of "40%, 30%, 30%"
 946        flexes: Arc<Mutex<Vec<f32>>>,
 947        bounding_boxes: Arc<Mutex<Vec<Option<Bounds<Pixels>>>>>,
 948        children: SmallVec<[AnyElement; 2]>,
 949        active_pane_ix: Option<usize>,
 950        workspace: WeakEntity<Workspace>,
 951        // Track which children are leaf panes (Member::Pane) vs axes (Member::Axis)
 952        is_leaf_pane_mask: Vec<bool>,
 953    }
 954
 955    pub struct PaneAxisLayout {
 956        dragged_handle: Rc<RefCell<Option<usize>>>,
 957        children: Vec<PaneAxisChildLayout>,
 958    }
 959
 960    struct PaneAxisChildLayout {
 961        bounds: Bounds<Pixels>,
 962        element: AnyElement,
 963        handle: Option<PaneAxisHandleLayout>,
 964        is_leaf_pane: bool,
 965    }
 966
 967    struct PaneAxisHandleLayout {
 968        hitbox: Hitbox,
 969        divider_bounds: Bounds<Pixels>,
 970    }
 971
 972    impl PaneAxisElement {
 973        pub fn with_active_pane(mut self, active_pane_ix: Option<usize>) -> Self {
 974            self.active_pane_ix = active_pane_ix;
 975            self
 976        }
 977
 978        pub fn with_is_leaf_pane_mask(mut self, mask: Vec<bool>) -> Self {
 979            self.is_leaf_pane_mask = mask;
 980            self
 981        }
 982
 983        fn compute_resize(
 984            flexes: &Arc<Mutex<Vec<f32>>>,
 985            e: &MouseMoveEvent,
 986            ix: usize,
 987            axis: Axis,
 988            child_start: Point<Pixels>,
 989            container_size: Size<Pixels>,
 990            workspace: WeakEntity<Workspace>,
 991            window: &mut Window,
 992            cx: &mut App,
 993        ) {
 994            let min_size = match axis {
 995                Axis::Horizontal => px(HORIZONTAL_MIN_SIZE),
 996                Axis::Vertical => px(VERTICAL_MIN_SIZE),
 997            };
 998            let mut flexes = flexes.lock();
 999            debug_assert!(flex_values_in_bounds(flexes.as_slice()));
1000
1001            // Math to convert a flex value to a pixel value
1002            let size = move |ix, flexes: &[f32]| {
1003                container_size.along(axis) * (flexes[ix] / flexes.len() as f32)
1004            };
1005
1006            // Don't allow resizing to less than the minimum size, if elements are already too small
1007            if min_size - px(1.) > size(ix, flexes.as_slice()) {
1008                return;
1009            }
1010
1011            // This is basically a "bucket" of pixel changes that need to be applied in response to this
1012            // mouse event. Probably a small, fractional number like 0.5 or 1.5 pixels
1013            let mut proposed_current_pixel_change =
1014                (e.position - child_start).along(axis) - size(ix, flexes.as_slice());
1015
1016            // This takes a pixel change, and computes the flex changes that correspond to this pixel change
1017            // as well as the next one, for some reason
1018            let flex_changes = |pixel_dx, target_ix, next: isize, flexes: &[f32]| {
1019                let flex_change = pixel_dx / container_size.along(axis);
1020                let current_target_flex = flexes[target_ix] + flex_change;
1021                let next_target_flex = flexes[(target_ix as isize + next) as usize] - flex_change;
1022                (current_target_flex, next_target_flex)
1023            };
1024
1025            // Generate the list of flex successors, from the current index.
1026            // If you're dragging column 3 forward, out of 6 columns, then this code will produce [4, 5, 6]
1027            // If you're dragging column 3 backward, out of 6 columns, then this code will produce [2, 1, 0]
1028            let mut successors = iter::from_fn({
1029                let forward = proposed_current_pixel_change > px(0.);
1030                let mut ix_offset = 0;
1031                let len = flexes.len();
1032                move || {
1033                    let result = if forward {
1034                        (ix + 1 + ix_offset < len).then(|| ix + ix_offset)
1035                    } else {
1036                        (ix as isize - ix_offset as isize >= 0).then(|| ix - ix_offset)
1037                    };
1038
1039                    ix_offset += 1;
1040
1041                    result
1042                }
1043            });
1044
1045            // Now actually loop over these, and empty our bucket of pixel changes
1046            while proposed_current_pixel_change.abs() > px(0.) {
1047                let Some(current_ix) = successors.next() else {
1048                    break;
1049                };
1050
1051                let next_target_size = Pixels::max(
1052                    size(current_ix + 1, flexes.as_slice()) - proposed_current_pixel_change,
1053                    min_size,
1054                );
1055
1056                let current_target_size = Pixels::max(
1057                    size(current_ix, flexes.as_slice()) + size(current_ix + 1, flexes.as_slice())
1058                        - next_target_size,
1059                    min_size,
1060                );
1061
1062                let current_pixel_change =
1063                    current_target_size - size(current_ix, flexes.as_slice());
1064
1065                let (current_target_flex, next_target_flex) =
1066                    flex_changes(current_pixel_change, current_ix, 1, flexes.as_slice());
1067
1068                flexes[current_ix] = current_target_flex;
1069                flexes[current_ix + 1] = next_target_flex;
1070
1071                proposed_current_pixel_change -= current_pixel_change;
1072            }
1073
1074            workspace
1075                .update(cx, |this, cx| this.serialize_workspace(window, cx))
1076                .log_err();
1077            cx.stop_propagation();
1078            window.refresh();
1079        }
1080
1081        fn layout_handle(
1082            axis: Axis,
1083            pane_bounds: Bounds<Pixels>,
1084            window: &mut Window,
1085            _cx: &mut App,
1086        ) -> PaneAxisHandleLayout {
1087            let handle_bounds = Bounds {
1088                origin: pane_bounds.origin.apply_along(axis, |origin| {
1089                    origin + pane_bounds.size.along(axis) - px(HANDLE_HITBOX_SIZE / 2.)
1090                }),
1091                size: pane_bounds
1092                    .size
1093                    .apply_along(axis, |_| px(HANDLE_HITBOX_SIZE)),
1094            };
1095            let divider_bounds = Bounds {
1096                origin: pane_bounds
1097                    .origin
1098                    .apply_along(axis, |origin| origin + pane_bounds.size.along(axis)),
1099                size: pane_bounds.size.apply_along(axis, |_| px(DIVIDER_SIZE)),
1100            };
1101
1102            PaneAxisHandleLayout {
1103                hitbox: window.insert_hitbox(handle_bounds, HitboxBehavior::BlockMouse),
1104                divider_bounds,
1105            }
1106        }
1107    }
1108
1109    impl IntoElement for PaneAxisElement {
1110        type Element = Self;
1111
1112        fn into_element(self) -> Self::Element {
1113            self
1114        }
1115    }
1116
1117    impl Element for PaneAxisElement {
1118        type RequestLayoutState = ();
1119        type PrepaintState = PaneAxisLayout;
1120
1121        fn id(&self) -> Option<ElementId> {
1122            Some(self.basis.into())
1123        }
1124
1125        fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
1126            None
1127        }
1128
1129        fn request_layout(
1130            &mut self,
1131            _global_id: Option<&GlobalElementId>,
1132            _inspector_id: Option<&gpui::InspectorElementId>,
1133            window: &mut Window,
1134            cx: &mut App,
1135        ) -> (gpui::LayoutId, Self::RequestLayoutState) {
1136            let style = Style {
1137                flex_grow: 1.,
1138                flex_shrink: 1.,
1139                flex_basis: relative(0.).into(),
1140                size: size(relative(1.).into(), relative(1.).into()),
1141                ..Style::default()
1142            };
1143            (window.request_layout(style, None, cx), ())
1144        }
1145
1146        fn prepaint(
1147            &mut self,
1148            global_id: Option<&GlobalElementId>,
1149            _inspector_id: Option<&gpui::InspectorElementId>,
1150            bounds: Bounds<Pixels>,
1151            _state: &mut Self::RequestLayoutState,
1152            window: &mut Window,
1153            cx: &mut App,
1154        ) -> PaneAxisLayout {
1155            let dragged_handle = window.with_element_state::<Rc<RefCell<Option<usize>>>, _>(
1156                global_id.unwrap(),
1157                |state, _cx| {
1158                    let state = state.unwrap_or_else(|| Rc::new(RefCell::new(None)));
1159                    (state.clone(), state)
1160                },
1161            );
1162            let flexes = self.flexes.lock().clone();
1163            let len = self.children.len();
1164            debug_assert!(flexes.len() == len);
1165            debug_assert!(flex_values_in_bounds(flexes.as_slice()));
1166
1167            let total_flex = len as f32;
1168
1169            let mut origin = bounds.origin;
1170            let space_per_flex = bounds.size.along(self.axis) / total_flex;
1171
1172            let mut bounding_boxes = self.bounding_boxes.lock();
1173            bounding_boxes.clear();
1174
1175            let mut layout = PaneAxisLayout {
1176                dragged_handle: dragged_handle.clone(),
1177                children: Vec::new(),
1178            };
1179            for (ix, mut child) in mem::take(&mut self.children).into_iter().enumerate() {
1180                let child_flex = flexes[ix];
1181
1182                let child_size = bounds
1183                    .size
1184                    .apply_along(self.axis, |_| space_per_flex * child_flex)
1185                    .map(|d| d.round());
1186
1187                let child_bounds = Bounds {
1188                    origin,
1189                    size: child_size,
1190                };
1191
1192                bounding_boxes.push(Some(child_bounds));
1193                child.layout_as_root(child_size.into(), window, cx);
1194                child.prepaint_at(origin, window, cx);
1195
1196                origin = origin.apply_along(self.axis, |val| val + child_size.along(self.axis));
1197
1198                let is_leaf_pane = self.is_leaf_pane_mask.get(ix).copied().unwrap_or(true);
1199
1200                layout.children.push(PaneAxisChildLayout {
1201                    bounds: child_bounds,
1202                    element: child,
1203                    handle: None,
1204                    is_leaf_pane,
1205                })
1206            }
1207
1208            for (ix, child_layout) in layout.children.iter_mut().enumerate() {
1209                if ix < len - 1 {
1210                    child_layout.handle = Some(Self::layout_handle(
1211                        self.axis,
1212                        child_layout.bounds,
1213                        window,
1214                        cx,
1215                    ));
1216                }
1217            }
1218
1219            layout
1220        }
1221
1222        fn paint(
1223            &mut self,
1224            _id: Option<&GlobalElementId>,
1225            _inspector_id: Option<&gpui::InspectorElementId>,
1226            bounds: gpui::Bounds<ui::prelude::Pixels>,
1227            _: &mut Self::RequestLayoutState,
1228            layout: &mut Self::PrepaintState,
1229            window: &mut Window,
1230            cx: &mut App,
1231        ) {
1232            for child in &mut layout.children {
1233                child.element.paint(window, cx);
1234            }
1235
1236            let overlay_opacity = WorkspaceSettings::get(None, cx)
1237                .active_pane_modifiers
1238                .inactive_opacity
1239                .map(|val| val.clamp(0.0, 1.0))
1240                .and_then(|val| (val <= 1.).then_some(val));
1241
1242            let mut overlay_background = cx.theme().colors().editor_background;
1243            if let Some(opacity) = overlay_opacity {
1244                overlay_background.fade_out(opacity);
1245            }
1246
1247            let overlay_border = WorkspaceSettings::get(None, cx)
1248                .active_pane_modifiers
1249                .border_size
1250                .and_then(|val| (val >= 0.).then_some(val));
1251
1252            for (ix, child) in &mut layout.children.iter_mut().enumerate() {
1253                if overlay_opacity.is_some() || overlay_border.is_some() {
1254                    // the overlay has to be painted in origin+1px with size width-1px
1255                    // in order to accommodate the divider between panels
1256                    let overlay_bounds = Bounds {
1257                        origin: child
1258                            .bounds
1259                            .origin
1260                            .apply_along(Axis::Horizontal, |val| val + Pixels(1.)),
1261                        size: child
1262                            .bounds
1263                            .size
1264                            .apply_along(Axis::Horizontal, |val| val - Pixels(1.)),
1265                    };
1266
1267                    if overlay_opacity.is_some()
1268                        && child.is_leaf_pane
1269                        && self.active_pane_ix != Some(ix)
1270                    {
1271                        window.paint_quad(gpui::fill(overlay_bounds, overlay_background));
1272                    }
1273
1274                    if let Some(border) = overlay_border
1275                        && self.active_pane_ix == Some(ix) && child.is_leaf_pane {
1276                            window.paint_quad(gpui::quad(
1277                                overlay_bounds,
1278                                0.,
1279                                gpui::transparent_black(),
1280                                border,
1281                                cx.theme().colors().border_selected,
1282                                BorderStyle::Solid,
1283                            ));
1284                        }
1285                }
1286
1287                if let Some(handle) = child.handle.as_mut() {
1288                    let cursor_style = match self.axis {
1289                        Axis::Vertical => CursorStyle::ResizeRow,
1290                        Axis::Horizontal => CursorStyle::ResizeColumn,
1291                    };
1292
1293                    if layout
1294                        .dragged_handle
1295                        .borrow()
1296                        .is_some_and(|dragged_ix| dragged_ix == ix)
1297                    {
1298                        window.set_window_cursor_style(cursor_style);
1299                    } else {
1300                        window.set_cursor_style(cursor_style, &handle.hitbox);
1301                    }
1302
1303                    window.paint_quad(gpui::fill(
1304                        handle.divider_bounds,
1305                        cx.theme().colors().pane_group_border,
1306                    ));
1307
1308                    window.on_mouse_event({
1309                        let dragged_handle = layout.dragged_handle.clone();
1310                        let flexes = self.flexes.clone();
1311                        let workspace = self.workspace.clone();
1312                        let handle_hitbox = handle.hitbox.clone();
1313                        move |e: &MouseDownEvent, phase, window, cx| {
1314                            if phase.bubble() && handle_hitbox.is_hovered(window) {
1315                                dragged_handle.replace(Some(ix));
1316                                if e.click_count >= 2 {
1317                                    let mut borrow = flexes.lock();
1318                                    *borrow = vec![1.; borrow.len()];
1319                                    workspace
1320                                        .update(cx, |this, cx| this.serialize_workspace(window, cx))
1321                                        .log_err();
1322
1323                                    window.refresh();
1324                                }
1325                                cx.stop_propagation();
1326                            }
1327                        }
1328                    });
1329                    window.on_mouse_event({
1330                        let workspace = self.workspace.clone();
1331                        let dragged_handle = layout.dragged_handle.clone();
1332                        let flexes = self.flexes.clone();
1333                        let child_bounds = child.bounds;
1334                        let axis = self.axis;
1335                        move |e: &MouseMoveEvent, phase, window, cx| {
1336                            let dragged_handle = dragged_handle.borrow();
1337                            if phase.bubble() && *dragged_handle == Some(ix) {
1338                                Self::compute_resize(
1339                                    &flexes,
1340                                    e,
1341                                    ix,
1342                                    axis,
1343                                    child_bounds.origin,
1344                                    bounds.size,
1345                                    workspace.clone(),
1346                                    window,
1347                                    cx,
1348                                )
1349                            }
1350                        }
1351                    });
1352                }
1353            }
1354
1355            window.on_mouse_event({
1356                let dragged_handle = layout.dragged_handle.clone();
1357                move |_: &MouseUpEvent, phase, _window, _cx| {
1358                    if phase.bubble() {
1359                        dragged_handle.replace(None);
1360                    }
1361                }
1362            });
1363        }
1364    }
1365
1366    impl ParentElement for PaneAxisElement {
1367        fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
1368            self.children.extend(elements)
1369        }
1370    }
1371
1372    fn flex_values_in_bounds(flexes: &[f32]) -> bool {
1373        (flexes.iter().copied().sum::<f32>() - flexes.len() as f32).abs() < 0.001
1374    }
1375}