pane_group.rs

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