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                let leader = follower_states.get(pane).and_then(|state| {
176                    let room = active_call?.read(cx).room()?.read(cx);
177                    room.remote_participant_for_peer_id(state.leader_id)
178                });
179
180                let mut leader_border = None;
181                let mut leader_status_box = None;
182                if let Some(leader) = &leader {
183                    let mut leader_color = cx
184                        .theme()
185                        .players()
186                        .color_for_participant(leader.participant_index.0)
187                        .cursor;
188                    leader_color.fade_out(0.3);
189                    leader_border = Some(leader_color);
190
191                    leader_status_box = match leader.location {
192                        ParticipantLocation::SharedProject {
193                            project_id: leader_project_id,
194                        } => {
195                            if Some(leader_project_id) == project.read(cx).remote_id() {
196                                None
197                            } else {
198                                let leader_user = leader.user.clone();
199                                let leader_user_id = leader.user.id;
200                                Some(
201                                    Button::new(
202                                        ("leader-status", pane.entity_id()),
203                                        format!(
204                                            "Follow {} to their active project",
205                                            leader_user.github_login,
206                                        ),
207                                    )
208                                    .on_click(cx.listener(
209                                        move |this, _, cx| {
210                                            crate::join_remote_project(
211                                                leader_project_id,
212                                                leader_user_id,
213                                                this.app_state().clone(),
214                                                cx,
215                                            )
216                                            .detach_and_log_err(cx);
217                                        },
218                                    )),
219                                )
220                            }
221                        }
222                        ParticipantLocation::UnsharedProject => Some(Button::new(
223                            ("leader-status", pane.entity_id()),
224                            format!(
225                                "{} is viewing an unshared Zed project",
226                                leader.user.github_login
227                            ),
228                        )),
229                        ParticipantLocation::External => Some(Button::new(
230                            ("leader-status", pane.entity_id()),
231                            format!(
232                                "{} is viewing a window outside of Zed",
233                                leader.user.github_login
234                            ),
235                        )),
236                    };
237                }
238
239                div()
240                    .relative()
241                    .size_full()
242                    .child(pane.clone())
243                    .when_some(leader_border, |this, color| {
244                        this.border_2().border_color(color)
245                    })
246                    .when_some(leader_status_box, |this, status_box| {
247                        this.child(
248                            div()
249                                .absolute()
250                                .w_96()
251                                .bottom_3()
252                                .right_3()
253                                .z_index(1)
254                                .child(status_box),
255                        )
256                    })
257                    .into_any()
258
259                // let el = div()
260                //     .flex()
261                //     .flex_1()
262                //     .gap_px()
263                //     .w_full()
264                //     .h_full()
265                //     .bg(cx.theme().colors().editor)
266                //     .children();
267            }
268            Member::Axis(axis) => axis
269                .render(
270                    project,
271                    basis + 1,
272                    follower_states,
273                    active_pane,
274                    zoomed,
275                    app_state,
276                    cx,
277                )
278                .into_any(),
279        }
280
281        // enum FollowIntoExternalProject {}
282
283        // match self {
284        //     Member::Pane(pane) => {
285        //         let pane_element = if Some(&**pane) == zoomed {
286        //             Empty::new().into_any()
287        //         } else {
288        //             ChildView::new(pane, cx).into_any()
289        //         };
290
291        //         let leader = follower_states.get(pane).and_then(|state| {
292        //             let room = active_call?.read(cx).room()?.read(cx);
293        //             room.remote_participant_for_peer_id(state.leader_id)
294        //         });
295
296        //         let mut leader_border = Border::default();
297        //         let mut leader_status_box = None;
298        //         if let Some(leader) = &leader {
299        //             let leader_color = theme
300        //                 .editor
301        //                 .selection_style_for_room_participant(leader.participant_index.0)
302        //                 .cursor;
303        //             leader_border = Border::all(theme.workspace.leader_border_width, leader_color);
304        //             leader_border
305        //                 .color
306        //                 .fade_out(1. - theme.workspace.leader_border_opacity);
307        //             leader_border.overlay = true;
308
309        //             leader_status_box = match leader.location {
310        //                 ParticipantLocation::SharedProject {
311        //                     project_id: leader_project_id,
312        //                 } => {
313        //                     if Some(leader_project_id) == project.read(cx).remote_id() {
314        //                         None
315        //                     } else {
316        //                         let leader_user = leader.user.clone();
317        //                         let leader_user_id = leader.user.id;
318        //                         Some(
319        //                             MouseEventHandler::new::<FollowIntoExternalProject, _>(
320        //                                 pane.id(),
321        //                                 cx,
322        //                                 |_, _| {
323        //                                     Label::new(
324        //                                         format!(
325        //                                             "Follow {} to their active project",
326        //                                             leader_user.github_login,
327        //                                         ),
328        //                                         theme
329        //                                             .workspace
330        //                                             .external_location_message
331        //                                             .text
332        //                                             .clone(),
333        //                                     )
334        //                                     .contained()
335        //                                     .with_style(
336        //                                         theme.workspace.external_location_message.container,
337        //                                     )
338        //                                 },
339        //                             )
340        //                             .with_cursor_style(CursorStyle::PointingHand)
341        //                             .on_click(MouseButton::Left, move |_, this, cx| {
342        //                                 crate::join_remote_project(
343        //                                     leader_project_id,
344        //                                     leader_user_id,
345        //                                     this.app_state().clone(),
346        //                                     cx,
347        //                                 )
348        //                                 .detach_and_log_err(cx);
349        //                             })
350        //                             .aligned()
351        //                             .bottom()
352        //                             .right()
353        //                             .into_any(),
354        //                         )
355        //                     }
356        //                 }
357        //                 ParticipantLocation::UnsharedProject => Some(
358        //                     Label::new(
359        //                         format!(
360        //                             "{} is viewing an unshared Zed project",
361        //                             leader.user.github_login
362        //                         ),
363        //                         theme.workspace.external_location_message.text.clone(),
364        //                     )
365        //                     .contained()
366        //                     .with_style(theme.workspace.external_location_message.container)
367        //                     .aligned()
368        //                     .bottom()
369        //                     .right()
370        //                     .into_any(),
371        //                 ),
372        //                 ParticipantLocation::External => Some(
373        //                     Label::new(
374        //                         format!(
375        //                             "{} is viewing a window outside of Zed",
376        //                             leader.user.github_login
377        //                         ),
378        //                         theme.workspace.external_location_message.text.clone(),
379        //                     )
380        //                     .contained()
381        //                     .with_style(theme.workspace.external_location_message.container)
382        //                     .aligned()
383        //                     .bottom()
384        //                     .right()
385        //                     .into_any(),
386        //                 ),
387        //             };
388        //         }
389
390        //         Stack::new()
391        //             .with_child(pane_element.contained().with_border(leader_border))
392        //             .with_children(leader_status_box)
393        //             .into_any()
394        //     }
395        //     Member::Axis(axis) => axis.render(
396        //         project,
397        //         basis + 1,
398        //         theme,
399        //         follower_states,
400        //         active_call,
401        //         active_pane,
402        //         zoomed,
403        //         app_state,
404        //         cx,
405        //     ),
406        // }
407    }
408
409    fn collect_panes<'a>(&'a self, panes: &mut Vec<&'a View<Pane>>) {
410        match self {
411            Member::Axis(axis) => {
412                for member in &axis.members {
413                    member.collect_panes(panes);
414                }
415            }
416            Member::Pane(pane) => panes.push(pane),
417        }
418    }
419}
420
421#[derive(Clone)]
422pub(crate) struct PaneAxis {
423    pub axis: Axis,
424    pub members: Vec<Member>,
425    pub flexes: Arc<Mutex<Vec<f32>>>,
426    pub bounding_boxes: Arc<Mutex<Vec<Option<Bounds<Pixels>>>>>,
427}
428
429impl PaneAxis {
430    pub fn new(axis: Axis, members: Vec<Member>) -> Self {
431        let flexes = Arc::new(Mutex::new(vec![1.; members.len()]));
432        let bounding_boxes = Arc::new(Mutex::new(vec![None; members.len()]));
433        Self {
434            axis,
435            members,
436            flexes,
437            bounding_boxes,
438        }
439    }
440
441    pub fn load(axis: Axis, members: Vec<Member>, flexes: Option<Vec<f32>>) -> Self {
442        let flexes = flexes.unwrap_or_else(|| vec![1.; members.len()]);
443        debug_assert!(members.len() == flexes.len());
444
445        let flexes = Arc::new(Mutex::new(flexes));
446        let bounding_boxes = Arc::new(Mutex::new(vec![None; members.len()]));
447        Self {
448            axis,
449            members,
450            flexes,
451            bounding_boxes,
452        }
453    }
454
455    fn split(
456        &mut self,
457        old_pane: &View<Pane>,
458        new_pane: &View<Pane>,
459        direction: SplitDirection,
460    ) -> Result<()> {
461        for (mut idx, member) in self.members.iter_mut().enumerate() {
462            match member {
463                Member::Axis(axis) => {
464                    if axis.split(old_pane, new_pane, direction).is_ok() {
465                        return Ok(());
466                    }
467                }
468                Member::Pane(pane) => {
469                    if pane == old_pane {
470                        if direction.axis() == self.axis {
471                            if direction.increasing() {
472                                idx += 1;
473                            }
474
475                            self.members.insert(idx, Member::Pane(new_pane.clone()));
476                            *self.flexes.lock() = vec![1.; self.members.len()];
477                        } else {
478                            *member =
479                                Member::new_axis(old_pane.clone(), new_pane.clone(), direction);
480                        }
481                        return Ok(());
482                    }
483                }
484            }
485        }
486        Err(anyhow!("Pane not found"))
487    }
488
489    fn remove(&mut self, pane_to_remove: &View<Pane>) -> Result<Option<Member>> {
490        let mut found_pane = false;
491        let mut remove_member = None;
492        for (idx, member) in self.members.iter_mut().enumerate() {
493            match member {
494                Member::Axis(axis) => {
495                    if let Ok(last_pane) = axis.remove(pane_to_remove) {
496                        if let Some(last_pane) = last_pane {
497                            *member = last_pane;
498                        }
499                        found_pane = true;
500                        break;
501                    }
502                }
503                Member::Pane(pane) => {
504                    if pane == pane_to_remove {
505                        found_pane = true;
506                        remove_member = Some(idx);
507                        break;
508                    }
509                }
510            }
511        }
512
513        if found_pane {
514            if let Some(idx) = remove_member {
515                self.members.remove(idx);
516                *self.flexes.lock() = vec![1.; self.members.len()];
517            }
518
519            if self.members.len() == 1 {
520                let result = self.members.pop();
521                *self.flexes.lock() = vec![1.; self.members.len()];
522                Ok(result)
523            } else {
524                Ok(None)
525            }
526        } else {
527            Err(anyhow!("Pane not found"))
528        }
529    }
530
531    fn swap(&mut self, from: &View<Pane>, to: &View<Pane>) {
532        for member in self.members.iter_mut() {
533            match member {
534                Member::Axis(axis) => axis.swap(from, to),
535                Member::Pane(pane) => {
536                    if pane == from {
537                        *member = Member::Pane(to.clone());
538                    } else if pane == to {
539                        *member = Member::Pane(from.clone())
540                    }
541                }
542            }
543        }
544    }
545
546    fn bounding_box_for_pane(&self, pane: &View<Pane>) -> Option<Bounds<Pixels>> {
547        debug_assert!(self.members.len() == self.bounding_boxes.lock().len());
548
549        for (idx, member) in self.members.iter().enumerate() {
550            match member {
551                Member::Pane(found) => {
552                    if pane == found {
553                        return self.bounding_boxes.lock()[idx];
554                    }
555                }
556                Member::Axis(axis) => {
557                    if let Some(rect) = axis.bounding_box_for_pane(pane) {
558                        return Some(rect);
559                    }
560                }
561            }
562        }
563        None
564    }
565
566    fn pane_at_pixel_position(&self, coordinate: Point<Pixels>) -> Option<&View<Pane>> {
567        debug_assert!(self.members.len() == self.bounding_boxes.lock().len());
568
569        let bounding_boxes = self.bounding_boxes.lock();
570
571        for (idx, member) in self.members.iter().enumerate() {
572            if let Some(coordinates) = bounding_boxes[idx] {
573                if coordinates.contains(&coordinate) {
574                    return match member {
575                        Member::Pane(found) => Some(found),
576                        Member::Axis(axis) => axis.pane_at_pixel_position(coordinate),
577                    };
578                }
579            }
580        }
581        None
582    }
583
584    fn render(
585        &self,
586        project: &Model<Project>,
587        basis: usize,
588        follower_states: &HashMap<View<Pane>, FollowerState>,
589        active_pane: &View<Pane>,
590        zoomed: Option<&AnyWeakView>,
591        app_state: &Arc<AppState>,
592        cx: &mut ViewContext<Workspace>,
593    ) -> gpui::AnyElement {
594        debug_assert!(self.members.len() == self.flexes.lock().len());
595        let mut active_pane_ix = None;
596
597        pane_axis(
598            self.axis,
599            basis,
600            self.flexes.clone(),
601            self.bounding_boxes.clone(),
602        )
603        .children(self.members.iter().enumerate().map(|(ix, member)| {
604            if member.contains(active_pane) {
605                active_pane_ix = Some(ix);
606            }
607
608            match member {
609                Member::Axis(axis) => axis
610                    .render(
611                        project,
612                        (basis + ix) * 10,
613                        follower_states,
614                        active_pane,
615                        zoomed,
616                        app_state,
617                        cx,
618                    )
619                    .into_any_element(),
620                Member::Pane(pane) => div()
621                    .size_full()
622                    .border()
623                    .child(pane.clone())
624                    .into_any_element(),
625            }
626        }))
627        .with_active_pane(active_pane_ix)
628        .into_any_element()
629    }
630}
631
632#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
633pub enum SplitDirection {
634    Up,
635    Down,
636    Left,
637    Right,
638}
639
640impl SplitDirection {
641    pub fn all() -> [Self; 4] {
642        [Self::Up, Self::Down, Self::Left, Self::Right]
643    }
644
645    pub fn edge(&self, rect: Bounds<Pixels>) -> Pixels {
646        match self {
647            Self::Up => rect.origin.y,
648            Self::Down => rect.lower_left().y,
649            Self::Left => rect.lower_left().x,
650            Self::Right => rect.lower_right().x,
651        }
652    }
653
654    pub fn along_edge(&self, bounds: Bounds<Pixels>, length: Pixels) -> Bounds<Pixels> {
655        match self {
656            Self::Up => Bounds {
657                origin: bounds.origin,
658                size: size(bounds.size.width, length),
659            },
660            Self::Down => Bounds {
661                origin: point(bounds.lower_left().x, bounds.lower_left().y - length),
662                size: size(bounds.size.width, length),
663            },
664            Self::Left => Bounds {
665                origin: bounds.origin,
666                size: size(length, bounds.size.height),
667            },
668            Self::Right => Bounds {
669                origin: point(bounds.lower_right().x - length, bounds.lower_left().y),
670                size: size(length, bounds.size.height),
671            },
672        }
673    }
674
675    pub fn axis(&self) -> Axis {
676        match self {
677            Self::Up | Self::Down => Axis::Vertical,
678            Self::Left | Self::Right => Axis::Horizontal,
679        }
680    }
681
682    pub fn increasing(&self) -> bool {
683        match self {
684            Self::Left | Self::Up => false,
685            Self::Down | Self::Right => true,
686        }
687    }
688}
689
690mod element {
691
692    use std::{cell::RefCell, iter, rc::Rc, sync::Arc};
693
694    use gpui::{
695        px, relative, Along, AnyElement, Axis, Bounds, CursorStyle, Element, IntoElement,
696        MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point, Size, Style,
697        WindowContext,
698    };
699    use parking_lot::Mutex;
700    use smallvec::SmallVec;
701
702    use super::{HANDLE_HITBOX_SIZE, HORIZONTAL_MIN_SIZE, VERTICAL_MIN_SIZE};
703
704    pub fn pane_axis(
705        axis: Axis,
706        basis: usize,
707        flexes: Arc<Mutex<Vec<f32>>>,
708        bounding_boxes: Arc<Mutex<Vec<Option<Bounds<Pixels>>>>>,
709    ) -> PaneAxisElement {
710        PaneAxisElement {
711            axis,
712            basis,
713            flexes,
714            bounding_boxes,
715            children: SmallVec::new(),
716            active_pane_ix: None,
717        }
718    }
719
720    pub struct PaneAxisElement {
721        axis: Axis,
722        basis: usize,
723        flexes: Arc<Mutex<Vec<f32>>>,
724        bounding_boxes: Arc<Mutex<Vec<Option<Bounds<Pixels>>>>>,
725        children: SmallVec<[AnyElement; 2]>,
726        active_pane_ix: Option<usize>,
727    }
728
729    impl PaneAxisElement {
730        pub fn with_active_pane(mut self, active_pane_ix: Option<usize>) -> Self {
731            self.active_pane_ix = active_pane_ix;
732            self
733        }
734
735        fn compute_resize(
736            flexes: &Arc<Mutex<Vec<f32>>>,
737            e: &MouseMoveEvent,
738            ix: usize,
739            axis: Axis,
740            child_start: Point<Pixels>,
741            container_size: Size<Pixels>,
742            cx: &mut WindowContext,
743        ) {
744            let min_size = match axis {
745                Axis::Horizontal => px(HORIZONTAL_MIN_SIZE),
746                Axis::Vertical => px(VERTICAL_MIN_SIZE),
747            };
748            let mut flexes = flexes.lock();
749            debug_assert!(flex_values_in_bounds(flexes.as_slice()));
750
751            let size = move |ix, flexes: &[f32]| {
752                container_size.along(axis) * (flexes[ix] / flexes.len() as f32)
753            };
754
755            // Don't allow resizing to less than the minimum size, if elements are already too small
756            if min_size - px(1.) > size(ix, flexes.as_slice()) {
757                return;
758            }
759
760            let mut proposed_current_pixel_change =
761                (e.position - child_start).along(axis) - size(ix, flexes.as_slice());
762
763            let flex_changes = |pixel_dx, target_ix, next: isize, flexes: &[f32]| {
764                let flex_change = pixel_dx / container_size.along(axis);
765                let current_target_flex = flexes[target_ix] + flex_change;
766                let next_target_flex = flexes[(target_ix as isize + next) as usize] - flex_change;
767                (current_target_flex, next_target_flex)
768            };
769
770            let mut successors = iter::from_fn({
771                let forward = proposed_current_pixel_change > px(0.);
772                let mut ix_offset = 0;
773                let len = flexes.len();
774                move || {
775                    let result = if forward {
776                        (ix + 1 + ix_offset < len).then(|| ix + ix_offset)
777                    } else {
778                        (ix as isize - ix_offset as isize >= 0).then(|| ix - ix_offset)
779                    };
780
781                    ix_offset += 1;
782
783                    result
784                }
785            });
786
787            while proposed_current_pixel_change.abs() > px(0.) {
788                let Some(current_ix) = successors.next() else {
789                    break;
790                };
791
792                let next_target_size = Pixels::max(
793                    size(current_ix + 1, flexes.as_slice()) - proposed_current_pixel_change,
794                    min_size,
795                );
796
797                let current_target_size = Pixels::max(
798                    size(current_ix, flexes.as_slice()) + size(current_ix + 1, flexes.as_slice())
799                        - next_target_size,
800                    min_size,
801                );
802
803                let current_pixel_change =
804                    current_target_size - size(current_ix, flexes.as_slice());
805
806                let (current_target_flex, next_target_flex) =
807                    flex_changes(current_pixel_change, current_ix, 1, flexes.as_slice());
808
809                flexes[current_ix] = current_target_flex;
810                flexes[current_ix + 1] = next_target_flex;
811
812                proposed_current_pixel_change -= current_pixel_change;
813            }
814
815            // todo!(schedule serialize)
816            // workspace.schedule_serialize(cx);
817            cx.notify();
818        }
819
820        fn push_handle(
821            flexes: Arc<Mutex<Vec<f32>>>,
822            dragged_handle: Rc<RefCell<Option<usize>>>,
823            axis: Axis,
824            ix: usize,
825            pane_bounds: Bounds<Pixels>,
826            axis_bounds: Bounds<Pixels>,
827            cx: &mut WindowContext,
828        ) {
829            let handle_bounds = Bounds {
830                origin: pane_bounds.origin.apply_along(axis, |o| {
831                    o + pane_bounds.size.along(axis) - Pixels(HANDLE_HITBOX_SIZE / 2.)
832                }),
833                size: pane_bounds
834                    .size
835                    .apply_along(axis, |_| Pixels(HANDLE_HITBOX_SIZE)),
836            };
837
838            cx.with_z_index(3, |cx| {
839                if handle_bounds.contains(&cx.mouse_position()) {
840                    cx.set_cursor_style(match axis {
841                        Axis::Vertical => CursorStyle::ResizeUpDown,
842                        Axis::Horizontal => CursorStyle::ResizeLeftRight,
843                    })
844                }
845
846                cx.add_opaque_layer(handle_bounds);
847
848                cx.on_mouse_event({
849                    let dragged_handle = dragged_handle.clone();
850                    move |e: &MouseDownEvent, phase, _cx| {
851                        if phase.bubble() && handle_bounds.contains(&e.position) {
852                            dragged_handle.replace(Some(ix));
853                        }
854                    }
855                });
856                cx.on_mouse_event(move |e: &MouseMoveEvent, phase, cx| {
857                    let dragged_handle = dragged_handle.borrow();
858                    if phase.bubble() && *dragged_handle == Some(ix) {
859                        Self::compute_resize(
860                            &flexes,
861                            e,
862                            ix,
863                            axis,
864                            pane_bounds.origin,
865                            axis_bounds.size,
866                            cx,
867                        )
868                    }
869                });
870            });
871        }
872    }
873
874    impl IntoElement for PaneAxisElement {
875        type Element = Self;
876
877        fn element_id(&self) -> Option<ui::prelude::ElementId> {
878            Some(self.basis.into())
879        }
880
881        fn into_element(self) -> Self::Element {
882            self
883        }
884    }
885
886    impl Element for PaneAxisElement {
887        type State = Rc<RefCell<Option<usize>>>;
888
889        fn layout(
890            &mut self,
891            state: Option<Self::State>,
892            cx: &mut ui::prelude::WindowContext,
893        ) -> (gpui::LayoutId, Self::State) {
894            let mut style = Style::default();
895            style.size.width = relative(1.).into();
896            style.size.height = relative(1.).into();
897            let layout_id = cx.request_layout(&style, None);
898            let dragged_pane = state.unwrap_or_else(|| Rc::new(RefCell::new(None)));
899            (layout_id, dragged_pane)
900        }
901
902        fn paint(
903            &mut self,
904            bounds: gpui::Bounds<ui::prelude::Pixels>,
905            state: &mut Self::State,
906            cx: &mut ui::prelude::WindowContext,
907        ) {
908            let flexes = self.flexes.lock().clone();
909            let len = self.children.len();
910            debug_assert!(flexes.len() == len);
911            debug_assert!(flex_values_in_bounds(flexes.as_slice()));
912
913            let mut origin = bounds.origin;
914            let space_per_flex = bounds.size.along(self.axis) / len as f32;
915
916            let mut bounding_boxes = self.bounding_boxes.lock();
917            bounding_boxes.clear();
918
919            for (ix, child) in self.children.iter_mut().enumerate() {
920                //todo!(active_pane_magnification)
921                // If usign active pane magnification, need to switch to using
922                // 1 for all non-active panes, and then the magnification for the
923                // active pane.
924                let child_size = bounds
925                    .size
926                    .apply_along(self.axis, |_| space_per_flex * flexes[ix]);
927
928                let child_bounds = Bounds {
929                    origin,
930                    size: child_size,
931                };
932                bounding_boxes.push(Some(child_bounds));
933                cx.with_z_index(0, |cx| {
934                    child.draw(origin, child_size.into(), cx);
935                });
936                cx.with_z_index(1, |cx| {
937                    if ix < len - 1 {
938                        Self::push_handle(
939                            self.flexes.clone(),
940                            state.clone(),
941                            self.axis,
942                            ix,
943                            child_bounds,
944                            bounds,
945                            cx,
946                        );
947                    }
948                });
949
950                origin = origin.apply_along(self.axis, |val| val + child_size.along(self.axis));
951            }
952
953            cx.with_z_index(1, |cx| {
954                cx.on_mouse_event({
955                    let state = state.clone();
956                    move |_: &MouseUpEvent, phase, _cx| {
957                        if phase.bubble() {
958                            state.replace(None);
959                        }
960                    }
961                });
962            })
963        }
964    }
965
966    impl ParentElement for PaneAxisElement {
967        fn children_mut(&mut self) -> &mut smallvec::SmallVec<[AnyElement; 2]> {
968            &mut self.children
969        }
970    }
971
972    fn flex_values_in_bounds(flexes: &[f32]) -> bool {
973        (flexes.iter().copied().sum::<f32>() - flexes.len() as f32).abs() < 0.001
974    }
975}