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