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                    if 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            }
 634            found_axis_index?; // no pane found
 635        }
 636
 637        let min_size = match axis {
 638            Axis::Horizontal => px(HORIZONTAL_MIN_SIZE),
 639            Axis::Vertical => px(VERTICAL_MIN_SIZE),
 640        };
 641        let mut flexes = self.flexes.lock();
 642
 643        let ix = if found_pane {
 644            self.members.iter().position(|m| {
 645                if let Member::Pane(p) = m {
 646                    p == pane
 647                } else {
 648                    false
 649                }
 650            })
 651        } else {
 652            found_axis_index
 653        };
 654
 655        if ix.is_none() {
 656            return Some(true);
 657        }
 658
 659        let ix = ix.unwrap_or(0);
 660
 661        let size = move |ix, flexes: &[f32]| {
 662            container_size.along(axis) * (flexes[ix] / flexes.len() as f32)
 663        };
 664
 665        // Don't allow resizing to less than the minimum size, if elements are already too small
 666        if min_size - px(1.) > size(ix, flexes.as_slice()) {
 667            return Some(true);
 668        }
 669
 670        let flex_changes = |pixel_dx, target_ix, next: isize, flexes: &[f32]| {
 671            let flex_change = flexes.len() as f32 * pixel_dx / container_size.along(axis);
 672            let current_target_flex = flexes[target_ix] + flex_change;
 673            let next_target_flex = flexes[(target_ix as isize + next) as usize] - flex_change;
 674            (current_target_flex, next_target_flex)
 675        };
 676
 677        let apply_changes =
 678            |current_ix: usize, proposed_current_pixel_change: Pixels, flexes: &mut [f32]| {
 679                let next_target_size = Pixels::max(
 680                    size(current_ix + 1, flexes) - proposed_current_pixel_change,
 681                    min_size,
 682                );
 683                let current_target_size = Pixels::max(
 684                    size(current_ix, flexes) + size(current_ix + 1, flexes) - next_target_size,
 685                    min_size,
 686                );
 687
 688                let current_pixel_change = current_target_size - size(current_ix, flexes);
 689
 690                let (current_target_flex, next_target_flex) =
 691                    flex_changes(current_pixel_change, current_ix, 1, flexes);
 692
 693                flexes[current_ix] = current_target_flex;
 694                flexes[current_ix + 1] = next_target_flex;
 695            };
 696
 697        if ix + 1 == flexes.len() {
 698            apply_changes(ix - 1, -1.0 * amount, flexes.as_mut_slice());
 699        } else {
 700            apply_changes(ix, amount, flexes.as_mut_slice());
 701        }
 702        Some(true)
 703    }
 704
 705    fn swap(&mut self, from: &Entity<Pane>, to: &Entity<Pane>) {
 706        for member in self.members.iter_mut() {
 707            match member {
 708                Member::Axis(axis) => axis.swap(from, to),
 709                Member::Pane(pane) => {
 710                    if pane == from {
 711                        *member = Member::Pane(to.clone());
 712                    } else if pane == to {
 713                        *member = Member::Pane(from.clone())
 714                    }
 715                }
 716            }
 717        }
 718    }
 719
 720    fn bounding_box_for_pane(&self, pane: &Entity<Pane>) -> Option<Bounds<Pixels>> {
 721        debug_assert!(self.members.len() == self.bounding_boxes.lock().len());
 722
 723        for (idx, member) in self.members.iter().enumerate() {
 724            match member {
 725                Member::Pane(found) => {
 726                    if pane == found {
 727                        return self.bounding_boxes.lock()[idx];
 728                    }
 729                }
 730                Member::Axis(axis) => {
 731                    if let Some(rect) = axis.bounding_box_for_pane(pane) {
 732                        return Some(rect);
 733                    }
 734                }
 735            }
 736        }
 737        None
 738    }
 739
 740    fn pane_at_pixel_position(&self, coordinate: Point<Pixels>) -> Option<&Entity<Pane>> {
 741        debug_assert!(self.members.len() == self.bounding_boxes.lock().len());
 742
 743        let bounding_boxes = self.bounding_boxes.lock();
 744
 745        for (idx, member) in self.members.iter().enumerate() {
 746            if let Some(coordinates) = bounding_boxes[idx] {
 747                if coordinates.contains(&coordinate) {
 748                    return match member {
 749                        Member::Pane(found) => Some(found),
 750                        Member::Axis(axis) => axis.pane_at_pixel_position(coordinate),
 751                    };
 752                }
 753            }
 754        }
 755        None
 756    }
 757
 758    fn render(
 759        &self,
 760        basis: usize,
 761        zoomed: Option<&AnyWeakView>,
 762        render_cx: &dyn PaneLeaderDecorator,
 763        window: &mut Window,
 764        cx: &mut App,
 765    ) -> PaneRenderResult {
 766        debug_assert!(self.members.len() == self.flexes.lock().len());
 767        let mut active_pane_ix = None;
 768        let mut contains_active_pane = false;
 769        let mut is_leaf_pane = vec![false; self.members.len()];
 770
 771        let rendered_children = self
 772            .members
 773            .iter()
 774            .enumerate()
 775            .map(|(ix, member)| {
 776                match member {
 777                    Member::Pane(pane) => {
 778                        is_leaf_pane[ix] = true;
 779                        if pane == render_cx.active_pane() {
 780                            active_pane_ix = Some(ix);
 781                            contains_active_pane = true;
 782                        }
 783                    }
 784                    Member::Axis(_) => {
 785                        is_leaf_pane[ix] = false;
 786                    }
 787                }
 788
 789                let result = member.render((basis + ix) * 10, zoomed, render_cx, window, cx);
 790                if result.contains_active_pane {
 791                    contains_active_pane = true;
 792                }
 793                result.element.into_any_element()
 794            })
 795            .collect::<Vec<_>>();
 796
 797        let element = pane_axis(
 798            self.axis,
 799            basis,
 800            self.flexes.clone(),
 801            self.bounding_boxes.clone(),
 802            render_cx.workspace().clone(),
 803        )
 804        .with_is_leaf_pane_mask(is_leaf_pane)
 805        .children(rendered_children)
 806        .with_active_pane(active_pane_ix)
 807        .into_any_element();
 808
 809        PaneRenderResult {
 810            element,
 811            contains_active_pane,
 812        }
 813    }
 814}
 815
 816#[derive(Clone, Copy, Debug, Deserialize, PartialEq, JsonSchema)]
 817#[serde(rename_all = "snake_case")]
 818pub enum SplitDirection {
 819    Up,
 820    Down,
 821    Left,
 822    Right,
 823}
 824
 825impl std::fmt::Display for SplitDirection {
 826    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 827        match self {
 828            SplitDirection::Up => write!(f, "up"),
 829            SplitDirection::Down => write!(f, "down"),
 830            SplitDirection::Left => write!(f, "left"),
 831            SplitDirection::Right => write!(f, "right"),
 832        }
 833    }
 834}
 835
 836impl SplitDirection {
 837    pub fn all() -> [Self; 4] {
 838        [Self::Up, Self::Down, Self::Left, Self::Right]
 839    }
 840
 841    pub fn vertical(cx: &mut App) -> Self {
 842        match WorkspaceSettings::get_global(cx).pane_split_direction_vertical {
 843            PaneSplitDirectionVertical::Left => SplitDirection::Left,
 844            PaneSplitDirectionVertical::Right => SplitDirection::Right,
 845        }
 846    }
 847
 848    pub fn horizontal(cx: &mut App) -> Self {
 849        match WorkspaceSettings::get_global(cx).pane_split_direction_horizontal {
 850            PaneSplitDirectionHorizontal::Down => SplitDirection::Down,
 851            PaneSplitDirectionHorizontal::Up => SplitDirection::Up,
 852        }
 853    }
 854
 855    pub fn edge(&self, rect: Bounds<Pixels>) -> Pixels {
 856        match self {
 857            Self::Up => rect.origin.y,
 858            Self::Down => rect.bottom_left().y,
 859            Self::Left => rect.bottom_left().x,
 860            Self::Right => rect.bottom_right().x,
 861        }
 862    }
 863
 864    pub fn along_edge(&self, bounds: Bounds<Pixels>, length: Pixels) -> Bounds<Pixels> {
 865        match self {
 866            Self::Up => Bounds {
 867                origin: bounds.origin,
 868                size: size(bounds.size.width, length),
 869            },
 870            Self::Down => Bounds {
 871                origin: point(bounds.bottom_left().x, bounds.bottom_left().y - length),
 872                size: size(bounds.size.width, length),
 873            },
 874            Self::Left => Bounds {
 875                origin: bounds.origin,
 876                size: size(length, bounds.size.height),
 877            },
 878            Self::Right => Bounds {
 879                origin: point(bounds.bottom_right().x - length, bounds.bottom_left().y),
 880                size: size(length, bounds.size.height),
 881            },
 882        }
 883    }
 884
 885    pub fn axis(&self) -> Axis {
 886        match self {
 887            Self::Up | Self::Down => Axis::Vertical,
 888            Self::Left | Self::Right => Axis::Horizontal,
 889        }
 890    }
 891
 892    pub fn increasing(&self) -> bool {
 893        match self {
 894            Self::Left | Self::Up => false,
 895            Self::Down | Self::Right => true,
 896        }
 897    }
 898}
 899
 900mod element {
 901    use std::mem;
 902    use std::{cell::RefCell, iter, rc::Rc, sync::Arc};
 903
 904    use gpui::{
 905        Along, AnyElement, App, Axis, BorderStyle, Bounds, Element, GlobalElementId,
 906        HitboxBehavior, IntoElement, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement,
 907        Pixels, Point, Size, Style, WeakEntity, Window, px, relative, size,
 908    };
 909    use gpui::{CursorStyle, Hitbox};
 910    use parking_lot::Mutex;
 911    use settings::Settings;
 912    use smallvec::SmallVec;
 913    use ui::prelude::*;
 914    use util::ResultExt;
 915
 916    use crate::Workspace;
 917
 918    use crate::WorkspaceSettings;
 919
 920    use super::{HANDLE_HITBOX_SIZE, HORIZONTAL_MIN_SIZE, VERTICAL_MIN_SIZE};
 921
 922    const DIVIDER_SIZE: f32 = 1.0;
 923
 924    pub(super) fn pane_axis(
 925        axis: Axis,
 926        basis: usize,
 927        flexes: Arc<Mutex<Vec<f32>>>,
 928        bounding_boxes: Arc<Mutex<Vec<Option<Bounds<Pixels>>>>>,
 929        workspace: WeakEntity<Workspace>,
 930    ) -> PaneAxisElement {
 931        PaneAxisElement {
 932            axis,
 933            basis,
 934            flexes,
 935            bounding_boxes,
 936            children: SmallVec::new(),
 937            active_pane_ix: None,
 938            workspace,
 939            is_leaf_pane_mask: Vec::new(),
 940        }
 941    }
 942
 943    pub struct PaneAxisElement {
 944        axis: Axis,
 945        basis: usize,
 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            let size = move |ix, flexes: &[f32]| {
1002                container_size.along(axis) * (flexes[ix] / flexes.len() as f32)
1003            };
1004
1005            // Don't allow resizing to less than the minimum size, if elements are already too small
1006            if min_size - px(1.) > size(ix, flexes.as_slice()) {
1007                return;
1008            }
1009
1010            let mut proposed_current_pixel_change =
1011                (e.position - child_start).along(axis) - size(ix, flexes.as_slice());
1012
1013            let flex_changes = |pixel_dx, target_ix, next: isize, flexes: &[f32]| {
1014                let flex_change = pixel_dx / container_size.along(axis);
1015                let current_target_flex = flexes[target_ix] + flex_change;
1016                let next_target_flex = flexes[(target_ix as isize + next) as usize] - flex_change;
1017                (current_target_flex, next_target_flex)
1018            };
1019
1020            let mut successors = iter::from_fn({
1021                let forward = proposed_current_pixel_change > px(0.);
1022                let mut ix_offset = 0;
1023                let len = flexes.len();
1024                move || {
1025                    let result = if forward {
1026                        (ix + 1 + ix_offset < len).then(|| ix + ix_offset)
1027                    } else {
1028                        (ix as isize - ix_offset as isize >= 0).then(|| ix - ix_offset)
1029                    };
1030
1031                    ix_offset += 1;
1032
1033                    result
1034                }
1035            });
1036
1037            while proposed_current_pixel_change.abs() > px(0.) {
1038                let Some(current_ix) = successors.next() else {
1039                    break;
1040                };
1041
1042                let next_target_size = Pixels::max(
1043                    size(current_ix + 1, flexes.as_slice()) - proposed_current_pixel_change,
1044                    min_size,
1045                );
1046
1047                let current_target_size = Pixels::max(
1048                    size(current_ix, flexes.as_slice()) + size(current_ix + 1, flexes.as_slice())
1049                        - next_target_size,
1050                    min_size,
1051                );
1052
1053                let current_pixel_change =
1054                    current_target_size - size(current_ix, flexes.as_slice());
1055
1056                let (current_target_flex, next_target_flex) =
1057                    flex_changes(current_pixel_change, current_ix, 1, flexes.as_slice());
1058
1059                flexes[current_ix] = current_target_flex;
1060                flexes[current_ix + 1] = next_target_flex;
1061
1062                proposed_current_pixel_change -= current_pixel_change;
1063            }
1064
1065            workspace
1066                .update(cx, |this, cx| this.serialize_workspace(window, cx))
1067                .log_err();
1068            cx.stop_propagation();
1069            window.refresh();
1070        }
1071
1072        fn layout_handle(
1073            axis: Axis,
1074            pane_bounds: Bounds<Pixels>,
1075            window: &mut Window,
1076            _cx: &mut App,
1077        ) -> PaneAxisHandleLayout {
1078            let handle_bounds = Bounds {
1079                origin: pane_bounds.origin.apply_along(axis, |origin| {
1080                    origin + pane_bounds.size.along(axis) - px(HANDLE_HITBOX_SIZE / 2.)
1081                }),
1082                size: pane_bounds
1083                    .size
1084                    .apply_along(axis, |_| px(HANDLE_HITBOX_SIZE)),
1085            };
1086            let divider_bounds = Bounds {
1087                origin: pane_bounds
1088                    .origin
1089                    .apply_along(axis, |origin| origin + pane_bounds.size.along(axis)),
1090                size: pane_bounds.size.apply_along(axis, |_| px(DIVIDER_SIZE)),
1091            };
1092
1093            PaneAxisHandleLayout {
1094                hitbox: window.insert_hitbox(handle_bounds, HitboxBehavior::BlockMouse),
1095                divider_bounds,
1096            }
1097        }
1098    }
1099
1100    impl IntoElement for PaneAxisElement {
1101        type Element = Self;
1102
1103        fn into_element(self) -> Self::Element {
1104            self
1105        }
1106    }
1107
1108    impl Element for PaneAxisElement {
1109        type RequestLayoutState = ();
1110        type PrepaintState = PaneAxisLayout;
1111
1112        fn id(&self) -> Option<ElementId> {
1113            Some(self.basis.into())
1114        }
1115
1116        fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
1117            None
1118        }
1119
1120        fn request_layout(
1121            &mut self,
1122            _global_id: Option<&GlobalElementId>,
1123            _inspector_id: Option<&gpui::InspectorElementId>,
1124            window: &mut Window,
1125            cx: &mut App,
1126        ) -> (gpui::LayoutId, Self::RequestLayoutState) {
1127            let style = Style {
1128                flex_grow: 1.,
1129                flex_shrink: 1.,
1130                flex_basis: relative(0.).into(),
1131                size: size(relative(1.).into(), relative(1.).into()),
1132                ..Style::default()
1133            };
1134            (window.request_layout(style, None, cx), ())
1135        }
1136
1137        fn prepaint(
1138            &mut self,
1139            global_id: Option<&GlobalElementId>,
1140            _inspector_id: Option<&gpui::InspectorElementId>,
1141            bounds: Bounds<Pixels>,
1142            _state: &mut Self::RequestLayoutState,
1143            window: &mut Window,
1144            cx: &mut App,
1145        ) -> PaneAxisLayout {
1146            let dragged_handle = window.with_element_state::<Rc<RefCell<Option<usize>>>, _>(
1147                global_id.unwrap(),
1148                |state, _cx| {
1149                    let state = state.unwrap_or_else(|| Rc::new(RefCell::new(None)));
1150                    (state.clone(), state)
1151                },
1152            );
1153            let flexes = self.flexes.lock().clone();
1154            let len = self.children.len();
1155            debug_assert!(flexes.len() == len);
1156            debug_assert!(flex_values_in_bounds(flexes.as_slice()));
1157
1158            let total_flex = len as f32;
1159
1160            let mut origin = bounds.origin;
1161            let space_per_flex = bounds.size.along(self.axis) / total_flex;
1162
1163            let mut bounding_boxes = self.bounding_boxes.lock();
1164            bounding_boxes.clear();
1165
1166            let mut layout = PaneAxisLayout {
1167                dragged_handle: dragged_handle.clone(),
1168                children: Vec::new(),
1169            };
1170            for (ix, mut child) in mem::take(&mut self.children).into_iter().enumerate() {
1171                let child_flex = flexes[ix];
1172
1173                let child_size = bounds
1174                    .size
1175                    .apply_along(self.axis, |_| space_per_flex * child_flex)
1176                    .map(|d| d.round());
1177
1178                let child_bounds = Bounds {
1179                    origin,
1180                    size: child_size,
1181                };
1182
1183                bounding_boxes.push(Some(child_bounds));
1184                child.layout_as_root(child_size.into(), window, cx);
1185                child.prepaint_at(origin, window, cx);
1186
1187                origin = origin.apply_along(self.axis, |val| val + child_size.along(self.axis));
1188
1189                let is_leaf_pane = self.is_leaf_pane_mask.get(ix).copied().unwrap_or(true);
1190
1191                layout.children.push(PaneAxisChildLayout {
1192                    bounds: child_bounds,
1193                    element: child,
1194                    handle: None,
1195                    is_leaf_pane,
1196                })
1197            }
1198
1199            for (ix, child_layout) in layout.children.iter_mut().enumerate() {
1200                if ix < len - 1 {
1201                    child_layout.handle = Some(Self::layout_handle(
1202                        self.axis,
1203                        child_layout.bounds,
1204                        window,
1205                        cx,
1206                    ));
1207                }
1208            }
1209
1210            layout
1211        }
1212
1213        fn paint(
1214            &mut self,
1215            _id: Option<&GlobalElementId>,
1216            _inspector_id: Option<&gpui::InspectorElementId>,
1217            bounds: gpui::Bounds<ui::prelude::Pixels>,
1218            _: &mut Self::RequestLayoutState,
1219            layout: &mut Self::PrepaintState,
1220            window: &mut Window,
1221            cx: &mut App,
1222        ) {
1223            for child in &mut layout.children {
1224                child.element.paint(window, cx);
1225            }
1226
1227            let overlay_opacity = WorkspaceSettings::get(None, cx)
1228                .active_pane_modifiers
1229                .inactive_opacity
1230                .map(|val| val.clamp(0.0, 1.0))
1231                .and_then(|val| (val <= 1.).then_some(val));
1232
1233            let mut overlay_background = cx.theme().colors().editor_background;
1234            if let Some(opacity) = overlay_opacity {
1235                overlay_background.fade_out(opacity);
1236            }
1237
1238            let overlay_border = WorkspaceSettings::get(None, cx)
1239                .active_pane_modifiers
1240                .border_size
1241                .and_then(|val| (val >= 0.).then_some(val));
1242
1243            for (ix, child) in &mut layout.children.iter_mut().enumerate() {
1244                if overlay_opacity.is_some() || overlay_border.is_some() {
1245                    // the overlay has to be painted in origin+1px with size width-1px
1246                    // in order to accommodate the divider between panels
1247                    let overlay_bounds = Bounds {
1248                        origin: child
1249                            .bounds
1250                            .origin
1251                            .apply_along(Axis::Horizontal, |val| val + Pixels(1.)),
1252                        size: child
1253                            .bounds
1254                            .size
1255                            .apply_along(Axis::Horizontal, |val| val - Pixels(1.)),
1256                    };
1257
1258                    if overlay_opacity.is_some()
1259                        && child.is_leaf_pane
1260                        && self.active_pane_ix != Some(ix)
1261                    {
1262                        window.paint_quad(gpui::fill(overlay_bounds, overlay_background));
1263                    }
1264
1265                    if let Some(border) = overlay_border {
1266                        if self.active_pane_ix == Some(ix) && child.is_leaf_pane {
1267                            window.paint_quad(gpui::quad(
1268                                overlay_bounds,
1269                                0.,
1270                                gpui::transparent_black(),
1271                                border,
1272                                cx.theme().colors().border_selected,
1273                                BorderStyle::Solid,
1274                            ));
1275                        }
1276                    }
1277                }
1278
1279                if let Some(handle) = child.handle.as_mut() {
1280                    let cursor_style = match self.axis {
1281                        Axis::Vertical => CursorStyle::ResizeRow,
1282                        Axis::Horizontal => CursorStyle::ResizeColumn,
1283                    };
1284
1285                    if layout
1286                        .dragged_handle
1287                        .borrow()
1288                        .is_some_and(|dragged_ix| dragged_ix == ix)
1289                    {
1290                        window.set_window_cursor_style(cursor_style);
1291                    } else {
1292                        window.set_cursor_style(cursor_style, &handle.hitbox);
1293                    }
1294
1295                    window.paint_quad(gpui::fill(
1296                        handle.divider_bounds,
1297                        cx.theme().colors().pane_group_border,
1298                    ));
1299
1300                    window.on_mouse_event({
1301                        let dragged_handle = layout.dragged_handle.clone();
1302                        let flexes = self.flexes.clone();
1303                        let workspace = self.workspace.clone();
1304                        let handle_hitbox = handle.hitbox.clone();
1305                        move |e: &MouseDownEvent, phase, window, cx| {
1306                            if phase.bubble() && handle_hitbox.is_hovered(window) {
1307                                dragged_handle.replace(Some(ix));
1308                                if e.click_count >= 2 {
1309                                    let mut borrow = flexes.lock();
1310                                    *borrow = vec![1.; borrow.len()];
1311                                    workspace
1312                                        .update(cx, |this, cx| this.serialize_workspace(window, cx))
1313                                        .log_err();
1314
1315                                    window.refresh();
1316                                }
1317                                cx.stop_propagation();
1318                            }
1319                        }
1320                    });
1321                    window.on_mouse_event({
1322                        let workspace = self.workspace.clone();
1323                        let dragged_handle = layout.dragged_handle.clone();
1324                        let flexes = self.flexes.clone();
1325                        let child_bounds = child.bounds;
1326                        let axis = self.axis;
1327                        move |e: &MouseMoveEvent, phase, window, cx| {
1328                            let dragged_handle = dragged_handle.borrow();
1329                            if phase.bubble() && *dragged_handle == Some(ix) {
1330                                Self::compute_resize(
1331                                    &flexes,
1332                                    e,
1333                                    ix,
1334                                    axis,
1335                                    child_bounds.origin,
1336                                    bounds.size,
1337                                    workspace.clone(),
1338                                    window,
1339                                    cx,
1340                                )
1341                            }
1342                        }
1343                    });
1344                }
1345            }
1346
1347            window.on_mouse_event({
1348                let dragged_handle = layout.dragged_handle.clone();
1349                move |_: &MouseUpEvent, phase, _window, _cx| {
1350                    if phase.bubble() {
1351                        dragged_handle.replace(None);
1352                    }
1353                }
1354            });
1355        }
1356    }
1357
1358    impl ParentElement for PaneAxisElement {
1359        fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
1360            self.children.extend(elements)
1361        }
1362    }
1363
1364    fn flex_values_in_bounds(flexes: &[f32]) -> bool {
1365        (flexes.iter().copied().sum::<f32>() - flexes.len() as f32).abs() < 0.001
1366    }
1367}