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