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            .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::{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(Element)]
176    pub struct TabStory<S: 'static + Send + Sync + Clone> {
177        state_type: PhantomData<S>,
178    }
179
180    impl<S: 'static + Send + Sync + Clone> TabStory<S> {
181        pub fn new() -> Self {
182            Self {
183                state_type: PhantomData,
184            }
185        }
186
187        fn render(
188            &mut self,
189            _view: &mut S,
190            cx: &mut ViewContext<S>,
191        ) -> impl Element<ViewState = S> {
192            let git_statuses = GitStatus::iter();
193            let fs_statuses = FileSystemStatus::iter();
194
195            Story::container(cx)
196                .child(Story::title_for::<_, Tab<S>>(cx))
197                .child(
198                    h_stack().child(
199                        v_stack()
200                            .gap_2()
201                            .child(Story::label(cx, "Default"))
202                            .child(Tab::new("default")),
203                    ),
204                )
205                .child(
206                    h_stack().child(
207                        v_stack().gap_2().child(Story::label(cx, "Current")).child(
208                            h_stack()
209                                .gap_4()
210                                .child(
211                                    Tab::new("current")
212                                        .title("Current".to_string())
213                                        .current(true),
214                                )
215                                .child(
216                                    Tab::new("not_current")
217                                        .title("Not Current".to_string())
218                                        .current(false),
219                                ),
220                        ),
221                    ),
222                )
223                .child(
224                    h_stack().child(
225                        v_stack()
226                            .gap_2()
227                            .child(Story::label(cx, "Titled"))
228                            .child(Tab::new("titled").title("label".to_string())),
229                    ),
230                )
231                .child(
232                    h_stack().child(
233                        v_stack()
234                            .gap_2()
235                            .child(Story::label(cx, "With Icon"))
236                            .child(
237                                Tab::new("with_icon")
238                                    .title("label".to_string())
239                                    .icon(Some(Icon::Envelope)),
240                            ),
241                    ),
242                )
243                .child(
244                    h_stack().child(
245                        v_stack()
246                            .gap_2()
247                            .child(Story::label(cx, "Close Side"))
248                            .child(
249                                h_stack()
250                                    .gap_4()
251                                    .child(
252                                        Tab::new("left")
253                                            .title("Left".to_string())
254                                            .close_side(IconSide::Left),
255                                    )
256                                    .child(Tab::new("right").title("Right".to_string())),
257                            ),
258                    ),
259                )
260                .child(
261                    v_stack()
262                        .gap_2()
263                        .child(Story::label(cx, "Git Status"))
264                        .child(h_stack().gap_4().children(git_statuses.map(|git_status| {
265                            Tab::new("git_status")
266                                .title(git_status.to_string())
267                                .git_status(git_status)
268                        }))),
269                )
270                .child(
271                    v_stack()
272                        .gap_2()
273                        .child(Story::label(cx, "File System Status"))
274                        .child(h_stack().gap_4().children(fs_statuses.map(|fs_status| {
275                            Tab::new("file_system_status")
276                                .title(fs_status.to_string())
277                                .fs_status(fs_status)
278                        }))),
279                )
280        }
281    }
282}