pane_group.rs

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