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