Render other tab icons in the start slot (#14683)

This PR reworks the rendering for tab icons to allow us to render all of
the tab icons—not just file icons—in the tab's start slot.

The `Item` trait now has a separate `tab_icon` method that can be used
to indicate what icon should be shown for the tab.

Release Notes:

- N/A
This commit is contained in:
Marshall Bowers 2024-07-17 16:59:41 -04:00 committed by GitHub
parent 2edf224599
commit 00c3c02f7d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 80 additions and 80 deletions

2
Cargo.lock generated
View File

@ -3601,6 +3601,7 @@ dependencies = [
"db", "db",
"emojis", "emojis",
"env_logger", "env_logger",
"file_icons",
"futures 0.3.28", "futures 0.3.28",
"fuzzy", "fuzzy",
"git", "git",
@ -13336,7 +13337,6 @@ dependencies = [
"derive_more", "derive_more",
"dev_server_projects", "dev_server_projects",
"env_logger", "env_logger",
"file_icons",
"fs", "fs",
"futures 0.3.28", "futures 0.3.28",
"gpui", "gpui",

View File

@ -37,6 +37,7 @@ collections.workspace = true
convert_case = "0.6.0" convert_case = "0.6.0"
db.workspace = true db.workspace = true
emojis.workspace = true emojis.workspace = true
file_icons.workspace = true
futures.workspace = true futures.workspace = true
fuzzy.workspace = true fuzzy.workspace = true
git.workspace = true git.workspace = true

View File

@ -5,6 +5,7 @@ use crate::{
}; };
use anyhow::{anyhow, Context as _, Result}; use anyhow::{anyhow, Context as _, Result};
use collections::HashSet; use collections::HashSet;
use file_icons::FileIcons;
use futures::future::try_join_all; use futures::future::try_join_all;
use git::repository::GitFileStatus; use git::repository::GitFileStatus;
use gpui::{ use gpui::{
@ -590,6 +591,20 @@ impl Item for Editor {
Some(path.to_string_lossy().to_string().into()) Some(path.to_string_lossy().to_string().into())
} }
fn tab_icon(&self, cx: &WindowContext) -> Option<Icon> {
ItemSettings::get_global(cx)
.file_icons
.then(|| {
self.buffer
.read(cx)
.as_singleton()
.and_then(|buffer| buffer.read(cx).project_path(cx))
.and_then(|path| FileIcons::get_icon(path.path.as_ref(), cx))
})
.flatten()
.map(|icon| Icon::from_path(icon))
}
fn tab_content(&self, params: TabContentParams, cx: &WindowContext) -> AnyElement { fn tab_content(&self, params: TabContentParams, cx: &WindowContext) -> AnyElement {
let label_color = if ItemSettings::get_global(cx).git_status { let label_color = if ItemSettings::get_global(cx).git_status {
self.buffer() self.buffer()

View File

@ -452,27 +452,22 @@ impl EventEmitter<PreviewEvent> for MarkdownPreviewView {}
impl Item for MarkdownPreviewView { impl Item for MarkdownPreviewView {
type Event = PreviewEvent; type Event = PreviewEvent;
fn tab_icon(&self, _cx: &WindowContext) -> Option<Icon> {
Some(Icon::new(IconName::FileDoc))
}
fn tab_content(&self, params: TabContentParams, _cx: &WindowContext) -> AnyElement { fn tab_content(&self, params: TabContentParams, _cx: &WindowContext) -> AnyElement {
h_flex() Label::new(if let Some(description) = &self.tab_description {
.gap_2() description.clone().into()
.child(Icon::new(IconName::FileDoc).color(if params.selected { } else {
Color::Default self.fallback_tab_description.clone()
} else { })
Color::Muted .color(if params.selected {
})) Color::Default
.child( } else {
Label::new(if let Some(description) = &self.tab_description { Color::Muted
description.clone().into() })
} else { .into_any_element()
self.fallback_tab_description.clone()
})
.color(if params.selected {
Color::Default
} else {
Color::Muted
}),
)
.into_any()
} }
fn telemetry_event_text(&self) -> Option<&'static str> { fn telemetry_event_text(&self) -> Option<&'static str> {

View File

@ -12,11 +12,11 @@ use editor::{
MAX_TAB_TITLE_LEN, MAX_TAB_TITLE_LEN,
}; };
use gpui::{ use gpui::{
actions, div, Action, AnyElement, AnyView, AppContext, Context as _, Element, EntityId, actions, div, Action, AnyElement, AnyView, AppContext, Context as _, EntityId, EventEmitter,
EventEmitter, FocusHandle, FocusableView, FontStyle, Global, Hsla, InteractiveElement, FocusHandle, FocusableView, FontStyle, Global, Hsla, InteractiveElement, IntoElement,
IntoElement, KeyContext, Model, ModelContext, ParentElement, Point, Render, SharedString, KeyContext, Model, ModelContext, ParentElement, Point, Render, SharedString, Styled,
Styled, Subscription, Task, TextStyle, UpdateGlobal, View, ViewContext, VisualContext, Subscription, Task, TextStyle, UpdateGlobal, View, ViewContext, VisualContext, WeakModel,
WeakModel, WhiteSpace, WindowContext, WhiteSpace, WindowContext,
}; };
use menu::Confirm; use menu::Confirm;
use project::{search::SearchQuery, search_history::SearchHistoryCursor, Project, ProjectPath}; use project::{search::SearchQuery, search_history::SearchHistoryCursor, Project, ProjectPath};
@ -370,6 +370,10 @@ impl Item for ProjectSearchView {
.update(cx, |editor, cx| editor.deactivated(cx)); .update(cx, |editor, cx| editor.deactivated(cx));
} }
fn tab_icon(&self, _cx: &WindowContext) -> Option<Icon> {
Some(Icon::new(IconName::MagnifyingGlass))
}
fn tab_content(&self, params: TabContentParams, cx: &WindowContext<'_>) -> AnyElement { fn tab_content(&self, params: TabContentParams, cx: &WindowContext<'_>) -> AnyElement {
let last_query: Option<SharedString> = self let last_query: Option<SharedString> = self
.model .model
@ -384,21 +388,13 @@ impl Item for ProjectSearchView {
let tab_name = last_query let tab_name = last_query
.filter(|query| !query.is_empty()) .filter(|query| !query.is_empty())
.unwrap_or_else(|| "Project Search".into()); .unwrap_or_else(|| "Project Search".into());
h_flex() Label::new(tab_name)
.gap_2() .color(if params.selected {
.child(
Icon::new(IconName::MagnifyingGlass).color(if params.selected {
Color::Default
} else {
Color::Muted
}),
)
.child(Label::new(tab_name).color(if params.selected {
Color::Default Color::Default
} else { } else {
Color::Muted Color::Muted
})) })
.into_any() .into_any_element()
} }
fn telemetry_event_text(&self) -> Option<&'static str> { fn telemetry_event_text(&self) -> Option<&'static str> {

View File

@ -943,13 +943,13 @@ impl Item for TerminalView {
let terminal = self.terminal().read(cx); let terminal = self.terminal().read(cx);
let title = terminal.title(true); let title = terminal.title(true);
let (icon, icon_color, rerun_btn) = match terminal.task() { let (icon, icon_color, rerun_button) = match terminal.task() {
Some(terminal_task) => match &terminal_task.status { Some(terminal_task) => match &terminal_task.status {
TaskStatus::Unknown => (IconName::ExclamationTriangle, Color::Warning, None), TaskStatus::Unknown => (IconName::ExclamationTriangle, Color::Warning, None),
TaskStatus::Running => (IconName::Play, Color::Disabled, None), TaskStatus::Running => (IconName::Play, Color::Disabled, None),
TaskStatus::Completed { success } => { TaskStatus::Completed { success } => {
let task_id = terminal_task.id.clone(); let task_id = terminal_task.id.clone();
let rerun_btn = IconButton::new("rerun-icon", IconName::Rerun) let rerun_button = IconButton::new("rerun-icon", IconName::Rerun)
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
.size(ButtonSize::Compact) .size(ButtonSize::Compact)
.icon_color(Color::Default) .icon_color(Color::Default)
@ -963,9 +963,9 @@ impl Item for TerminalView {
}); });
if *success { if *success {
(IconName::Check, Color::Success, Some(rerun_btn)) (IconName::Check, Color::Success, Some(rerun_button))
} else { } else {
(IconName::XCircle, Color::Error, Some(rerun_btn)) (IconName::XCircle, Color::Error, Some(rerun_button))
} }
} }
}, },
@ -980,17 +980,17 @@ impl Item for TerminalView {
.group("term-tab-icon") .group("term-tab-icon")
.child( .child(
div() div()
.when(rerun_btn.is_some(), |this| { .when(rerun_button.is_some(), |this| {
this.hover(|style| style.invisible().w_0()) this.hover(|style| style.invisible().w_0())
}) })
.child(Icon::new(icon).color(icon_color)), .child(Icon::new(icon).color(icon_color)),
) )
.when_some(rerun_btn, |this, rerun_btn| { .when_some(rerun_button, |this, rerun_button| {
this.child( this.child(
div() div()
.absolute() .absolute()
.visible_on_hover("term-tab-icon") .visible_on_hover("term-tab-icon")
.child(rerun_btn), .child(rerun_button),
) )
}), }),
) )

View File

@ -36,7 +36,6 @@ clock.workspace = true
collections.workspace = true collections.workspace = true
db.workspace = true db.workspace = true
derive_more.workspace = true derive_more.workspace = true
file_icons.workspace = true
fs.workspace = true fs.workspace = true
futures.workspace = true futures.workspace = true
gpui.workspace = true gpui.workspace = true

View File

@ -31,7 +31,7 @@ use std::{
time::Duration, time::Duration,
}; };
use theme::Theme; use theme::Theme;
use ui::Element as _; use ui::{Element as _, Icon};
use util::ResultExt; use util::ResultExt;
pub const LEADER_UPDATE_THROTTLE: Duration = Duration::from_millis(200); pub const LEADER_UPDATE_THROTTLE: Duration = Duration::from_millis(200);
@ -147,6 +147,11 @@ pub trait Item: FocusableView + EventEmitter<Self::Event> {
fn tab_content(&self, _params: TabContentParams, _cx: &WindowContext) -> AnyElement { fn tab_content(&self, _params: TabContentParams, _cx: &WindowContext) -> AnyElement {
gpui::Empty.into_any() gpui::Empty.into_any()
} }
fn tab_icon(&self, _cx: &WindowContext) -> Option<Icon> {
None
}
fn to_item_events(_event: &Self::Event, _f: impl FnMut(ItemEvent)) {} fn to_item_events(_event: &Self::Event, _f: impl FnMut(ItemEvent)) {}
fn deactivated(&mut self, _: &mut ViewContext<Self>) {} fn deactivated(&mut self, _: &mut ViewContext<Self>) {}
@ -330,6 +335,7 @@ pub trait ItemHandle: 'static + Send {
fn tab_tooltip_text(&self, cx: &AppContext) -> Option<SharedString>; fn tab_tooltip_text(&self, cx: &AppContext) -> Option<SharedString>;
fn tab_description(&self, detail: usize, cx: &AppContext) -> Option<SharedString>; fn tab_description(&self, detail: usize, cx: &AppContext) -> Option<SharedString>;
fn tab_content(&self, params: TabContentParams, cx: &WindowContext) -> AnyElement; fn tab_content(&self, params: TabContentParams, cx: &WindowContext) -> AnyElement;
fn tab_icon(&self, cx: &WindowContext) -> Option<Icon>;
fn telemetry_event_text(&self, cx: &WindowContext) -> Option<&'static str>; fn telemetry_event_text(&self, cx: &WindowContext) -> Option<&'static str>;
fn dragged_tab_content(&self, params: TabContentParams, cx: &WindowContext) -> AnyElement; fn dragged_tab_content(&self, params: TabContentParams, cx: &WindowContext) -> AnyElement;
fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>; fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
@ -441,6 +447,10 @@ impl<T: Item> ItemHandle for View<T> {
self.read(cx).tab_content(params, cx) self.read(cx).tab_content(params, cx)
} }
fn tab_icon(&self, cx: &WindowContext) -> Option<Icon> {
self.read(cx).tab_icon(cx)
}
fn dragged_tab_content(&self, params: TabContentParams, cx: &WindowContext) -> AnyElement { fn dragged_tab_content(&self, params: TabContentParams, cx: &WindowContext) -> AnyElement {
self.read(cx).tab_content( self.read(cx).tab_content(
TabContentParams { TabContentParams {

View File

@ -10,7 +10,6 @@ use crate::{
}; };
use anyhow::Result; use anyhow::Result;
use collections::{BTreeSet, HashMap, HashSet, VecDeque}; use collections::{BTreeSet, HashMap, HashSet, VecDeque};
use file_icons::FileIcons;
use futures::{stream::FuturesUnordered, StreamExt}; use futures::{stream::FuturesUnordered, StreamExt};
use gpui::{ use gpui::{
actions, anchored, deferred, impl_actions, prelude::*, Action, AnchorCorner, AnyElement, actions, anchored, deferred, impl_actions, prelude::*, Action, AnchorCorner, AnyElement,
@ -1590,6 +1589,7 @@ impl Pane {
}, },
cx, cx,
); );
let icon = item.tab_icon(cx);
let close_side = &ItemSettings::get_global(cx).close_position; let close_side = &ItemSettings::get_global(cx).close_position;
let indicator = render_item_indicator(item.boxed_clone(), cx); let indicator = render_item_indicator(item.boxed_clone(), cx);
let item_id = item.item_id(); let item_id = item.item_id();
@ -1597,14 +1597,6 @@ impl Pane {
let is_last_item = ix == self.items.len() - 1; let is_last_item = ix == self.items.len() - 1;
let position_relative_to_active_item = ix.cmp(&self.active_item_index); let position_relative_to_active_item = ix.cmp(&self.active_item_index);
let file_icon = ItemSettings::get_global(cx)
.file_icons
.then(|| {
item.project_path(cx)
.and_then(|path| FileIcons::get_icon(path.path.as_ref(), cx))
})
.flatten();
let tab = Tab::new(ix) let tab = Tab::new(ix)
.position(if is_first_item { .position(if is_first_item {
TabPosition::First TabPosition::First
@ -1675,14 +1667,12 @@ impl Pane {
}) })
.map(|tab| match indicator { .map(|tab| match indicator {
Some(indicator) => tab.start_slot(indicator), Some(indicator) => tab.start_slot(indicator),
None => tab.start_slot::<Icon>(file_icon.map(|icon| { None => tab.start_slot::<Icon>(icon.map(|icon| {
Icon::from_path(icon.to_string()) icon.size(IconSize::XSmall).color(if is_active {
.size(IconSize::XSmall) Color::Default
.color(if is_active { } else {
Color::Default Color::Muted
} else { })
Color::Muted
})
})), })),
}) })
.end_slot( .end_slot(

View File

@ -7,12 +7,12 @@ use call::participant::{Frame, RemoteVideoTrack};
use client::{proto::PeerId, User}; use client::{proto::PeerId, User};
use futures::StreamExt; use futures::StreamExt;
use gpui::{ use gpui::{
div, img, AppContext, Element, EventEmitter, FocusHandle, FocusableView, InteractiveElement, div, img, AppContext, EventEmitter, FocusHandle, FocusableView, InteractiveElement,
ParentElement, Render, SharedString, Styled, Task, View, ViewContext, VisualContext, ParentElement, Render, SharedString, Styled, Task, View, ViewContext, VisualContext,
WindowContext, WindowContext,
}; };
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
use ui::{h_flex, prelude::*, Icon, IconName, Label}; use ui::{prelude::*, Icon, IconName, Label};
pub enum Event { pub enum Event {
Close, Close,
@ -93,24 +93,18 @@ impl Item for SharedScreen {
} }
} }
fn tab_icon(&self, _cx: &WindowContext) -> Option<Icon> {
Some(Icon::new(IconName::Screen))
}
fn tab_content(&self, params: TabContentParams, _: &WindowContext<'_>) -> gpui::AnyElement { fn tab_content(&self, params: TabContentParams, _: &WindowContext<'_>) -> gpui::AnyElement {
h_flex() Label::new(format!("{}'s screen", self.user.github_login))
.gap_1() .color(if params.selected {
.child(Icon::new(IconName::Screen).color(if params.selected {
Color::Default Color::Default
} else { } else {
Color::Muted Color::Muted
})) })
.child( .into_any_element()
Label::new(format!("{}'s screen", self.user.github_login)).color(
if params.selected {
Color::Default
} else {
Color::Muted
},
),
)
.into_any()
} }
fn telemetry_event_text(&self) -> Option<&'static str> { fn telemetry_event_text(&self) -> Option<&'static str> {