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