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