pane_group.rs

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