pane_group.rs

   1use crate::{
   2    pane_group::element::pane_axis,
   3    workspace_settings::{PaneSplitDirectionHorizontal, PaneSplitDirectionVertical},
   4    AppState, FollowerState, Pane, Workspace, WorkspaceSettings,
   5};
   6use anyhow::{anyhow, Result};
   7use call::{ActiveCall, ParticipantLocation};
   8use client::proto::PeerId;
   9use collections::HashMap;
  10use gpui::{
  11    point, size, AnyView, AnyWeakView, Axis, Bounds, IntoElement, Model, MouseButton, Pixels,
  12    Point, StyleRefinement, View, ViewContext,
  13};
  14use parking_lot::Mutex;
  15use project::Project;
  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(crate) root: Member,
  31}
  32
  33impl PaneGroup {
  34    pub(crate) fn with_root(root: Member) -> Self {
  35        Self { root }
  36    }
  37
  38    pub fn new(pane: View<Pane>) -> Self {
  39        Self {
  40            root: Member::Pane(pane),
  41        }
  42    }
  43
  44    pub fn split(
  45        &mut self,
  46        old_pane: &View<Pane>,
  47        new_pane: &View<Pane>,
  48        direction: SplitDirection,
  49    ) -> Result<()> {
  50        match &mut self.root {
  51            Member::Pane(pane) => {
  52                if pane == old_pane {
  53                    self.root = Member::new_axis(old_pane.clone(), new_pane.clone(), direction);
  54                    Ok(())
  55                } else {
  56                    Err(anyhow!("Pane not found"))
  57                }
  58            }
  59            Member::Axis(axis) => axis.split(old_pane, new_pane, direction),
  60        }
  61    }
  62
  63    pub fn bounding_box_for_pane(&self, pane: &View<Pane>) -> Option<Bounds<Pixels>> {
  64        match &self.root {
  65            Member::Pane(_) => None,
  66            Member::Axis(axis) => axis.bounding_box_for_pane(pane),
  67        }
  68    }
  69
  70    pub fn pane_at_pixel_position(&self, coordinate: Point<Pixels>) -> Option<&View<Pane>> {
  71        match &self.root {
  72            Member::Pane(pane) => Some(pane),
  73            Member::Axis(axis) => axis.pane_at_pixel_position(coordinate),
  74        }
  75    }
  76
  77    /// Returns:
  78    /// - Ok(true) if it found and removed a pane
  79    /// - Ok(false) if it found but did not remove the pane
  80    /// - Err(_) if it did not find the pane
  81    pub fn remove(&mut self, pane: &View<Pane>) -> Result<bool> {
  82        match &mut self.root {
  83            Member::Pane(_) => Ok(false),
  84            Member::Axis(axis) => {
  85                if let Some(last_pane) = axis.remove(pane)? {
  86                    self.root = last_pane;
  87                }
  88                Ok(true)
  89            }
  90        }
  91    }
  92
  93    pub fn swap(&mut self, from: &View<Pane>, to: &View<Pane>) {
  94        match &mut self.root {
  95            Member::Pane(_) => {}
  96            Member::Axis(axis) => axis.swap(from, to),
  97        };
  98    }
  99
 100    #[allow(clippy::too_many_arguments)]
 101    pub(crate) fn render(
 102        &self,
 103        project: &Model<Project>,
 104        follower_states: &HashMap<PeerId, FollowerState>,
 105        active_call: Option<&Model<ActiveCall>>,
 106        active_pane: &View<Pane>,
 107        zoomed: Option<&AnyWeakView>,
 108        app_state: &Arc<AppState>,
 109        cx: &mut ViewContext<Workspace>,
 110    ) -> impl IntoElement {
 111        self.root.render(
 112            project,
 113            0,
 114            follower_states,
 115            active_call,
 116            active_pane,
 117            zoomed,
 118            app_state,
 119            cx,
 120        )
 121    }
 122
 123    pub(crate) fn panes(&self) -> Vec<&View<Pane>> {
 124        let mut panes = Vec::new();
 125        self.root.collect_panes(&mut panes);
 126        panes
 127    }
 128
 129    pub(crate) fn first_pane(&self) -> View<Pane> {
 130        self.root.first_pane()
 131    }
 132}
 133
 134#[derive(Clone)]
 135pub(crate) enum Member {
 136    Axis(PaneAxis),
 137    Pane(View<Pane>),
 138}
 139
 140impl Member {
 141    fn new_axis(old_pane: View<Pane>, new_pane: View<Pane>, direction: SplitDirection) -> Self {
 142        use Axis::*;
 143        use SplitDirection::*;
 144
 145        let axis = match direction {
 146            Up | Down => Vertical,
 147            Left | Right => Horizontal,
 148        };
 149
 150        let members = match direction {
 151            Up | Left => vec![Member::Pane(new_pane), Member::Pane(old_pane)],
 152            Down | Right => vec![Member::Pane(old_pane), Member::Pane(new_pane)],
 153        };
 154
 155        Member::Axis(PaneAxis::new(axis, members))
 156    }
 157
 158    fn contains(&self, needle: &View<Pane>) -> bool {
 159        match self {
 160            Member::Axis(axis) => axis.members.iter().any(|member| member.contains(needle)),
 161            Member::Pane(pane) => pane == needle,
 162        }
 163    }
 164
 165    fn first_pane(&self) -> View<Pane> {
 166        match self {
 167            Member::Axis(axis) => axis.members[0].first_pane(),
 168            Member::Pane(pane) => pane.clone(),
 169        }
 170    }
 171
 172    #[allow(clippy::too_many_arguments)]
 173    pub fn render(
 174        &self,
 175        project: &Model<Project>,
 176        basis: usize,
 177        follower_states: &HashMap<PeerId, FollowerState>,
 178        active_call: Option<&Model<ActiveCall>>,
 179        active_pane: &View<Pane>,
 180        zoomed: Option<&AnyWeakView>,
 181        app_state: &Arc<AppState>,
 182        cx: &mut ViewContext<Workspace>,
 183    ) -> impl IntoElement {
 184        match self {
 185            Member::Pane(pane) => {
 186                if zoomed == Some(&pane.downgrade().into()) {
 187                    return div().into_any();
 188                }
 189
 190                let follower_state = follower_states.iter().find_map(|(leader_id, state)| {
 191                    if state.center_pane == *pane {
 192                        Some((*leader_id, state))
 193                    } else {
 194                        None
 195                    }
 196                });
 197
 198                let leader = follower_state.as_ref().and_then(|(leader_id, _)| {
 199                    let room = active_call?.read(cx).room()?.read(cx);
 200                    room.remote_participant_for_peer_id(*leader_id)
 201                });
 202
 203                let is_in_unshared_view = follower_state.as_ref().map_or(false, |(_, state)| {
 204                    state.active_view_id.is_some_and(|view_id| {
 205                        !state.items_by_leader_view_id.contains_key(&view_id)
 206                    })
 207                });
 208
 209                let is_in_panel = follower_state
 210                    .as_ref()
 211                    .map_or(false, |(_, state)| state.dock_pane.is_some());
 212
 213                let mut leader_border = None;
 214                let mut leader_status_box = None;
 215                let mut leader_join_data = None;
 216                if let Some(leader) = &leader {
 217                    let mut leader_color = cx
 218                        .theme()
 219                        .players()
 220                        .color_for_participant(leader.participant_index.0)
 221                        .cursor;
 222                    if is_in_panel {
 223                        leader_color.fade_out(0.75);
 224                    } else {
 225                        leader_color.fade_out(0.3);
 226                    }
 227                    leader_border = Some(leader_color);
 228
 229                    leader_status_box = match leader.location {
 230                        ParticipantLocation::SharedProject {
 231                            project_id: leader_project_id,
 232                        } => {
 233                            if Some(leader_project_id) == project.read(cx).remote_id() {
 234                                if is_in_unshared_view {
 235                                    Some(Label::new(format!(
 236                                        "{} is in an unshared pane",
 237                                        leader.user.github_login
 238                                    )))
 239                                } else {
 240                                    None
 241                                }
 242                            } else {
 243                                leader_join_data = Some((leader_project_id, leader.user.id));
 244                                Some(Label::new(format!(
 245                                    "Follow {} to their active project",
 246                                    leader.user.github_login,
 247                                )))
 248                            }
 249                        }
 250                        ParticipantLocation::UnsharedProject => Some(Label::new(format!(
 251                            "{} is viewing an unshared Zed project",
 252                            leader.user.github_login
 253                        ))),
 254                        ParticipantLocation::External => Some(Label::new(format!(
 255                            "{} is viewing a window outside of Zed",
 256                            leader.user.github_login
 257                        ))),
 258                    };
 259                }
 260
 261                div()
 262                    .relative()
 263                    .flex_1()
 264                    .size_full()
 265                    .child(
 266                        AnyView::from(pane.clone())
 267                            .cached(StyleRefinement::default().v_flex().size_full()),
 268                    )
 269                    .when_some(leader_border, |this, color| {
 270                        this.child(
 271                            div()
 272                                .absolute()
 273                                .size_full()
 274                                .left_0()
 275                                .top_0()
 276                                .border_2()
 277                                .border_color(color),
 278                        )
 279                    })
 280                    .when_some(leader_status_box, |this, status_box| {
 281                        this.child(
 282                            div()
 283                                .absolute()
 284                                .w_96()
 285                                .bottom_3()
 286                                .right_3()
 287                                .elevation_2(cx)
 288                                .p_1()
 289                                .child(status_box)
 290                                .when_some(
 291                                    leader_join_data,
 292                                    |this, (leader_project_id, leader_user_id)| {
 293                                        this.cursor_pointer().on_mouse_down(
 294                                            MouseButton::Left,
 295                                            cx.listener(move |this, _, cx| {
 296                                                crate::join_in_room_project(
 297                                                    leader_project_id,
 298                                                    leader_user_id,
 299                                                    this.app_state().clone(),
 300                                                    cx,
 301                                                )
 302                                                .detach_and_log_err(cx);
 303                                            }),
 304                                        )
 305                                    },
 306                                ),
 307                        )
 308                    })
 309                    .into_any()
 310            }
 311            Member::Axis(axis) => axis
 312                .render(
 313                    project,
 314                    basis + 1,
 315                    follower_states,
 316                    active_call,
 317                    active_pane,
 318                    zoomed,
 319                    app_state,
 320                    cx,
 321                )
 322                .into_any(),
 323        }
 324    }
 325
 326    fn collect_panes<'a>(&'a self, panes: &mut Vec<&'a View<Pane>>) {
 327        match self {
 328            Member::Axis(axis) => {
 329                for member in &axis.members {
 330                    member.collect_panes(panes);
 331                }
 332            }
 333            Member::Pane(pane) => panes.push(pane),
 334        }
 335    }
 336}
 337
 338#[derive(Clone)]
 339pub(crate) struct PaneAxis {
 340    pub axis: Axis,
 341    pub members: Vec<Member>,
 342    pub flexes: Arc<Mutex<Vec<f32>>>,
 343    pub bounding_boxes: Arc<Mutex<Vec<Option<Bounds<Pixels>>>>>,
 344}
 345
 346impl PaneAxis {
 347    pub fn new(axis: Axis, members: Vec<Member>) -> Self {
 348        let flexes = Arc::new(Mutex::new(vec![1.; members.len()]));
 349        let bounding_boxes = Arc::new(Mutex::new(vec![None; members.len()]));
 350        Self {
 351            axis,
 352            members,
 353            flexes,
 354            bounding_boxes,
 355        }
 356    }
 357
 358    pub fn load(axis: Axis, members: Vec<Member>, flexes: Option<Vec<f32>>) -> Self {
 359        let flexes = flexes.unwrap_or_else(|| vec![1.; members.len()]);
 360        // debug_assert!(members.len() == flexes.len());
 361
 362        let flexes = Arc::new(Mutex::new(flexes));
 363        let bounding_boxes = Arc::new(Mutex::new(vec![None; members.len()]));
 364        Self {
 365            axis,
 366            members,
 367            flexes,
 368            bounding_boxes,
 369        }
 370    }
 371
 372    fn split(
 373        &mut self,
 374        old_pane: &View<Pane>,
 375        new_pane: &View<Pane>,
 376        direction: SplitDirection,
 377    ) -> Result<()> {
 378        for (mut idx, member) in self.members.iter_mut().enumerate() {
 379            match member {
 380                Member::Axis(axis) => {
 381                    if axis.split(old_pane, new_pane, direction).is_ok() {
 382                        return Ok(());
 383                    }
 384                }
 385                Member::Pane(pane) => {
 386                    if pane == old_pane {
 387                        if direction.axis() == self.axis {
 388                            if direction.increasing() {
 389                                idx += 1;
 390                            }
 391
 392                            self.members.insert(idx, Member::Pane(new_pane.clone()));
 393                            *self.flexes.lock() = vec![1.; self.members.len()];
 394                        } else {
 395                            *member =
 396                                Member::new_axis(old_pane.clone(), new_pane.clone(), direction);
 397                        }
 398                        return Ok(());
 399                    }
 400                }
 401            }
 402        }
 403        Err(anyhow!("Pane not found"))
 404    }
 405
 406    fn remove(&mut self, pane_to_remove: &View<Pane>) -> Result<Option<Member>> {
 407        let mut found_pane = false;
 408        let mut remove_member = None;
 409        for (idx, member) in self.members.iter_mut().enumerate() {
 410            match member {
 411                Member::Axis(axis) => {
 412                    if let Ok(last_pane) = axis.remove(pane_to_remove) {
 413                        if let Some(last_pane) = last_pane {
 414                            *member = last_pane;
 415                        }
 416                        found_pane = true;
 417                        break;
 418                    }
 419                }
 420                Member::Pane(pane) => {
 421                    if pane == pane_to_remove {
 422                        found_pane = true;
 423                        remove_member = Some(idx);
 424                        break;
 425                    }
 426                }
 427            }
 428        }
 429
 430        if found_pane {
 431            if let Some(idx) = remove_member {
 432                self.members.remove(idx);
 433                *self.flexes.lock() = vec![1.; self.members.len()];
 434            }
 435
 436            if self.members.len() == 1 {
 437                let result = self.members.pop();
 438                *self.flexes.lock() = vec![1.; self.members.len()];
 439                Ok(result)
 440            } else {
 441                Ok(None)
 442            }
 443        } else {
 444            Err(anyhow!("Pane not found"))
 445        }
 446    }
 447
 448    fn swap(&mut self, from: &View<Pane>, to: &View<Pane>) {
 449        for member in self.members.iter_mut() {
 450            match member {
 451                Member::Axis(axis) => axis.swap(from, to),
 452                Member::Pane(pane) => {
 453                    if pane == from {
 454                        *member = Member::Pane(to.clone());
 455                    } else if pane == to {
 456                        *member = Member::Pane(from.clone())
 457                    }
 458                }
 459            }
 460        }
 461    }
 462
 463    fn bounding_box_for_pane(&self, pane: &View<Pane>) -> Option<Bounds<Pixels>> {
 464        debug_assert!(self.members.len() == self.bounding_boxes.lock().len());
 465
 466        for (idx, member) in self.members.iter().enumerate() {
 467            match member {
 468                Member::Pane(found) => {
 469                    if pane == found {
 470                        return self.bounding_boxes.lock()[idx];
 471                    }
 472                }
 473                Member::Axis(axis) => {
 474                    if let Some(rect) = axis.bounding_box_for_pane(pane) {
 475                        return Some(rect);
 476                    }
 477                }
 478            }
 479        }
 480        None
 481    }
 482
 483    fn pane_at_pixel_position(&self, coordinate: Point<Pixels>) -> Option<&View<Pane>> {
 484        debug_assert!(self.members.len() == self.bounding_boxes.lock().len());
 485
 486        let bounding_boxes = self.bounding_boxes.lock();
 487
 488        for (idx, member) in self.members.iter().enumerate() {
 489            if let Some(coordinates) = bounding_boxes[idx] {
 490                if coordinates.contains(&coordinate) {
 491                    return match member {
 492                        Member::Pane(found) => Some(found),
 493                        Member::Axis(axis) => axis.pane_at_pixel_position(coordinate),
 494                    };
 495                }
 496            }
 497        }
 498        None
 499    }
 500
 501    #[allow(clippy::too_many_arguments)]
 502    fn render(
 503        &self,
 504        project: &Model<Project>,
 505        basis: usize,
 506        follower_states: &HashMap<PeerId, FollowerState>,
 507        active_call: Option<&Model<ActiveCall>>,
 508        active_pane: &View<Pane>,
 509        zoomed: Option<&AnyWeakView>,
 510        app_state: &Arc<AppState>,
 511        cx: &mut ViewContext<Workspace>,
 512    ) -> gpui::AnyElement {
 513        debug_assert!(self.members.len() == self.flexes.lock().len());
 514        let mut active_pane_ix = None;
 515
 516        pane_axis(
 517            self.axis,
 518            basis,
 519            self.flexes.clone(),
 520            self.bounding_boxes.clone(),
 521            cx.view().downgrade(),
 522        )
 523        .children(self.members.iter().enumerate().map(|(ix, member)| {
 524            if member.contains(active_pane) {
 525                active_pane_ix = Some(ix);
 526            }
 527            member
 528                .render(
 529                    project,
 530                    (basis + ix) * 10,
 531                    follower_states,
 532                    active_call,
 533                    active_pane,
 534                    zoomed,
 535                    app_state,
 536                    cx,
 537                )
 538                .into_any_element()
 539        }))
 540        .with_active_pane(active_pane_ix)
 541        .into_any_element()
 542    }
 543}
 544
 545#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
 546pub enum SplitDirection {
 547    Up,
 548    Down,
 549    Left,
 550    Right,
 551}
 552
 553impl std::fmt::Display for SplitDirection {
 554    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 555        match self {
 556            SplitDirection::Up => write!(f, "up"),
 557            SplitDirection::Down => write!(f, "down"),
 558            SplitDirection::Left => write!(f, "left"),
 559            SplitDirection::Right => write!(f, "right"),
 560        }
 561    }
 562}
 563
 564impl SplitDirection {
 565    pub fn all() -> [Self; 4] {
 566        [Self::Up, Self::Down, Self::Left, Self::Right]
 567    }
 568
 569    pub fn vertical(cx: &WindowContext) -> Self {
 570        match WorkspaceSettings::get_global(cx).pane_split_direction_vertical {
 571            PaneSplitDirectionVertical::Left => SplitDirection::Left,
 572            PaneSplitDirectionVertical::Right => SplitDirection::Right,
 573        }
 574    }
 575
 576    pub fn horizontal(cx: &WindowContext) -> Self {
 577        match WorkspaceSettings::get_global(cx).pane_split_direction_horizontal {
 578            PaneSplitDirectionHorizontal::Down => SplitDirection::Down,
 579            PaneSplitDirectionHorizontal::Up => SplitDirection::Up,
 580        }
 581    }
 582
 583    pub fn edge(&self, rect: Bounds<Pixels>) -> Pixels {
 584        match self {
 585            Self::Up => rect.origin.y,
 586            Self::Down => rect.lower_left().y,
 587            Self::Left => rect.lower_left().x,
 588            Self::Right => rect.lower_right().x,
 589        }
 590    }
 591
 592    pub fn along_edge(&self, bounds: Bounds<Pixels>, length: Pixels) -> Bounds<Pixels> {
 593        match self {
 594            Self::Up => Bounds {
 595                origin: bounds.origin,
 596                size: size(bounds.size.width, length),
 597            },
 598            Self::Down => Bounds {
 599                origin: point(bounds.lower_left().x, bounds.lower_left().y - length),
 600                size: size(bounds.size.width, length),
 601            },
 602            Self::Left => Bounds {
 603                origin: bounds.origin,
 604                size: size(length, bounds.size.height),
 605            },
 606            Self::Right => Bounds {
 607                origin: point(bounds.lower_right().x - length, bounds.lower_left().y),
 608                size: size(length, bounds.size.height),
 609            },
 610        }
 611    }
 612
 613    pub fn axis(&self) -> Axis {
 614        match self {
 615            Self::Up | Self::Down => Axis::Vertical,
 616            Self::Left | Self::Right => Axis::Horizontal,
 617        }
 618    }
 619
 620    pub fn increasing(&self) -> bool {
 621        match self {
 622            Self::Left | Self::Up => false,
 623            Self::Down | Self::Right => true,
 624        }
 625    }
 626}
 627
 628mod element {
 629
 630    use std::mem;
 631    use std::{cell::RefCell, iter, rc::Rc, sync::Arc};
 632
 633    use gpui::{
 634        px, relative, size, Along, AnyElement, Axis, Bounds, Element, GlobalElementId, IntoElement,
 635        MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point, Size, Style,
 636        WeakView, WindowContext,
 637    };
 638    use gpui::{CursorStyle, Hitbox};
 639    use parking_lot::Mutex;
 640    use settings::Settings;
 641    use smallvec::SmallVec;
 642    use ui::prelude::*;
 643    use util::ResultExt;
 644
 645    use crate::Workspace;
 646
 647    use crate::WorkspaceSettings;
 648
 649    use super::{HANDLE_HITBOX_SIZE, HORIZONTAL_MIN_SIZE, VERTICAL_MIN_SIZE};
 650
 651    const DIVIDER_SIZE: f32 = 1.0;
 652
 653    pub(super) fn pane_axis(
 654        axis: Axis,
 655        basis: usize,
 656        flexes: Arc<Mutex<Vec<f32>>>,
 657        bounding_boxes: Arc<Mutex<Vec<Option<Bounds<Pixels>>>>>,
 658        workspace: WeakView<Workspace>,
 659    ) -> PaneAxisElement {
 660        PaneAxisElement {
 661            axis,
 662            basis,
 663            flexes,
 664            bounding_boxes,
 665            children: SmallVec::new(),
 666            active_pane_ix: None,
 667            workspace,
 668        }
 669    }
 670
 671    pub struct PaneAxisElement {
 672        axis: Axis,
 673        basis: usize,
 674        flexes: Arc<Mutex<Vec<f32>>>,
 675        bounding_boxes: Arc<Mutex<Vec<Option<Bounds<Pixels>>>>>,
 676        children: SmallVec<[AnyElement; 2]>,
 677        active_pane_ix: Option<usize>,
 678        workspace: WeakView<Workspace>,
 679    }
 680
 681    pub struct PaneAxisLayout {
 682        dragged_handle: Rc<RefCell<Option<usize>>>,
 683        children: Vec<PaneAxisChildLayout>,
 684    }
 685
 686    struct PaneAxisChildLayout {
 687        bounds: Bounds<Pixels>,
 688        element: AnyElement,
 689        handle: Option<PaneAxisHandleLayout>,
 690    }
 691
 692    struct PaneAxisHandleLayout {
 693        hitbox: Hitbox,
 694        divider_bounds: Bounds<Pixels>,
 695    }
 696
 697    impl PaneAxisElement {
 698        pub fn with_active_pane(mut self, active_pane_ix: Option<usize>) -> Self {
 699            self.active_pane_ix = active_pane_ix;
 700            self
 701        }
 702
 703        #[allow(clippy::too_many_arguments)]
 704        fn compute_resize(
 705            flexes: &Arc<Mutex<Vec<f32>>>,
 706            e: &MouseMoveEvent,
 707            ix: usize,
 708            axis: Axis,
 709            child_start: Point<Pixels>,
 710            container_size: Size<Pixels>,
 711            workspace: WeakView<Workspace>,
 712            cx: &mut WindowContext,
 713        ) {
 714            let min_size = match axis {
 715                Axis::Horizontal => px(HORIZONTAL_MIN_SIZE),
 716                Axis::Vertical => px(VERTICAL_MIN_SIZE),
 717            };
 718            let mut flexes = flexes.lock();
 719            debug_assert!(flex_values_in_bounds(flexes.as_slice()));
 720
 721            let size = move |ix, flexes: &[f32]| {
 722                container_size.along(axis) * (flexes[ix] / flexes.len() as f32)
 723            };
 724
 725            // Don't allow resizing to less than the minimum size, if elements are already too small
 726            if min_size - px(1.) > size(ix, flexes.as_slice()) {
 727                return;
 728            }
 729
 730            let mut proposed_current_pixel_change =
 731                (e.position - child_start).along(axis) - size(ix, flexes.as_slice());
 732
 733            let flex_changes = |pixel_dx, target_ix, next: isize, flexes: &[f32]| {
 734                let flex_change = pixel_dx / container_size.along(axis);
 735                let current_target_flex = flexes[target_ix] + flex_change;
 736                let next_target_flex = flexes[(target_ix as isize + next) as usize] - flex_change;
 737                (current_target_flex, next_target_flex)
 738            };
 739
 740            let mut successors = iter::from_fn({
 741                let forward = proposed_current_pixel_change > px(0.);
 742                let mut ix_offset = 0;
 743                let len = flexes.len();
 744                move || {
 745                    let result = if forward {
 746                        (ix + 1 + ix_offset < len).then(|| ix + ix_offset)
 747                    } else {
 748                        (ix as isize - ix_offset as isize >= 0).then(|| ix - ix_offset)
 749                    };
 750
 751                    ix_offset += 1;
 752
 753                    result
 754                }
 755            });
 756
 757            while proposed_current_pixel_change.abs() > px(0.) {
 758                let Some(current_ix) = successors.next() else {
 759                    break;
 760                };
 761
 762                let next_target_size = Pixels::max(
 763                    size(current_ix + 1, flexes.as_slice()) - proposed_current_pixel_change,
 764                    min_size,
 765                );
 766
 767                let current_target_size = Pixels::max(
 768                    size(current_ix, flexes.as_slice()) + size(current_ix + 1, flexes.as_slice())
 769                        - next_target_size,
 770                    min_size,
 771                );
 772
 773                let current_pixel_change =
 774                    current_target_size - size(current_ix, flexes.as_slice());
 775
 776                let (current_target_flex, next_target_flex) =
 777                    flex_changes(current_pixel_change, current_ix, 1, flexes.as_slice());
 778
 779                flexes[current_ix] = current_target_flex;
 780                flexes[current_ix + 1] = next_target_flex;
 781
 782                proposed_current_pixel_change -= current_pixel_change;
 783            }
 784
 785            workspace
 786                .update(cx, |this, cx| this.serialize_workspace(cx))
 787                .log_err();
 788            cx.stop_propagation();
 789            cx.refresh();
 790        }
 791
 792        #[allow(clippy::too_many_arguments)]
 793        fn layout_handle(
 794            axis: Axis,
 795            pane_bounds: Bounds<Pixels>,
 796            cx: &mut WindowContext,
 797        ) -> PaneAxisHandleLayout {
 798            let handle_bounds = Bounds {
 799                origin: pane_bounds.origin.apply_along(axis, |origin| {
 800                    origin + pane_bounds.size.along(axis) - px(HANDLE_HITBOX_SIZE / 2.)
 801                }),
 802                size: pane_bounds
 803                    .size
 804                    .apply_along(axis, |_| px(HANDLE_HITBOX_SIZE)),
 805            };
 806            let divider_bounds = Bounds {
 807                origin: pane_bounds
 808                    .origin
 809                    .apply_along(axis, |origin| origin + pane_bounds.size.along(axis)),
 810                size: pane_bounds.size.apply_along(axis, |_| px(DIVIDER_SIZE)),
 811            };
 812
 813            PaneAxisHandleLayout {
 814                hitbox: cx.insert_hitbox(handle_bounds, true),
 815                divider_bounds,
 816            }
 817        }
 818    }
 819
 820    impl IntoElement for PaneAxisElement {
 821        type Element = Self;
 822
 823        fn into_element(self) -> Self::Element {
 824            self
 825        }
 826    }
 827
 828    impl Element for PaneAxisElement {
 829        type RequestLayoutState = ();
 830        type PrepaintState = PaneAxisLayout;
 831
 832        fn id(&self) -> Option<ElementId> {
 833            Some(self.basis.into())
 834        }
 835
 836        fn request_layout(
 837            &mut self,
 838            _global_id: Option<&GlobalElementId>,
 839            cx: &mut ui::prelude::WindowContext,
 840        ) -> (gpui::LayoutId, Self::RequestLayoutState) {
 841            let style = Style {
 842                flex_grow: 1.,
 843                flex_shrink: 1.,
 844                flex_basis: relative(0.).into(),
 845                size: size(relative(1.).into(), relative(1.).into()),
 846                ..Style::default()
 847            };
 848            (cx.request_layout(style, None), ())
 849        }
 850
 851        fn prepaint(
 852            &mut self,
 853            global_id: Option<&GlobalElementId>,
 854            bounds: Bounds<Pixels>,
 855            _state: &mut Self::RequestLayoutState,
 856            cx: &mut WindowContext,
 857        ) -> PaneAxisLayout {
 858            let dragged_handle = cx.with_element_state::<Rc<RefCell<Option<usize>>>, _>(
 859                global_id.unwrap(),
 860                |state, _cx| {
 861                    let state = state.unwrap_or_else(|| Rc::new(RefCell::new(None)));
 862                    (state.clone(), state)
 863                },
 864            );
 865            let flexes = self.flexes.lock().clone();
 866            let len = self.children.len();
 867            debug_assert!(flexes.len() == len);
 868            debug_assert!(flex_values_in_bounds(flexes.as_slice()));
 869
 870            let active_pane_magnification = WorkspaceSettings::get(None, cx)
 871                .active_pane_modifiers
 872                .magnification
 873                .and_then(|val| if val == 1.0 { None } else { Some(val) });
 874
 875            let total_flex = if let Some(flex) = active_pane_magnification {
 876                self.children.len() as f32 - 1. + flex
 877            } else {
 878                len as f32
 879            };
 880
 881            let mut origin = bounds.origin;
 882            let space_per_flex = bounds.size.along(self.axis) / total_flex;
 883
 884            let mut bounding_boxes = self.bounding_boxes.lock();
 885            bounding_boxes.clear();
 886
 887            let mut layout = PaneAxisLayout {
 888                dragged_handle: dragged_handle.clone(),
 889                children: Vec::new(),
 890            };
 891            for (ix, mut child) in mem::take(&mut self.children).into_iter().enumerate() {
 892                let child_flex = active_pane_magnification
 893                    .map(|magnification| {
 894                        if self.active_pane_ix == Some(ix) {
 895                            magnification
 896                        } else {
 897                            1.
 898                        }
 899                    })
 900                    .unwrap_or_else(|| flexes[ix]);
 901
 902                let child_size = bounds
 903                    .size
 904                    .apply_along(self.axis, |_| space_per_flex * child_flex)
 905                    .map(|d| d.round());
 906
 907                let child_bounds = Bounds {
 908                    origin,
 909                    size: child_size,
 910                };
 911
 912                bounding_boxes.push(Some(child_bounds));
 913                child.layout_as_root(child_size.into(), cx);
 914                child.prepaint_at(origin, cx);
 915
 916                origin = origin.apply_along(self.axis, |val| val + child_size.along(self.axis));
 917                layout.children.push(PaneAxisChildLayout {
 918                    bounds: child_bounds,
 919                    element: child,
 920                    handle: None,
 921                })
 922            }
 923
 924            for (ix, child_layout) in layout.children.iter_mut().enumerate() {
 925                if active_pane_magnification.is_none() && ix < len - 1 {
 926                    child_layout.handle =
 927                        Some(Self::layout_handle(self.axis, child_layout.bounds, cx));
 928                }
 929            }
 930
 931            layout
 932        }
 933
 934        fn paint(
 935            &mut self,
 936            _id: Option<&GlobalElementId>,
 937            bounds: gpui::Bounds<ui::prelude::Pixels>,
 938            _: &mut Self::RequestLayoutState,
 939            layout: &mut Self::PrepaintState,
 940            cx: &mut ui::prelude::WindowContext,
 941        ) {
 942            for child in &mut layout.children {
 943                child.element.paint(cx);
 944            }
 945
 946            let overlay_opacity = WorkspaceSettings::get(None, cx)
 947                .active_pane_modifiers
 948                .inactive_opacity
 949                .map(|val| val.clamp(0.0, 1.0))
 950                .and_then(|val| (val <= 1.).then_some(val));
 951
 952            let mut overlay_background = cx.theme().colors().editor_background;
 953            if let Some(opacity) = overlay_opacity {
 954                overlay_background.fade_out(opacity);
 955            }
 956
 957            let overlay_border = WorkspaceSettings::get(None, cx)
 958                .active_pane_modifiers
 959                .border_size
 960                .and_then(|val| (val >= 0.).then_some(val));
 961
 962            for (ix, child) in &mut layout.children.iter_mut().enumerate() {
 963                if overlay_opacity.is_some() || overlay_border.is_some() {
 964                    // the overlay has to be painted in origin+1px with size width-1px
 965                    // in order to accommodate the divider between panels
 966                    let overlay_bounds = Bounds {
 967                        origin: child
 968                            .bounds
 969                            .origin
 970                            .apply_along(Axis::Horizontal, |val| val + Pixels(1.)),
 971                        size: child
 972                            .bounds
 973                            .size
 974                            .apply_along(Axis::Horizontal, |val| val - Pixels(1.)),
 975                    };
 976
 977                    if overlay_opacity.is_some() && self.active_pane_ix != Some(ix) {
 978                        cx.paint_quad(gpui::fill(overlay_bounds, overlay_background));
 979                    }
 980
 981                    if let Some(border) = overlay_border {
 982                        if self.active_pane_ix == Some(ix) {
 983                            cx.paint_quad(gpui::quad(
 984                                overlay_bounds,
 985                                0.,
 986                                gpui::transparent_black(),
 987                                border,
 988                                cx.theme().colors().border_selected,
 989                            ));
 990                        }
 991                    }
 992                }
 993
 994                if let Some(handle) = child.handle.as_mut() {
 995                    let cursor_style = match self.axis {
 996                        Axis::Vertical => CursorStyle::ResizeRow,
 997                        Axis::Horizontal => CursorStyle::ResizeColumn,
 998                    };
 999                    cx.set_cursor_style(cursor_style, &handle.hitbox);
1000                    cx.paint_quad(gpui::fill(
1001                        handle.divider_bounds,
1002                        cx.theme().colors().pane_group_border,
1003                    ));
1004
1005                    cx.on_mouse_event({
1006                        let dragged_handle = layout.dragged_handle.clone();
1007                        let flexes = self.flexes.clone();
1008                        let workspace = self.workspace.clone();
1009                        let handle_hitbox = handle.hitbox.clone();
1010                        move |e: &MouseDownEvent, phase, cx| {
1011                            if phase.bubble() && handle_hitbox.is_hovered(cx) {
1012                                dragged_handle.replace(Some(ix));
1013                                if e.click_count >= 2 {
1014                                    let mut borrow = flexes.lock();
1015                                    *borrow = vec![1.; borrow.len()];
1016                                    workspace
1017                                        .update(cx, |this, cx| this.serialize_workspace(cx))
1018                                        .log_err();
1019
1020                                    cx.refresh();
1021                                }
1022                                cx.stop_propagation();
1023                            }
1024                        }
1025                    });
1026                    cx.on_mouse_event({
1027                        let workspace = self.workspace.clone();
1028                        let dragged_handle = layout.dragged_handle.clone();
1029                        let flexes = self.flexes.clone();
1030                        let child_bounds = child.bounds;
1031                        let axis = self.axis;
1032                        move |e: &MouseMoveEvent, phase, cx| {
1033                            let dragged_handle = dragged_handle.borrow();
1034                            if phase.bubble() && *dragged_handle == Some(ix) {
1035                                Self::compute_resize(
1036                                    &flexes,
1037                                    e,
1038                                    ix,
1039                                    axis,
1040                                    child_bounds.origin,
1041                                    bounds.size,
1042                                    workspace.clone(),
1043                                    cx,
1044                                )
1045                            }
1046                        }
1047                    });
1048                }
1049            }
1050
1051            cx.on_mouse_event({
1052                let dragged_handle = layout.dragged_handle.clone();
1053                move |_: &MouseUpEvent, phase, _cx| {
1054                    if phase.bubble() {
1055                        dragged_handle.replace(None);
1056                    }
1057                }
1058            });
1059        }
1060    }
1061
1062    impl ParentElement for PaneAxisElement {
1063        fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
1064            self.children.extend(elements)
1065        }
1066    }
1067
1068    fn flex_values_in_bounds(flexes: &[f32]) -> bool {
1069        (flexes.iter().copied().sum::<f32>() - flexes.len() as f32).abs() < 0.001
1070    }
1071}