1use crate::{pane_group::element::pane_axis, AppState, FollowerState, Pane, Workspace};
2use anyhow::{anyhow, Result};
3use call::{ActiveCall, ParticipantLocation};
4use collections::HashMap;
5use gpui::{
6 point, size, AnyView, AnyWeakView, Axis, Bounds, IntoElement, Model, MouseButton, Pixels,
7 Point, StyleRefinement, View, ViewContext,
8};
9use parking_lot::Mutex;
10use project::Project;
11use serde::Deserialize;
12use std::sync::Arc;
13use ui::prelude::*;
14
15pub const HANDLE_HITBOX_SIZE: f32 = 4.0;
16const HORIZONTAL_MIN_SIZE: f32 = 80.;
17const VERTICAL_MIN_SIZE: f32 = 100.;
18
19#[derive(Clone)]
20pub struct PaneGroup {
21 pub(crate) root: Member,
22}
23
24impl PaneGroup {
25 pub(crate) fn with_root(root: Member) -> Self {
26 Self { root }
27 }
28
29 pub fn new(pane: View<Pane>) -> Self {
30 Self {
31 root: Member::Pane(pane),
32 }
33 }
34
35 pub fn split(
36 &mut self,
37 old_pane: &View<Pane>,
38 new_pane: &View<Pane>,
39 direction: SplitDirection,
40 ) -> Result<()> {
41 match &mut self.root {
42 Member::Pane(pane) => {
43 if pane == old_pane {
44 self.root = Member::new_axis(old_pane.clone(), new_pane.clone(), direction);
45 Ok(())
46 } else {
47 Err(anyhow!("Pane not found"))
48 }
49 }
50 Member::Axis(axis) => axis.split(old_pane, new_pane, direction),
51 }
52 }
53
54 pub fn bounding_box_for_pane(&self, pane: &View<Pane>) -> Option<Bounds<Pixels>> {
55 match &self.root {
56 Member::Pane(_) => None,
57 Member::Axis(axis) => axis.bounding_box_for_pane(pane),
58 }
59 }
60
61 pub fn pane_at_pixel_position(&self, coordinate: Point<Pixels>) -> Option<&View<Pane>> {
62 match &self.root {
63 Member::Pane(pane) => Some(pane),
64 Member::Axis(axis) => axis.pane_at_pixel_position(coordinate),
65 }
66 }
67
68 /// Returns:
69 /// - Ok(true) if it found and removed a pane
70 /// - Ok(false) if it found but did not remove the pane
71 /// - Err(_) if it did not find the pane
72 pub fn remove(&mut self, pane: &View<Pane>) -> Result<bool> {
73 match &mut self.root {
74 Member::Pane(_) => Ok(false),
75 Member::Axis(axis) => {
76 if let Some(last_pane) = axis.remove(pane)? {
77 self.root = last_pane;
78 }
79 Ok(true)
80 }
81 }
82 }
83
84 pub fn swap(&mut self, from: &View<Pane>, to: &View<Pane>) {
85 match &mut self.root {
86 Member::Pane(_) => {}
87 Member::Axis(axis) => axis.swap(from, to),
88 };
89 }
90
91 #[allow(clippy::too_many_arguments)]
92 pub(crate) fn render(
93 &self,
94 project: &Model<Project>,
95 follower_states: &HashMap<View<Pane>, FollowerState>,
96 active_call: Option<&Model<ActiveCall>>,
97 active_pane: &View<Pane>,
98 zoomed: Option<&AnyWeakView>,
99 app_state: &Arc<AppState>,
100 cx: &mut ViewContext<Workspace>,
101 ) -> impl IntoElement {
102 self.root.render(
103 project,
104 0,
105 follower_states,
106 active_call,
107 active_pane,
108 zoomed,
109 app_state,
110 cx,
111 )
112 }
113
114 pub(crate) fn panes(&self) -> Vec<&View<Pane>> {
115 let mut panes = Vec::new();
116 self.root.collect_panes(&mut panes);
117 panes
118 }
119
120 pub(crate) fn first_pane(&self) -> View<Pane> {
121 self.root.first_pane()
122 }
123}
124
125#[derive(Clone)]
126pub(crate) enum Member {
127 Axis(PaneAxis),
128 Pane(View<Pane>),
129}
130
131impl Member {
132 fn new_axis(old_pane: View<Pane>, new_pane: View<Pane>, direction: SplitDirection) -> Self {
133 use Axis::*;
134 use SplitDirection::*;
135
136 let axis = match direction {
137 Up | Down => Vertical,
138 Left | Right => Horizontal,
139 };
140
141 let members = match direction {
142 Up | Left => vec![Member::Pane(new_pane), Member::Pane(old_pane)],
143 Down | Right => vec![Member::Pane(old_pane), Member::Pane(new_pane)],
144 };
145
146 Member::Axis(PaneAxis::new(axis, members))
147 }
148
149 fn contains(&self, needle: &View<Pane>) -> bool {
150 match self {
151 Member::Axis(axis) => axis.members.iter().any(|member| member.contains(needle)),
152 Member::Pane(pane) => pane == needle,
153 }
154 }
155
156 fn first_pane(&self) -> View<Pane> {
157 match self {
158 Member::Axis(axis) => axis.members[0].first_pane(),
159 Member::Pane(pane) => pane.clone(),
160 }
161 }
162
163 #[allow(clippy::too_many_arguments)]
164 pub fn render(
165 &self,
166 project: &Model<Project>,
167 basis: usize,
168 follower_states: &HashMap<View<Pane>, FollowerState>,
169 active_call: Option<&Model<ActiveCall>>,
170 active_pane: &View<Pane>,
171 zoomed: Option<&AnyWeakView>,
172 app_state: &Arc<AppState>,
173 cx: &mut ViewContext<Workspace>,
174 ) -> impl IntoElement {
175 match self {
176 Member::Pane(pane) => {
177 if zoomed == Some(&pane.downgrade().into()) {
178 return div().into_any();
179 }
180
181 let follower_state = follower_states.get(pane);
182
183 let leader = follower_state.and_then(|state| {
184 let room = active_call?.read(cx).room()?.read(cx);
185 room.remote_participant_for_peer_id(state.leader_id)
186 });
187
188 let is_in_unshared_view = follower_state.map_or(false, |state| {
189 state.active_view_id.is_some_and(|view_id| {
190 !state.items_by_leader_view_id.contains_key(&view_id)
191 })
192 });
193
194 let mut leader_border = None;
195 let mut leader_status_box = None;
196 let mut leader_join_data = None;
197 if let Some(leader) = &leader {
198 let mut leader_color = cx
199 .theme()
200 .players()
201 .color_for_participant(leader.participant_index.0)
202 .cursor;
203 leader_color.fade_out(0.3);
204 leader_border = Some(leader_color);
205
206 leader_status_box = match leader.location {
207 ParticipantLocation::SharedProject {
208 project_id: leader_project_id,
209 } => {
210 if Some(leader_project_id) == project.read(cx).remote_id() {
211 if is_in_unshared_view {
212 Some(Label::new(format!(
213 "{} is in an unshared pane",
214 leader.user.github_login
215 )))
216 } else {
217 None
218 }
219 } else {
220 leader_join_data = Some((leader_project_id, leader.user.id));
221 Some(Label::new(format!(
222 "Follow {} to their active project",
223 leader.user.github_login,
224 )))
225 }
226 }
227 ParticipantLocation::UnsharedProject => Some(Label::new(format!(
228 "{} is viewing an unshared Zed project",
229 leader.user.github_login
230 ))),
231 ParticipantLocation::External => Some(Label::new(format!(
232 "{} is viewing a window outside of Zed",
233 leader.user.github_login
234 ))),
235 };
236 }
237
238 div()
239 .relative()
240 .flex_1()
241 .size_full()
242 .child(
243 AnyView::from(pane.clone())
244 .cached(StyleRefinement::default().v_flex().size_full()),
245 )
246 .when_some(leader_border, |this, color| {
247 this.child(
248 div()
249 .absolute()
250 .size_full()
251 .left_0()
252 .top_0()
253 .border_2()
254 .border_color(color),
255 )
256 })
257 .when_some(leader_status_box, |this, status_box| {
258 this.child(
259 div()
260 .absolute()
261 .w_96()
262 .bottom_3()
263 .right_3()
264 .elevation_2(cx)
265 .p_1()
266 .child(status_box)
267 .when_some(
268 leader_join_data,
269 |this, (leader_project_id, leader_user_id)| {
270 this.cursor_pointer().on_mouse_down(
271 MouseButton::Left,
272 cx.listener(move |this, _, cx| {
273 crate::join_in_room_project(
274 leader_project_id,
275 leader_user_id,
276 this.app_state().clone(),
277 cx,
278 )
279 .detach_and_log_err(cx);
280 }),
281 )
282 },
283 ),
284 )
285 })
286 .into_any()
287 }
288 Member::Axis(axis) => axis
289 .render(
290 project,
291 basis + 1,
292 follower_states,
293 active_call,
294 active_pane,
295 zoomed,
296 app_state,
297 cx,
298 )
299 .into_any(),
300 }
301 }
302
303 fn collect_panes<'a>(&'a self, panes: &mut Vec<&'a View<Pane>>) {
304 match self {
305 Member::Axis(axis) => {
306 for member in &axis.members {
307 member.collect_panes(panes);
308 }
309 }
310 Member::Pane(pane) => panes.push(pane),
311 }
312 }
313}
314
315#[derive(Clone)]
316pub(crate) struct PaneAxis {
317 pub axis: Axis,
318 pub members: Vec<Member>,
319 pub flexes: Arc<Mutex<Vec<f32>>>,
320 pub bounding_boxes: Arc<Mutex<Vec<Option<Bounds<Pixels>>>>>,
321}
322
323impl PaneAxis {
324 pub fn new(axis: Axis, members: Vec<Member>) -> Self {
325 let flexes = Arc::new(Mutex::new(vec![1.; members.len()]));
326 let bounding_boxes = Arc::new(Mutex::new(vec![None; members.len()]));
327 Self {
328 axis,
329 members,
330 flexes,
331 bounding_boxes,
332 }
333 }
334
335 pub fn load(axis: Axis, members: Vec<Member>, flexes: Option<Vec<f32>>) -> Self {
336 let flexes = flexes.unwrap_or_else(|| vec![1.; members.len()]);
337 debug_assert!(members.len() == flexes.len());
338
339 let flexes = Arc::new(Mutex::new(flexes));
340 let bounding_boxes = Arc::new(Mutex::new(vec![None; members.len()]));
341 Self {
342 axis,
343 members,
344 flexes,
345 bounding_boxes,
346 }
347 }
348
349 fn split(
350 &mut self,
351 old_pane: &View<Pane>,
352 new_pane: &View<Pane>,
353 direction: SplitDirection,
354 ) -> Result<()> {
355 for (mut idx, member) in self.members.iter_mut().enumerate() {
356 match member {
357 Member::Axis(axis) => {
358 if axis.split(old_pane, new_pane, direction).is_ok() {
359 return Ok(());
360 }
361 }
362 Member::Pane(pane) => {
363 if pane == old_pane {
364 if direction.axis() == self.axis {
365 if direction.increasing() {
366 idx += 1;
367 }
368
369 self.members.insert(idx, Member::Pane(new_pane.clone()));
370 *self.flexes.lock() = vec![1.; self.members.len()];
371 } else {
372 *member =
373 Member::new_axis(old_pane.clone(), new_pane.clone(), direction);
374 }
375 return Ok(());
376 }
377 }
378 }
379 }
380 Err(anyhow!("Pane not found"))
381 }
382
383 fn remove(&mut self, pane_to_remove: &View<Pane>) -> Result<Option<Member>> {
384 let mut found_pane = false;
385 let mut remove_member = None;
386 for (idx, member) in self.members.iter_mut().enumerate() {
387 match member {
388 Member::Axis(axis) => {
389 if let Ok(last_pane) = axis.remove(pane_to_remove) {
390 if let Some(last_pane) = last_pane {
391 *member = last_pane;
392 }
393 found_pane = true;
394 break;
395 }
396 }
397 Member::Pane(pane) => {
398 if pane == pane_to_remove {
399 found_pane = true;
400 remove_member = Some(idx);
401 break;
402 }
403 }
404 }
405 }
406
407 if found_pane {
408 if let Some(idx) = remove_member {
409 self.members.remove(idx);
410 *self.flexes.lock() = vec![1.; self.members.len()];
411 }
412
413 if self.members.len() == 1 {
414 let result = self.members.pop();
415 *self.flexes.lock() = vec![1.; self.members.len()];
416 Ok(result)
417 } else {
418 Ok(None)
419 }
420 } else {
421 Err(anyhow!("Pane not found"))
422 }
423 }
424
425 fn swap(&mut self, from: &View<Pane>, to: &View<Pane>) {
426 for member in self.members.iter_mut() {
427 match member {
428 Member::Axis(axis) => axis.swap(from, to),
429 Member::Pane(pane) => {
430 if pane == from {
431 *member = Member::Pane(to.clone());
432 } else if pane == to {
433 *member = Member::Pane(from.clone())
434 }
435 }
436 }
437 }
438 }
439
440 fn bounding_box_for_pane(&self, pane: &View<Pane>) -> Option<Bounds<Pixels>> {
441 debug_assert!(self.members.len() == self.bounding_boxes.lock().len());
442
443 for (idx, member) in self.members.iter().enumerate() {
444 match member {
445 Member::Pane(found) => {
446 if pane == found {
447 return self.bounding_boxes.lock()[idx];
448 }
449 }
450 Member::Axis(axis) => {
451 if let Some(rect) = axis.bounding_box_for_pane(pane) {
452 return Some(rect);
453 }
454 }
455 }
456 }
457 None
458 }
459
460 fn pane_at_pixel_position(&self, coordinate: Point<Pixels>) -> Option<&View<Pane>> {
461 debug_assert!(self.members.len() == self.bounding_boxes.lock().len());
462
463 let bounding_boxes = self.bounding_boxes.lock();
464
465 for (idx, member) in self.members.iter().enumerate() {
466 if let Some(coordinates) = bounding_boxes[idx] {
467 if coordinates.contains(&coordinate) {
468 return match member {
469 Member::Pane(found) => Some(found),
470 Member::Axis(axis) => axis.pane_at_pixel_position(coordinate),
471 };
472 }
473 }
474 }
475 None
476 }
477
478 #[allow(clippy::too_many_arguments)]
479 fn render(
480 &self,
481 project: &Model<Project>,
482 basis: usize,
483 follower_states: &HashMap<View<Pane>, FollowerState>,
484 active_call: Option<&Model<ActiveCall>>,
485 active_pane: &View<Pane>,
486 zoomed: Option<&AnyWeakView>,
487 app_state: &Arc<AppState>,
488 cx: &mut ViewContext<Workspace>,
489 ) -> gpui::AnyElement {
490 debug_assert!(self.members.len() == self.flexes.lock().len());
491 let mut active_pane_ix = None;
492
493 pane_axis(
494 self.axis,
495 basis,
496 self.flexes.clone(),
497 self.bounding_boxes.clone(),
498 cx.view().downgrade(),
499 )
500 .children(self.members.iter().enumerate().map(|(ix, member)| {
501 if member.contains(active_pane) {
502 active_pane_ix = Some(ix);
503 }
504 member
505 .render(
506 project,
507 (basis + ix) * 10,
508 follower_states,
509 active_call,
510 active_pane,
511 zoomed,
512 app_state,
513 cx,
514 )
515 .into_any_element()
516 }))
517 .with_active_pane(active_pane_ix)
518 .into_any_element()
519 }
520}
521
522#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
523pub enum SplitDirection {
524 Up,
525 Down,
526 Left,
527 Right,
528}
529
530impl std::fmt::Display for SplitDirection {
531 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
532 match self {
533 SplitDirection::Up => write!(f, "up"),
534 SplitDirection::Down => write!(f, "down"),
535 SplitDirection::Left => write!(f, "left"),
536 SplitDirection::Right => write!(f, "right"),
537 }
538 }
539}
540
541impl SplitDirection {
542 pub fn all() -> [Self; 4] {
543 [Self::Up, Self::Down, Self::Left, Self::Right]
544 }
545
546 pub fn edge(&self, rect: Bounds<Pixels>) -> Pixels {
547 match self {
548 Self::Up => rect.origin.y,
549 Self::Down => rect.lower_left().y,
550 Self::Left => rect.lower_left().x,
551 Self::Right => rect.lower_right().x,
552 }
553 }
554
555 pub fn along_edge(&self, bounds: Bounds<Pixels>, length: Pixels) -> Bounds<Pixels> {
556 match self {
557 Self::Up => Bounds {
558 origin: bounds.origin,
559 size: size(bounds.size.width, length),
560 },
561 Self::Down => Bounds {
562 origin: point(bounds.lower_left().x, bounds.lower_left().y - length),
563 size: size(bounds.size.width, length),
564 },
565 Self::Left => Bounds {
566 origin: bounds.origin,
567 size: size(length, bounds.size.height),
568 },
569 Self::Right => Bounds {
570 origin: point(bounds.lower_right().x - length, bounds.lower_left().y),
571 size: size(length, bounds.size.height),
572 },
573 }
574 }
575
576 pub fn axis(&self) -> Axis {
577 match self {
578 Self::Up | Self::Down => Axis::Vertical,
579 Self::Left | Self::Right => Axis::Horizontal,
580 }
581 }
582
583 pub fn increasing(&self) -> bool {
584 match self {
585 Self::Left | Self::Up => false,
586 Self::Down | Self::Right => true,
587 }
588 }
589}
590
591mod element {
592
593 use std::mem;
594 use std::{cell::RefCell, iter, rc::Rc, sync::Arc};
595
596 use gpui::{
597 px, relative, Along, AnyElement, Axis, Bounds, Element, IntoElement, MouseDownEvent,
598 MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point, Size, Style, WeakView,
599 WindowContext,
600 };
601 use gpui::{CursorStyle, Hitbox};
602 use parking_lot::Mutex;
603 use settings::Settings;
604 use smallvec::SmallVec;
605 use ui::prelude::*;
606 use util::ResultExt;
607
608 use crate::Workspace;
609
610 use crate::WorkspaceSettings;
611
612 use super::{HANDLE_HITBOX_SIZE, HORIZONTAL_MIN_SIZE, VERTICAL_MIN_SIZE};
613
614 const DIVIDER_SIZE: f32 = 1.0;
615
616 pub(super) fn pane_axis(
617 axis: Axis,
618 basis: usize,
619 flexes: Arc<Mutex<Vec<f32>>>,
620 bounding_boxes: Arc<Mutex<Vec<Option<Bounds<Pixels>>>>>,
621 workspace: WeakView<Workspace>,
622 ) -> PaneAxisElement {
623 PaneAxisElement {
624 axis,
625 basis,
626 flexes,
627 bounding_boxes,
628 children: SmallVec::new(),
629 active_pane_ix: None,
630 workspace,
631 }
632 }
633
634 pub struct PaneAxisElement {
635 axis: Axis,
636 basis: usize,
637 flexes: Arc<Mutex<Vec<f32>>>,
638 bounding_boxes: Arc<Mutex<Vec<Option<Bounds<Pixels>>>>>,
639 children: SmallVec<[AnyElement; 2]>,
640 active_pane_ix: Option<usize>,
641 workspace: WeakView<Workspace>,
642 }
643
644 pub struct PaneAxisLayout {
645 dragged_handle: Rc<RefCell<Option<usize>>>,
646 children: Vec<PaneAxisChildLayout>,
647 }
648
649 struct PaneAxisChildLayout {
650 bounds: Bounds<Pixels>,
651 element: AnyElement,
652 handle: Option<PaneAxisHandleLayout>,
653 }
654
655 struct PaneAxisHandleLayout {
656 hitbox: Hitbox,
657 divider_bounds: Bounds<Pixels>,
658 }
659
660 impl PaneAxisElement {
661 pub fn with_active_pane(mut self, active_pane_ix: Option<usize>) -> Self {
662 self.active_pane_ix = active_pane_ix;
663 self
664 }
665
666 #[allow(clippy::too_many_arguments)]
667 fn compute_resize(
668 flexes: &Arc<Mutex<Vec<f32>>>,
669 e: &MouseMoveEvent,
670 ix: usize,
671 axis: Axis,
672 child_start: Point<Pixels>,
673 container_size: Size<Pixels>,
674 workspace: WeakView<Workspace>,
675 cx: &mut WindowContext,
676 ) {
677 let min_size = match axis {
678 Axis::Horizontal => px(HORIZONTAL_MIN_SIZE),
679 Axis::Vertical => px(VERTICAL_MIN_SIZE),
680 };
681 let mut flexes = flexes.lock();
682 debug_assert!(flex_values_in_bounds(flexes.as_slice()));
683
684 let size = move |ix, flexes: &[f32]| {
685 container_size.along(axis) * (flexes[ix] / flexes.len() as f32)
686 };
687
688 // Don't allow resizing to less than the minimum size, if elements are already too small
689 if min_size - px(1.) > size(ix, flexes.as_slice()) {
690 return;
691 }
692
693 let mut proposed_current_pixel_change =
694 (e.position - child_start).along(axis) - size(ix, flexes.as_slice());
695
696 let flex_changes = |pixel_dx, target_ix, next: isize, flexes: &[f32]| {
697 let flex_change = pixel_dx / container_size.along(axis);
698 let current_target_flex = flexes[target_ix] + flex_change;
699 let next_target_flex = flexes[(target_ix as isize + next) as usize] - flex_change;
700 (current_target_flex, next_target_flex)
701 };
702
703 let mut successors = iter::from_fn({
704 let forward = proposed_current_pixel_change > px(0.);
705 let mut ix_offset = 0;
706 let len = flexes.len();
707 move || {
708 let result = if forward {
709 (ix + 1 + ix_offset < len).then(|| ix + ix_offset)
710 } else {
711 (ix as isize - ix_offset as isize >= 0).then(|| ix - ix_offset)
712 };
713
714 ix_offset += 1;
715
716 result
717 }
718 });
719
720 while proposed_current_pixel_change.abs() > px(0.) {
721 let Some(current_ix) = successors.next() else {
722 break;
723 };
724
725 let next_target_size = Pixels::max(
726 size(current_ix + 1, flexes.as_slice()) - proposed_current_pixel_change,
727 min_size,
728 );
729
730 let current_target_size = Pixels::max(
731 size(current_ix, flexes.as_slice()) + size(current_ix + 1, flexes.as_slice())
732 - next_target_size,
733 min_size,
734 );
735
736 let current_pixel_change =
737 current_target_size - size(current_ix, flexes.as_slice());
738
739 let (current_target_flex, next_target_flex) =
740 flex_changes(current_pixel_change, current_ix, 1, flexes.as_slice());
741
742 flexes[current_ix] = current_target_flex;
743 flexes[current_ix + 1] = next_target_flex;
744
745 proposed_current_pixel_change -= current_pixel_change;
746 }
747
748 workspace
749 .update(cx, |this, cx| this.schedule_serialize(cx))
750 .log_err();
751 cx.stop_propagation();
752 cx.refresh();
753 }
754
755 #[allow(clippy::too_many_arguments)]
756 fn layout_handle(
757 axis: Axis,
758 pane_bounds: Bounds<Pixels>,
759 cx: &mut ElementContext,
760 ) -> PaneAxisHandleLayout {
761 let handle_bounds = Bounds {
762 origin: pane_bounds.origin.apply_along(axis, |origin| {
763 origin + pane_bounds.size.along(axis) - px(HANDLE_HITBOX_SIZE / 2.)
764 }),
765 size: pane_bounds
766 .size
767 .apply_along(axis, |_| px(HANDLE_HITBOX_SIZE)),
768 };
769 let divider_bounds = Bounds {
770 origin: pane_bounds
771 .origin
772 .apply_along(axis, |origin| origin + pane_bounds.size.along(axis)),
773 size: pane_bounds.size.apply_along(axis, |_| px(DIVIDER_SIZE)),
774 };
775
776 PaneAxisHandleLayout {
777 hitbox: cx.insert_hitbox(handle_bounds, true),
778 divider_bounds,
779 }
780 }
781 }
782
783 impl IntoElement for PaneAxisElement {
784 type Element = Self;
785
786 fn into_element(self) -> Self::Element {
787 self
788 }
789 }
790
791 impl Element for PaneAxisElement {
792 type BeforeLayout = ();
793 type AfterLayout = PaneAxisLayout;
794
795 fn before_layout(
796 &mut self,
797 cx: &mut ui::prelude::ElementContext,
798 ) -> (gpui::LayoutId, Self::BeforeLayout) {
799 let mut style = Style::default();
800 style.flex_grow = 1.;
801 style.flex_shrink = 1.;
802 style.flex_basis = relative(0.).into();
803 style.size.width = relative(1.).into();
804 style.size.height = relative(1.).into();
805 (cx.request_layout(&style, None), ())
806 }
807
808 fn after_layout(
809 &mut self,
810 bounds: Bounds<Pixels>,
811 _state: &mut Self::BeforeLayout,
812 cx: &mut ElementContext,
813 ) -> PaneAxisLayout {
814 let dragged_handle = cx.with_element_state::<Rc<RefCell<Option<usize>>>, _>(
815 Some(self.basis.into()),
816 |state, _cx| {
817 let state = state
818 .unwrap()
819 .unwrap_or_else(|| Rc::new(RefCell::new(None)));
820 (state.clone(), Some(state))
821 },
822 );
823 let flexes = self.flexes.lock().clone();
824 let len = self.children.len();
825 debug_assert!(flexes.len() == len);
826 debug_assert!(flex_values_in_bounds(flexes.as_slice()));
827
828 let magnification_value = WorkspaceSettings::get(None, cx).active_pane_magnification;
829 let active_pane_magnification = if magnification_value == 1. {
830 None
831 } else {
832 Some(magnification_value)
833 };
834
835 let total_flex = if let Some(flex) = active_pane_magnification {
836 self.children.len() as f32 - 1. + flex
837 } else {
838 len as f32
839 };
840
841 let mut origin = bounds.origin;
842 let space_per_flex = bounds.size.along(self.axis) / total_flex;
843
844 let mut bounding_boxes = self.bounding_boxes.lock();
845 bounding_boxes.clear();
846
847 let mut layout = PaneAxisLayout {
848 dragged_handle: dragged_handle.clone(),
849 children: Vec::new(),
850 };
851 for (ix, mut child) in mem::take(&mut self.children).into_iter().enumerate() {
852 let child_flex = active_pane_magnification
853 .map(|magnification| {
854 if self.active_pane_ix == Some(ix) {
855 magnification
856 } else {
857 1.
858 }
859 })
860 .unwrap_or_else(|| flexes[ix]);
861
862 let child_size = bounds
863 .size
864 .apply_along(self.axis, |_| space_per_flex * child_flex)
865 .map(|d| d.round());
866
867 let child_bounds = Bounds {
868 origin,
869 size: child_size,
870 };
871 bounding_boxes.push(Some(child_bounds));
872 child.layout(origin, child_size.into(), cx);
873
874 origin = origin.apply_along(self.axis, |val| val + child_size.along(self.axis));
875 layout.children.push(PaneAxisChildLayout {
876 bounds: child_bounds,
877 element: child,
878 handle: None,
879 })
880 }
881
882 for (ix, child_layout) in layout.children.iter_mut().enumerate() {
883 if active_pane_magnification.is_none() {
884 if ix < len - 1 {
885 child_layout.handle =
886 Some(Self::layout_handle(self.axis, child_layout.bounds, cx));
887 }
888 }
889 }
890
891 layout
892 }
893
894 fn paint(
895 &mut self,
896 bounds: gpui::Bounds<ui::prelude::Pixels>,
897 _: &mut Self::BeforeLayout,
898 layout: &mut Self::AfterLayout,
899 cx: &mut ui::prelude::ElementContext,
900 ) {
901 for child in &mut layout.children {
902 child.element.paint(cx);
903 }
904
905 for (ix, child) in &mut layout.children.iter_mut().enumerate() {
906 if let Some(handle) = child.handle.as_mut() {
907 let cursor_style = match self.axis {
908 Axis::Vertical => CursorStyle::ResizeUpDown,
909 Axis::Horizontal => CursorStyle::ResizeLeftRight,
910 };
911 cx.set_cursor_style(cursor_style, &handle.hitbox);
912 cx.paint_quad(gpui::fill(
913 handle.divider_bounds,
914 cx.theme().colors().border,
915 ));
916
917 cx.on_mouse_event({
918 let dragged_handle = layout.dragged_handle.clone();
919 let flexes = self.flexes.clone();
920 let workspace = self.workspace.clone();
921 let handle_hitbox = handle.hitbox.clone();
922 move |e: &MouseDownEvent, phase, cx| {
923 if phase.bubble() && handle_hitbox.is_hovered(cx) {
924 dragged_handle.replace(Some(ix));
925 if e.click_count >= 2 {
926 let mut borrow = flexes.lock();
927 *borrow = vec![1.; borrow.len()];
928 workspace
929 .update(cx, |this, cx| this.schedule_serialize(cx))
930 .log_err();
931
932 cx.refresh();
933 }
934 cx.stop_propagation();
935 }
936 }
937 });
938 cx.on_mouse_event({
939 let workspace = self.workspace.clone();
940 let dragged_handle = layout.dragged_handle.clone();
941 let flexes = self.flexes.clone();
942 let child_bounds = child.bounds;
943 let axis = self.axis;
944 move |e: &MouseMoveEvent, phase, cx| {
945 let dragged_handle = dragged_handle.borrow();
946 if phase.bubble() {
947 if *dragged_handle == Some(ix) {
948 Self::compute_resize(
949 &flexes,
950 e,
951 ix,
952 axis,
953 child_bounds.origin,
954 bounds.size,
955 workspace.clone(),
956 cx,
957 )
958 }
959 }
960 }
961 });
962 }
963 }
964
965 cx.on_mouse_event({
966 let dragged_handle = layout.dragged_handle.clone();
967 move |_: &MouseUpEvent, phase, _cx| {
968 if phase.bubble() {
969 dragged_handle.replace(None);
970 }
971 }
972 });
973 }
974 }
975
976 impl ParentElement for PaneAxisElement {
977 fn extend(&mut self, elements: impl Iterator<Item = AnyElement>) {
978 self.children.extend(elements)
979 }
980 }
981
982 fn flex_values_in_bounds(flexes: &[f32]) -> bool {
983 (flexes.iter().copied().sum::<f32>() - flexes.len() as f32).abs() < 0.001
984 }
985}