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)]
 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            .px_2()
130            .py_0p5()
131            .flex()
132            .items_center()
133            .justify_center()
134            .bg(tab_bg)
135            .hover(|h| h.bg(tab_hover_bg))
136            .active(|a| a.bg(tab_active_bg))
137            .child(
138                div()
139                    .px_1()
140                    .flex()
141                    .items_center()
142                    .gap_1()
143                    .children(has_fs_conflict.then(|| {
144                        IconElement::new(Icon::ExclamationTriangle)
145                            .size(crate::IconSize::Small)
146                            .color(IconColor::Warning)
147                    }))
148                    .children(self.icon.map(IconElement::new))
149                    .children(if self.close_side == IconSide::Left {
150                        Some(close_icon())
151                    } else {
152                        None
153                    })
154                    .child(label)
155                    .children(if self.close_side == IconSide::Right {
156                        Some(close_icon())
157                    } else {
158                        None
159                    }),
160            )
161    }
162}
163
164use gpui2::{black, red, Drag, ElementId};
165#[cfg(feature = "stories")]
166pub use stories::*;
167
168#[cfg(feature = "stories")]
169mod stories {
170    use strum::IntoEnumIterator;
171
172    use crate::{h_stack, v_stack, Icon, Story};
173
174    use super::*;
175
176    #[derive(Element)]
177    pub struct TabStory<S: 'static + Send + Sync + Clone> {
178        state_type: PhantomData<S>,
179    }
180
181    impl<S: 'static + Send + Sync + Clone> TabStory<S> {
182        pub fn new() -> Self {
183            Self {
184                state_type: PhantomData,
185            }
186        }
187
188        fn render(
189            &mut self,
190            _view: &mut S,
191            cx: &mut ViewContext<S>,
192        ) -> impl Element<ViewState = S> {
193            let git_statuses = GitStatus::iter();
194            let fs_statuses = FileSystemStatus::iter();
195
196            Story::container(cx)
197                .child(Story::title_for::<_, Tab<S>>(cx))
198                .child(
199                    h_stack().child(
200                        v_stack()
201                            .gap_2()
202                            .child(Story::label(cx, "Default"))
203                            .child(Tab::new("default")),
204                    ),
205                )
206                .child(
207                    h_stack().child(
208                        v_stack().gap_2().child(Story::label(cx, "Current")).child(
209                            h_stack()
210                                .gap_4()
211                                .child(
212                                    Tab::new("current")
213                                        .title("Current".to_string())
214                                        .current(true),
215                                )
216                                .child(
217                                    Tab::new("not_current")
218                                        .title("Not Current".to_string())
219                                        .current(false),
220                                ),
221                        ),
222                    ),
223                )
224                .child(
225                    h_stack().child(
226                        v_stack()
227                            .gap_2()
228                            .child(Story::label(cx, "Titled"))
229                            .child(Tab::new("titled").title("label".to_string())),
230                    ),
231                )
232                .child(
233                    h_stack().child(
234                        v_stack()
235                            .gap_2()
236                            .child(Story::label(cx, "With Icon"))
237                            .child(
238                                Tab::new("with_icon")
239                                    .title("label".to_string())
240                                    .icon(Some(Icon::Envelope)),
241                            ),
242                    ),
243                )
244                .child(
245                    h_stack().child(
246                        v_stack()
247                            .gap_2()
248                            .child(Story::label(cx, "Close Side"))
249                            .child(
250                                h_stack()
251                                    .gap_4()
252                                    .child(
253                                        Tab::new("left")
254                                            .title("Left".to_string())
255                                            .close_side(IconSide::Left),
256                                    )
257                                    .child(Tab::new("right").title("Right".to_string())),
258                            ),
259                    ),
260                )
261                .child(
262                    v_stack()
263                        .gap_2()
264                        .child(Story::label(cx, "Git Status"))
265                        .child(h_stack().gap_4().children(git_statuses.map(|git_status| {
266                            Tab::new("git_status")
267                                .title(git_status.to_string())
268                                .git_status(git_status)
269                        }))),
270                )
271                .child(
272                    v_stack()
273                        .gap_2()
274                        .child(Story::label(cx, "File System Status"))
275                        .child(h_stack().gap_4().children(fs_statuses.map(|fs_status| {
276                            Tab::new("file_system_status")
277                                .title(fs_status.to_string())
278                                .fs_status(fs_status)
279                        }))),
280                )
281        }
282    }
283}