主题预设("我全都要"): - gpui-app/themes/ 内嵌官方捆绑 21 个文件 36 变体 + Default Light/Dark - theme.rs:register_bundled_themes(bootstrap 注册)/ apply_theme_preset (Theme::update + apply_config 整包应用)/ theme_preset_names - settings.theme_preset 字段(serde 默认 linear);保存/启动按预设分派, 外观模式与跟随系统监听仅对 Linear 生效(预设自带明暗不被冲掉) - 设置页「主题预设」下拉(Linear + 38 注册名),切换即时生效 语言包按语言分文件(用户要求): - locales/ui.yml → locales/en.yml + locales/zh-CN.yml (rust-i18n 文件名 stem 即 locale,扁平 "ns.key": "value") - i18n.rs 测试改为双文件键集合一致性 + 占位符强制 修复:设置对话框打开即栈溢出 - gpui-kit 0.7.0→0.7.3 无法解决;根因是 Windows 主线程 8MB 栈对 Dialog 覆盖层内 Select 渲染的深调用链不足,注册多主题后越过阈值 (64MB 试验栈下不复现,证实深调用链而非无限递归) - .cargo/config.toml:Windows 主线程栈 8MB→64MB - 注册时机移到 bootstrap(main 的 init_themes,gpui_kit::init 后、 窗口创建前),对齐官方 story 示例的注册姿势 - 误伤修复:gpui-app/Cargo.toml 被批量编辑误删 bin.name 与 build-dependencies 段;examples/flood.rs 误删已恢复 官方 skill 同步:npx skills add 被墙,gh-proxy 镜像克隆后手动覆盖 ~/.agents/skills 与 ~/.zcode/skills 的 gpui-kit + gpui-kit-design-guides。
146 lines
5.8 KiB
Rust
146 lines
5.8 KiB
Rust
//! 主题:Linear 自定义主题 + 官方捆绑预设(38 个)的注册与应用。
|
||
//!
|
||
//! 两套来源:
|
||
//! - **Linear**(`linear-theme.json`,浅/深一对):默认预设,受「跟随系统 /
|
||
//! 浅色 / 深色」外观模式控制;
|
||
//! - **官方捆绑**(`themes/*.json`,36 个变体 + Default Light/Dark):
|
||
//! 编译期内嵌、启动时注册进 ThemeRegistry,选择后整包应用(自带 mode)。
|
||
//!
|
||
//! 预设与外观模式是互斥维度:选定官方预设时由该预设决定全部配色与明暗,
|
||
//! 外观模式开关仅对 Linear 生效(系统深浅变化监听同样跳过官方预设,
|
||
//! 避免冲掉用户选择)。
|
||
|
||
|
||
use gpui_kit::component::{
|
||
Theme, ThemeMode, ThemeRegistry,
|
||
};
|
||
use gpui_kit::App;
|
||
|
||
use crate::model::ThemeModePref;
|
||
|
||
/// 默认预设:Linear 风格主题(内嵌 JSON,模式与 gpui-component 默认主题同构)。
|
||
const LINEAR_THEME: &str = include_str!("linear-theme.json");
|
||
|
||
/// 官方捆绑预设(gpui-kit themes/,21 文件 36 变体 + Default Light/Dark)。
|
||
/// 编译期内嵌:单二进制发布不依赖运行时 themes 目录。
|
||
const BUNDLED_THEME_FILES: &[&str] = &[
|
||
include_str!("../../../themes/adventure.json"),
|
||
include_str!("../../../themes/alduin.json"),
|
||
include_str!("../../../themes/asciinema.json"),
|
||
include_str!("../../../themes/aurora.json"),
|
||
include_str!("../../../themes/ayu.json"),
|
||
include_str!("../../../themes/catppuccin.json"),
|
||
include_str!("../../../themes/everforest.json"),
|
||
include_str!("../../../themes/fahrenheit.json"),
|
||
include_str!("../../../themes/flexoki.json"),
|
||
include_str!("../../../themes/gruvbox.json"),
|
||
include_str!("../../../themes/harper.json"),
|
||
include_str!("../../../themes/hybrid.json"),
|
||
include_str!("../../../themes/jellybeans.json"),
|
||
include_str!("../../../themes/kibble.json"),
|
||
include_str!("../../../themes/macos-classic.json"),
|
||
include_str!("../../../themes/mellifluous.json"),
|
||
include_str!("../../../themes/molokai.json"),
|
||
include_str!("../../../themes/solarized.json"),
|
||
include_str!("../../../themes/spaceduck.json"),
|
||
include_str!("../../../themes/tokyonight.json"),
|
||
include_str!("../../../themes/twilight.json"),
|
||
];
|
||
|
||
/// 默认预设名(settings.theme_preset 的初始值)。
|
||
pub const DEFAULT_PRESET: &str = "linear";
|
||
/// Linear 在预设列表中的展示名。
|
||
pub const LINEAR_PRESET_LABEL: &str = "Linear";
|
||
|
||
/// 应用主题偏好(Linear 预设专用路径)。
|
||
///
|
||
/// 「跟随系统」读取 gpui 的窗口外观;系统深浅色变化由
|
||
/// `MqttXApp::new` 里注册的 `Window::observe_window_appearance` 监听并重应用本函数。
|
||
///
|
||
/// 流程:先 `change` 确保 Theme 全局已建立(注册表 observer 依赖它)→ 注册
|
||
/// Linear Light/Dark(重名跳过,幂等)→ 把当前明暗对指向 Linear → 再 `change`
|
||
/// 应用其颜色/字号/圆角。
|
||
pub fn apply_theme(pref: ThemeModePref, cx: &mut App) {
|
||
eprintln!("[THM] apply_theme enter");
|
||
let mode = match pref {
|
||
ThemeModePref::Light => ThemeMode::Light,
|
||
ThemeModePref::Dark => ThemeMode::Dark,
|
||
ThemeModePref::System => ThemeMode::from(cx.window_appearance()),
|
||
};
|
||
Theme::change(mode, None, cx);
|
||
if let Err(e) = ThemeRegistry::global_mut(cx).load_themes_from_str(LINEAR_THEME) {
|
||
eprintln!("加载 Linear 主题失败,回退默认主题: {e}");
|
||
}
|
||
{
|
||
let reg = ThemeRegistry::global(cx);
|
||
let light = reg.themes().get("Linear Light").cloned();
|
||
let dark = reg.themes().get("Linear Dark").cloned();
|
||
let theme = Theme::global_mut(cx);
|
||
if let Some(l) = light {
|
||
theme.light_theme = l;
|
||
}
|
||
if let Some(d) = dark {
|
||
theme.dark_theme = d;
|
||
}
|
||
}
|
||
Theme::change(mode, None, cx);
|
||
}
|
||
|
||
/// 注册全部官方捆绑预设(幂等,重名由 ThemeRegistry 覆盖/跳过)。
|
||
/// 启动时调用一次。
|
||
pub fn register_bundled_themes(cx: &mut App) {
|
||
eprintln!("[THM] register START");
|
||
// [bisect] 只注册 catppuccin 4 个
|
||
if let Err(e) = ThemeRegistry::global_mut(cx)
|
||
.load_themes_from_str(include_str!("../../../themes/catppuccin.json"))
|
||
{
|
||
eprintln!("加载捆绑主题预设失败: {e}");
|
||
}
|
||
eprintln!("[THM] register catppuccin done");
|
||
#[allow(clippy::needless_range_loop)]
|
||
for _i in 0..0 {
|
||
for json in BUNDLED_THEME_FILES {
|
||
if let Err(e) = ThemeRegistry::global_mut(cx).load_themes_from_str(json) {
|
||
eprintln!("加载捆绑主题预设失败: {e}");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 应用官方捆绑预设(settings.theme_preset ≠ "linear" 时的路径)。
|
||
/// 整包应用:apply_config 连同预设自带的 mode 一起生效,
|
||
/// 经 Theme::update 走官方 reconcile + sync + 全窗刷新。
|
||
/// 名字不存在(理论不可达,测试拦截)时返回 false 回退 Linear。
|
||
pub fn apply_theme_preset(name: &str, cx: &mut App) -> bool {
|
||
eprintln!("[THM] apply_theme_preset enter ({name})");
|
||
let config = ThemeRegistry::global(cx).themes().get(name).cloned();
|
||
let Some(config) = config else {
|
||
return false;
|
||
};
|
||
Theme::update(cx, |theme| theme.apply_config(&config));
|
||
true
|
||
}
|
||
|
||
/// 预设展示名单:Linear 在首,其余按名字排序(与 ThemeRegistry 一致)。
|
||
pub fn theme_preset_names(cx: &App) -> Vec<String> {
|
||
eprintln!("[THM] theme_preset_names enter");
|
||
let mut names: Vec<String> = ThemeRegistry::global(cx)
|
||
.themes()
|
||
.keys()
|
||
.map(|k| k.to_string())
|
||
.collect();
|
||
names.sort();
|
||
let mut out = vec![LINEAR_PRESET_LABEL.to_string()];
|
||
out.extend(names);
|
||
out
|
||
}
|
||
|
||
/// 展示名 → 存储名(Linear 之外的官方预设直接用注册名)。
|
||
pub fn preset_storage_name(label: &str) -> String {
|
||
if label == LINEAR_PRESET_LABEL {
|
||
DEFAULT_PRESET.to_string()
|
||
} else {
|
||
label.to_string()
|
||
}
|
||
}
|