Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T03:59:24.344Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

pane_group.rs

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