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