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