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