1use std::{cell::RefCell, rc::Rc, sync::Arc};
2
3use crate::{
4 pane_group::element::PaneAxisElement, AppState, FollowerStatesByLeader, Pane, Workspace,
5};
6use anyhow::{anyhow, Result};
7use call::{ActiveCall, ParticipantLocation};
8use gpui::{
9 elements::*,
10 geometry::{rect::RectF, vector::Vector2F},
11 platform::{CursorStyle, MouseButton},
12 AnyViewHandle, Axis, ModelHandle, ViewContext, ViewHandle,
13};
14use project::Project;
15use serde::Deserialize;
16use theme::Theme;
17
18const HANDLE_HITBOX_SIZE: f32 = 4.0;
19const HORIZONTAL_MIN_SIZE: f32 = 80.;
20const VERTICAL_MIN_SIZE: f32 = 100.;
21
22#[derive(Clone, Debug, PartialEq)]
23pub struct PaneGroup {
24 pub(crate) root: Member,
25}
26
27impl PaneGroup {
28 pub(crate) fn with_root(root: Member) -> Self {
29 Self { root }
30 }
31
32 pub fn new(pane: ViewHandle<Pane>) -> Self {
33 Self {
34 root: Member::Pane(pane),
35 }
36 }
37
38 pub fn split(
39 &mut self,
40 old_pane: &ViewHandle<Pane>,
41 new_pane: &ViewHandle<Pane>,
42 direction: SplitDirection,
43 ) -> Result<()> {
44 match &mut self.root {
45 Member::Pane(pane) => {
46 if pane == old_pane {
47 self.root = Member::new_axis(old_pane.clone(), new_pane.clone(), direction);
48 Ok(())
49 } else {
50 Err(anyhow!("Pane not found"))
51 }
52 }
53 Member::Axis(axis) => axis.split(old_pane, new_pane, direction),
54 }
55 }
56
57 pub fn bounding_box_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<RectF> {
58 match &self.root {
59 Member::Pane(_) => None,
60 Member::Axis(axis) => axis.bounding_box_for_pane(pane),
61 }
62 }
63
64 pub fn pane_at_pixel_position(&self, coordinate: Vector2F) -> Option<&ViewHandle<Pane>> {
65 match &self.root {
66 Member::Pane(pane) => Some(pane),
67 Member::Axis(axis) => axis.pane_at_pixel_position(coordinate),
68 }
69 }
70
71 /// Returns:
72 /// - Ok(true) if it found and removed a pane
73 /// - Ok(false) if it found but did not remove the pane
74 /// - Err(_) if it did not find the pane
75 pub fn remove(&mut self, pane: &ViewHandle<Pane>) -> Result<bool> {
76 match &mut self.root {
77 Member::Pane(_) => Ok(false),
78 Member::Axis(axis) => {
79 if let Some(last_pane) = axis.remove(pane)? {
80 self.root = last_pane;
81 }
82 Ok(true)
83 }
84 }
85 }
86
87 pub(crate) fn render(
88 &self,
89 project: &ModelHandle<Project>,
90 theme: &Theme,
91 follower_states: &FollowerStatesByLeader,
92 active_call: Option<&ModelHandle<ActiveCall>>,
93 active_pane: &ViewHandle<Pane>,
94 zoomed: Option<&AnyViewHandle>,
95 app_state: &Arc<AppState>,
96 cx: &mut ViewContext<Workspace>,
97 ) -> AnyElement<Workspace> {
98 self.root.render(
99 project,
100 0,
101 theme,
102 follower_states,
103 active_call,
104 active_pane,
105 zoomed,
106 app_state,
107 cx,
108 )
109 }
110
111 pub(crate) fn panes(&self) -> Vec<&ViewHandle<Pane>> {
112 let mut panes = Vec::new();
113 self.root.collect_panes(&mut panes);
114 panes
115 }
116}
117
118#[derive(Clone, Debug, PartialEq)]
119pub(crate) enum Member {
120 Axis(PaneAxis),
121 Pane(ViewHandle<Pane>),
122}
123
124impl Member {
125 fn new_axis(
126 old_pane: ViewHandle<Pane>,
127 new_pane: ViewHandle<Pane>,
128 direction: SplitDirection,
129 ) -> Self {
130 use Axis::*;
131 use SplitDirection::*;
132
133 let axis = match direction {
134 Up | Down => Vertical,
135 Left | Right => Horizontal,
136 };
137
138 let members = match direction {
139 Up | Left => vec![Member::Pane(new_pane), Member::Pane(old_pane)],
140 Down | Right => vec![Member::Pane(old_pane), Member::Pane(new_pane)],
141 };
142
143 Member::Axis(PaneAxis::new(axis, members))
144 }
145
146 fn contains(&self, needle: &ViewHandle<Pane>) -> bool {
147 match self {
148 Member::Axis(axis) => axis.members.iter().any(|member| member.contains(needle)),
149 Member::Pane(pane) => pane == needle,
150 }
151 }
152
153 pub fn render(
154 &self,
155 project: &ModelHandle<Project>,
156 basis: usize,
157 theme: &Theme,
158 follower_states: &FollowerStatesByLeader,
159 active_call: Option<&ModelHandle<ActiveCall>>,
160 active_pane: &ViewHandle<Pane>,
161 zoomed: Option<&AnyViewHandle>,
162 app_state: &Arc<AppState>,
163 cx: &mut ViewContext<Workspace>,
164 ) -> AnyElement<Workspace> {
165 enum FollowIntoExternalProject {}
166
167 match self {
168 Member::Pane(pane) => {
169 let pane_element = if Some(&**pane) == zoomed {
170 Empty::new().into_any()
171 } else {
172 ChildView::new(pane, cx).into_any()
173 };
174
175 let leader = follower_states
176 .iter()
177 .find_map(|(leader_id, follower_states)| {
178 if follower_states.contains_key(pane) {
179 Some(leader_id)
180 } else {
181 None
182 }
183 })
184 .and_then(|leader_id| {
185 let room = active_call?.read(cx).room()?.read(cx);
186 let collaborator = project.read(cx).collaborators().get(leader_id)?;
187 let participant = room.remote_participant_for_peer_id(*leader_id)?;
188 Some((collaborator.replica_id, participant))
189 });
190
191 let border = if let Some((replica_id, _)) = leader.as_ref() {
192 let leader_color = theme.editor.replica_selection_style(*replica_id).cursor;
193 let mut border = Border::all(theme.workspace.leader_border_width, leader_color);
194 border
195 .color
196 .fade_out(1. - theme.workspace.leader_border_opacity);
197 border.overlay = true;
198 border
199 } else {
200 Border::default()
201 };
202
203 let leader_status_box = if let Some((_, leader)) = leader {
204 match leader.location {
205 ParticipantLocation::SharedProject {
206 project_id: leader_project_id,
207 } => {
208 if Some(leader_project_id) == project.read(cx).remote_id() {
209 None
210 } else {
211 let leader_user = leader.user.clone();
212 let leader_user_id = leader.user.id;
213 let app_state = Arc::downgrade(app_state);
214 Some(
215 MouseEventHandler::new::<FollowIntoExternalProject, _>(
216 pane.id(),
217 cx,
218 |_, _| {
219 Label::new(
220 format!(
221 "Follow {} on their active project",
222 leader_user.github_login,
223 ),
224 theme
225 .workspace
226 .external_location_message
227 .text
228 .clone(),
229 )
230 .contained()
231 .with_style(
232 theme.workspace.external_location_message.container,
233 )
234 },
235 )
236 .with_cursor_style(CursorStyle::PointingHand)
237 .on_click(MouseButton::Left, move |_, _, cx| {
238 if let Some(app_state) = app_state.upgrade() {
239 crate::join_remote_project(
240 leader_project_id,
241 leader_user_id,
242 app_state,
243 cx,
244 )
245 .detach_and_log_err(cx);
246 }
247 })
248 .aligned()
249 .bottom()
250 .right()
251 .into_any(),
252 )
253 }
254 }
255 ParticipantLocation::UnsharedProject => Some(
256 Label::new(
257 format!(
258 "{} is viewing an unshared Zed project",
259 leader.user.github_login
260 ),
261 theme.workspace.external_location_message.text.clone(),
262 )
263 .contained()
264 .with_style(theme.workspace.external_location_message.container)
265 .aligned()
266 .bottom()
267 .right()
268 .into_any(),
269 ),
270 ParticipantLocation::External => Some(
271 Label::new(
272 format!(
273 "{} is viewing a window outside of Zed",
274 leader.user.github_login
275 ),
276 theme.workspace.external_location_message.text.clone(),
277 )
278 .contained()
279 .with_style(theme.workspace.external_location_message.container)
280 .aligned()
281 .bottom()
282 .right()
283 .into_any(),
284 ),
285 }
286 } else {
287 None
288 };
289
290 Stack::new()
291 .with_child(pane_element.contained().with_border(border))
292 .with_children(leader_status_box)
293 .into_any()
294 }
295 Member::Axis(axis) => axis.render(
296 project,
297 basis + 1,
298 theme,
299 follower_states,
300 active_call,
301 active_pane,
302 zoomed,
303 app_state,
304 cx,
305 ),
306 }
307 }
308
309 fn collect_panes<'a>(&'a self, panes: &mut Vec<&'a ViewHandle<Pane>>) {
310 match self {
311 Member::Axis(axis) => {
312 for member in &axis.members {
313 member.collect_panes(panes);
314 }
315 }
316 Member::Pane(pane) => panes.push(pane),
317 }
318 }
319}
320
321#[derive(Clone, Debug, PartialEq)]
322pub(crate) struct PaneAxis {
323 pub axis: Axis,
324 pub members: Vec<Member>,
325 pub flexes: Rc<RefCell<Vec<f32>>>,
326 pub bounding_boxes: Rc<RefCell<Vec<Option<RectF>>>>,
327}
328
329impl PaneAxis {
330 pub fn new(axis: Axis, members: Vec<Member>) -> Self {
331 let flexes = Rc::new(RefCell::new(vec![1.; members.len()]));
332 let bounding_boxes = Rc::new(RefCell::new(vec![None; members.len()]));
333 Self {
334 axis,
335 members,
336 flexes,
337 bounding_boxes,
338 }
339 }
340
341 pub fn load(axis: Axis, members: Vec<Member>, flexes: Option<Vec<f32>>) -> Self {
342 let flexes = flexes.unwrap_or_else(|| vec![1.; members.len()]);
343 debug_assert!(members.len() == flexes.len());
344
345 let flexes = Rc::new(RefCell::new(flexes));
346 let bounding_boxes = Rc::new(RefCell::new(vec![None; members.len()]));
347 Self {
348 axis,
349 members,
350 flexes,
351 bounding_boxes,
352 }
353 }
354
355 fn split(
356 &mut self,
357 old_pane: &ViewHandle<Pane>,
358 new_pane: &ViewHandle<Pane>,
359 direction: SplitDirection,
360 ) -> Result<()> {
361 for (mut idx, member) in self.members.iter_mut().enumerate() {
362 match member {
363 Member::Axis(axis) => {
364 if axis.split(old_pane, new_pane, direction).is_ok() {
365 return Ok(());
366 }
367 }
368 Member::Pane(pane) => {
369 if pane == old_pane {
370 if direction.axis() == self.axis {
371 if direction.increasing() {
372 idx += 1;
373 }
374
375 self.members.insert(idx, Member::Pane(new_pane.clone()));
376 *self.flexes.borrow_mut() = vec![1.; self.members.len()];
377 } else {
378 *member =
379 Member::new_axis(old_pane.clone(), new_pane.clone(), direction);
380 }
381 return Ok(());
382 }
383 }
384 }
385 }
386 Err(anyhow!("Pane not found"))
387 }
388
389 fn remove(&mut self, pane_to_remove: &ViewHandle<Pane>) -> Result<Option<Member>> {
390 let mut found_pane = false;
391 let mut remove_member = None;
392 for (idx, member) in self.members.iter_mut().enumerate() {
393 match member {
394 Member::Axis(axis) => {
395 if let Ok(last_pane) = axis.remove(pane_to_remove) {
396 if let Some(last_pane) = last_pane {
397 *member = last_pane;
398 }
399 found_pane = true;
400 break;
401 }
402 }
403 Member::Pane(pane) => {
404 if pane == pane_to_remove {
405 found_pane = true;
406 remove_member = Some(idx);
407 break;
408 }
409 }
410 }
411 }
412
413 if found_pane {
414 if let Some(idx) = remove_member {
415 self.members.remove(idx);
416 *self.flexes.borrow_mut() = vec![1.; self.members.len()];
417 }
418
419 if self.members.len() == 1 {
420 let result = self.members.pop();
421 *self.flexes.borrow_mut() = vec![1.; self.members.len()];
422 Ok(result)
423 } else {
424 Ok(None)
425 }
426 } else {
427 Err(anyhow!("Pane not found"))
428 }
429 }
430
431 fn bounding_box_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<RectF> {
432 debug_assert!(self.members.len() == self.bounding_boxes.borrow().len());
433
434 for (idx, member) in self.members.iter().enumerate() {
435 match member {
436 Member::Pane(found) => {
437 if pane == found {
438 return self.bounding_boxes.borrow()[idx];
439 }
440 }
441 Member::Axis(axis) => {
442 if let Some(rect) = axis.bounding_box_for_pane(pane) {
443 return Some(rect);
444 }
445 }
446 }
447 }
448 None
449 }
450
451 fn pane_at_pixel_position(&self, coordinate: Vector2F) -> Option<&ViewHandle<Pane>> {
452 debug_assert!(self.members.len() == self.bounding_boxes.borrow().len());
453
454 let bounding_boxes = self.bounding_boxes.borrow();
455
456 for (idx, member) in self.members.iter().enumerate() {
457 if let Some(coordinates) = bounding_boxes[idx] {
458 if coordinates.contains_point(coordinate) {
459 return match member {
460 Member::Pane(found) => Some(found),
461 Member::Axis(axis) => axis.pane_at_pixel_position(coordinate),
462 };
463 }
464 }
465 }
466 None
467 }
468
469 fn render(
470 &self,
471 project: &ModelHandle<Project>,
472 basis: usize,
473 theme: &Theme,
474 follower_state: &FollowerStatesByLeader,
475 active_call: Option<&ModelHandle<ActiveCall>>,
476 active_pane: &ViewHandle<Pane>,
477 zoomed: Option<&AnyViewHandle>,
478 app_state: &Arc<AppState>,
479 cx: &mut ViewContext<Workspace>,
480 ) -> AnyElement<Workspace> {
481 debug_assert!(self.members.len() == self.flexes.borrow().len());
482
483 let mut pane_axis = PaneAxisElement::new(
484 self.axis,
485 basis,
486 self.flexes.clone(),
487 self.bounding_boxes.clone(),
488 );
489 let mut active_pane_ix = None;
490
491 let mut members = self.members.iter().enumerate().peekable();
492 while let Some((ix, member)) = members.next() {
493 let last = members.peek().is_none();
494
495 if member.contains(active_pane) {
496 active_pane_ix = Some(ix);
497 }
498
499 let mut member = member.render(
500 project,
501 (basis + ix) * 10,
502 theme,
503 follower_state,
504 active_call,
505 active_pane,
506 zoomed,
507 app_state,
508 cx,
509 );
510
511 if !last {
512 let mut border = theme.workspace.pane_divider;
513 border.left = false;
514 border.right = false;
515 border.top = false;
516 border.bottom = false;
517
518 match self.axis {
519 Axis::Vertical => border.bottom = true,
520 Axis::Horizontal => border.right = true,
521 }
522
523 member = member.contained().with_border(border).into_any();
524 }
525
526 pane_axis = pane_axis.with_child(member.into_any());
527 }
528 pane_axis.set_active_pane(active_pane_ix);
529 pane_axis.into_any()
530 }
531}
532
533#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
534pub enum SplitDirection {
535 Up,
536 Down,
537 Left,
538 Right,
539}
540
541impl SplitDirection {
542 pub fn all() -> [Self; 4] {
543 [Self::Up, Self::Down, Self::Left, Self::Right]
544 }
545
546 pub fn edge(&self, rect: RectF) -> f32 {
547 match self {
548 Self::Up => rect.min_y(),
549 Self::Down => rect.max_y(),
550 Self::Left => rect.min_x(),
551 Self::Right => rect.max_x(),
552 }
553 }
554
555 // Returns a new rectangle which shares an edge in SplitDirection and has `size` along SplitDirection
556 pub fn along_edge(&self, rect: RectF, size: f32) -> RectF {
557 match self {
558 Self::Up => RectF::new(rect.origin(), Vector2F::new(rect.width(), size)),
559 Self::Down => RectF::new(
560 rect.lower_left() - Vector2F::new(0., size),
561 Vector2F::new(rect.width(), size),
562 ),
563 Self::Left => RectF::new(rect.origin(), Vector2F::new(size, rect.height())),
564 Self::Right => RectF::new(
565 rect.upper_right() - Vector2F::new(size, 0.),
566 Vector2F::new(size, rect.height()),
567 ),
568 }
569 }
570
571 pub fn axis(&self) -> Axis {
572 match self {
573 Self::Up | Self::Down => Axis::Vertical,
574 Self::Left | Self::Right => Axis::Horizontal,
575 }
576 }
577
578 pub fn increasing(&self) -> bool {
579 match self {
580 Self::Left | Self::Up => false,
581 Self::Down | Self::Right => true,
582 }
583 }
584}
585
586mod element {
587 use std::{cell::RefCell, iter::from_fn, ops::Range, rc::Rc};
588
589 use gpui::{
590 geometry::{
591 rect::RectF,
592 vector::{vec2f, Vector2F},
593 },
594 json::{self, ToJson},
595 platform::{CursorStyle, MouseButton},
596 scene::MouseDrag,
597 AnyElement, Axis, CursorRegion, Element, EventContext, MouseRegion, RectFExt,
598 SizeConstraint, Vector2FExt, ViewContext,
599 };
600
601 use crate::{
602 pane_group::{HANDLE_HITBOX_SIZE, HORIZONTAL_MIN_SIZE, VERTICAL_MIN_SIZE},
603 Workspace, WorkspaceSettings,
604 };
605
606 pub struct PaneAxisElement {
607 axis: Axis,
608 basis: usize,
609 active_pane_ix: Option<usize>,
610 flexes: Rc<RefCell<Vec<f32>>>,
611 children: Vec<AnyElement<Workspace>>,
612 bounding_boxes: Rc<RefCell<Vec<Option<RectF>>>>,
613 }
614
615 impl PaneAxisElement {
616 pub fn new(
617 axis: Axis,
618 basis: usize,
619 flexes: Rc<RefCell<Vec<f32>>>,
620 bounding_boxes: Rc<RefCell<Vec<Option<RectF>>>>,
621 ) -> Self {
622 Self {
623 axis,
624 basis,
625 flexes,
626 bounding_boxes,
627 active_pane_ix: None,
628 children: Default::default(),
629 }
630 }
631
632 pub fn set_active_pane(&mut self, active_pane_ix: Option<usize>) {
633 self.active_pane_ix = active_pane_ix;
634 }
635
636 fn layout_children(
637 &mut self,
638 active_pane_magnification: f32,
639 constraint: SizeConstraint,
640 remaining_space: &mut f32,
641 remaining_flex: &mut f32,
642 cross_axis_max: &mut f32,
643 view: &mut Workspace,
644 cx: &mut ViewContext<Workspace>,
645 ) {
646 let flexes = self.flexes.borrow();
647 let cross_axis = self.axis.invert();
648 for (ix, child) in self.children.iter_mut().enumerate() {
649 let flex = if active_pane_magnification != 1. {
650 if let Some(active_pane_ix) = self.active_pane_ix {
651 if ix == active_pane_ix {
652 active_pane_magnification
653 } else {
654 1.
655 }
656 } else {
657 1.
658 }
659 } else {
660 flexes[ix]
661 };
662
663 let child_size = if *remaining_flex == 0.0 {
664 *remaining_space
665 } else {
666 let space_per_flex = *remaining_space / *remaining_flex;
667 space_per_flex * flex
668 };
669
670 let child_constraint = match self.axis {
671 Axis::Horizontal => SizeConstraint::new(
672 vec2f(child_size, constraint.min.y()),
673 vec2f(child_size, constraint.max.y()),
674 ),
675 Axis::Vertical => SizeConstraint::new(
676 vec2f(constraint.min.x(), child_size),
677 vec2f(constraint.max.x(), child_size),
678 ),
679 };
680 let child_size = child.layout(child_constraint, view, cx);
681 *remaining_space -= child_size.along(self.axis);
682 *remaining_flex -= flex;
683 *cross_axis_max = cross_axis_max.max(child_size.along(cross_axis));
684 }
685 }
686
687 fn handle_resize(
688 flexes: Rc<RefCell<Vec<f32>>>,
689 axis: Axis,
690 preceding_ix: usize,
691 child_start: Vector2F,
692 drag_bounds: RectF,
693 ) -> impl Fn(MouseDrag, &mut Workspace, &mut EventContext<Workspace>) {
694 let size = move |ix, flexes: &[f32]| {
695 drag_bounds.length_along(axis) * (flexes[ix] / flexes.len() as f32)
696 };
697
698 move |drag, workspace: &mut Workspace, cx| {
699 if drag.end {
700 // TODO: Clear cascading resize state
701 return;
702 }
703 let min_size = match axis {
704 Axis::Horizontal => HORIZONTAL_MIN_SIZE,
705 Axis::Vertical => VERTICAL_MIN_SIZE,
706 };
707 let mut flexes = flexes.borrow_mut();
708
709 // Don't allow resizing to less than the minimum size, if elements are already too small
710 if min_size - 1. > size(preceding_ix, flexes.as_slice()) {
711 return;
712 }
713
714 let mut proposed_current_pixel_change = (drag.position - child_start).along(axis)
715 - size(preceding_ix, flexes.as_slice());
716
717 let flex_changes = |pixel_dx, target_ix, next: isize, flexes: &[f32]| {
718 let flex_change = pixel_dx / drag_bounds.length_along(axis);
719 let current_target_flex = flexes[target_ix] + flex_change;
720 let next_target_flex =
721 flexes[(target_ix as isize + next) as usize] - flex_change;
722 (current_target_flex, next_target_flex)
723 };
724
725 let mut successors = from_fn({
726 let forward = proposed_current_pixel_change > 0.;
727 let mut ix_offset = 0;
728 let len = flexes.len();
729 move || {
730 let result = if forward {
731 (preceding_ix + 1 + ix_offset < len).then(|| preceding_ix + ix_offset)
732 } else {
733 (preceding_ix as isize - ix_offset as isize >= 0)
734 .then(|| preceding_ix - ix_offset)
735 };
736
737 ix_offset += 1;
738
739 result
740 }
741 });
742
743 while proposed_current_pixel_change.abs() > 0. {
744 let Some(current_ix) = successors.next() else {
745 break;
746 };
747
748 let next_target_size = f32::max(
749 size(current_ix + 1, flexes.as_slice()) - proposed_current_pixel_change,
750 min_size,
751 );
752
753 let current_target_size = f32::max(
754 size(current_ix, flexes.as_slice())
755 + size(current_ix + 1, flexes.as_slice())
756 - next_target_size,
757 min_size,
758 );
759
760 let current_pixel_change =
761 current_target_size - size(current_ix, flexes.as_slice());
762
763 let (current_target_flex, next_target_flex) =
764 flex_changes(current_pixel_change, current_ix, 1, flexes.as_slice());
765
766 flexes[current_ix] = current_target_flex;
767 flexes[current_ix + 1] = next_target_flex;
768
769 proposed_current_pixel_change -= current_pixel_change;
770 }
771
772 workspace.schedule_serialize(cx);
773 cx.notify();
774 }
775 }
776 }
777
778 impl Extend<AnyElement<Workspace>> for PaneAxisElement {
779 fn extend<T: IntoIterator<Item = AnyElement<Workspace>>>(&mut self, children: T) {
780 self.children.extend(children);
781 }
782 }
783
784 impl Element<Workspace> for PaneAxisElement {
785 type LayoutState = f32;
786 type PaintState = ();
787
788 fn layout(
789 &mut self,
790 constraint: SizeConstraint,
791 view: &mut Workspace,
792 cx: &mut ViewContext<Workspace>,
793 ) -> (Vector2F, Self::LayoutState) {
794 debug_assert!(self.children.len() == self.flexes.borrow().len());
795
796 let active_pane_magnification =
797 settings::get::<WorkspaceSettings>(cx).active_pane_magnification;
798
799 let mut remaining_flex = 0.;
800
801 if active_pane_magnification != 1. {
802 let active_pane_flex = self
803 .active_pane_ix
804 .map(|_| active_pane_magnification)
805 .unwrap_or(1.);
806 remaining_flex += self.children.len() as f32 - 1. + active_pane_flex;
807 } else {
808 for flex in self.flexes.borrow().iter() {
809 remaining_flex += flex;
810 }
811 }
812
813 let mut cross_axis_max: f32 = 0.0;
814 let mut remaining_space = constraint.max_along(self.axis);
815
816 if remaining_space.is_infinite() {
817 panic!("flex contains flexible children but has an infinite constraint along the flex axis");
818 }
819
820 self.layout_children(
821 active_pane_magnification,
822 constraint,
823 &mut remaining_space,
824 &mut remaining_flex,
825 &mut cross_axis_max,
826 view,
827 cx,
828 );
829
830 let mut size = match self.axis {
831 Axis::Horizontal => vec2f(constraint.max.x() - remaining_space, cross_axis_max),
832 Axis::Vertical => vec2f(cross_axis_max, constraint.max.y() - remaining_space),
833 };
834
835 if constraint.min.x().is_finite() {
836 size.set_x(size.x().max(constraint.min.x()));
837 }
838 if constraint.min.y().is_finite() {
839 size.set_y(size.y().max(constraint.min.y()));
840 }
841
842 if size.x() > constraint.max.x() {
843 size.set_x(constraint.max.x());
844 }
845 if size.y() > constraint.max.y() {
846 size.set_y(constraint.max.y());
847 }
848
849 (size, remaining_space)
850 }
851
852 fn paint(
853 &mut self,
854 bounds: RectF,
855 visible_bounds: RectF,
856 remaining_space: &mut Self::LayoutState,
857 view: &mut Workspace,
858 cx: &mut ViewContext<Workspace>,
859 ) -> Self::PaintState {
860 let can_resize = settings::get::<WorkspaceSettings>(cx).active_pane_magnification == 1.;
861 let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
862
863 let overflowing = *remaining_space < 0.;
864 if overflowing {
865 cx.scene().push_layer(Some(visible_bounds));
866 }
867
868 let mut child_origin = bounds.origin();
869
870 let mut bounding_boxes = self.bounding_boxes.borrow_mut();
871 bounding_boxes.clear();
872
873 let mut children_iter = self.children.iter_mut().enumerate().peekable();
874 while let Some((ix, child)) = children_iter.next() {
875 let child_start = child_origin.clone();
876 child.paint(child_origin, visible_bounds, view, cx);
877
878 bounding_boxes.push(Some(RectF::new(child_origin, child.size())));
879
880 match self.axis {
881 Axis::Horizontal => child_origin += vec2f(child.size().x(), 0.0),
882 Axis::Vertical => child_origin += vec2f(0.0, child.size().y()),
883 }
884
885 if can_resize && children_iter.peek().is_some() {
886 cx.scene().push_stacking_context(None, None);
887
888 let handle_origin = match self.axis {
889 Axis::Horizontal => child_origin - vec2f(HANDLE_HITBOX_SIZE / 2., 0.0),
890 Axis::Vertical => child_origin - vec2f(0.0, HANDLE_HITBOX_SIZE / 2.),
891 };
892
893 let handle_bounds = match self.axis {
894 Axis::Horizontal => RectF::new(
895 handle_origin,
896 vec2f(HANDLE_HITBOX_SIZE, visible_bounds.height()),
897 ),
898 Axis::Vertical => RectF::new(
899 handle_origin,
900 vec2f(visible_bounds.width(), HANDLE_HITBOX_SIZE),
901 ),
902 };
903
904 let style = match self.axis {
905 Axis::Horizontal => CursorStyle::ResizeLeftRight,
906 Axis::Vertical => CursorStyle::ResizeUpDown,
907 };
908
909 cx.scene().push_cursor_region(CursorRegion {
910 bounds: handle_bounds,
911 style,
912 });
913
914 enum ResizeHandle {}
915 let mut mouse_region = MouseRegion::new::<ResizeHandle>(
916 cx.view_id(),
917 self.basis + ix,
918 handle_bounds,
919 );
920 mouse_region = mouse_region
921 .on_drag(
922 MouseButton::Left,
923 Self::handle_resize(
924 self.flexes.clone(),
925 self.axis,
926 ix,
927 child_start,
928 visible_bounds.clone(),
929 ),
930 )
931 .on_click(MouseButton::Left, {
932 let flexes = self.flexes.clone();
933 move |e, v: &mut Workspace, cx| {
934 if e.click_count >= 2 {
935 let mut borrow = flexes.borrow_mut();
936 *borrow = vec![1.; borrow.len()];
937 v.schedule_serialize(cx);
938 cx.notify();
939 }
940 }
941 });
942 cx.scene().push_mouse_region(mouse_region);
943
944 cx.scene().pop_stacking_context();
945 }
946 }
947
948 if overflowing {
949 cx.scene().pop_layer();
950 }
951 }
952
953 fn rect_for_text_range(
954 &self,
955 range_utf16: Range<usize>,
956 _: RectF,
957 _: RectF,
958 _: &Self::LayoutState,
959 _: &Self::PaintState,
960 view: &Workspace,
961 cx: &ViewContext<Workspace>,
962 ) -> Option<RectF> {
963 self.children
964 .iter()
965 .find_map(|child| child.rect_for_text_range(range_utf16.clone(), view, cx))
966 }
967
968 fn debug(
969 &self,
970 bounds: RectF,
971 _: &Self::LayoutState,
972 _: &Self::PaintState,
973 view: &Workspace,
974 cx: &ViewContext<Workspace>,
975 ) -> json::Value {
976 serde_json::json!({
977 "type": "PaneAxis",
978 "bounds": bounds.to_json(),
979 "axis": self.axis.to_json(),
980 "flexes": *self.flexes.borrow(),
981 "children": self.children.iter().map(|child| child.debug(view, cx)).collect::<Vec<json::Value>>()
982 })
983 }
984 }
985}