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                {
 625                    if done {
 626                        return Some(true); // pane found and operations already done
 627                    } else if self.axis != axis {
 628                        return Some(false); // pane found but this is not the correct axis direction
 629                    } else {
 630                        found_axis_index = Some(i); // pane found and this is correct direction
 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                && coordinates.contains(&coordinate)
 748            {
 749                return match member {
 750                    Member::Pane(found) => Some(found),
 751                    Member::Axis(axis) => axis.pane_at_pixel_position(coordinate),
 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        /// Equivalent to ColumnWidths (but in terms of flexes instead of percentages)
 947        /// For example, flexes "1.33, 1, 1", instead of "40%, 30%, 30%"
 948        flexes: Arc<Mutex<Vec<f32>>>,
 949        bounding_boxes: Arc<Mutex<Vec<Option<Bounds<Pixels>>>>>,
 950        children: SmallVec<[AnyElement; 2]>,
 951        active_pane_ix: Option<usize>,
 952        workspace: WeakEntity<Workspace>,
 953        // Track which children are leaf panes (Member::Pane) vs axes (Member::Axis)
 954        is_leaf_pane_mask: Vec<bool>,
 955    }
 956
 957    pub struct PaneAxisLayout {
 958        dragged_handle: Rc<RefCell<Option<usize>>>,
 959        children: Vec<PaneAxisChildLayout>,
 960    }
 961
 962    struct PaneAxisChildLayout {
 963        bounds: Bounds<Pixels>,
 964        element: AnyElement,
 965        handle: Option<PaneAxisHandleLayout>,
 966        is_leaf_pane: bool,
 967    }
 968
 969    struct PaneAxisHandleLayout {
 970        hitbox: Hitbox,
 971        divider_bounds: Bounds<Pixels>,
 972    }
 973
 974    impl PaneAxisElement {
 975        pub fn with_active_pane(mut self, active_pane_ix: Option<usize>) -> Self {
 976            self.active_pane_ix = active_pane_ix;
 977            self
 978        }
 979
 980        pub fn with_is_leaf_pane_mask(mut self, mask: Vec<bool>) -> Self {
 981            self.is_leaf_pane_mask = mask;
 982            self
 983        }
 984
 985        fn compute_resize(
 986            flexes: &Arc<Mutex<Vec<f32>>>,
 987            e: &MouseMoveEvent,
 988            ix: usize,
 989            axis: Axis,
 990            child_start: Point<Pixels>,
 991            container_size: Size<Pixels>,
 992            workspace: WeakEntity<Workspace>,
 993            window: &mut Window,
 994            cx: &mut App,
 995        ) {
 996            let min_size = match axis {
 997                Axis::Horizontal => px(HORIZONTAL_MIN_SIZE),
 998                Axis::Vertical => px(VERTICAL_MIN_SIZE),
 999            };
1000            let mut flexes = flexes.lock();
1001            debug_assert!(flex_values_in_bounds(flexes.as_slice()));
1002
1003            // Math to convert a flex value to a pixel value
1004            let size = move |ix, flexes: &[f32]| {
1005                container_size.along(axis) * (flexes[ix] / flexes.len() as f32)
1006            };
1007
1008            // Don't allow resizing to less than the minimum size, if elements are already too small
1009            if min_size - px(1.) > size(ix, flexes.as_slice()) {
1010                return;
1011            }
1012
1013            // This is basically a "bucket" of pixel changes that need to be applied in response to this
1014            // mouse event. Probably a small, fractional number like 0.5 or 1.5 pixels
1015            let mut proposed_current_pixel_change =
1016                (e.position - child_start).along(axis) - size(ix, flexes.as_slice());
1017
1018            // This takes a pixel change, and computes the flex changes that correspond to this pixel change
1019            // as well as the next one, for some reason
1020            let flex_changes = |pixel_dx, target_ix, next: isize, flexes: &[f32]| {
1021                let flex_change = pixel_dx / container_size.along(axis);
1022                let current_target_flex = flexes[target_ix] + flex_change;
1023                let next_target_flex = flexes[(target_ix as isize + next) as usize] - flex_change;
1024                (current_target_flex, next_target_flex)
1025            };
1026
1027            // Generate the list of flex successors, from the current index.
1028            // If you're dragging column 3 forward, out of 6 columns, then this code will produce [4, 5, 6]
1029            // If you're dragging column 3 backward, out of 6 columns, then this code will produce [2, 1, 0]
1030            let mut successors = iter::from_fn({
1031                let forward = proposed_current_pixel_change > px(0.);
1032                let mut ix_offset = 0;
1033                let len = flexes.len();
1034                move || {
1035                    let result = if forward {
1036                        (ix + 1 + ix_offset < len).then(|| ix + ix_offset)
1037                    } else {
1038                        (ix as isize - ix_offset as isize >= 0).then(|| ix - ix_offset)
1039                    };
1040
1041                    ix_offset += 1;
1042
1043                    result
1044                }
1045            });
1046
1047            // Now actually loop over these, and empty our bucket of pixel changes
1048            while proposed_current_pixel_change.abs() > px(0.) {
1049                let Some(current_ix) = successors.next() else {
1050                    break;
1051                };
1052
1053                let next_target_size = Pixels::max(
1054                    size(current_ix + 1, flexes.as_slice()) - proposed_current_pixel_change,
1055                    min_size,
1056                );
1057
1058                let current_target_size = Pixels::max(
1059                    size(current_ix, flexes.as_slice()) + size(current_ix + 1, flexes.as_slice())
1060                        - next_target_size,
1061                    min_size,
1062                );
1063
1064                let current_pixel_change =
1065                    current_target_size - size(current_ix, flexes.as_slice());
1066
1067                let (current_target_flex, next_target_flex) =
1068                    flex_changes(current_pixel_change, current_ix, 1, flexes.as_slice());
1069
1070                flexes[current_ix] = current_target_flex;
1071                flexes[current_ix + 1] = next_target_flex;
1072
1073                proposed_current_pixel_change -= current_pixel_change;
1074            }
1075
1076            workspace
1077                .update(cx, |this, cx| this.serialize_workspace(window, cx))
1078                .log_err();
1079            cx.stop_propagation();
1080            window.refresh();
1081        }
1082
1083        fn layout_handle(
1084            axis: Axis,
1085            pane_bounds: Bounds<Pixels>,
1086            window: &mut Window,
1087            _cx: &mut App,
1088        ) -> PaneAxisHandleLayout {
1089            let handle_bounds = Bounds {
1090                origin: pane_bounds.origin.apply_along(axis, |origin| {
1091                    origin + pane_bounds.size.along(axis) - px(HANDLE_HITBOX_SIZE / 2.)
1092                }),
1093                size: pane_bounds
1094                    .size
1095                    .apply_along(axis, |_| px(HANDLE_HITBOX_SIZE)),
1096            };
1097            let divider_bounds = Bounds {
1098                origin: pane_bounds
1099                    .origin
1100                    .apply_along(axis, |origin| origin + pane_bounds.size.along(axis)),
1101                size: pane_bounds.size.apply_along(axis, |_| px(DIVIDER_SIZE)),
1102            };
1103
1104            PaneAxisHandleLayout {
1105                hitbox: window.insert_hitbox(handle_bounds, HitboxBehavior::BlockMouse),
1106                divider_bounds,
1107            }
1108        }
1109    }
1110
1111    impl IntoElement for PaneAxisElement {
1112        type Element = Self;
1113
1114        fn into_element(self) -> Self::Element {
1115            self
1116        }
1117    }
1118
1119    impl Element for PaneAxisElement {
1120        type RequestLayoutState = ();
1121        type PrepaintState = PaneAxisLayout;
1122
1123        fn id(&self) -> Option<ElementId> {
1124            Some(self.basis.into())
1125        }
1126
1127        fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
1128            None
1129        }
1130
1131        fn request_layout(
1132            &mut self,
1133            _global_id: Option<&GlobalElementId>,
1134            _inspector_id: Option<&gpui::InspectorElementId>,
1135            window: &mut Window,
1136            cx: &mut App,
1137        ) -> (gpui::LayoutId, Self::RequestLayoutState) {
1138            let style = Style {
1139                flex_grow: 1.,
1140                flex_shrink: 1.,
1141                flex_basis: relative(0.).into(),
1142                size: size(relative(1.).into(), relative(1.).into()),
1143                ..Style::default()
1144            };
1145            (window.request_layout(style, None, cx), ())
1146        }
1147
1148        fn prepaint(
1149            &mut self,
1150            global_id: Option<&GlobalElementId>,
1151            _inspector_id: Option<&gpui::InspectorElementId>,
1152            bounds: Bounds<Pixels>,
1153            _state: &mut Self::RequestLayoutState,
1154            window: &mut Window,
1155            cx: &mut App,
1156        ) -> PaneAxisLayout {
1157            let dragged_handle = window.with_element_state::<Rc<RefCell<Option<usize>>>, _>(
1158                global_id.unwrap(),
1159                |state, _cx| {
1160                    let state = state.unwrap_or_else(|| Rc::new(RefCell::new(None)));
1161                    (state.clone(), state)
1162                },
1163            );
1164            let flexes = self.flexes.lock().clone();
1165            let len = self.children.len();
1166            debug_assert!(flexes.len() == len);
1167            debug_assert!(flex_values_in_bounds(flexes.as_slice()));
1168
1169            let total_flex = len as f32;
1170
1171            let mut origin = bounds.origin;
1172            let space_per_flex = bounds.size.along(self.axis) / total_flex;
1173
1174            let mut bounding_boxes = self.bounding_boxes.lock();
1175            bounding_boxes.clear();
1176
1177            let mut layout = PaneAxisLayout {
1178                dragged_handle,
1179                children: Vec::new(),
1180            };
1181            for (ix, mut child) in mem::take(&mut self.children).into_iter().enumerate() {
1182                let child_flex = flexes[ix];
1183
1184                let child_size = bounds
1185                    .size
1186                    .apply_along(self.axis, |_| space_per_flex * child_flex)
1187                    .map(|d| d.round());
1188
1189                let child_bounds = Bounds {
1190                    origin,
1191                    size: child_size,
1192                };
1193
1194                bounding_boxes.push(Some(child_bounds));
1195                child.layout_as_root(child_size.into(), window, cx);
1196                child.prepaint_at(origin, window, cx);
1197
1198                origin = origin.apply_along(self.axis, |val| val + child_size.along(self.axis));
1199
1200                let is_leaf_pane = self.is_leaf_pane_mask.get(ix).copied().unwrap_or(true);
1201
1202                layout.children.push(PaneAxisChildLayout {
1203                    bounds: child_bounds,
1204                    element: child,
1205                    handle: None,
1206                    is_leaf_pane,
1207                })
1208            }
1209
1210            for (ix, child_layout) in layout.children.iter_mut().enumerate() {
1211                if ix < len - 1 {
1212                    child_layout.handle = Some(Self::layout_handle(
1213                        self.axis,
1214                        child_layout.bounds,
1215                        window,
1216                        cx,
1217                    ));
1218                }
1219            }
1220
1221            layout
1222        }
1223
1224        fn paint(
1225            &mut self,
1226            _id: Option<&GlobalElementId>,
1227            _inspector_id: Option<&gpui::InspectorElementId>,
1228            bounds: gpui::Bounds<ui::prelude::Pixels>,
1229            _: &mut Self::RequestLayoutState,
1230            layout: &mut Self::PrepaintState,
1231            window: &mut Window,
1232            cx: &mut App,
1233        ) {
1234            for child in &mut layout.children {
1235                child.element.paint(window, cx);
1236            }
1237
1238            let overlay_opacity = WorkspaceSettings::get(None, cx)
1239                .active_pane_modifiers
1240                .inactive_opacity
1241                .map(|val| val.clamp(0.0, 1.0))
1242                .and_then(|val| (val <= 1.).then_some(val));
1243
1244            let mut overlay_background = cx.theme().colors().editor_background;
1245            if let Some(opacity) = overlay_opacity {
1246                overlay_background.fade_out(opacity);
1247            }
1248
1249            let overlay_border = WorkspaceSettings::get(None, cx)
1250                .active_pane_modifiers
1251                .border_size
1252                .and_then(|val| (val >= 0.).then_some(val));
1253
1254            for (ix, child) in &mut layout.children.iter_mut().enumerate() {
1255                if overlay_opacity.is_some() || overlay_border.is_some() {
1256                    // the overlay has to be painted in origin+1px with size width-1px
1257                    // in order to accommodate the divider between panels
1258                    let overlay_bounds = Bounds {
1259                        origin: child
1260                            .bounds
1261                            .origin
1262                            .apply_along(Axis::Horizontal, |val| val + px(1.)),
1263                        size: child
1264                            .bounds
1265                            .size
1266                            .apply_along(Axis::Horizontal, |val| val - px(1.)),
1267                    };
1268
1269                    if overlay_opacity.is_some()
1270                        && child.is_leaf_pane
1271                        && self.active_pane_ix != Some(ix)
1272                    {
1273                        window.paint_quad(gpui::fill(overlay_bounds, overlay_background));
1274                    }
1275
1276                    if let Some(border) = overlay_border
1277                        && self.active_pane_ix == Some(ix)
1278                        && child.is_leaf_pane
1279                    {
1280                        window.paint_quad(gpui::quad(
1281                            overlay_bounds,
1282                            0.,
1283                            gpui::transparent_black(),
1284                            border,
1285                            cx.theme().colors().border_selected,
1286                            BorderStyle::Solid,
1287                        ));
1288                    }
1289                }
1290
1291                if let Some(handle) = child.handle.as_mut() {
1292                    let cursor_style = match self.axis {
1293                        Axis::Vertical => CursorStyle::ResizeRow,
1294                        Axis::Horizontal => CursorStyle::ResizeColumn,
1295                    };
1296
1297                    if layout
1298                        .dragged_handle
1299                        .borrow()
1300                        .is_some_and(|dragged_ix| dragged_ix == ix)
1301                    {
1302                        window.set_window_cursor_style(cursor_style);
1303                    } else {
1304                        window.set_cursor_style(cursor_style, &handle.hitbox);
1305                    }
1306
1307                    window.paint_quad(gpui::fill(
1308                        handle.divider_bounds,
1309                        cx.theme().colors().pane_group_border,
1310                    ));
1311
1312                    window.on_mouse_event({
1313                        let dragged_handle = layout.dragged_handle.clone();
1314                        let flexes = self.flexes.clone();
1315                        let workspace = self.workspace.clone();
1316                        let handle_hitbox = handle.hitbox.clone();
1317                        move |e: &MouseDownEvent, phase, window, cx| {
1318                            if phase.bubble() && handle_hitbox.is_hovered(window) {
1319                                dragged_handle.replace(Some(ix));
1320                                if e.click_count >= 2 {
1321                                    let mut borrow = flexes.lock();
1322                                    *borrow = vec![1.; borrow.len()];
1323                                    workspace
1324                                        .update(cx, |this, cx| this.serialize_workspace(window, cx))
1325                                        .log_err();
1326
1327                                    window.refresh();
1328                                }
1329                                cx.stop_propagation();
1330                            }
1331                        }
1332                    });
1333                    window.on_mouse_event({
1334                        let workspace = self.workspace.clone();
1335                        let dragged_handle = layout.dragged_handle.clone();
1336                        let flexes = self.flexes.clone();
1337                        let child_bounds = child.bounds;
1338                        let axis = self.axis;
1339                        move |e: &MouseMoveEvent, phase, window, cx| {
1340                            let dragged_handle = dragged_handle.borrow();
1341                            if phase.bubble() && *dragged_handle == Some(ix) {
1342                                Self::compute_resize(
1343                                    &flexes,
1344                                    e,
1345                                    ix,
1346                                    axis,
1347                                    child_bounds.origin,
1348                                    bounds.size,
1349                                    workspace.clone(),
1350                                    window,
1351                                    cx,
1352                                )
1353                            }
1354                        }
1355                    });
1356                }
1357            }
1358
1359            window.on_mouse_event({
1360                let dragged_handle = layout.dragged_handle.clone();
1361                move |_: &MouseUpEvent, phase, _window, _cx| {
1362                    if phase.bubble() {
1363                        dragged_handle.replace(None);
1364                    }
1365                }
1366            });
1367        }
1368    }
1369
1370    impl ParentElement for PaneAxisElement {
1371        fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
1372            self.children.extend(elements)
1373        }
1374    }
1375
1376    fn flex_values_in_bounds(flexes: &[f32]) -> bool {
1377        (flexes.iter().copied().sum::<f32>() - flexes.len() as f32).abs() < 0.001
1378    }
1379}