pane_group.rs

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