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