pane_group.rs

  1use crate::{pane_group::element::pane_axis, AppState, FollowerState, Pane, Workspace};
  2use anyhow::{anyhow, Result};
  3use call::{ActiveCall, ParticipantLocation};
  4use collections::HashMap;
  5use gpui::{
  6    point, size, AnyWeakView, Axis, Bounds, Entity as _, IntoElement, Model, Pixels, Point, View,
  7    ViewContext,
  8};
  9use parking_lot::Mutex;
 10use project::Project;
 11use serde::Deserialize;
 12use std::sync::Arc;
 13use ui::{prelude::*, Button};
 14
 15const HANDLE_HITBOX_SIZE: f32 = 10.0; //todo!(change this back to 4)
 16const HORIZONTAL_MIN_SIZE: f32 = 80.;
 17const VERTICAL_MIN_SIZE: f32 = 100.;
 18
 19#[derive(Clone)]
 20pub struct PaneGroup {
 21    pub(crate) root: Member,
 22}
 23
 24impl PaneGroup {
 25    pub(crate) fn with_root(root: Member) -> Self {
 26        Self { root }
 27    }
 28
 29    pub fn new(pane: View<Pane>) -> Self {
 30        Self {
 31            root: Member::Pane(pane),
 32        }
 33    }
 34
 35    pub fn split(
 36        &mut self,
 37        old_pane: &View<Pane>,
 38        new_pane: &View<Pane>,
 39        direction: SplitDirection,
 40    ) -> Result<()> {
 41        match &mut self.root {
 42            Member::Pane(pane) => {
 43                if pane == old_pane {
 44                    self.root = Member::new_axis(old_pane.clone(), new_pane.clone(), direction);
 45                    Ok(())
 46                } else {
 47                    Err(anyhow!("Pane not found"))
 48                }
 49            }
 50            Member::Axis(axis) => axis.split(old_pane, new_pane, direction),
 51        }
 52    }
 53
 54    pub fn bounding_box_for_pane(&self, pane: &View<Pane>) -> Option<Bounds<Pixels>> {
 55        match &self.root {
 56            Member::Pane(_) => None,
 57            Member::Axis(axis) => axis.bounding_box_for_pane(pane),
 58        }
 59    }
 60
 61    pub fn pane_at_pixel_position(&self, coordinate: Point<Pixels>) -> Option<&View<Pane>> {
 62        match &self.root {
 63            Member::Pane(pane) => Some(pane),
 64            Member::Axis(axis) => axis.pane_at_pixel_position(coordinate),
 65        }
 66    }
 67
 68    /// Returns:
 69    /// - Ok(true) if it found and removed a pane
 70    /// - Ok(false) if it found but did not remove the pane
 71    /// - Err(_) if it did not find the pane
 72    pub fn remove(&mut self, pane: &View<Pane>) -> Result<bool> {
 73        match &mut self.root {
 74            Member::Pane(_) => Ok(false),
 75            Member::Axis(axis) => {
 76                if let Some(last_pane) = axis.remove(pane)? {
 77                    self.root = last_pane;
 78                }
 79                Ok(true)
 80            }
 81        }
 82    }
 83
 84    pub fn swap(&mut self, from: &View<Pane>, to: &View<Pane>) {
 85        match &mut self.root {
 86            Member::Pane(_) => {}
 87            Member::Axis(axis) => axis.swap(from, to),
 88        };
 89    }
 90
 91    pub(crate) fn render(
 92        &self,
 93        project: &Model<Project>,
 94        follower_states: &HashMap<View<Pane>, FollowerState>,
 95        active_call: Option<&Model<ActiveCall>>,
 96        active_pane: &View<Pane>,
 97        zoomed: Option<&AnyWeakView>,
 98        app_state: &Arc<AppState>,
 99        cx: &mut ViewContext<Workspace>,
100    ) -> impl IntoElement {
101        self.root.render(
102            project,
103            0,
104            follower_states,
105            active_call,
106            active_pane,
107            zoomed,
108            app_state,
109            cx,
110        )
111    }
112
113    pub(crate) fn panes(&self) -> Vec<&View<Pane>> {
114        let mut panes = Vec::new();
115        self.root.collect_panes(&mut panes);
116        panes
117    }
118
119    pub(crate) fn first_pane(&self) -> View<Pane> {
120        self.root.first_pane()
121    }
122}
123
124#[derive(Clone)]
125pub(crate) enum Member {
126    Axis(PaneAxis),
127    Pane(View<Pane>),
128}
129
130impl Member {
131    fn new_axis(old_pane: View<Pane>, new_pane: View<Pane>, direction: SplitDirection) -> Self {
132        use Axis::*;
133        use SplitDirection::*;
134
135        let axis = match direction {
136            Up | Down => Vertical,
137            Left | Right => Horizontal,
138        };
139
140        let members = match direction {
141            Up | Left => vec![Member::Pane(new_pane), Member::Pane(old_pane)],
142            Down | Right => vec![Member::Pane(old_pane), Member::Pane(new_pane)],
143        };
144
145        Member::Axis(PaneAxis::new(axis, members))
146    }
147
148    fn contains(&self, needle: &View<Pane>) -> bool {
149        match self {
150            Member::Axis(axis) => axis.members.iter().any(|member| member.contains(needle)),
151            Member::Pane(pane) => pane == needle,
152        }
153    }
154
155    fn first_pane(&self) -> View<Pane> {
156        match self {
157            Member::Axis(axis) => axis.members[0].first_pane(),
158            Member::Pane(pane) => pane.clone(),
159        }
160    }
161
162    pub fn render(
163        &self,
164        project: &Model<Project>,
165        basis: usize,
166        follower_states: &HashMap<View<Pane>, FollowerState>,
167        active_call: Option<&Model<ActiveCall>>,
168        active_pane: &View<Pane>,
169        zoomed: Option<&AnyWeakView>,
170        app_state: &Arc<AppState>,
171        cx: &mut ViewContext<Workspace>,
172    ) -> impl IntoElement {
173        match self {
174            Member::Pane(pane) => {
175                if zoomed == Some(&pane.downgrade().into()) {
176                    return div().into_any();
177                }
178
179                let leader = follower_states.get(pane).and_then(|state| {
180                    let room = active_call?.read(cx).room()?.read(cx);
181                    room.remote_participant_for_peer_id(state.leader_id)
182                });
183
184                let mut leader_border = None;
185                let mut leader_status_box = None;
186                if let Some(leader) = &leader {
187                    let mut leader_color = cx
188                        .theme()
189                        .players()
190                        .color_for_participant(leader.participant_index.0)
191                        .cursor;
192                    leader_color.fade_out(0.3);
193                    leader_border = Some(leader_color);
194
195                    leader_status_box = match leader.location {
196                        ParticipantLocation::SharedProject {
197                            project_id: leader_project_id,
198                        } => {
199                            if Some(leader_project_id) == project.read(cx).remote_id() {
200                                None
201                            } else {
202                                let leader_user = leader.user.clone();
203                                let leader_user_id = leader.user.id;
204                                Some(
205                                    Button::new(
206                                        ("leader-status", pane.entity_id()),
207                                        format!(
208                                            "Follow {} to their active project",
209                                            leader_user.github_login,
210                                        ),
211                                    )
212                                    .on_click(cx.listener(
213                                        move |this, _, cx| {
214                                            crate::join_remote_project(
215                                                leader_project_id,
216                                                leader_user_id,
217                                                this.app_state().clone(),
218                                                cx,
219                                            )
220                                            .detach_and_log_err(cx);
221                                        },
222                                    )),
223                                )
224                            }
225                        }
226                        ParticipantLocation::UnsharedProject => Some(Button::new(
227                            ("leader-status", pane.entity_id()),
228                            format!(
229                                "{} is viewing an unshared Zed project",
230                                leader.user.github_login
231                            ),
232                        )),
233                        ParticipantLocation::External => Some(Button::new(
234                            ("leader-status", pane.entity_id()),
235                            format!(
236                                "{} is viewing a window outside of Zed",
237                                leader.user.github_login
238                            ),
239                        )),
240                    };
241                }
242
243                div()
244                    .relative()
245                    .flex_1()
246                    .size_full()
247                    .child(pane.clone())
248                    .when_some(leader_border, |this, color| {
249                        this.child(
250                            div()
251                                .absolute()
252                                .size_full()
253                                .left_0()
254                                .top_0()
255                                .border_2()
256                                .border_color(color),
257                        )
258                    })
259                    .when_some(leader_status_box, |this, status_box| {
260                        this.child(
261                            div()
262                                .absolute()
263                                .w_96()
264                                .bottom_3()
265                                .right_3()
266                                .z_index(1)
267                                .child(status_box),
268                        )
269                    })
270                    .into_any()
271
272                // let el = div()
273                //     .flex()
274                //     .flex_1()
275                //     .gap_px()
276                //     .w_full()
277                //     .h_full()
278                //     .bg(cx.theme().colors().editor)
279                //     .children();
280            }
281            Member::Axis(axis) => axis
282                .render(
283                    project,
284                    basis + 1,
285                    follower_states,
286                    active_call,
287                    active_pane,
288                    zoomed,
289                    app_state,
290                    cx,
291                )
292                .into_any(),
293        }
294    }
295
296    fn collect_panes<'a>(&'a self, panes: &mut Vec<&'a View<Pane>>) {
297        match self {
298            Member::Axis(axis) => {
299                for member in &axis.members {
300                    member.collect_panes(panes);
301                }
302            }
303            Member::Pane(pane) => panes.push(pane),
304        }
305    }
306}
307
308#[derive(Clone)]
309pub(crate) struct PaneAxis {
310    pub axis: Axis,
311    pub members: Vec<Member>,
312    pub flexes: Arc<Mutex<Vec<f32>>>,
313    pub bounding_boxes: Arc<Mutex<Vec<Option<Bounds<Pixels>>>>>,
314}
315
316impl PaneAxis {
317    pub fn new(axis: Axis, members: Vec<Member>) -> Self {
318        let flexes = Arc::new(Mutex::new(vec![1.; members.len()]));
319        let bounding_boxes = Arc::new(Mutex::new(vec![None; members.len()]));
320        Self {
321            axis,
322            members,
323            flexes,
324            bounding_boxes,
325        }
326    }
327
328    pub fn load(axis: Axis, members: Vec<Member>, flexes: Option<Vec<f32>>) -> Self {
329        let flexes = flexes.unwrap_or_else(|| vec![1.; members.len()]);
330        debug_assert!(members.len() == flexes.len());
331
332        let flexes = Arc::new(Mutex::new(flexes));
333        let bounding_boxes = Arc::new(Mutex::new(vec![None; members.len()]));
334        Self {
335            axis,
336            members,
337            flexes,
338            bounding_boxes,
339        }
340    }
341
342    fn split(
343        &mut self,
344        old_pane: &View<Pane>,
345        new_pane: &View<Pane>,
346        direction: SplitDirection,
347    ) -> Result<()> {
348        for (mut idx, member) in self.members.iter_mut().enumerate() {
349            match member {
350                Member::Axis(axis) => {
351                    if axis.split(old_pane, new_pane, direction).is_ok() {
352                        return Ok(());
353                    }
354                }
355                Member::Pane(pane) => {
356                    if pane == old_pane {
357                        if direction.axis() == self.axis {
358                            if direction.increasing() {
359                                idx += 1;
360                            }
361
362                            self.members.insert(idx, Member::Pane(new_pane.clone()));
363                            *self.flexes.lock() = vec![1.; self.members.len()];
364                        } else {
365                            *member =
366                                Member::new_axis(old_pane.clone(), new_pane.clone(), direction);
367                        }
368                        return Ok(());
369                    }
370                }
371            }
372        }
373        Err(anyhow!("Pane not found"))
374    }
375
376    fn remove(&mut self, pane_to_remove: &View<Pane>) -> Result<Option<Member>> {
377        let mut found_pane = false;
378        let mut remove_member = None;
379        for (idx, member) in self.members.iter_mut().enumerate() {
380            match member {
381                Member::Axis(axis) => {
382                    if let Ok(last_pane) = axis.remove(pane_to_remove) {
383                        if let Some(last_pane) = last_pane {
384                            *member = last_pane;
385                        }
386                        found_pane = true;
387                        break;
388                    }
389                }
390                Member::Pane(pane) => {
391                    if pane == pane_to_remove {
392                        found_pane = true;
393                        remove_member = Some(idx);
394                        break;
395                    }
396                }
397            }
398        }
399
400        if found_pane {
401            if let Some(idx) = remove_member {
402                self.members.remove(idx);
403                *self.flexes.lock() = vec![1.; self.members.len()];
404            }
405
406            if self.members.len() == 1 {
407                let result = self.members.pop();
408                *self.flexes.lock() = vec![1.; self.members.len()];
409                Ok(result)
410            } else {
411                Ok(None)
412            }
413        } else {
414            Err(anyhow!("Pane not found"))
415        }
416    }
417
418    fn swap(&mut self, from: &View<Pane>, to: &View<Pane>) {
419        for member in self.members.iter_mut() {
420            match member {
421                Member::Axis(axis) => axis.swap(from, to),
422                Member::Pane(pane) => {
423                    if pane == from {
424                        *member = Member::Pane(to.clone());
425                    } else if pane == to {
426                        *member = Member::Pane(from.clone())
427                    }
428                }
429            }
430        }
431    }
432
433    fn bounding_box_for_pane(&self, pane: &View<Pane>) -> Option<Bounds<Pixels>> {
434        debug_assert!(self.members.len() == self.bounding_boxes.lock().len());
435
436        for (idx, member) in self.members.iter().enumerate() {
437            match member {
438                Member::Pane(found) => {
439                    if pane == found {
440                        return self.bounding_boxes.lock()[idx];
441                    }
442                }
443                Member::Axis(axis) => {
444                    if let Some(rect) = axis.bounding_box_for_pane(pane) {
445                        return Some(rect);
446                    }
447                }
448            }
449        }
450        None
451    }
452
453    fn pane_at_pixel_position(&self, coordinate: Point<Pixels>) -> Option<&View<Pane>> {
454        debug_assert!(self.members.len() == self.bounding_boxes.lock().len());
455
456        let bounding_boxes = self.bounding_boxes.lock();
457
458        for (idx, member) in self.members.iter().enumerate() {
459            if let Some(coordinates) = bounding_boxes[idx] {
460                if coordinates.contains(&coordinate) {
461                    return match member {
462                        Member::Pane(found) => Some(found),
463                        Member::Axis(axis) => axis.pane_at_pixel_position(coordinate),
464                    };
465                }
466            }
467        }
468        None
469    }
470
471    fn render(
472        &self,
473        project: &Model<Project>,
474        basis: usize,
475        follower_states: &HashMap<View<Pane>, FollowerState>,
476        active_call: Option<&Model<ActiveCall>>,
477        active_pane: &View<Pane>,
478        zoomed: Option<&AnyWeakView>,
479        app_state: &Arc<AppState>,
480        cx: &mut ViewContext<Workspace>,
481    ) -> gpui::AnyElement {
482        debug_assert!(self.members.len() == self.flexes.lock().len());
483        let mut active_pane_ix = None;
484
485        pane_axis(
486            self.axis,
487            basis,
488            self.flexes.clone(),
489            self.bounding_boxes.clone(),
490            cx.view().downgrade(),
491        )
492        .children(self.members.iter().enumerate().map(|(ix, member)| {
493            if member.contains(active_pane) {
494                active_pane_ix = Some(ix);
495            }
496            member
497                .render(
498                    project,
499                    (basis + ix) * 10,
500                    follower_states,
501                    active_call,
502                    active_pane,
503                    zoomed,
504                    app_state,
505                    cx,
506                )
507                .into_any_element()
508        }))
509        .with_active_pane(active_pane_ix)
510        .into_any_element()
511    }
512}
513
514#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
515pub enum SplitDirection {
516    Up,
517    Down,
518    Left,
519    Right,
520}
521
522impl SplitDirection {
523    pub fn all() -> [Self; 4] {
524        [Self::Up, Self::Down, Self::Left, Self::Right]
525    }
526
527    pub fn edge(&self, rect: Bounds<Pixels>) -> Pixels {
528        match self {
529            Self::Up => rect.origin.y,
530            Self::Down => rect.lower_left().y,
531            Self::Left => rect.lower_left().x,
532            Self::Right => rect.lower_right().x,
533        }
534    }
535
536    pub fn along_edge(&self, bounds: Bounds<Pixels>, length: Pixels) -> Bounds<Pixels> {
537        match self {
538            Self::Up => Bounds {
539                origin: bounds.origin,
540                size: size(bounds.size.width, length),
541            },
542            Self::Down => Bounds {
543                origin: point(bounds.lower_left().x, bounds.lower_left().y - length),
544                size: size(bounds.size.width, length),
545            },
546            Self::Left => Bounds {
547                origin: bounds.origin,
548                size: size(length, bounds.size.height),
549            },
550            Self::Right => Bounds {
551                origin: point(bounds.lower_right().x - length, bounds.lower_left().y),
552                size: size(length, bounds.size.height),
553            },
554        }
555    }
556
557    pub fn axis(&self) -> Axis {
558        match self {
559            Self::Up | Self::Down => Axis::Vertical,
560            Self::Left | Self::Right => Axis::Horizontal,
561        }
562    }
563
564    pub fn increasing(&self) -> bool {
565        match self {
566            Self::Left | Self::Up => false,
567            Self::Down | Self::Right => true,
568        }
569    }
570}
571
572mod element {
573
574    use std::{cell::RefCell, iter, rc::Rc, sync::Arc};
575
576    use gpui::{
577        px, relative, Along, AnyElement, Axis, Bounds, CursorStyle, Element, InteractiveBounds,
578        IntoElement, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point,
579        Size, Style, WeakView, WindowContext,
580    };
581    use parking_lot::Mutex;
582    use smallvec::SmallVec;
583    use ui::prelude::*;
584    use util::ResultExt;
585
586    use crate::Workspace;
587
588    use super::{HANDLE_HITBOX_SIZE, HORIZONTAL_MIN_SIZE, VERTICAL_MIN_SIZE};
589
590    const DIVIDER_SIZE: f32 = 1.0;
591
592    pub(super) fn pane_axis(
593        axis: Axis,
594        basis: usize,
595        flexes: Arc<Mutex<Vec<f32>>>,
596        bounding_boxes: Arc<Mutex<Vec<Option<Bounds<Pixels>>>>>,
597        workspace: WeakView<Workspace>,
598    ) -> PaneAxisElement {
599        PaneAxisElement {
600            axis,
601            basis,
602            flexes,
603            bounding_boxes,
604            children: SmallVec::new(),
605            active_pane_ix: None,
606            workspace,
607        }
608    }
609
610    pub struct PaneAxisElement {
611        axis: Axis,
612        basis: usize,
613        flexes: Arc<Mutex<Vec<f32>>>,
614        bounding_boxes: Arc<Mutex<Vec<Option<Bounds<Pixels>>>>>,
615        children: SmallVec<[AnyElement; 2]>,
616        active_pane_ix: Option<usize>,
617        workspace: WeakView<Workspace>,
618    }
619
620    impl PaneAxisElement {
621        pub fn with_active_pane(mut self, active_pane_ix: Option<usize>) -> Self {
622            self.active_pane_ix = active_pane_ix;
623            self
624        }
625
626        fn compute_resize(
627            flexes: &Arc<Mutex<Vec<f32>>>,
628            e: &MouseMoveEvent,
629            ix: usize,
630            axis: Axis,
631            child_start: Point<Pixels>,
632            container_size: Size<Pixels>,
633            workspace: WeakView<Workspace>,
634            cx: &mut WindowContext,
635        ) {
636            let min_size = match axis {
637                Axis::Horizontal => px(HORIZONTAL_MIN_SIZE),
638                Axis::Vertical => px(VERTICAL_MIN_SIZE),
639            };
640            let mut flexes = flexes.lock();
641            debug_assert!(flex_values_in_bounds(flexes.as_slice()));
642
643            let size = move |ix, flexes: &[f32]| {
644                container_size.along(axis) * (flexes[ix] / flexes.len() as f32)
645            };
646
647            // Don't allow resizing to less than the minimum size, if elements are already too small
648            if min_size - px(1.) > size(ix, flexes.as_slice()) {
649                return;
650            }
651
652            let mut proposed_current_pixel_change =
653                (e.position - child_start).along(axis) - size(ix, flexes.as_slice());
654
655            let flex_changes = |pixel_dx, target_ix, next: isize, flexes: &[f32]| {
656                let flex_change = pixel_dx / container_size.along(axis);
657                let current_target_flex = flexes[target_ix] + flex_change;
658                let next_target_flex = flexes[(target_ix as isize + next) as usize] - flex_change;
659                (current_target_flex, next_target_flex)
660            };
661
662            let mut successors = iter::from_fn({
663                let forward = proposed_current_pixel_change > px(0.);
664                let mut ix_offset = 0;
665                let len = flexes.len();
666                move || {
667                    let result = if forward {
668                        (ix + 1 + ix_offset < len).then(|| ix + ix_offset)
669                    } else {
670                        (ix as isize - ix_offset as isize >= 0).then(|| ix - ix_offset)
671                    };
672
673                    ix_offset += 1;
674
675                    result
676                }
677            });
678
679            while proposed_current_pixel_change.abs() > px(0.) {
680                let Some(current_ix) = successors.next() else {
681                    break;
682                };
683
684                let next_target_size = Pixels::max(
685                    size(current_ix + 1, flexes.as_slice()) - proposed_current_pixel_change,
686                    min_size,
687                );
688
689                let current_target_size = Pixels::max(
690                    size(current_ix, flexes.as_slice()) + size(current_ix + 1, flexes.as_slice())
691                        - next_target_size,
692                    min_size,
693                );
694
695                let current_pixel_change =
696                    current_target_size - size(current_ix, flexes.as_slice());
697
698                let (current_target_flex, next_target_flex) =
699                    flex_changes(current_pixel_change, current_ix, 1, flexes.as_slice());
700
701                flexes[current_ix] = current_target_flex;
702                flexes[current_ix + 1] = next_target_flex;
703
704                proposed_current_pixel_change -= current_pixel_change;
705            }
706
707            // todo!(schedule serialize)
708            workspace
709                .update(cx, |this, cx| this.schedule_serialize(cx))
710                .log_err();
711            cx.notify();
712        }
713
714        fn push_handle(
715            flexes: Arc<Mutex<Vec<f32>>>,
716            dragged_handle: Rc<RefCell<Option<usize>>>,
717            axis: Axis,
718            ix: usize,
719            pane_bounds: Bounds<Pixels>,
720            axis_bounds: Bounds<Pixels>,
721            workspace: WeakView<Workspace>,
722            cx: &mut WindowContext,
723        ) {
724            let handle_bounds = Bounds {
725                origin: pane_bounds.origin.apply_along(axis, |origin| {
726                    origin + pane_bounds.size.along(axis) - px(HANDLE_HITBOX_SIZE / 2.)
727                }),
728                size: pane_bounds
729                    .size
730                    .apply_along(axis, |_| px(HANDLE_HITBOX_SIZE)),
731            };
732            let divider_bounds = Bounds {
733                origin: pane_bounds
734                    .origin
735                    .apply_along(axis, |origin| origin + pane_bounds.size.along(axis)),
736                size: pane_bounds.size.apply_along(axis, |_| px(DIVIDER_SIZE)),
737            };
738
739            cx.with_z_index(3, |cx| {
740                let interactive_handle_bounds = InteractiveBounds {
741                    bounds: handle_bounds,
742                    stacking_order: cx.stacking_order().clone(),
743                };
744                if interactive_handle_bounds.visibly_contains(&cx.mouse_position(), cx) {
745                    cx.set_cursor_style(match axis {
746                        Axis::Vertical => CursorStyle::ResizeUpDown,
747                        Axis::Horizontal => CursorStyle::ResizeLeftRight,
748                    })
749                }
750
751                cx.add_opaque_layer(handle_bounds);
752                cx.paint_quad(gpui::fill(divider_bounds, cx.theme().colors().border));
753
754                cx.on_mouse_event({
755                    let dragged_handle = dragged_handle.clone();
756                    let flexes = flexes.clone();
757                    let workspace = workspace.clone();
758                    move |e: &MouseDownEvent, phase, cx| {
759                        if phase.bubble() && handle_bounds.contains(&e.position) {
760                            dragged_handle.replace(Some(ix));
761                            if e.click_count >= 2 {
762                                let mut borrow = flexes.lock();
763                                *borrow = vec![1.; borrow.len()];
764                                workspace
765                                    .update(cx, |this, cx| this.schedule_serialize(cx))
766                                    .log_err();
767                                cx.notify();
768                            }
769                        }
770                    }
771                });
772                cx.on_mouse_event({
773                    let workspace = workspace.clone();
774                    move |e: &MouseMoveEvent, phase, cx| {
775                        let dragged_handle = dragged_handle.borrow();
776
777                        if phase.bubble() && *dragged_handle == Some(ix) {
778                            Self::compute_resize(
779                                &flexes,
780                                e,
781                                ix,
782                                axis,
783                                pane_bounds.origin,
784                                axis_bounds.size,
785                                workspace.clone(),
786                                cx,
787                            )
788                        }
789                    }
790                });
791            });
792        }
793    }
794
795    impl IntoElement for PaneAxisElement {
796        type Element = Self;
797
798        fn element_id(&self) -> Option<ui::prelude::ElementId> {
799            Some(self.basis.into())
800        }
801
802        fn into_element(self) -> Self::Element {
803            self
804        }
805    }
806
807    impl Element for PaneAxisElement {
808        type State = Rc<RefCell<Option<usize>>>;
809
810        fn request_layout(
811            &mut self,
812            state: Option<Self::State>,
813            cx: &mut ui::prelude::WindowContext,
814        ) -> (gpui::LayoutId, Self::State) {
815            let mut style = Style::default();
816            style.flex_grow = 1.;
817            style.flex_shrink = 1.;
818            style.flex_basis = relative(0.).into();
819            style.size.width = relative(1.).into();
820            style.size.height = relative(1.).into();
821            let layout_id = cx.request_layout(&style, None);
822            let dragged_pane = state.unwrap_or_else(|| Rc::new(RefCell::new(None)));
823            (layout_id, dragged_pane)
824        }
825
826        fn paint(
827            &mut self,
828            bounds: gpui::Bounds<ui::prelude::Pixels>,
829            state: &mut Self::State,
830            cx: &mut ui::prelude::WindowContext,
831        ) {
832            let flexes = self.flexes.lock().clone();
833            let len = self.children.len();
834            debug_assert!(flexes.len() == len);
835            debug_assert!(flex_values_in_bounds(flexes.as_slice()));
836
837            let mut origin = bounds.origin;
838            let space_per_flex = bounds.size.along(self.axis) / len as f32;
839
840            let mut bounding_boxes = self.bounding_boxes.lock();
841            bounding_boxes.clear();
842
843            for (ix, child) in self.children.iter_mut().enumerate() {
844                //todo!(active_pane_magnification)
845                // If using active pane magnification, need to switch to using
846                // 1 for all non-active panes, and then the magnification for the
847                // active pane.
848                let child_size = bounds
849                    .size
850                    .apply_along(self.axis, |_| space_per_flex * flexes[ix]);
851
852                let child_bounds = Bounds {
853                    origin,
854                    size: child_size,
855                };
856                bounding_boxes.push(Some(child_bounds));
857                cx.with_z_index(0, |cx| {
858                    child.draw(origin, child_size.into(), cx);
859                });
860                cx.with_z_index(1, |cx| {
861                    if ix < len - 1 {
862                        Self::push_handle(
863                            self.flexes.clone(),
864                            state.clone(),
865                            self.axis,
866                            ix,
867                            child_bounds,
868                            bounds,
869                            self.workspace.clone(),
870                            cx,
871                        );
872                    }
873                });
874
875                origin = origin.apply_along(self.axis, |val| val + child_size.along(self.axis));
876            }
877
878            cx.with_z_index(1, |cx| {
879                cx.on_mouse_event({
880                    let state = state.clone();
881                    move |_: &MouseUpEvent, phase, _cx| {
882                        if phase.bubble() {
883                            state.replace(None);
884                        }
885                    }
886                });
887            })
888        }
889    }
890
891    impl ParentElement for PaneAxisElement {
892        fn children_mut(&mut self) -> &mut smallvec::SmallVec<[AnyElement; 2]> {
893            &mut self.children
894        }
895    }
896
897    fn flex_values_in_bounds(flexes: &[f32]) -> bool {
898        (flexes.iter().copied().sum::<f32>() - flexes.len() as f32).abs() < 0.001
899    }
900}