主题预设("我全都要"): - 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。
98 lines
2.8 KiB
Rust
98 lines
2.8 KiB
Rust
//! 可搜索的枚举下拉:选项模型、委托与统一外观的 Select 包装。
|
|
|
|
use gpui_kit::component::searchable_list::{ SearchableListDelegate, SearchableListItem };
|
|
use gpui_kit::component::select::{ Select, SelectState };
|
|
use gpui_kit::component::IndexPath;
|
|
use gpui_kit::{ Entity, SharedString, Window };
|
|
|
|
#[derive(Clone)]
|
|
pub struct OptionItem {
|
|
pub value: usize,
|
|
pub label: SharedString,
|
|
}
|
|
|
|
impl SearchableListItem for OptionItem {
|
|
type Value = usize;
|
|
fn title(&self) -> SharedString {
|
|
self.label.clone()
|
|
}
|
|
fn value(&self) -> &usize {
|
|
&self.value
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct OptionDelegate {
|
|
pub items: Vec<OptionItem>,
|
|
}
|
|
|
|
impl OptionDelegate {
|
|
pub fn new(labels: &[&str]) -> Self {
|
|
Self {
|
|
items: labels
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(i, l)| OptionItem {
|
|
value: i,
|
|
label: (*l).into(),
|
|
})
|
|
.collect(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl SearchableListDelegate for OptionDelegate {
|
|
type Item = OptionItem;
|
|
fn items_count(&self, _section: usize) -> usize {
|
|
eprintln!("[SEL] items_count={}", self.items.len());
|
|
self.items.len()
|
|
}
|
|
fn item(&self, ix: IndexPath) -> Option<&Self::Item> {
|
|
eprintln!("[SEL] item row={}", ix.row);
|
|
self.items.get(ix.row)
|
|
}
|
|
fn position<V>(&self, value: &V) -> Option<IndexPath>
|
|
where
|
|
Self::Item: SearchableListItem<Value = V>,
|
|
V: PartialEq,
|
|
{
|
|
// 框架契约:按值反查索引,set_selected_value 与过滤视图都依赖它。
|
|
// 返回 None 会导致下拉显示空、保存写默认值,这里必须如实实现。
|
|
self.items
|
|
.iter()
|
|
.position(|i| i.value() == value)
|
|
.map(IndexPath::new)
|
|
}
|
|
}
|
|
|
|
/// 创建字符串枚举下拉状态(在任意实体上下文里调用)。
|
|
pub fn make_select<C: gpui_kit::AppContext>(
|
|
labels: &[&str],
|
|
selected: usize,
|
|
window: &mut Window,
|
|
cx: &mut C,
|
|
) -> Entity<SelectState<OptionDelegate>> {
|
|
let selected = selected.min(labels.len().saturating_sub(1));
|
|
cx.new(|cx| {
|
|
SelectState::new(
|
|
OptionDelegate::new(labels),
|
|
Some(IndexPath::new(selected)),
|
|
window,
|
|
cx,
|
|
)
|
|
})
|
|
}
|
|
|
|
// ─── 字段行 ──────────────────────────────────────────────────────────────────
|
|
|
|
|
|
/// Select::new 的包装,统一外观。
|
|
pub fn select_element<D: SearchableListDelegate>(
|
|
state: &Entity<SelectState<D>>,
|
|
) -> Select<D>
|
|
where
|
|
<D::Item as SearchableListItem>::Value: PartialEq + Clone,
|
|
{
|
|
Select::new(state)
|
|
}
|