tab.rs

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