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, PartialEq)]
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, PartialEq)]
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 PartialEq for PaneAxis {
430 fn eq(&self, other: &Self) -> bool {
431 todo!()
432 }
433}
434
435impl PaneAxis {
436 pub fn new(axis: Axis, members: Vec<Member>) -> Self {
437 let flexes = Arc::new(Mutex::new(vec![1.; members.len()]));
438 let bounding_boxes = Arc::new(Mutex::new(vec![None; members.len()]));
439 Self {
440 axis,
441 members,
442 flexes,
443 bounding_boxes,
444 }
445 }
446
447 pub fn load(axis: Axis, members: Vec<Member>, flexes: Option<Vec<f32>>) -> Self {
448 let flexes = flexes.unwrap_or_else(|| vec![1.; members.len()]);
449 debug_assert!(members.len() == flexes.len());
450
451 let flexes = Arc::new(Mutex::new(flexes));
452 let bounding_boxes = Arc::new(Mutex::new(vec![None; members.len()]));
453 Self {
454 axis,
455 members,
456 flexes,
457 bounding_boxes,
458 }
459 }
460
461 fn split(
462 &mut self,
463 old_pane: &View<Pane>,
464 new_pane: &View<Pane>,
465 direction: SplitDirection,
466 ) -> Result<()> {
467 for (mut idx, member) in self.members.iter_mut().enumerate() {
468 match member {
469 Member::Axis(axis) => {
470 if axis.split(old_pane, new_pane, direction).is_ok() {
471 return Ok(());
472 }
473 }
474 Member::Pane(pane) => {
475 if pane == old_pane {
476 if direction.axis() == self.axis {
477 if direction.increasing() {
478 idx += 1;
479 }
480
481 self.members.insert(idx, Member::Pane(new_pane.clone()));
482 *self.flexes.lock() = vec![1.; self.members.len()];
483 } else {
484 *member =
485 Member::new_axis(old_pane.clone(), new_pane.clone(), direction);
486 }
487 return Ok(());
488 }
489 }
490 }
491 }
492 Err(anyhow!("Pane not found"))
493 }
494
495 fn remove(&mut self, pane_to_remove: &View<Pane>) -> Result<Option<Member>> {
496 let mut found_pane = false;
497 let mut remove_member = None;
498 for (idx, member) in self.members.iter_mut().enumerate() {
499 match member {
500 Member::Axis(axis) => {
501 if let Ok(last_pane) = axis.remove(pane_to_remove) {
502 if let Some(last_pane) = last_pane {
503 *member = last_pane;
504 }
505 found_pane = true;
506 break;
507 }
508 }
509 Member::Pane(pane) => {
510 if pane == pane_to_remove {
511 found_pane = true;
512 remove_member = Some(idx);
513 break;
514 }
515 }
516 }
517 }
518
519 if found_pane {
520 if let Some(idx) = remove_member {
521 self.members.remove(idx);
522 *self.flexes.lock() = vec![1.; self.members.len()];
523 }
524
525 if self.members.len() == 1 {
526 let result = self.members.pop();
527 *self.flexes.lock() = vec![1.; self.members.len()];
528 Ok(result)
529 } else {
530 Ok(None)
531 }
532 } else {
533 Err(anyhow!("Pane not found"))
534 }
535 }
536
537 fn swap(&mut self, from: &View<Pane>, to: &View<Pane>) {
538 for member in self.members.iter_mut() {
539 match member {
540 Member::Axis(axis) => axis.swap(from, to),
541 Member::Pane(pane) => {
542 if pane == from {
543 *member = Member::Pane(to.clone());
544 } else if pane == to {
545 *member = Member::Pane(from.clone())
546 }
547 }
548 }
549 }
550 }
551
552 fn bounding_box_for_pane(&self, pane: &View<Pane>) -> Option<Bounds<Pixels>> {
553 debug_assert!(self.members.len() == self.bounding_boxes.lock().len());
554
555 for (idx, member) in self.members.iter().enumerate() {
556 match member {
557 Member::Pane(found) => {
558 if pane == found {
559 return self.bounding_boxes.lock()[idx];
560 }
561 }
562 Member::Axis(axis) => {
563 if let Some(rect) = axis.bounding_box_for_pane(pane) {
564 return Some(rect);
565 }
566 }
567 }
568 }
569 None
570 }
571
572 fn pane_at_pixel_position(&self, coordinate: Point<Pixels>) -> Option<&View<Pane>> {
573 debug_assert!(self.members.len() == self.bounding_boxes.lock().len());
574
575 let bounding_boxes = self.bounding_boxes.lock();
576
577 for (idx, member) in self.members.iter().enumerate() {
578 if let Some(coordinates) = bounding_boxes[idx] {
579 if coordinates.contains(&coordinate) {
580 return match member {
581 Member::Pane(found) => Some(found),
582 Member::Axis(axis) => axis.pane_at_pixel_position(coordinate),
583 };
584 }
585 }
586 }
587 None
588 }
589
590 fn render(
591 &self,
592 project: &Model<Project>,
593 basis: usize,
594 follower_states: &HashMap<View<Pane>, FollowerState>,
595 active_pane: &View<Pane>,
596 zoomed: Option<&AnyWeakView>,
597 app_state: &Arc<AppState>,
598 cx: &mut ViewContext<Workspace>,
599 ) -> gpui::AnyElement {
600 debug_assert!(self.members.len() == self.flexes.lock().len());
601 let mut active_pane_ix = None;
602
603 pane_axis(
604 self.axis,
605 basis,
606 self.flexes.clone(),
607 self.bounding_boxes.clone(),
608 )
609 .children(self.members.iter().enumerate().map(|(ix, member)| {
610 if member.contains(active_pane) {
611 active_pane_ix = Some(ix);
612 }
613
614 match member {
615 Member::Axis(axis) => axis
616 .render(
617 project,
618 (basis + ix) * 10,
619 follower_states,
620 active_pane,
621 zoomed,
622 app_state,
623 cx,
624 )
625 .into_any_element(),
626 Member::Pane(pane) => div()
627 .size_full()
628 .border()
629 .child(pane.clone())
630 .into_any_element(),
631 }
632 }))
633 .with_active_pane(active_pane_ix)
634 .into_any_element()
635 }
636}
637
638#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
639pub enum SplitDirection {
640 Up,
641 Down,
642 Left,
643 Right,
644}
645
646impl SplitDirection {
647 pub fn all() -> [Self; 4] {
648 [Self::Up, Self::Down, Self::Left, Self::Right]
649 }
650
651 pub fn edge(&self, rect: Bounds<Pixels>) -> Pixels {
652 match self {
653 Self::Up => rect.origin.y,
654 Self::Down => rect.lower_left().y,
655 Self::Left => rect.lower_left().x,
656 Self::Right => rect.lower_right().x,
657 }
658 }
659
660 pub fn along_edge(&self, bounds: Bounds<Pixels>, length: Pixels) -> Bounds<Pixels> {
661 match self {
662 Self::Up => Bounds {
663 origin: bounds.origin,
664 size: size(bounds.size.width, length),
665 },
666 Self::Down => Bounds {
667 origin: point(bounds.lower_left().x, bounds.lower_left().y - length),
668 size: size(bounds.size.width, length),
669 },
670 Self::Left => Bounds {
671 origin: bounds.origin,
672 size: size(length, bounds.size.height),
673 },
674 Self::Right => Bounds {
675 origin: point(bounds.lower_right().x - length, bounds.lower_left().y),
676 size: size(length, bounds.size.height),
677 },
678 }
679 }
680
681 pub fn axis(&self) -> Axis {
682 match self {
683 Self::Up | Self::Down => Axis::Vertical,
684 Self::Left | Self::Right => Axis::Horizontal,
685 }
686 }
687
688 pub fn increasing(&self) -> bool {
689 match self {
690 Self::Left | Self::Up => false,
691 Self::Down | Self::Right => true,
692 }
693 }
694}
695
696mod element {
697
698 use std::{cell::RefCell, iter, rc::Rc, sync::Arc};
699
700 use gpui::{
701 px, relative, Along, AnyElement, Axis, Bounds, CursorStyle, Element, IntoElement,
702 MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Style, WindowContext,
703 };
704 use parking_lot::Mutex;
705 use smallvec::SmallVec;
706
707 use super::{HANDLE_HITBOX_SIZE, HORIZONTAL_MIN_SIZE, VERTICAL_MIN_SIZE};
708
709 pub fn pane_axis(
710 axis: Axis,
711 basis: usize,
712 flexes: Arc<Mutex<Vec<f32>>>,
713 bounding_boxes: Arc<Mutex<Vec<Option<Bounds<Pixels>>>>>,
714 ) -> PaneAxisElement {
715 PaneAxisElement {
716 axis,
717 basis,
718 flexes,
719 bounding_boxes,
720 children: SmallVec::new(),
721 active_pane_ix: None,
722 }
723 }
724
725 pub struct PaneAxisElement {
726 axis: Axis,
727 basis: usize,
728 flexes: Arc<Mutex<Vec<f32>>>,
729 bounding_boxes: Arc<Mutex<Vec<Option<Bounds<Pixels>>>>>,
730 children: SmallVec<[AnyElement; 2]>,
731 active_pane_ix: Option<usize>,
732 }
733
734 impl PaneAxisElement {
735 pub fn with_active_pane(mut self, active_pane_ix: Option<usize>) -> Self {
736 self.active_pane_ix = active_pane_ix;
737 self
738 }
739
740 fn compute_resize(
741 flexes: &Arc<Mutex<Vec<f32>>>,
742 e: &MouseMoveEvent,
743 ix: usize,
744 axis: Axis,
745 axis_bounds: Bounds<Pixels>,
746 cx: &mut WindowContext,
747 ) {
748 let min_size = match axis {
749 Axis::Horizontal => px(HORIZONTAL_MIN_SIZE),
750 Axis::Vertical => px(VERTICAL_MIN_SIZE),
751 };
752 let mut flexes = flexes.lock();
753 debug_assert!(flex_values_in_bounds(flexes.as_slice()));
754
755 let size = move |ix, flexes: &[f32]| {
756 axis_bounds.size.along(axis) * (flexes[ix] / flexes.len() as f32)
757 };
758
759 // Don't allow resizing to less than the minimum size, if elements are already too small
760 if min_size - px(1.) > size(ix, flexes.as_slice()) {
761 return;
762 }
763
764 let mut proposed_current_pixel_change =
765 (e.position - axis_bounds.origin).along(axis) - size(ix, flexes.as_slice());
766
767 let flex_changes = |pixel_dx, target_ix, next: isize, flexes: &[f32]| {
768 let flex_change = pixel_dx / axis_bounds.size.along(axis);
769 let current_target_flex = flexes[target_ix] + flex_change;
770 let next_target_flex = flexes[(target_ix as isize + next) as usize] - flex_change;
771 (current_target_flex, next_target_flex)
772 };
773
774 let mut successors = iter::from_fn({
775 let forward = proposed_current_pixel_change > px(0.);
776 let mut ix_offset = 0;
777 let len = flexes.len();
778 move || {
779 let result = if forward {
780 (ix + 1 + ix_offset < len).then(|| ix + ix_offset)
781 } else {
782 (ix as isize - ix_offset as isize >= 0).then(|| ix - ix_offset)
783 };
784
785 ix_offset += 1;
786
787 result
788 }
789 });
790
791 while proposed_current_pixel_change.abs() > px(0.) {
792 let Some(current_ix) = successors.next() else {
793 break;
794 };
795
796 let next_target_size = Pixels::max(
797 size(current_ix + 1, flexes.as_slice()) - proposed_current_pixel_change,
798 min_size,
799 );
800
801 let current_target_size = Pixels::max(
802 size(current_ix, flexes.as_slice()) + size(current_ix + 1, flexes.as_slice())
803 - next_target_size,
804 min_size,
805 );
806
807 let current_pixel_change =
808 current_target_size - size(current_ix, flexes.as_slice());
809
810 let (current_target_flex, next_target_flex) =
811 flex_changes(current_pixel_change, current_ix, 1, flexes.as_slice());
812
813 flexes[current_ix] = current_target_flex;
814 flexes[current_ix + 1] = next_target_flex;
815
816 proposed_current_pixel_change -= current_pixel_change;
817 }
818
819 // todo!(reserialize workspace)
820 // workspace.schedule_serialize(cx);
821 cx.notify();
822 }
823
824 fn push_handle(
825 flexes: Arc<Mutex<Vec<f32>>>,
826 dragged_handle: Rc<RefCell<Option<usize>>>,
827 axis: Axis,
828 ix: usize,
829 pane_bounds: Bounds<Pixels>,
830 axis_bounds: Bounds<Pixels>,
831 cx: &mut WindowContext,
832 ) {
833 let handle_bounds = Bounds {
834 origin: pane_bounds.origin.apply_along(axis, |o| {
835 o + pane_bounds.size.along(axis) - Pixels(HANDLE_HITBOX_SIZE / 2.)
836 }),
837 size: pane_bounds
838 .size
839 .apply_along(axis, |_| Pixels(HANDLE_HITBOX_SIZE)),
840 };
841
842 cx.with_z_index(3, |cx| {
843 if handle_bounds.contains(&cx.mouse_position()) {
844 cx.set_cursor_style(match axis {
845 Axis::Vertical => CursorStyle::ResizeUpDown,
846 Axis::Horizontal => CursorStyle::ResizeLeftRight,
847 })
848 }
849
850 cx.add_opaque_layer(handle_bounds);
851
852 cx.on_mouse_event({
853 let dragged_handle = dragged_handle.clone();
854 move |e: &MouseDownEvent, phase, cx| {
855 if phase.bubble() && handle_bounds.contains(&e.position) {
856 dragged_handle.replace(Some(ix));
857 }
858 }
859 });
860 cx.on_mouse_event(move |e: &MouseMoveEvent, phase, cx| {
861 let dragged_handle = dragged_handle.borrow();
862 if *dragged_handle == Some(ix) {
863 Self::compute_resize(&flexes, e, ix, axis, axis_bounds, cx)
864 }
865 });
866 });
867 }
868 }
869
870 impl IntoElement for PaneAxisElement {
871 type Element = Self;
872
873 fn element_id(&self) -> Option<ui::prelude::ElementId> {
874 Some(self.basis.into())
875 }
876
877 fn into_element(self) -> Self::Element {
878 self
879 }
880 }
881
882 impl Element for PaneAxisElement {
883 type State = Rc<RefCell<Option<usize>>>;
884
885 fn layout(
886 &mut self,
887 state: Option<Self::State>,
888 cx: &mut ui::prelude::WindowContext,
889 ) -> (gpui::LayoutId, Self::State) {
890 let mut style = Style::default();
891 style.size.width = relative(1.).into();
892 style.size.height = relative(1.).into();
893 let layout_id = cx.request_layout(&style, None);
894 let dragged_pane = state.unwrap_or_else(|| Rc::new(RefCell::new(None)));
895 (layout_id, dragged_pane)
896 }
897
898 fn paint(
899 self,
900 bounds: gpui::Bounds<ui::prelude::Pixels>,
901 state: &mut Self::State,
902 cx: &mut ui::prelude::WindowContext,
903 ) {
904 let flexes = self.flexes.lock().clone();
905 let len = self.children.len();
906 debug_assert!(flexes.len() == len);
907 debug_assert!(flex_values_in_bounds(flexes.as_slice()));
908
909 let mut origin = bounds.origin;
910 let space_per_flex = bounds.size.along(self.axis) / len as f32;
911
912 let mut bounding_boxes = self.bounding_boxes.lock();
913 bounding_boxes.clear();
914
915 for (ix, child) in self.children.into_iter().enumerate() {
916 //todo!(active_pane_magnification)
917 // If usign active pane magnification, need to switch to using
918 // 1 for all non-active panes, and then the magnification for the
919 // active pane.
920 let child_size = bounds
921 .size
922 .apply_along(self.axis, |_| space_per_flex * flexes[ix]);
923
924 let child_bounds = Bounds {
925 origin,
926 size: child_size,
927 };
928 bounding_boxes.push(Some(child_bounds));
929 cx.with_z_index(0, |cx| {
930 child.draw(origin, child_size.into(), cx);
931 });
932 cx.with_z_index(1, |cx| {
933 if ix < len - 1 {
934 Self::push_handle(
935 self.flexes.clone(),
936 state.clone(),
937 self.axis,
938 ix,
939 child_bounds,
940 bounds,
941 cx,
942 );
943 }
944 });
945
946 origin = origin.apply_along(self.axis, |val| val + child_size.along(self.axis));
947 }
948
949 cx.with_z_index(1, |cx| {
950 cx.on_mouse_event({
951 let state = state.clone();
952 move |e: &MouseUpEvent, phase, cx| {
953 if phase.bubble() {
954 state.replace(None);
955 }
956 }
957 });
958 })
959 }
960 }
961
962 impl ParentElement for PaneAxisElement {
963 fn children_mut(&mut self) -> &mut smallvec::SmallVec<[AnyElement; 2]> {
964 &mut self.children
965 }
966 }
967
968 fn flex_values_in_bounds(flexes: &[f32]) -> bool {
969 (flexes.iter().copied().sum::<f32>() - flexes.len() as f32).abs() < 0.001
970 }
971 // // use std::{cell::RefCell, iter::from_fn, ops::Range, rc::Rc};
972
973 // // use gpui::{
974 // // geometry::{
975 // // rect::Bounds<Pixels>,
976 // // vector::{vec2f, Vector2F},
977 // // },
978 // // json::{self, ToJson},
979 // // platform::{CursorStyle, MouseButton},
980 // // scene::MouseDrag,
981 // // AnyElement, Axis, CursorRegion, Element, EventContext, MouseRegion, Bounds<Pixels>Ext,
982 // // SizeConstraint, Vector2FExt, ViewContext,
983 // // };
984
985 // use crate::{
986 // pane_group::{HANDLE_HITBOX_SIZE, HORIZONTAL_MIN_SIZE, VERTICAL_MIN_SIZE},
987 // Workspace, WorkspaceSettings,
988 // };
989
990 // pub struct PaneAxisElement {
991 // axis: Axis,
992 // basis: usize,
993 // active_pane_ix: Option<usize>,
994 // flexes: Rc<RefCell<Vec<f32>>>,
995 // children: Vec<AnyElement<Workspace>>,
996 // bounding_boxes: Rc<RefCell<Vec<Option<Bounds<Pixels>>>>>,
997 // }
998
999 // impl PaneAxisElement {
1000 // pub fn new(
1001 // axis: Axis,
1002 // basis: usize,
1003 // flexes: Rc<RefCell<Vec<f32>>>,
1004 // bounding_boxes: Rc<RefCell<Vec<Option<Bounds<Pixels>>>>>,
1005 // ) -> Self {
1006 // Self {
1007 // axis,
1008 // basis,
1009 // flexes,
1010 // bounding_boxes,
1011 // active_pane_ix: None,
1012 // children: Default::default(),
1013 // }
1014 // }
1015
1016 // pub fn set_active_pane(&mut self, active_pane_ix: Option<usize>) {
1017 // self.active_pane_ix = active_pane_ix;
1018 // }
1019
1020 // fn layout_children(
1021 // &mut self,
1022 // active_pane_magnification: f32,
1023 // constraint: SizeConstraint,
1024 // remaining_space: &mut f32,
1025 // remaining_flex: &mut f32,
1026 // cross_axis_max: &mut f32,
1027 // view: &mut Workspace,
1028 // cx: &mut ViewContext<Workspace>,
1029 // ) {
1030 // let flexes = self.flexes.borrow();
1031 // let cross_axis = self.axis.invert();
1032 // for (ix, child) in self.children.iter_mut().enumerate() {
1033 // let flex = if active_pane_magnification != 1. {
1034 // if let Some(active_pane_ix) = self.active_pane_ix {
1035 // if ix == active_pane_ix {
1036 // active_pane_magnification
1037 // } else {
1038 // 1.
1039 // }
1040 // } else {
1041 // 1.
1042 // }
1043 // } else {
1044 // flexes[ix]
1045 // };
1046
1047 // let child_size = if *remaining_flex == 0.0 {
1048 // *remaining_space
1049 // } else {
1050 // let space_per_flex = *remaining_space / *remaining_flex;
1051 // space_per_flex * flex
1052 // };
1053
1054 // let child_constraint = match self.axis {
1055 // Axis::Horizontal => SizeConstraint::new(
1056 // vec2f(child_size, constraint.min.y()),
1057 // vec2f(child_size, constraint.max.y()),
1058 // ),
1059 // Axis::Vertical => SizeConstraint::new(
1060 // vec2f(constraint.min.x(), child_size),
1061 // vec2f(constraint.max.x(), child_size),
1062 // ),
1063 // };
1064 // let child_size = child.layout(child_constraint, view, cx);
1065 // *remaining_space -= child_size.along(self.axis);
1066 // *remaining_flex -= flex;
1067 // *cross_axis_max = cross_axis_max.max(child_size.along(cross_axis));
1068 // }
1069 // }
1070
1071 // fn handle_resize(
1072 // flexes: Rc<RefCell<Vec<f32>>>,
1073 // axis: Axis,
1074 // preceding_ix: usize,
1075 // child_start: Vector2F,
1076 // drag_bounds: Bounds<Pixels>,
1077 // ) -> impl Fn(MouseDrag, &mut Workspace, &mut EventContext<Workspace>) {
1078 // let size = move |ix, flexes: &[f32]| {
1079 // drag_bounds.length_along(axis) * (flexes[ix] / flexes.len() as f32)
1080 // };
1081
1082 // move |drag, workspace: &mut Workspace, cx| {
1083 // if drag.end {
1084 // // TODO: Clear cascading resize state
1085 // return;
1086 // }
1087 // let min_size = match axis {
1088 // Axis::Horizontal => HORIZONTAL_MIN_SIZE,
1089 // Axis::Vertical => VERTICAL_MIN_SIZE,
1090 // };
1091 // let mut flexes = flexes.borrow_mut();
1092
1093 // // Don't allow resizing to less than the minimum size, if elements are already too small
1094 // if min_size - 1. > size(preceding_ix, flexes.as_slice()) {
1095 // return;
1096 // }
1097
1098 // let mut proposed_current_pixel_change = (drag.position - child_start).along(axis)
1099 // - size(preceding_ix, flexes.as_slice());
1100
1101 // let flex_changes = |pixel_dx, target_ix, next: isize, flexes: &[f32]| {
1102 // let flex_change = pixel_dx / drag_bounds.length_along(axis);
1103 // let current_target_flex = flexes[target_ix] + flex_change;
1104 // let next_target_flex =
1105 // flexes[(target_ix as isize + next) as usize] - flex_change;
1106 // (current_target_flex, next_target_flex)
1107 // };
1108
1109 // let mut successors = from_fn({
1110 // let forward = proposed_current_pixel_change > 0.;
1111 // let mut ix_offset = 0;
1112 // let len = flexes.len();
1113 // move || {
1114 // let result = if forward {
1115 // (preceding_ix + 1 + ix_offset < len).then(|| preceding_ix + ix_offset)
1116 // } else {
1117 // (preceding_ix as isize - ix_offset as isize >= 0)
1118 // .then(|| preceding_ix - ix_offset)
1119 // };
1120
1121 // ix_offset += 1;
1122
1123 // result
1124 // }
1125 // });
1126
1127 // while proposed_current_pixel_change.abs() > 0. {
1128 // let Some(current_ix) = successors.next() else {
1129 // break;
1130 // };
1131
1132 // let next_target_size = f32::max(
1133 // size(current_ix + 1, flexes.as_slice()) - proposed_current_pixel_change,
1134 // min_size,
1135 // );
1136
1137 // let current_target_size = f32::max(
1138 // size(current_ix, flexes.as_slice())
1139 // + size(current_ix + 1, flexes.as_slice())
1140 // - next_target_size,
1141 // min_size,
1142 // );
1143
1144 // let current_pixel_change =
1145 // current_target_size - size(current_ix, flexes.as_slice());
1146
1147 // let (current_target_flex, next_target_flex) =
1148 // flex_changes(current_pixel_change, current_ix, 1, flexes.as_slice());
1149
1150 // flexes[current_ix] = current_target_flex;
1151 // flexes[current_ix + 1] = next_target_flex;
1152
1153 // proposed_current_pixel_change -= current_pixel_change;
1154 // }
1155
1156 // workspace.schedule_serialize(cx);
1157 // cx.notify();
1158 // }
1159 // }
1160 // }
1161
1162 // impl Extend<AnyElement<Workspace>> for PaneAxisElement {
1163 // fn extend<T: IntoIterator<Item = AnyElement<Workspace>>>(&mut self, children: T) {
1164 // self.children.extend(children);
1165 // }
1166 // }
1167
1168 // impl Element<Workspace> for PaneAxisElement {
1169 // type LayoutState = f32;
1170 // type PaintState = ();
1171
1172 // fn layout(
1173 // &mut self,
1174 // constraint: SizeConstraint,
1175 // view: &mut Workspace,
1176 // cx: &mut ViewContext<Workspace>,
1177 // ) -> (Vector2F, Self::LayoutState) {
1178 // debug_assert!(self.children.len() == self.flexes.borrow().len());
1179
1180 // let active_pane_magnification =
1181 // settings::get::<WorkspaceSettings>(cx).active_pane_magnification;
1182
1183 // let mut remaining_flex = 0.;
1184
1185 // if active_pane_magnification != 1. {
1186 // let active_pane_flex = self
1187 // .active_pane_ix
1188 // .map(|_| active_pane_magnification)
1189 // .unwrap_or(1.);
1190 // remaining_flex += self.children.len() as f32 - 1. + active_pane_flex;
1191 // } else {
1192 // for flex in self.flexes.borrow().iter() {
1193 // remaining_flex += flex;
1194 // }
1195 // }
1196
1197 // let mut cross_axis_max: f32 = 0.0;
1198 // let mut remaining_space = constraint.max_along(self.axis);
1199
1200 // if remaining_space.is_infinite() {
1201 // panic!("flex contains flexible children but has an infinite constraint along the flex axis");
1202 // }
1203
1204 // self.layout_children(
1205 // active_pane_magnification,
1206 // constraint,
1207 // &mut remaining_space,
1208 // &mut remaining_flex,
1209 // &mut cross_axis_max,
1210 // view,
1211 // cx,
1212 // );
1213
1214 // let mut size = match self.axis {
1215 // Axis::Horizontal => vec2f(constraint.max.x() - remaining_space, cross_axis_max),
1216 // Axis::Vertical => vec2f(cross_axis_max, constraint.max.y() - remaining_space),
1217 // };
1218
1219 // if constraint.min.x().is_finite() {
1220 // size.set_x(size.x().max(constraint.min.x()));
1221 // }
1222 // if constraint.min.y().is_finite() {
1223 // size.set_y(size.y().max(constraint.min.y()));
1224 // }
1225
1226 // if size.x() > constraint.max.x() {
1227 // size.set_x(constraint.max.x());
1228 // }
1229 // if size.y() > constraint.max.y() {
1230 // size.set_y(constraint.max.y());
1231 // }
1232
1233 // (size, remaining_space)
1234 // }
1235
1236 // fn paint(
1237 // &mut self,
1238 // bounds: Bounds<Pixels>,
1239 // visible_bounds: Bounds<Pixels>,
1240 // remaining_space: &mut Self::LayoutState,
1241 // view: &mut Workspace,
1242 // cx: &mut ViewContext<Workspace>,
1243 // ) -> Self::PaintState {
1244 // let can_resize = settings::get::<WorkspaceSettings>(cx).active_pane_magnification == 1.;
1245 // let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
1246
1247 // let overflowing = *remaining_space < 0.;
1248 // if overflowing {
1249 // cx.scene().push_layer(Some(visible_bounds));
1250 // }
1251
1252 // let mut child_origin = bounds.origin();
1253
1254 // let mut bounding_boxes = self.bounding_boxes.borrow_mut();
1255 // bounding_boxes.clear();
1256
1257 // let mut children_iter = self.children.iter_mut().enumerate().peekable();
1258 // while let Some((ix, child)) = children_iter.next() {
1259 // let child_start = child_origin.clone();
1260 // child.paint(child_origin, visible_bounds, view, cx);
1261
1262 // bounding_boxes.push(Some(Bounds<Pixels>::new(child_origin, child.size())));
1263
1264 // match self.axis {
1265 // Axis::Horizontal => child_origin += vec2f(child.size().x(), 0.0),
1266 // Axis::Vertical => child_origin += vec2f(0.0, child.size().y()),
1267 // }
1268
1269 // if can_resize && children_iter.peek().is_some() {
1270 // cx.scene().push_stacking_context(None, None);
1271
1272 // let handle_origin = match self.axis {
1273 // Axis::Horizontal => child_origin - vec2f(HANDLE_HITBOX_SIZE / 2., 0.0),
1274 // Axis::Vertical => child_origin - vec2f(0.0, HANDLE_HITBOX_SIZE / 2.),
1275 // };
1276
1277 // let handle_bounds = match self.axis {
1278 // Axis::Horizontal => Bounds<Pixels>::new(
1279 // handle_origin,
1280 // vec2f(HANDLE_HITBOX_SIZE, visible_bounds.height()),
1281 // ),
1282 // Axis::Vertical => Bounds<Pixels>::new(
1283 // handle_origin,
1284 // vec2f(visible_bounds.width(), HANDLE_HITBOX_SIZE),
1285 // ),
1286 // };
1287
1288 // let style = match self.axis {
1289 // Axis::Horizontal => CursorStyle::ResizeLeftRight,
1290 // Axis::Vertical => CursorStyle::ResizeUpDown,
1291 // };
1292
1293 // cx.scene().push_cursor_region(CursorRegion {
1294 // bounds: handle_bounds,
1295 // style,
1296 // });
1297
1298 // enum ResizeHandle {}
1299 // let mut mouse_region = MouseRegion::new::<ResizeHandle>(
1300 // cx.view_id(),
1301 // self.basis + ix,
1302 // handle_bounds,
1303 // );
1304 // mouse_region = mouse_region
1305 // .on_drag(
1306 // MouseButton::Left,
1307 // Self::handle_resize(
1308 // self.flexes.clone(),
1309 // self.axis,
1310 // ix,
1311 // child_start,
1312 // visible_bounds.clone(),
1313 // ),
1314 // )
1315 // .on_click(MouseButton::Left, {
1316 // let flexes = self.flexes.clone();
1317 // move |e, v: &mut Workspace, cx| {
1318 // if e.click_count >= 2 {
1319 // let mut borrow = flexes.borrow_mut();
1320 // *borrow = vec![1.; borrow.len()];
1321 // v.schedule_serialize(cx);
1322 // cx.notify();
1323 // }
1324 // }
1325 // });
1326 // cx.scene().push_mouse_region(mouse_region);
1327
1328 // cx.scene().pop_stacking_context();
1329 // }
1330 // }
1331
1332 // if overflowing {
1333 // cx.scene().pop_layer();
1334 // }
1335 // }
1336
1337 // fn rect_for_text_range(
1338 // &self,
1339 // range_utf16: Range<usize>,
1340 // _: Bounds<Pixels>,
1341 // _: Bounds<Pixels>,
1342 // _: &Self::LayoutState,
1343 // _: &Self::PaintState,
1344 // view: &Workspace,
1345 // cx: &ViewContext<Workspace>,
1346 // ) -> Option<Bounds<Pixels>> {
1347 // self.children
1348 // .iter()
1349 // .find_map(|child| child.rect_for_text_range(range_utf16.clone(), view, cx))
1350 // }
1351
1352 // fn debug(
1353 // &self,
1354 // bounds: Bounds<Pixels>,
1355 // _: &Self::LayoutState,
1356 // _: &Self::PaintState,
1357 // view: &Workspace,
1358 // cx: &ViewContext<Workspace>,
1359 // ) -> json::Value {
1360 // serde_json::json!({
1361 // "type": "PaneAxis",
1362 // "bounds": bounds.to_json(),
1363 // "axis": self.axis.to_json(),
1364 // "flexes": *self.flexes.borrow(),
1365 // "children": self.children.iter().map(|child| child.debug(view, cx)).collect::<Vec<json::Value>>()
1366 // })
1367 // }
1368 // }
1369}