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