1use std::rc::Rc;
4
5use gpui::{
6 App, Axis, Div, ElementId, IntoElement, Pixels, Point, RenderOnce, ScrollHandle, Stateful,
7 Window, div, prelude::*, px,
8};
9use motion::Painter;
10
11use super::{GAP, Strip};
12use crate::drag::{self, Domain};
13
14pub struct Reorder<Id> {
16 domain: Domain<(), Id>,
17 scroll: ScrollHandle,
18}
19
20impl<Id> Clone for Reorder<Id> {
21 fn clone(&self) -> Self {
22 Self {
23 domain: self.domain.clone(),
24 scroll: self.scroll.clone(),
25 }
26 }
27}
28
29impl<Id: Clone + PartialEq + 'static> Reorder<Id> {
30 pub fn new(painter: Painter) -> Self {
31 Self {
32 domain: Domain::new(painter),
33 scroll: ScrollHandle::new(),
34 }
35 }
36
37 pub fn bar(
39 &self,
40 id: impl Into<ElementId>,
41 strip: &Strip<Id>,
42 tabs: impl IntoIterator<Item = (Id, Stateful<Div>)>,
43 ) -> Bar<Id> {
44 let tabs: Vec<_> = tabs.into_iter().collect();
45 debug_assert!(tabs.iter().map(|(id, _)| id).eq(strip.tabs()));
46 if self
47 .domain
48 .carried()
49 .is_some_and(|carried| !strip.contains(&carried))
50 {
51 self.domain.invalidate();
52 }
53 Bar {
54 reorder: self.clone(),
55 id: id.into(),
56 tabs,
57 moved: None,
58 outside: None,
59 }
60 }
61}
62
63#[derive(Clone, Copy, Debug, PartialEq, Eq)]
65pub struct Move {
66 pub from: usize,
67 pub to: usize,
68}
69
70#[derive(Clone, Debug)]
72pub struct OutsideDrop<Id> {
73 pub id: Id,
74 pub position: Point<Pixels>,
75}
76
77type OnMove = dyn Fn(&Move, &mut Window, &mut App);
78type OnOutside<Id> = dyn Fn(&OutsideDrop<Id>, &mut Window, &mut App);
79
80#[derive(IntoElement)]
81pub struct Bar<Id: Clone + PartialEq + 'static> {
82 reorder: Reorder<Id>,
83 id: ElementId,
84 tabs: Vec<(Id, Stateful<Div>)>,
85 moved: Option<Rc<OnMove>>,
86 outside: Option<Rc<OnOutside<Id>>>,
87}
88
89impl<Id: Clone + PartialEq + 'static> Bar<Id> {
90 pub fn on_reorder(mut self, moved: impl Fn(&Move, &mut Window, &mut App) + 'static) -> Self {
92 self.moved = Some(Rc::new(moved));
93 self
94 }
95
96 pub fn on_drop_outside(
97 mut self,
98 outside: impl Fn(&OutsideDrop<Id>, &mut Window, &mut App) + 'static,
99 ) -> Self {
100 self.outside = Some(Rc::new(outside));
101 self
102 }
103}
104
105impl<Id: Clone + PartialEq + 'static> RenderOnce for Bar<Id> {
106 fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
107 let raised = theme::Theme::of(cx).surface_raised;
108 let Reorder { domain, scroll } = self.reorder;
109 let order: Vec<Id> = self.tabs.iter().map(|(id, _)| id.clone()).collect();
110 let row = div()
111 .id("tab-row")
112 .flex_1()
113 .min_w_0()
114 .flex()
115 .flex_row()
116 .items_center()
117 .gap(px(GAP))
118 .overflow_x_scroll()
119 .track_scroll(&scroll)
120 .children(self.tabs.into_iter().map(|(id, tab)| {
121 let tab = tab.flex_none();
122 let tab = match domain.carries(&id) {
123 true => tab.bg(raised).cursor_grabbing(),
124 false => tab,
125 };
126 domain.handle(id, tab)
127 }));
128 let mut region = domain
129 .region("tab-region", (), Axis::Horizontal, row)
130 .axis_locked()
131 .track_scroll(&scroll)
132 .flex_1()
133 .min_w_0()
134 .min_h_0()
135 .flex();
136 if let Some(moved) = self.moved {
137 region = region.on_drop(move |event: &drag::Drop<(), Id>, window, cx| {
138 let Some(from) = order.iter().position(|id| *id == event.item) else {
139 return;
140 };
141 let rest: Vec<&Id> = order.iter().filter(|id| **id != event.item).collect();
142 let to = match (&event.after, &event.before) {
143 (Some(after), _) => rest.iter().position(|id| *id == after).map(|at| at + 1),
144 (None, Some(before)) => rest.iter().position(|id| *id == before),
145 (None, None) => Some(0),
146 };
147 if let Some(to) = to {
148 moved(&Move { from, to }, window, cx);
149 }
150 });
151 }
152 if let Some(outside) = self.outside {
153 region = region.on_drop_outside(move |event: &drag::Outside<Id>, window, cx| {
154 outside(
155 &OutsideDrop {
156 id: event.item.clone(),
157 position: event.position,
158 },
159 window,
160 cx,
161 );
162 });
163 }
164 div()
166 .id(self.id)
167 .relative()
168 .min_w_0()
169 .min_h_0()
170 .flex()
171 .flex_row()
172 .child(super::bar("tab-bar").viewport(region.into_any_element(), &scroll))
173 }
174}