# gpui-component ## 项目概述 基于 GPUI 的跨平台桌面 UI 组件库,提供 60+ 组件,受 macOS/Windows 控件和 shadcn/ui 启发。 **本地路径**: `vendors/gpui-component` ## 目录结构 ``` gpui-component/ ├── Cargo.toml # workspace 配置 ├── crates/ │ ├── ui/ # 核心 UI 库 │ │ ├── Cargo.toml │ │ ├── src/ │ │ │ ├── lib.rs # 库入口 │ │ │ ├── init.rs # 初始化 │ │ │ ├── app.rs # 应用生命周期 │ │ │ ├── theme/ # 主题系统 │ │ │ │ ├── mod.rs │ │ │ │ ├── color.rs │ │ │ │ ├── registry.rs │ │ │ │ ├── schema.rs │ │ │ │ └── theme_color.rs │ │ │ ├── input/ # 输入组件 │ │ │ │ ├── mod.rs │ │ │ │ ├── input.rs │ │ │ │ ├── number_input.rs │ │ │ │ ├── state.rs │ │ │ │ └── lsp/ # LSP 集成 │ │ │ ├── button/ # 按钮组件 │ │ │ │ └── mod.rs │ │ │ ├── chart/ # 图表组件 │ │ │ │ └── mod.rs │ │ │ ├── dock/ # 停靠面板 │ │ │ │ └── mod.rs │ │ │ ├── menu/ # 菜单系统 │ │ │ │ └── mod.rs │ │ │ ├── list/ # 列表组件 │ │ │ │ └── mod.rs │ │ │ ├── table/ # 表格组件 │ │ │ │ ├── mod.rs │ │ │ │ ├── table.rs │ │ │ │ └── data_table.rs # 高级表格 │ │ │ ├── dialog/ # 对话框 │ │ │ │ └── mod.rs │ │ │ ├── navigation/ # 导航组件 │ │ │ │ ├── mod.rs │ │ │ │ ├── breadcrumb.rs │ │ │ │ ├── pagination.rs │ │ │ │ └── tabs.rs │ │ │ ├── feedback/ # 反馈组件 │ │ │ │ ├── mod.rs │ │ │ │ ├── alert.rs │ │ │ │ ├── loading.rs │ │ │ │ ├── progress.rs │ │ │ │ └── toast.rs │ │ │ ├── overlay/ # 浮层组件 │ │ │ │ ├── mod.rs │ │ │ │ ├── popover.rs │ │ │ │ ├── tooltip.rs │ │ │ │ └── sheet.rs │ │ │ ├── form/ # 表单组件 │ │ │ │ ├── mod.rs │ │ │ │ └── form.rs │ │ │ ├── scroll/ # 滚动组件 │ │ │ │ └── mod.rs │ │ │ ├── avatar/ # 头像组件 │ │ │ │ └── mod.rs │ │ │ ├── badge/ # 徽章组件 │ │ │ │ └── mod.rs │ │ │ ├── card/ # 卡片组件 │ │ │ │ └── mod.rs │ │ │ ├── divider/ # 分隔线 │ │ │ │ └── mod.rs │ │ │ ├── icon/ # 图标 │ │ │ │ └── mod.rs │ │ │ ├── image/ # 图像 │ │ │ │ └── mod.rs │ │ │ ├── select/ # 选择器 │ │ │ │ └── mod.rs │ │ │ ├── checkbox/ # 复选框 │ │ │ │ └── mod.rs │ │ │ ├── radio/ # 单选框 │ │ │ │ └── mod.rs │ │ │ ├── switch/ # 开关 │ │ │ │ └── mod.rs │ │ │ ├── slider/ # 滑块 │ │ │ │ └── mod.rs │ │ │ ├── date_picker/ # 日期选择 │ │ │ │ └── mod.rs │ │ │ ├── color_picker/ # 颜色选择 │ │ │ │ └── mod.rs │ │ │ ├── tree/ # 树形组件 │ │ │ │ └── mod.rs │ │ │ ├── virtual_list/ # 虚拟列表 │ │ │ │ └── mod.rs │ │ │ ├── markdown/ # Markdown │ │ │ │ └── mod.rs │ │ │ └── ... │ │ └── Cargo.toml │ │ │ ├── story/ # 组件展示应用 │ │ ├── Cargo.toml │ │ ├── src/ │ │ │ ├── main.rs │ │ │ ├── app.rs │ │ │ ├── storybook.rs # StoryBook 框架 │ │ │ └── stories/ # 组件示例 │ │ │ ├── button_story.rs │ │ │ ├── input_story.rs │ │ │ └── ... │ │ └── Cargo.toml │ │ │ ├── macros/ # 过程宏 │ │ ├── Cargo.toml │ │ ├── src/ │ │ │ └── lib.rs │ │ └── Cargo.toml │ │ │ ├── assets/ # 静态资源 │ │ ├── src/ │ │ │ └── lib.rs │ │ ├── icons/ # 图标资源 │ │ └── themes/ # 主题文件 │ │ └── Cargo.toml │ │ │ └── webview/ # WebView 支持 │ ├── Cargo.toml │ └── src/ │ ├── lib.rs │ └── webview.rs │ └── Cargo.toml ``` ## 核心依赖 ```toml [workspace] members = ["crates/*"] [dependencies] # GPUI 框架 gpui = "1.0" # 网络 reqwest = { version = "0.11", features = ["json", "blocking"] } # 文本处理 ropey = "1.6" unicode-width = "0.1" # 文件系统 notify = { version = "6.0", features = ["default"] } # 序列化 serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" # 日志 tracing = "0.1" tracing-subscriber = "0.3" # 国际化 rust-i18n = "2.0" # LSP lsp-types = "0.94" ``` ## 主题系统 ### 主题结构 ```rust // crates/ui/src/theme/mod.rs use std::sync::Arc; /// 主题配置 #[derive(Clone, Debug, Default)] pub struct Theme { /// 颜色配置 pub colors: ThemeColor, /// 亮色主题配置 pub light_theme: Rc, /// 暗色主题配置 pub dark_theme: Rc, /// 当前主题模式 pub mode: ThemeMode, /// 字体配置 pub font_family: SharedString, pub font_size: Pixels, /// 圆角 pub radius: Pixels, /// 滚动条显示 pub scrollbar_show: ScrollbarShow, /// 语法高亮主题 pub highlight_theme: Arc, } pub enum ThemeMode { Light, Dark, System, } pub struct ThemeConfig { /// 背景色 pub background: Hsla, /// 前景色 pub foreground: Hsla, /// 边框色 pub border: Hsla, /// 主要色 pub primary: Hsla, /// 次要色 pub secondary: Hsla, /// 强调色 pub accent: Hsla, /// 成功色 pub success: Hsla, /// 警告色 pub warning: Hsla, /// 错误色 pub error: Hsla, /// 信息色 pub info: Hsla, /// 悬停色 pub hover: Hsla, /// 激活色 pub active: Hsla, /// 选中色 pub selected: Hsla, /// 禁用色 pub disabled: Hsla, /// 边框样式 pub border: BorderStyle, /// 阴影 pub shadow: Shadow, } /// 主题颜色 #[derive(Clone, Debug)] pub struct ThemeColor { pub app_background: Hsla, pub sidebar_background: Hsla, pub panel_background: Hsla, pub editor_background: Hsla, pub status_bar_background: Hsla, pub title_bar_background: Hsla, // ... } ``` ### ActiveTheme Trait ```rust // 主题 Trait,用于获取当前主题 pub trait ActiveTheme { fn theme(&self) -> &Theme; } impl ActiveTheme for AppContext { fn theme(&self) -> &Theme { Theme::global(self) } } // 使用示例 fn my_component(cx: &mut ViewContext) -> impl IntoElement { div() .bg(cx.theme().colors.background) .text_color(cx.theme().colors.foreground) .border_1() .border_color(cx.theme().colors.border) } ``` ## 核心组件 ### Button 组件 ```rust // crates/ui/src/button/mod.rs use gpui::{div, prelude::*, AnyElement, ViewContext}; pub struct Button { /// 按钮文本 label: SharedString, /// 图标 icon: Option, /// 变体 variant: ButtonVariant, /// 尺寸 size: ButtonSize, /// 是否加载中 loading: bool, /// 是否禁用 disabled: bool, /// 点击事件 on_click: Arc, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ButtonVariant { Primary, Secondary, Outline, Ghost, Danger, Link, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ButtonSize { Small, Medium, Large, } impl Button { pub fn new( label: impl Into, on_click: impl Fn(&mut WindowContext) + 'static, ) -> Self { Self { label: label.into(), icon: None, variant: ButtonVariant::Primary, size: ButtonSize::Medium, loading: false, disabled: false, on_click: Arc::new(on_click), } } pub fn with_icon(mut self, icon: impl Into) -> Self { self.icon = Some(icon.into()); self } pub fn variant(mut self, variant: ButtonVariant) -> Self { self.variant = variant; self } pub fn size(mut self, size: ButtonSize) -> Self { self.size = size; self } pub fn loading(mut self, loading: bool) -> Self { self.loading = loading; self } pub fn disabled(mut self, disabled: bool) -> Self { self.disabled = disabled; self } } impl Render for Button { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { let variant_style = match self.variant { ButtonVariant::Primary => button_primary_style(cx), ButtonVariant::Secondary => button_secondary_style(cx), ButtonVariant::Outline => button_outline_style(cx), ButtonVariant::Ghost => button_ghost_style(cx), ButtonVariant::Danger => button_danger_style(cx), ButtonVariant::Link => button_link_style(cx), }; let size_style = match self.size { ButtonSize::Small => button_sm_style(cx), ButtonSize::Medium => button_md_style(cx), ButtonSize::Large => button_lg_style(cx), }; let disabled = self.disabled || self.loading; div() .id("button") .px_4() .py_2() .rounded-lg() .font_medium() .cursor(disabled.then_some(Cursor::NotAllowed)) .text_color(disabled.then_some(cx.theme().colors.disabled)) .bg(disabled.then_some(variant_style.bg).unwrap_or(variant_style.bg)) .border_1() .border_color(disabled.then_some(cx.theme().colors.disabled).unwrap_or(variant_style.border)) .transition() .when(!disabled, |this| { this.hover(|s| s.bg(variant_style.hover_bg)) .active(|s| s.bg(variant_style.active_bg)) }) .when(self.loading, |this| { this.child( div() .animate() .spin() .w_4() .h_4() .border_2() .border_current() .rounded_full() ) }) .when(!self.loading && self.icon.is_some(), |this| { this.child(div().child(self.icon.clone().unwrap())) }) .when(!self.loading, |this| { this.on_click(move |_, cx| { if !disabled { (self.on_click)(cx); } }) }) .child(self.label.clone()) } } /// 便捷函数 pub fn button( label: impl Into, on_click: impl Fn(&mut WindowContext) + 'static, ) -> Button { Button::new(label, on_click) } pub fn primary_button( label: impl Into, on_click: impl Fn(&mut WindowContext) + 'static, ) -> Button { Button::new(label, on_click).variant(ButtonVariant::Primary) } pub fn secondary_button( label: impl Into, on_click: impl Fn(&mut WindowContext) + 'static, ) -> Button { Button::new(label, on_click).variant(ButtonVariant::Secondary) } ``` ### Input 组件 ```rust // crates/ui/src/input/mod.rs use ropey::Rope; pub struct Input { /// 绑定值 value: SharedString, /// 占位符 placeholder: SharedString, /// 是否只读 readonly: bool, /// 是否禁用 disabled: bool, /// 多行模式 multiline: bool, /// 高度(多行模式) rows: usize, /// 焦点状态 focused: bool, /// 事件 on_change: Arc, on_focus: Arc, on_blur: Arc, on_enter: Arc, } impl Input { pub fn new( value: impl Into, on_change: impl Fn(String, &mut WindowContext) + 'static, ) -> Self { Self { value: value.into(), placeholder: "".into(), readonly: false, disabled: false, multiline: false, rows: 3, focused: false, on_change: Arc::new(on_change), on_focus: Arc::new(|_| {}), on_blur: Arc::new(|_| {}), on_enter: Arc::new(|_| {}), } } pub fn placeholder(mut self, placeholder: impl Into) -> Self { self.placeholder = placeholder.into(); self } pub fn disabled(mut self, disabled: bool) -> Self { self.disabled = disabled; self } pub fn multiline(mut self, rows: usize) -> Self { self.multiline = true; self.rows = rows; self } } impl Render for Input { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { let theme = cx.theme(); div() .w_full() .bg(theme.colors.surface) .border_1() .border_color( if self.focused { theme.colors.primary } else { theme.colors.border }, ) .rounded_md() .px_3() .py_2() .when(self.multiline, |this| this.min_h_10()) .when(!self.multiline, |this| this.h_10()) .child( if self.multiline { TextArea::new(self.value.clone(), move |s, _| s) .placeholder(self.placeholder.clone()) .disabled(self.disabled) .on_change(self.on_change.clone()) .into_any_element() } else { TextInput::new(self.value.clone(), move |s, _| s) .placeholder(self.placeholder.clone()) .disabled(self.disabled) .on_change(self.on_change.clone()) .into_any_element() } ) } } ``` ### DataTable 组件 ```rust // crates/ui/src/table/data_table.rs use std::collections::HashMap; /// 表格列配置 pub struct TableColumn { /// 列标题 pub title: SharedString, /// 列宽 pub width: Pixels, /// 渲染函数 pub render: Arc AnyElement>, /// 是否可排序 pub sortable: bool, /// 对齐方式 pub align: Align, } /// 表格状态 pub struct DataTableState { /// 数据 data: Vec, /// 排序字段 sort_by: Option, /// 排序方向 sort_direction: SortDirection, /// 过滤文本 filter: String, /// 分页 pagination: Option, } /// 分页状态 pub struct PaginationState { pub page: usize, pub page_size: usize, pub total: usize, } pub enum SortDirection { Asc, Desc, } ``` ### Dialog 组件 ```rust // crates/ui/src/dialog/mod.rs pub struct Dialog { /// 标题 title: SharedString, /// 内容 content: AnyElement, /// 底部按钮 actions: Vec