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