pane_group.rs

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