pane_group.rs

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