列表的悬停和选中分开:深色悬停调淡,选中行统一一种颜色
- 侧栏的主机、端口转发、凭据列表和「最近连接」的选中行同色:浅色是原来侧栏的深一档灰,深色用「最近连接」原来的选中色,不再刺眼 - 深色下列表和表格的悬停只有选中色的一半,和选中区分开 - 浅色「最近连接」的选中行原来和悬停一样,现在同侧栏 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3ddc7ebe62
commit
f8f7473e42
7 files changed
+87
-15
No files matched your search
@@ -69,7 +69,7 @@ ShellRS(crate 与二进制都叫 `shellrs`)是 Xshell / WinSCP 式的 SSH
|
||||
- **设置 › 应用 › 窗口**:「记住窗口大小」(含最大化)和「记住窗口位置」两个开关,默认都开,下次启动生效。屏幕不在了或窗口不在屏幕上就居中,比屏幕大就缩小;全屏不记。
|
||||
- **终端通知**:只做程序请求的(OSC 9 / 777)和响铃,不做 OSC 133 长命令通知:那要每台服务器装 shell 集成,收益不抵成本。不在前台发系统通知,在前台而终端在别的标签发应用内通知;响铃在终端就在眼前时不提醒。
|
||||
- **主题**:「应用外观」(浅色 / 深色 / 跟随系统)决定深浅;「外观 › 主题」左右两栏里浅色、深色各选一套,按当前外观生效。组里第一项「界面跟随主题」默认关闭:界面是中性的灰(浅色是 gpui-kit 默认主题,深色见下条),只有终端按主题配色;打开后界面也按主题配色。用户试过默认跟随,觉得中性界面更合适,别改回去。
|
||||
- 默认深色(ShellRS Dark)不用 gpui-kit 的近黑,用参照微信深色的炭灰:底色 `#1e1e1f`,标题栏、标签栏、状态栏 `#2f2f30`,边框和悬停 `#3b3b3d`,文字 `#e0e0e4`。用户嫌近黑刺眼。次要文字比微信的 `#7b7b7f` 亮一些(`#929296`),保持对比度 ≥ 4.5。页面「重置」对卡片和开关各管各的。
|
||||
- 默认深色(ShellRS Dark)不用 gpui-kit 的近黑,用参照微信深色的炭灰:底色 `#1e1e1f`,标题栏、标签栏、状态栏 `#2f2f30`,边框和悬停 `#3b3b3d`,文字 `#e0e0e4`。用户嫌近黑刺眼。次要文字比微信的 `#7b7b7f` 亮一些(`#929296`),保持对比度 ≥ 4.5。侧栏三个列表和「最近连接」的选中行同色(`selected_row_color`):浅色是悬停色三倍的对比,深色那样太刺眼,用加深后的悬停色,列表和表格的悬停只有它的一半(`DARK_HOVER_SHARE`)。两种外观下悬停和选中都要分得开。页面「重置」对卡片和开关各管各的。
|
||||
- 左栏只列浅色主题,右栏只列深色主题;卡片是名称加示例输出,选中的整张加底色,右上角是蓝底(主题的 `blue`)对勾。
|
||||
- 只有内置的,浅色、深色各 10 套,默认 ShellRS Light / Dark(底色与界面一致),Solarized 第二,Tokyo Day / Tokyo Night 第三。同一主题的浅、深两版在两栏同一行,没有另一版的(Rosé Pine Dawn、Everforest Light、Dracula、Nord)排最后。只收宽松许可(MIT、Apache-2.0)的配色。不做自定义主题、导入、按主机指定。
|
||||
- **键盘快捷键**:设置左栏单独一个分类,排在「关键字高亮」下面,分通用、标签、终端、编辑器四组。
|
||||
|
||||
+66
-7
@@ -52,23 +52,82 @@ pub fn init(cx: &mut App) {
|
||||
cx.on_action(|_: &Quit, cx: &mut App| quit::quit(cx));
|
||||
}
|
||||
|
||||
/// How much of a selected row's color hover gets in the dark appearance.
|
||||
/// Selected rows there have the deepened hover color itself; hover with all
|
||||
/// of it would look the same.
|
||||
const DARK_HOVER_SHARE: f32 = 0.5;
|
||||
|
||||
/// Use a stronger version of the theme's neutral list hover color throughout
|
||||
/// the app.
|
||||
/// the app. Dark, the stronger one is for selected rows
|
||||
/// (`selected_row_color`) and hover, in lists and tables, gets half of it.
|
||||
pub(crate) fn deepen_list_hover(cx: &mut App) {
|
||||
let theme = Theme::global_mut(cx);
|
||||
let hover = theme.list_hover;
|
||||
let deeper = hover.alpha(((hover.a + 0.2) * 1.2).min(1.0));
|
||||
theme.list_hover = deeper;
|
||||
theme.tokens.list_hover = deeper.into();
|
||||
if theme.is_dark() {
|
||||
let faint = deeper.alpha(deeper.a * DARK_HOVER_SHARE);
|
||||
theme.list_hover = faint;
|
||||
theme.tokens.list_hover = faint.into();
|
||||
theme.table_hover = faint;
|
||||
theme.tokens.table_hover = faint.into();
|
||||
} else {
|
||||
theme.list_hover = deeper;
|
||||
theme.tokens.list_hover = deeper.into();
|
||||
}
|
||||
Theme::sync_base(cx);
|
||||
}
|
||||
|
||||
/// Give the host tree three times the list hover's contrast against its sidebar.
|
||||
/// The recent-host list keeps the shared hover color unchanged.
|
||||
pub(crate) fn host_tree_selection_color(theme: &Theme) -> Hsla {
|
||||
/// The deepened list hover: light the hover itself, dark the full color
|
||||
/// hover has only a share of.
|
||||
fn deepened_hover(theme: &Theme) -> Hsla {
|
||||
let hover = theme.list_hover;
|
||||
if theme.is_dark() {
|
||||
hover.alpha((hover.a / DARK_HOVER_SHARE).min(1.0))
|
||||
} else {
|
||||
hover
|
||||
}
|
||||
}
|
||||
|
||||
/// The selected row of the sidebar's lists (hosts, port forwards,
|
||||
/// credentials) and of the recent hosts, so that it stands out from hover.
|
||||
/// Light, three times the list hover's contrast against the sidebar; dark,
|
||||
/// where that glares, the deepened hover.
|
||||
pub(crate) fn selected_row_color(theme: &Theme) -> Hsla {
|
||||
let sidebar = theme.sidebar;
|
||||
let hover = sidebar.blend(theme.list_hover);
|
||||
let hover = sidebar.blend(deepened_hover(theme));
|
||||
let mut selected = hover.alpha(1.0);
|
||||
if theme.is_dark() {
|
||||
return selected;
|
||||
}
|
||||
selected.l = (sidebar.l + (hover.l - sidebar.l) * 3.0).clamp(0.0, 1.0);
|
||||
selected
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use gpui_kit::TestAppContext;
|
||||
use gpui_kit::component::{ActiveTheme as _, Theme, ThemeMode};
|
||||
|
||||
use super::{deepen_list_hover, deepened_hover};
|
||||
|
||||
#[gpui_kit::test]
|
||||
fn dark_hover_is_fainter_than_a_selected_row(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
gpui_kit::component::init(cx);
|
||||
for mode in [ThemeMode::Light, ThemeMode::Dark] {
|
||||
Theme::change(mode, None, cx);
|
||||
deepen_list_hover(cx);
|
||||
let theme = cx.theme();
|
||||
let selected = deepened_hover(theme);
|
||||
assert!(selected.a > 0.9, "{mode:?}: {selected:?}");
|
||||
match mode {
|
||||
ThemeMode::Light => assert_eq!(theme.list_hover, selected),
|
||||
ThemeMode::Dark => {
|
||||
assert!((theme.list_hover.a * 2. - selected.a).abs() < 0.01);
|
||||
assert_eq!(theme.table_hover, theme.list_hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -295,7 +295,7 @@ impl CredentialPanel {
|
||||
.confirmed(selected)
|
||||
.aria_selected(selected)
|
||||
.when(selected, |item| {
|
||||
item.bg(crate::app::host_tree_selection_color(theme))
|
||||
item.bg(crate::app::selected_row_color(theme))
|
||||
})
|
||||
.child(
|
||||
h_flex()
|
||||
|
||||
@@ -320,7 +320,7 @@ impl ForwardPanel {
|
||||
.confirmed(selected)
|
||||
.aria_selected(selected)
|
||||
.when(selected, |item| {
|
||||
item.bg(crate::app::host_tree_selection_color(theme))
|
||||
item.bg(crate::app::selected_row_color(theme))
|
||||
})
|
||||
.child(
|
||||
h_flex()
|
||||
|
||||
@@ -608,7 +608,7 @@ fn render_row(
|
||||
// Make selection stronger than hover against the sidebar background.
|
||||
.confirmed(selected)
|
||||
.when(selected, |row| {
|
||||
row.bg(crate::app::host_tree_selection_color(cx.theme()))
|
||||
row.bg(crate::app::selected_row_color(cx.theme()))
|
||||
})
|
||||
.pl(rems(0.75 + entry.depth() as f32))
|
||||
.child(h_flex().gap_2().child(mark).child(item.label.clone()))
|
||||
|
||||
@@ -190,7 +190,9 @@ impl RecentHosts {
|
||||
.py_2()
|
||||
.rounded(cx.theme().radius)
|
||||
.confirmed(selected)
|
||||
.when(selected, |item| item.bg(cx.theme().tokens.list_hover))
|
||||
.when(selected, |item| {
|
||||
item.bg(crate::app::selected_row_color(cx.theme()))
|
||||
})
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_3()
|
||||
|
||||
+14
-3
@@ -2,13 +2,24 @@
|
||||
|
||||
use crate::support::*;
|
||||
|
||||
/// The app's list hover is in effect, not the theme's own (a 0.6 alpha):
|
||||
/// deepened, and dark halved to stay fainter than a selected row.
|
||||
fn app_list_hover(theme: &gpui_kit::component::Theme) -> bool {
|
||||
let alpha = theme.list_hover.a;
|
||||
if theme.is_dark() {
|
||||
(0.4..0.55).contains(&alpha)
|
||||
} else {
|
||||
alpha > 0.9
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui_kit::test]
|
||||
fn theme_toggle_flips_mode(cx: &mut TestAppContext) {
|
||||
let (handle, _) = open_workspace(cx);
|
||||
let before = cx.update(|cx| {
|
||||
let theme = cx.theme();
|
||||
assert_eq!(theme.list_hover, theme.tokens.list_hover.color);
|
||||
assert!(theme.list_hover.a > 0.9);
|
||||
assert!(app_list_hover(theme), "{:?}", theme.list_hover);
|
||||
theme.is_dark()
|
||||
});
|
||||
|
||||
@@ -22,7 +33,7 @@ fn theme_toggle_flips_mode(cx: &mut TestAppContext) {
|
||||
let after = cx.update(|cx| {
|
||||
let theme = cx.theme();
|
||||
assert_eq!(theme.list_hover, theme.tokens.list_hover.color);
|
||||
assert!(theme.list_hover.a > 0.9);
|
||||
assert!(app_list_hover(theme), "{:?}", theme.list_hover);
|
||||
theme.is_dark()
|
||||
});
|
||||
assert_ne!(before, after);
|
||||
@@ -296,7 +307,7 @@ fn the_appearance_setting_drives_the_theme_and_the_title_bar_switch(cx: &mut Tes
|
||||
assert!(theme.is_dark());
|
||||
// Applied like the title bar switch always did it.
|
||||
assert_eq!(theme.list_hover, theme.tokens.list_hover.color);
|
||||
assert!(theme.list_hover.a > 0.9);
|
||||
assert!(app_list_hover(theme), "{:?}", theme.list_hover);
|
||||
});
|
||||
cx.update_window(handle.into(), |_, window, cx| {
|
||||
window.render_frame(cx);
|
||||
|
||||
Reference in new issue
Block a user