pane_group.rs

   1use std::{cell::RefCell, rc::Rc, sync::Arc};
   2
   3use crate::{
   4    pane_group::element::PaneAxisElement, AppState, FollowerStatesByLeader, Pane, Workspace,
   5};
   6use anyhow::{anyhow, Result};
   7use call::{ActiveCall, ParticipantLocation};
   8use gpui::{
   9    elements::*,
  10    geometry::{rect::RectF, vector::Vector2F},
  11    platform::{CursorStyle, MouseButton},
  12    AnyViewHandle, Axis, ModelHandle, ViewContext, ViewHandle,
  13};
  14use project::Project;
  15use serde::Deserialize;
  16use theme::Theme;
  17
  18const HANDLE_HITBOX_SIZE: f32 = 4.0;
  19const HORIZONTAL_MIN_SIZE: f32 = 80.;
  20const VERTICAL_MIN_SIZE: f32 = 100.;
  21
  22#[derive(Clone, Debug, PartialEq)]
  23pub struct PaneGroup {
  24    pub(crate) root: Member,
  25}
  26
  27impl PaneGroup {
  28    pub(crate) fn with_root(root: Member) -> Self {
  29        Self { root }
  30    }
  31
  32    pub fn new(pane: ViewHandle<Pane>) -> Self {
  33        Self {
  34            root: Member::Pane(pane),
  35        }
  36    }
  37
  38    pub fn split(
  39        &mut self,
  40        old_pane: &ViewHandle<Pane>,
  41        new_pane: &ViewHandle<Pane>,
  42        direction: SplitDirection,
  43    ) -> Result<()> {
  44        match &mut self.root {
  45            Member::Pane(pane) => {
  46                if pane == old_pane {
  47                    self.root = Member::new_axis(old_pane.clone(), new_pane.clone(), direction);
  48                    Ok(())
  49                } else {
  50                    Err(anyhow!("Pane not found"))
  51                }
  52            }
  53            Member::Axis(axis) => axis.split(old_pane, new_pane, direction),
  54        }
  55    }
  56
  57    pub fn bounding_box_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<RectF> {
  58        match &self.root {
  59            Member::Pane(_) => None,
  60            Member::Axis(axis) => axis.bounding_box_for_pane(pane),
  61        }
  62    }
  63
  64    pub fn pane_at_pixel_position(&self, coordinate: Vector2F) -> Option<&ViewHandle<Pane>> {
  65        match &self.root {
  66            Member::Pane(pane) => Some(pane),
  67            Member::Axis(axis) => axis.pane_at_pixel_position(coordinate),
  68        }
  69    }
  70
  71    /// Returns:
  72    /// - Ok(true) if it found and removed a pane
  73    /// - Ok(false) if it found but did not remove the pane
  74    /// - Err(_) if it did not find the pane
  75    pub fn remove(&mut self, pane: &ViewHandle<Pane>) -> Result<bool> {
  76        match &mut self.root {
  77            Member::Pane(_) => Ok(false),
  78            Member::Axis(axis) => {
  79                if let Some(last_pane) = axis.remove(pane)? {
  80                    self.root = last_pane;
  81                }
  82                Ok(true)
  83            }
  84        }
  85    }
  86
  87    pub fn swap(&mut self, from: &ViewHandle<Pane>, to: &ViewHandle<Pane>) {
  88        match &mut self.root {
  89            Member::Pane(_) => {}
  90            Member::Axis(axis) => axis.swap(from, to),
  91        };
  92    }
  93
  94    pub(crate) fn render(
  95        &self,
  96        project: &ModelHandle<Project>,
  97        theme: &Theme,
  98        follower_states: &FollowerStatesByLeader,
  99        active_call: Option<&ModelHandle<ActiveCall>>,
 100        active_pane: &ViewHandle<Pane>,
 101        zoomed: Option<&AnyViewHandle>,
 102        app_state: &Arc<AppState>,
 103        cx: &mut ViewContext<Workspace>,
 104    ) -> AnyElement<Workspace> {
 105        self.root.render(
 106            project,
 107            0,
 108            theme,
 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<&ViewHandle<Pane>> {
 119        let mut panes = Vec::new();
 120        self.root.collect_panes(&mut panes);
 121        panes
 122    }
 123}
 124
 125#[derive(Clone, Debug, PartialEq)]
 126pub(crate) enum Member {
 127    Axis(PaneAxis),
 128    Pane(ViewHandle<Pane>),
 129}
 130
 131impl Member {
 132    fn new_axis(
 133        old_pane: ViewHandle<Pane>,
 134        new_pane: ViewHandle<Pane>,
 135        direction: SplitDirection,
 136    ) -> 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: &ViewHandle<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    pub fn render(
 161        &self,
 162        project: &ModelHandle<Project>,
 163        basis: usize,
 164        theme: &Theme,
 165        follower_states: &FollowerStatesByLeader,
 166        active_call: Option<&ModelHandle<ActiveCall>>,
 167        active_pane: &ViewHandle<Pane>,
 168        zoomed: Option<&AnyViewHandle>,
 169        app_state: &Arc<AppState>,
 170        cx: &mut ViewContext<Workspace>,
 171    ) -> AnyElement<Workspace> {
 172        enum FollowIntoExternalProject {}
 173
 174        match self {
 175            Member::Pane(pane) => {
 176                let pane_element = if Some(&**pane) == zoomed {
 177                    Empty::new().into_any()
 178                } else {
 179                    ChildView::new(pane, cx).into_any()
 180                };
 181
 182                let leader = follower_states
 183                    .iter()
 184                    .find_map(|(leader_id, follower_states)| {
 185                        if follower_states.contains_key(pane) {
 186                            Some(leader_id)
 187                        } else {
 188                            None
 189                        }
 190                    })
 191                    .and_then(|leader_id| {
 192                        let room = active_call?.read(cx).room()?.read(cx);
 193                        room.remote_participant_for_peer_id(*leader_id)
 194                    });
 195
 196                let mut leader_border = Border::default();
 197                let mut leader_status_box = None;
 198                if let Some(leader) = &leader {
 199                    let leader_color = theme
 200                        .editor
 201                        .selection_style_for_room_participant(leader.participant_index.0)
 202                        .cursor;
 203                    leader_border = Border::all(theme.workspace.leader_border_width, leader_color);
 204                    leader_border
 205                        .color
 206                        .fade_out(1. - theme.workspace.leader_border_opacity);
 207                    leader_border.overlay = true;
 208
 209                    leader_status_box = match leader.location {
 210                        ParticipantLocation::SharedProject {
 211                            project_id: leader_project_id,
 212                        } => {
 213                            if Some(leader_project_id) == project.read(cx).remote_id() {
 214                                None
 215                            } else {
 216                                let leader_user = leader.user.clone();
 217                                let leader_user_id = leader.user.id;
 218                                Some(
 219                                    MouseEventHandler::new::<FollowIntoExternalProject, _>(
 220                                        pane.id(),
 221                                        cx,
 222                                        |_, _| {
 223                                            Label::new(
 224                                                format!(
 225                                                    "Follow {} to their active project",
 226                                                    leader_user.github_login,
 227                                                ),
 228                                                theme
 229                                                    .workspace
 230                                                    .external_location_message
 231                                                    .text
 232                                                    .clone(),
 233                                            )
 234                                            .contained()
 235                                            .with_style(
 236                                                theme.workspace.external_location_message.container,
 237                                            )
 238                                        },
 239                                    )
 240                                    .with_cursor_style(CursorStyle::PointingHand)
 241                                    .on_click(MouseButton::Left, move |_, this, cx| {
 242                                        crate::join_remote_project(
 243                                            leader_project_id,
 244                                            leader_user_id,
 245                                            this.app_state().clone(),
 246                                            cx,
 247                                        )
 248                                        .detach_and_log_err(cx);
 249                                    })
 250                                    .aligned()
 251                                    .bottom()
 252                                    .right()
 253                                    .into_any(),
 254                                )
 255                            }
 256                        }
 257                        ParticipantLocation::UnsharedProject => Some(
 258                            Label::new(
 259                                format!(
 260                                    "{} is viewing an unshared Zed project",
 261                                    leader.user.github_login
 262                                ),
 263                                theme.workspace.external_location_message.text.clone(),
 264                            )
 265                            .contained()
 266                            .with_style(theme.workspace.external_location_message.container)
 267                            .aligned()
 268                            .bottom()
 269                            .right()
 270                            .into_any(),
 271                        ),
 272                        ParticipantLocation::External => Some(
 273                            Label::new(
 274                                format!(
 275                                    "{} is viewing a window outside of Zed",
 276                                    leader.user.github_login
 277                                ),
 278                                theme.workspace.external_location_message.text.clone(),
 279                            )
 280                            .contained()
 281                            .with_style(theme.workspace.external_location_message.container)
 282                            .aligned()
 283                            .bottom()
 284                            .right()
 285                            .into_any(),
 286                        ),
 287                    };
 288                }
 289
 290                Stack::new()
 291                    .with_child(pane_element.contained().with_border(leader_border))
 292                    .with_children(leader_status_box)
 293                    .into_any()
 294            }
 295            Member::Axis(axis) => axis.render(
 296                project,
 297                basis + 1,
 298                theme,
 299                follower_states,
 300                active_call,
 301                active_pane,
 302                zoomed,
 303                app_state,
 304                cx,
 305            ),
 306        }
 307    }
 308
 309    fn collect_panes<'a>(&'a self, panes: &mut Vec<&'a ViewHandle<Pane>>) {
 310        match self {
 311            Member::Axis(axis) => {
 312                for member in &axis.members {
 313                    member.collect_panes(panes);
 314                }
 315            }
 316            Member::Pane(pane) => panes.push(pane),
 317        }
 318    }
 319}
 320
 321#[derive(Clone, Debug, PartialEq)]
 322pub(crate) struct PaneAxis {
 323    pub axis: Axis,
 324    pub members: Vec<Member>,
 325    pub flexes: Rc<RefCell<Vec<f32>>>,
 326    pub bounding_boxes: Rc<RefCell<Vec<Option<RectF>>>>,
 327}
 328
 329impl PaneAxis {
 330    pub fn new(axis: Axis, members: Vec<Member>) -> Self {
 331        let flexes = Rc::new(RefCell::new(vec![1.; members.len()]));
 332        let bounding_boxes = Rc::new(RefCell::new(vec![None; members.len()]));
 333        Self {
 334            axis,
 335            members,
 336            flexes,
 337            bounding_boxes,
 338        }
 339    }
 340
 341    pub fn load(axis: Axis, members: Vec<Member>, flexes: Option<Vec<f32>>) -> Self {
 342        let flexes = flexes.unwrap_or_else(|| vec![1.; members.len()]);
 343        debug_assert!(members.len() == flexes.len());
 344
 345        let flexes = Rc::new(RefCell::new(flexes));
 346        let bounding_boxes = Rc::new(RefCell::new(vec![None; members.len()]));
 347        Self {
 348            axis,
 349            members,
 350            flexes,
 351            bounding_boxes,
 352        }
 353    }
 354
 355    fn split(
 356        &mut self,
 357        old_pane: &ViewHandle<Pane>,
 358        new_pane: &ViewHandle<Pane>,
 359        direction: SplitDirection,
 360    ) -> Result<()> {
 361        for (mut idx, member) in self.members.iter_mut().enumerate() {
 362            match member {
 363                Member::Axis(axis) => {
 364                    if axis.split(old_pane, new_pane, direction).is_ok() {
 365                        return Ok(());
 366                    }
 367                }
 368                Member::Pane(pane) => {
 369                    if pane == old_pane {
 370                        if direction.axis() == self.axis {
 371                            if direction.increasing() {
 372                                idx += 1;
 373                            }
 374
 375                            self.members.insert(idx, Member::Pane(new_pane.clone()));
 376                            *self.flexes.borrow_mut() = vec![1.; self.members.len()];
 377                        } else {
 378                            *member =
 379                                Member::new_axis(old_pane.clone(), new_pane.clone(), direction);
 380                        }
 381                        return Ok(());
 382                    }
 383                }
 384            }
 385        }
 386        Err(anyhow!("Pane not found"))
 387    }
 388
 389    fn remove(&mut self, pane_to_remove: &ViewHandle<Pane>) -> Result<Option<Member>> {
 390        let mut found_pane = false;
 391        let mut remove_member = None;
 392        for (idx, member) in self.members.iter_mut().enumerate() {
 393            match member {
 394                Member::Axis(axis) => {
 395                    if let Ok(last_pane) = axis.remove(pane_to_remove) {
 396                        if let Some(last_pane) = last_pane {
 397                            *member = last_pane;
 398                        }
 399                        found_pane = true;
 400                        break;
 401                    }
 402                }
 403                Member::Pane(pane) => {
 404                    if pane == pane_to_remove {
 405                        found_pane = true;
 406                        remove_member = Some(idx);
 407                        break;
 408                    }
 409                }
 410            }
 411        }
 412
 413        if found_pane {
 414            if let Some(idx) = remove_member {
 415                self.members.remove(idx);
 416                *self.flexes.borrow_mut() = vec![1.; self.members.len()];
 417            }
 418
 419            if self.members.len() == 1 {
 420                let result = self.members.pop();
 421                *self.flexes.borrow_mut() = vec![1.; self.members.len()];
 422                Ok(result)
 423            } else {
 424                Ok(None)
 425            }
 426        } else {
 427            Err(anyhow!("Pane not found"))
 428        }
 429    }
 430
 431    fn swap(&mut self, from: &ViewHandle<Pane>, to: &ViewHandle<Pane>) {
 432        for member in self.members.iter_mut() {
 433            match member {
 434                Member::Axis(axis) => axis.swap(from, to),
 435                Member::Pane(pane) => {
 436                    if pane == from {
 437                        *member = Member::Pane(to.clone());
 438                    } else if pane == to {
 439                        *member = Member::Pane(from.clone())
 440                    }
 441                }
 442            }
 443        }
 444    }
 445
 446    fn bounding_box_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<RectF> {
 447        debug_assert!(self.members.len() == self.bounding_boxes.borrow().len());
 448
 449        for (idx, member) in self.members.iter().enumerate() {
 450            match member {
 451                Member::Pane(found) => {
 452                    if pane == found {
 453                        return self.bounding_boxes.borrow()[idx];
 454                    }
 455                }
 456                Member::Axis(axis) => {
 457                    if let Some(rect) = axis.bounding_box_for_pane(pane) {
 458                        return Some(rect);
 459                    }
 460                }
 461            }
 462        }
 463        None
 464    }
 465
 466    fn pane_at_pixel_position(&self, coordinate: Vector2F) -> Option<&ViewHandle<Pane>> {
 467        debug_assert!(self.members.len() == self.bounding_boxes.borrow().len());
 468
 469        let bounding_boxes = self.bounding_boxes.borrow();
 470
 471        for (idx, member) in self.members.iter().enumerate() {
 472            if let Some(coordinates) = bounding_boxes[idx] {
 473                if coordinates.contains_point(coordinate) {
 474                    return match member {
 475                        Member::Pane(found) => Some(found),
 476                        Member::Axis(axis) => axis.pane_at_pixel_position(coordinate),
 477                    };
 478                }
 479            }
 480        }
 481        None
 482    }
 483
 484    fn render(
 485        &self,
 486        project: &ModelHandle<Project>,
 487        basis: usize,
 488        theme: &Theme,
 489        follower_state: &FollowerStatesByLeader,
 490        active_call: Option<&ModelHandle<ActiveCall>>,
 491        active_pane: &ViewHandle<Pane>,
 492        zoomed: Option<&AnyViewHandle>,
 493        app_state: &Arc<AppState>,
 494        cx: &mut ViewContext<Workspace>,
 495    ) -> AnyElement<Workspace> {
 496        debug_assert!(self.members.len() == self.flexes.borrow().len());
 497
 498        let mut pane_axis = PaneAxisElement::new(
 499            self.axis,
 500            basis,
 501            self.flexes.clone(),
 502            self.bounding_boxes.clone(),
 503        );
 504        let mut active_pane_ix = None;
 505
 506        let mut members = self.members.iter().enumerate().peekable();
 507        while let Some((ix, member)) = members.next() {
 508            let last = members.peek().is_none();
 509
 510            if member.contains(active_pane) {
 511                active_pane_ix = Some(ix);
 512            }
 513
 514            let mut member = member.render(
 515                project,
 516                (basis + ix) * 10,
 517                theme,
 518                follower_state,
 519                active_call,
 520                active_pane,
 521                zoomed,
 522                app_state,
 523                cx,
 524            );
 525
 526            if !last {
 527                let mut border = theme.workspace.pane_divider;
 528                border.left = false;
 529                border.right = false;
 530                border.top = false;
 531                border.bottom = false;
 532
 533                match self.axis {
 534                    Axis::Vertical => border.bottom = true,
 535                    Axis::Horizontal => border.right = true,
 536                }
 537
 538                member = member.contained().with_border(border).into_any();
 539            }
 540
 541            pane_axis = pane_axis.with_child(member.into_any());
 542        }
 543        pane_axis.set_active_pane(active_pane_ix);
 544        pane_axis.into_any()
 545    }
 546}
 547
 548#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
 549pub enum SplitDirection {
 550    Up,
 551    Down,
 552    Left,
 553    Right,
 554}
 555
 556impl SplitDirection {
 557    pub fn all() -> [Self; 4] {
 558        [Self::Up, Self::Down, Self::Left, Self::Right]
 559    }
 560
 561    pub fn edge(&self, rect: RectF) -> f32 {
 562        match self {
 563            Self::Up => rect.min_y(),
 564            Self::Down => rect.max_y(),
 565            Self::Left => rect.min_x(),
 566            Self::Right => rect.max_x(),
 567        }
 568    }
 569
 570    // Returns a new rectangle which shares an edge in SplitDirection and has `size` along SplitDirection
 571    pub fn along_edge(&self, rect: RectF, size: f32) -> RectF {
 572        match self {
 573            Self::Up => RectF::new(rect.origin(), Vector2F::new(rect.width(), size)),
 574            Self::Down => RectF::new(
 575                rect.lower_left() - Vector2F::new(0., size),
 576                Vector2F::new(rect.width(), size),
 577            ),
 578            Self::Left => RectF::new(rect.origin(), Vector2F::new(size, rect.height())),
 579            Self::Right => RectF::new(
 580                rect.upper_right() - Vector2F::new(size, 0.),
 581                Vector2F::new(size, rect.height()),
 582            ),
 583        }
 584    }
 585
 586    pub fn axis(&self) -> Axis {
 587        match self {
 588            Self::Up | Self::Down => Axis::Vertical,
 589            Self::Left | Self::Right => Axis::Horizontal,
 590        }
 591    }
 592
 593    pub fn increasing(&self) -> bool {
 594        match self {
 595            Self::Left | Self::Up => false,
 596            Self::Down | Self::Right => true,
 597        }
 598    }
 599}
 600
 601mod element {
 602    use std::{cell::RefCell, iter::from_fn, ops::Range, rc::Rc};
 603
 604    use gpui::{
 605        geometry::{
 606            rect::RectF,
 607            vector::{vec2f, Vector2F},
 608        },
 609        json::{self, ToJson},
 610        platform::{CursorStyle, MouseButton},
 611        scene::MouseDrag,
 612        AnyElement, Axis, CursorRegion, Element, EventContext, MouseRegion, RectFExt,
 613        SizeConstraint, Vector2FExt, ViewContext,
 614    };
 615
 616    use crate::{
 617        pane_group::{HANDLE_HITBOX_SIZE, HORIZONTAL_MIN_SIZE, VERTICAL_MIN_SIZE},
 618        Workspace, WorkspaceSettings,
 619    };
 620
 621    pub struct PaneAxisElement {
 622        axis: Axis,
 623        basis: usize,
 624        active_pane_ix: Option<usize>,
 625        flexes: Rc<RefCell<Vec<f32>>>,
 626        children: Vec<AnyElement<Workspace>>,
 627        bounding_boxes: Rc<RefCell<Vec<Option<RectF>>>>,
 628    }
 629
 630    impl PaneAxisElement {
 631        pub fn new(
 632            axis: Axis,
 633            basis: usize,
 634            flexes: Rc<RefCell<Vec<f32>>>,
 635            bounding_boxes: Rc<RefCell<Vec<Option<RectF>>>>,
 636        ) -> Self {
 637            Self {
 638                axis,
 639                basis,
 640                flexes,
 641                bounding_boxes,
 642                active_pane_ix: None,
 643                children: Default::default(),
 644            }
 645        }
 646
 647        pub fn set_active_pane(&mut self, active_pane_ix: Option<usize>) {
 648            self.active_pane_ix = active_pane_ix;
 649        }
 650
 651        fn layout_children(
 652            &mut self,
 653            active_pane_magnification: f32,
 654            constraint: SizeConstraint,
 655            remaining_space: &mut f32,
 656            remaining_flex: &mut f32,
 657            cross_axis_max: &mut f32,
 658            view: &mut Workspace,
 659            cx: &mut ViewContext<Workspace>,
 660        ) {
 661            let flexes = self.flexes.borrow();
 662            let cross_axis = self.axis.invert();
 663            for (ix, child) in self.children.iter_mut().enumerate() {
 664                let flex = if active_pane_magnification != 1. {
 665                    if let Some(active_pane_ix) = self.active_pane_ix {
 666                        if ix == active_pane_ix {
 667                            active_pane_magnification
 668                        } else {
 669                            1.
 670                        }
 671                    } else {
 672                        1.
 673                    }
 674                } else {
 675                    flexes[ix]
 676                };
 677
 678                let child_size = if *remaining_flex == 0.0 {
 679                    *remaining_space
 680                } else {
 681                    let space_per_flex = *remaining_space / *remaining_flex;
 682                    space_per_flex * flex
 683                };
 684
 685                let child_constraint = match self.axis {
 686                    Axis::Horizontal => SizeConstraint::new(
 687                        vec2f(child_size, constraint.min.y()),
 688                        vec2f(child_size, constraint.max.y()),
 689                    ),
 690                    Axis::Vertical => SizeConstraint::new(
 691                        vec2f(constraint.min.x(), child_size),
 692                        vec2f(constraint.max.x(), child_size),
 693                    ),
 694                };
 695                let child_size = child.layout(child_constraint, view, cx);
 696                *remaining_space -= child_size.along(self.axis);
 697                *remaining_flex -= flex;
 698                *cross_axis_max = cross_axis_max.max(child_size.along(cross_axis));
 699            }
 700        }
 701
 702        fn handle_resize(
 703            flexes: Rc<RefCell<Vec<f32>>>,
 704            axis: Axis,
 705            preceding_ix: usize,
 706            child_start: Vector2F,
 707            drag_bounds: RectF,
 708        ) -> impl Fn(MouseDrag, &mut Workspace, &mut EventContext<Workspace>) {
 709            let size = move |ix, flexes: &[f32]| {
 710                drag_bounds.length_along(axis) * (flexes[ix] / flexes.len() as f32)
 711            };
 712
 713            move |drag, workspace: &mut Workspace, cx| {
 714                if drag.end {
 715                    // TODO: Clear cascading resize state
 716                    return;
 717                }
 718                let min_size = match axis {
 719                    Axis::Horizontal => HORIZONTAL_MIN_SIZE,
 720                    Axis::Vertical => VERTICAL_MIN_SIZE,
 721                };
 722                let mut flexes = flexes.borrow_mut();
 723
 724                // Don't allow resizing to less than the minimum size, if elements are already too small
 725                if min_size - 1. > size(preceding_ix, flexes.as_slice()) {
 726                    return;
 727                }
 728
 729                let mut proposed_current_pixel_change = (drag.position - child_start).along(axis)
 730                    - size(preceding_ix, flexes.as_slice());
 731
 732                let flex_changes = |pixel_dx, target_ix, next: isize, flexes: &[f32]| {
 733                    let flex_change = pixel_dx / drag_bounds.length_along(axis);
 734                    let current_target_flex = flexes[target_ix] + flex_change;
 735                    let next_target_flex =
 736                        flexes[(target_ix as isize + next) as usize] - flex_change;
 737                    (current_target_flex, next_target_flex)
 738                };
 739
 740                let mut successors = from_fn({
 741                    let forward = proposed_current_pixel_change > 0.;
 742                    let mut ix_offset = 0;
 743                    let len = flexes.len();
 744                    move || {
 745                        let result = if forward {
 746                            (preceding_ix + 1 + ix_offset < len).then(|| preceding_ix + ix_offset)
 747                        } else {
 748                            (preceding_ix as isize - ix_offset as isize >= 0)
 749                                .then(|| preceding_ix - ix_offset)
 750                        };
 751
 752                        ix_offset += 1;
 753
 754                        result
 755                    }
 756                });
 757
 758                while proposed_current_pixel_change.abs() > 0. {
 759                    let Some(current_ix) = successors.next() else {
 760                        break;
 761                    };
 762
 763                    let next_target_size = f32::max(
 764                        size(current_ix + 1, flexes.as_slice()) - proposed_current_pixel_change,
 765                        min_size,
 766                    );
 767
 768                    let current_target_size = f32::max(
 769                        size(current_ix, flexes.as_slice())
 770                            + size(current_ix + 1, flexes.as_slice())
 771                            - next_target_size,
 772                        min_size,
 773                    );
 774
 775                    let current_pixel_change =
 776                        current_target_size - size(current_ix, flexes.as_slice());
 777
 778                    let (current_target_flex, next_target_flex) =
 779                        flex_changes(current_pixel_change, current_ix, 1, flexes.as_slice());
 780
 781                    flexes[current_ix] = current_target_flex;
 782                    flexes[current_ix + 1] = next_target_flex;
 783
 784                    proposed_current_pixel_change -= current_pixel_change;
 785                }
 786
 787                workspace.schedule_serialize(cx);
 788                cx.notify();
 789            }
 790        }
 791    }
 792
 793    impl Extend<AnyElement<Workspace>> for PaneAxisElement {
 794        fn extend<T: IntoIterator<Item = AnyElement<Workspace>>>(&mut self, children: T) {
 795            self.children.extend(children);
 796        }
 797    }
 798
 799    impl Element<Workspace> for PaneAxisElement {
 800        type LayoutState = f32;
 801        type PaintState = ();
 802
 803        fn layout(
 804            &mut self,
 805            constraint: SizeConstraint,
 806            view: &mut Workspace,
 807            cx: &mut ViewContext<Workspace>,
 808        ) -> (Vector2F, Self::LayoutState) {
 809            debug_assert!(self.children.len() == self.flexes.borrow().len());
 810
 811            let active_pane_magnification =
 812                settings::get::<WorkspaceSettings>(cx).active_pane_magnification;
 813
 814            let mut remaining_flex = 0.;
 815
 816            if active_pane_magnification != 1. {
 817                let active_pane_flex = self
 818                    .active_pane_ix
 819                    .map(|_| active_pane_magnification)
 820                    .unwrap_or(1.);
 821                remaining_flex += self.children.len() as f32 - 1. + active_pane_flex;
 822            } else {
 823                for flex in self.flexes.borrow().iter() {
 824                    remaining_flex += flex;
 825                }
 826            }
 827
 828            let mut cross_axis_max: f32 = 0.0;
 829            let mut remaining_space = constraint.max_along(self.axis);
 830
 831            if remaining_space.is_infinite() {
 832                panic!("flex contains flexible children but has an infinite constraint along the flex axis");
 833            }
 834
 835            self.layout_children(
 836                active_pane_magnification,
 837                constraint,
 838                &mut remaining_space,
 839                &mut remaining_flex,
 840                &mut cross_axis_max,
 841                view,
 842                cx,
 843            );
 844
 845            let mut size = match self.axis {
 846                Axis::Horizontal => vec2f(constraint.max.x() - remaining_space, cross_axis_max),
 847                Axis::Vertical => vec2f(cross_axis_max, constraint.max.y() - remaining_space),
 848            };
 849
 850            if constraint.min.x().is_finite() {
 851                size.set_x(size.x().max(constraint.min.x()));
 852            }
 853            if constraint.min.y().is_finite() {
 854                size.set_y(size.y().max(constraint.min.y()));
 855            }
 856
 857            if size.x() > constraint.max.x() {
 858                size.set_x(constraint.max.x());
 859            }
 860            if size.y() > constraint.max.y() {
 861                size.set_y(constraint.max.y());
 862            }
 863
 864            (size, remaining_space)
 865        }
 866
 867        fn paint(
 868            &mut self,
 869            bounds: RectF,
 870            visible_bounds: RectF,
 871            remaining_space: &mut Self::LayoutState,
 872            view: &mut Workspace,
 873            cx: &mut ViewContext<Workspace>,
 874        ) -> Self::PaintState {
 875            let can_resize = settings::get::<WorkspaceSettings>(cx).active_pane_magnification == 1.;
 876            let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
 877
 878            let overflowing = *remaining_space < 0.;
 879            if overflowing {
 880                cx.scene().push_layer(Some(visible_bounds));
 881            }
 882
 883            let mut child_origin = bounds.origin();
 884
 885            let mut bounding_boxes = self.bounding_boxes.borrow_mut();
 886            bounding_boxes.clear();
 887
 888            let mut children_iter = self.children.iter_mut().enumerate().peekable();
 889            while let Some((ix, child)) = children_iter.next() {
 890                let child_start = child_origin.clone();
 891                child.paint(child_origin, visible_bounds, view, cx);
 892
 893                bounding_boxes.push(Some(RectF::new(child_origin, child.size())));
 894
 895                match self.axis {
 896                    Axis::Horizontal => child_origin += vec2f(child.size().x(), 0.0),
 897                    Axis::Vertical => child_origin += vec2f(0.0, child.size().y()),
 898                }
 899
 900                if can_resize && children_iter.peek().is_some() {
 901                    cx.scene().push_stacking_context(None, None);
 902
 903                    let handle_origin = match self.axis {
 904                        Axis::Horizontal => child_origin - vec2f(HANDLE_HITBOX_SIZE / 2., 0.0),
 905                        Axis::Vertical => child_origin - vec2f(0.0, HANDLE_HITBOX_SIZE / 2.),
 906                    };
 907
 908                    let handle_bounds = match self.axis {
 909                        Axis::Horizontal => RectF::new(
 910                            handle_origin,
 911                            vec2f(HANDLE_HITBOX_SIZE, visible_bounds.height()),
 912                        ),
 913                        Axis::Vertical => RectF::new(
 914                            handle_origin,
 915                            vec2f(visible_bounds.width(), HANDLE_HITBOX_SIZE),
 916                        ),
 917                    };
 918
 919                    let style = match self.axis {
 920                        Axis::Horizontal => CursorStyle::ResizeLeftRight,
 921                        Axis::Vertical => CursorStyle::ResizeUpDown,
 922                    };
 923
 924                    cx.scene().push_cursor_region(CursorRegion {
 925                        bounds: handle_bounds,
 926                        style,
 927                    });
 928
 929                    enum ResizeHandle {}
 930                    let mut mouse_region = MouseRegion::new::<ResizeHandle>(
 931                        cx.view_id(),
 932                        self.basis + ix,
 933                        handle_bounds,
 934                    );
 935                    mouse_region = mouse_region
 936                        .on_drag(
 937                            MouseButton::Left,
 938                            Self::handle_resize(
 939                                self.flexes.clone(),
 940                                self.axis,
 941                                ix,
 942                                child_start,
 943                                visible_bounds.clone(),
 944                            ),
 945                        )
 946                        .on_click(MouseButton::Left, {
 947                            let flexes = self.flexes.clone();
 948                            move |e, v: &mut Workspace, cx| {
 949                                if e.click_count >= 2 {
 950                                    let mut borrow = flexes.borrow_mut();
 951                                    *borrow = vec![1.; borrow.len()];
 952                                    v.schedule_serialize(cx);
 953                                    cx.notify();
 954                                }
 955                            }
 956                        });
 957                    cx.scene().push_mouse_region(mouse_region);
 958
 959                    cx.scene().pop_stacking_context();
 960                }
 961            }
 962
 963            if overflowing {
 964                cx.scene().pop_layer();
 965            }
 966        }
 967
 968        fn rect_for_text_range(
 969            &self,
 970            range_utf16: Range<usize>,
 971            _: RectF,
 972            _: RectF,
 973            _: &Self::LayoutState,
 974            _: &Self::PaintState,
 975            view: &Workspace,
 976            cx: &ViewContext<Workspace>,
 977        ) -> Option<RectF> {
 978            self.children
 979                .iter()
 980                .find_map(|child| child.rect_for_text_range(range_utf16.clone(), view, cx))
 981        }
 982
 983        fn debug(
 984            &self,
 985            bounds: RectF,
 986            _: &Self::LayoutState,
 987            _: &Self::PaintState,
 988            view: &Workspace,
 989            cx: &ViewContext<Workspace>,
 990        ) -> json::Value {
 991            serde_json::json!({
 992                "type": "PaneAxis",
 993                "bounds": bounds.to_json(),
 994                "axis": self.axis.to_json(),
 995                "flexes": *self.flexes.borrow(),
 996                "children": self.children.iter().map(|child| child.debug(view, cx)).collect::<Vec<json::Value>>()
 997            })
 998        }
 999    }
1000}