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