tab.rs

  1use std::marker::PhantomData;
  2
  3use crate::prelude::*;
  4use crate::{Icon, IconColor, IconElement, Label, LabelColor};
  5
  6#[derive(Element, Clone)]
  7pub struct Tab<S: 'static + Send + Sync + Clone> {
  8    state_type: PhantomData<S>,
  9    id: ElementId,
 10    title: String,
 11    icon: Option<Icon>,
 12    current: bool,
 13    dirty: bool,
 14    fs_status: FileSystemStatus,
 15    git_status: GitStatus,
 16    diagnostic_status: DiagnosticStatus,
 17    close_side: IconSide,
 18}
 19
 20#[derive(Clone, Debug)]
 21struct TabDragState {
 22    title: String,
 23}
 24
 25impl<S: 'static + Send + Sync + Clone> Tab<S> {
 26    pub fn new(id: impl Into<ElementId>) -> Self {
 27        Self {
 28            state_type: PhantomData,
 29            id: id.into(),
 30            title: "untitled".to_string(),
 31            icon: None,
 32            current: false,
 33            dirty: false,
 34            fs_status: FileSystemStatus::None,
 35            git_status: GitStatus::None,
 36            diagnostic_status: DiagnosticStatus::None,
 37            close_side: IconSide::Right,
 38        }
 39    }
 40
 41    pub fn current(mut self, current: bool) -> Self {
 42        self.current = current;
 43        self
 44    }
 45
 46    pub fn title(mut self, title: String) -> Self {
 47        self.title = title;
 48        self
 49    }
 50
 51    pub fn icon<I>(mut self, icon: I) -> Self
 52    where
 53        I: Into<Option<Icon>>,
 54    {
 55        self.icon = icon.into();
 56        self
 57    }
 58
 59    pub fn dirty(mut self, dirty: bool) -> Self {
 60        self.dirty = dirty;
 61        self
 62    }
 63
 64    pub fn fs_status(mut self, fs_status: FileSystemStatus) -> Self {
 65        self.fs_status = fs_status;
 66        self
 67    }
 68
 69    pub fn git_status(mut self, git_status: GitStatus) -> Self {
 70        self.git_status = git_status;
 71        self
 72    }
 73
 74    pub fn diagnostic_status(mut self, diagnostic_status: DiagnosticStatus) -> Self {
 75        self.diagnostic_status = diagnostic_status;
 76        self
 77    }
 78
 79    pub fn close_side(mut self, close_side: IconSide) -> Self {
 80        self.close_side = close_side;
 81        self
 82    }
 83
 84    fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
 85        let color = ThemeColor::new(cx);
 86        let has_fs_conflict = self.fs_status == FileSystemStatus::Conflict;
 87        let is_deleted = self.fs_status == FileSystemStatus::Deleted;
 88
 89        let label = match (self.git_status, is_deleted) {
 90            (_, true) | (GitStatus::Deleted, false) => Label::new(self.title.clone())
 91                .color(LabelColor::Hidden)
 92                .set_strikethrough(true),
 93            (GitStatus::None, false) => Label::new(self.title.clone()),
 94            (GitStatus::Created, false) => {
 95                Label::new(self.title.clone()).color(LabelColor::Created)
 96            }
 97            (GitStatus::Modified, false) => {
 98                Label::new(self.title.clone()).color(LabelColor::Modified)
 99            }
100            (GitStatus::Renamed, false) => Label::new(self.title.clone()).color(LabelColor::Accent),
101            (GitStatus::Conflict, false) => Label::new(self.title.clone()),
102        };
103
104        let close_icon = || IconElement::new(Icon::Close).color(IconColor::Muted);
105
106        let (tab_bg, tab_hover_bg, tab_active_bg) = match self.current {
107            true => (
108                color.ghost_element,
109                color.ghost_element_hover,
110                color.ghost_element_active,
111            ),
112            false => (
113                color.filled_element,
114                color.filled_element_hover,
115                color.filled_element_active,
116            ),
117        };
118
119        let drag_state = TabDragState {
120            title: self.title.clone(),
121        };
122
123        div()
124            .id(self.id.clone())
125            .on_drag(move |_view, _cx| {
126                Drag::new(drag_state.clone(), |view, cx| div().w_8().h_4().bg(red()))
127            })
128            .drag_over::<TabDragState>(|d| d.bg(black()))
129            .on_drop(|_view, state: TabDragState, cx| {
130                dbg!(state);
131            })
132            .px_2()
133            .py_0p5()
134            .flex()
135            .items_center()
136            .justify_center()
137            .bg(tab_bg)
138            .hover(|h| h.bg(tab_hover_bg))
139            .active(|a| a.bg(tab_active_bg))
140            .child(
141                div()
142                    .px_1()
143                    .flex()
144                    .items_center()
145                    .gap_1()
146                    .children(has_fs_conflict.then(|| {
147                        IconElement::new(Icon::ExclamationTriangle)
148                            .size(crate::IconSize::Small)
149                            .color(IconColor::Warning)
150                    }))
151                    .children(self.icon.map(IconElement::new))
152                    .children(if self.close_side == IconSide::Left {
153                        Some(close_icon())
154                    } else {
155                        None
156                    })
157                    .child(label)
158                    .children(if self.close_side == IconSide::Right {
159                        Some(close_icon())
160                    } else {
161                        None
162                    }),
163            )
164    }
165}
166
167use gpui2::{black, red, Drag, ElementId};
168#[cfg(feature = "stories")]
169pub use stories::*;
170
171#[cfg(feature = "stories")]
172mod stories {
173    use strum::IntoEnumIterator;
174
175    use crate::{h_stack, v_stack, Icon, Story};
176
177    use super::*;
178
179    #[derive(Element)]
180    pub struct TabStory<S: 'static + Send + Sync + Clone> {
181        state_type: PhantomData<S>,
182    }
183
184    impl<S: 'static + Send + Sync + Clone> TabStory<S> {
185        pub fn new() -> Self {
186            Self {
187                state_type: PhantomData,
188            }
189        }
190
191        fn render(
192            &mut self,
193            _view: &mut S,
194            cx: &mut ViewContext<S>,
195        ) -> impl Element<ViewState = S> {
196            let git_statuses = GitStatus::iter();
197            let fs_statuses = FileSystemStatus::iter();
198
199            Story::container(cx)
200                .child(Story::title_for::<_, Tab<S>>(cx))
201                .child(
202                    h_stack().child(
203                        v_stack()
204                            .gap_2()
205                            .child(Story::label(cx, "Default"))
206                            .child(Tab::new("default")),
207                    ),
208                )
209                .child(
210                    h_stack().child(
211                        v_stack().gap_2().child(Story::label(cx, "Current")).child(
212                            h_stack()
213                                .gap_4()
214                                .child(
215                                    Tab::new("current")
216                                        .title("Current".to_string())
217                                        .current(true),
218                                )
219                                .child(
220                                    Tab::new("not_current")
221                                        .title("Not Current".to_string())
222                                        .current(false),
223                                ),
224                        ),
225                    ),
226                )
227                .child(
228                    h_stack().child(
229                        v_stack()
230                            .gap_2()
231                            .child(Story::label(cx, "Titled"))
232                            .child(Tab::new("titled").title("label".to_string())),
233                    ),
234                )
235                .child(
236                    h_stack().child(
237                        v_stack()
238                            .gap_2()
239                            .child(Story::label(cx, "With Icon"))
240                            .child(
241                                Tab::new("with_icon")
242                                    .title("label".to_string())
243                                    .icon(Some(Icon::Envelope)),
244                            ),
245                    ),
246                )
247                .child(
248                    h_stack().child(
249                        v_stack()
250                            .gap_2()
251                            .child(Story::label(cx, "Close Side"))
252                            .child(
253                                h_stack()
254                                    .gap_4()
255                                    .child(
256                                        Tab::new("left")
257                                            .title("Left".to_string())
258                                            .close_side(IconSide::Left),
259                                    )
260                                    .child(Tab::new("right").title("Right".to_string())),
261                            ),
262                    ),
263                )
264                .child(
265                    v_stack()
266                        .gap_2()
267                        .child(Story::label(cx, "Git Status"))
268                        .child(h_stack().gap_4().children(git_statuses.map(|git_status| {
269                            Tab::new("git_status")
270                                .title(git_status.to_string())
271                                .git_status(git_status)
272                        }))),
273                )
274                .child(
275                    v_stack()
276                        .gap_2()
277                        .child(Story::label(cx, "File System Status"))
278                        .child(h_stack().gap_4().children(fs_statuses.map(|fs_status| {
279                            Tab::new("file_system_status")
280                                .title(fs_status.to_string())
281                                .fs_status(fs_status)
282                        }))),
283                )
284        }
285    }
286}