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