pane_group.rs

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