mirror of
https://github.com/zed-industries/zed.git
synced 2024-09-18 18:08:07 +03:00
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:
parent
2edf224599
commit
00c3c02f7d
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -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",
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
|
@ -452,15 +452,11 @@ 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()
|
|
||||||
.gap_2()
|
|
||||||
.child(Icon::new(IconName::FileDoc).color(if params.selected {
|
|
||||||
Color::Default
|
|
||||||
} else {
|
|
||||||
Color::Muted
|
|
||||||
}))
|
|
||||||
.child(
|
|
||||||
Label::new(if let Some(description) = &self.tab_description {
|
Label::new(if let Some(description) = &self.tab_description {
|
||||||
description.clone().into()
|
description.clone().into()
|
||||||
} else {
|
} else {
|
||||||
@ -470,9 +466,8 @@ impl Item for MarkdownPreviewView {
|
|||||||
Color::Default
|
Color::Default
|
||||||
} else {
|
} else {
|
||||||
Color::Muted
|
Color::Muted
|
||||||
}),
|
})
|
||||||
)
|
.into_any_element()
|
||||||
.into_any()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn telemetry_event_text(&self) -> Option<&'static str> {
|
fn telemetry_event_text(&self) -> Option<&'static str> {
|
||||||
|
@ -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
|
Color::Default
|
||||||
} else {
|
} else {
|
||||||
Color::Muted
|
Color::Muted
|
||||||
}),
|
})
|
||||||
)
|
.into_any_element()
|
||||||
.child(Label::new(tab_name).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> {
|
||||||
|
@ -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),
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
@ -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
|
||||||
|
@ -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 {
|
||||||
|
@ -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,10 +1667,8 @@ 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(if is_active {
|
|
||||||
Color::Default
|
Color::Default
|
||||||
} else {
|
} else {
|
||||||
Color::Muted
|
Color::Muted
|
||||||
|
@ -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> {
|
||||||
|
Loading…
Reference in New Issue
Block a user