Skip to repository content1619 lines · 55.7 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T03:59:24.344Z Public web read
NIP-34 coordinate
30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omegaMaintainersHidden in public view
References2 branches · 1 tag
Read-only clone
git clone https://openagents.com/git/tenant.openagents/omega.gitBrowse files
pane_group.rs
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