gpui-style-guide by longbridge/gpui-component
npx skills add https://github.com/longbridge/gpui-component --skill gpui-style-guide源自 gpui-component 实现模式的代码风格指南。
基于:对 crates/ui 中 Button、Checkbox、Input、Select 等组件的分析
use gpui::{
div, prelude::FluentBuilder as _, AnyElement, App, Div, ElementId,
InteractiveElement, IntoElement, ParentElement, RenderOnce,
StatefulInteractiveElement, StyleRefinement, Styled, Window,
};
#[derive(IntoElement)]
pub struct MyComponent {
id: ElementId,
base: Div,
style: StyleRefinement,
// 配置字段
size: Size,
disabled: bool,
selected: bool,
// 内容字段
label: Option<Text>,
children: Vec<AnyElement>,
// 回调函数(使用 Rc 以实现 Clone)
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,
}
impl MyComponent {
pub fn new(id: impl Into<ElementId>) -> Self {
Self {
id: id.into(),
base: div(),
style: StyleRefinement::default(),
size: Size::default(),
disabled: false,
selected: false,
label: None,
children: Vec::new(),
on_click: None,
}
}
// 构建器方法
pub fn label(mut self, label: impl Into<Text>) -> Self {
self.label = Some(label.into());
self
}
pub fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static) -> Self {
self.on_click = Some(Rc::new(handler));
self
}
}
impl InteractiveElement for MyComponent {
fn interactivity(&mut self) -> &mut gpui::Interactivity {
self.base.interactivity()
}
}
impl StatefulInteractiveElement for MyComponent {}
impl Styled for MyComponent {
fn style(&mut self) -> &mut StyleRefinement {
&mut self.style
}
}
impl RenderOnce for MyComponent {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
// 实现
self.base
}
}
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
#[derive(IntoElement)]
pub struct Select {
state: Entity<SelectState>,
style: StyleRefinement,
size: Size,
// ...
}
pub struct SelectState {
open: bool,
selected_index: Option<usize>,
// ...
}
impl Select {
pub fn new(state: &Entity<SelectState>) -> Self {
Self {
state: state.clone(),
size: Size::default(),
style: StyleRefinement::default(),
}
}
}
impl Sizable for MyComponent {
fn with_size(mut self, size: impl Into<Size>) -> Self {
self.size = size.into();
self
}
}
impl Selectable for MyComponent {
fn selected(mut self, selected: bool) -> Self {
self.selected = selected;
self
}
fn is_selected(&self) -> bool {
self.selected
}
}
impl Disableable for MyComponent {
fn disabled(mut self, disabled: bool) -> Self {
self.disabled = disabled;
self
}
fn is_disabled(&self) -> bool {
self.disabled
}
}
#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)]
pub enum ButtonVariant {
Primary,
#[default]
Secondary,
Danger,
Success,
Warning,
Ghost,
Link,
}
pub trait ButtonVariants: Sized {
fn with_variant(self, variant: ButtonVariant) -> Self;
/// 使用 Button 的主样式。
fn primary(self) -> Self {
self.with_variant(ButtonVariant::Primary)
}
/// 使用 Button 的危险样式。
fn danger(self) -> Self {
self.with_variant(ButtonVariant::Danger)
}
// ... 更多变体
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct ButtonCustomVariant {
color: Hsla,
foreground: Hsla,
border: Hsla,
hover: Hsla,
active: Hsla,
shadow: bool,
}
impl ButtonCustomVariant {
pub fn new(cx: &App) -> Self {
Self {
color: cx.theme().transparent,
foreground: cx.theme().foreground,
// ...
shadow: false,
}
}
pub fn color(mut self, color: Hsla) -> Self {
self.color = color;
self
}
// ... 更多构建器方法
}
const CONTEXT: &str = "Select";
pub(crate) fn init(cx: &mut App) {
cx.bind_keys([
KeyBinding::new("up", SelectUp, Some(CONTEXT)),
KeyBinding::new("down", SelectDown, Some(CONTEXT)),
KeyBinding::new("enter", Confirm { secondary: false }, Some(CONTEXT)),
KeyBinding::new("escape", Cancel, Some(CONTEXT)),
])
}
use crate::actions::{Cancel, Confirm, SelectDown, SelectUp};
div()
.key_context(CONTEXT)
.on_action(cx.listener(Self::on_action_select_up))
.on_action(cx.listener(Self::on_action_confirm))
pub trait SelectItem: Clone {
type Value: Clone;
fn title(&self) -> SharedString;
fn display_title(&self) -> Option<AnyElement> {
None
}
fn render(&self, _: &mut Window, _: &mut App) -> impl IntoElement {
self.title().into_element()
}
fn value(&self) -> &Self::Value;
fn matches(&self, query: &str) -> bool {
self.title().to_lowercase().contains(&query.to_lowercase())
}
}
impl SelectItem for String {
type Value = Self;
fn title(&self) -> SharedString {
SharedString::from(self.to_string())
}
fn value(&self) -> &Self::Value {
&self
}
}
impl SelectItem for SharedString { /* ... */ }
impl SelectItem for &'static str { /* ... */ }
pub trait IconNamed {
fn path(self) -> SharedString;
}
impl<T: IconNamed> From<T> for Icon {
fn from(value: T) -> Self {
Icon::build(value)
}
}
#[derive(IntoElement, Clone)]
pub enum IconName {
ArrowDown,
ArrowUp,
Check,
Close,
// ... 所有图标名称
}
/// 一个 Checkbox 元素。
#[derive(IntoElement)]
pub struct Checkbox { }
impl Checkbox {
/// 使用给定的 id 创建一个新的 Checkbox。
pub fn new(id: impl Into<ElementId>) -> Self { }
/// 为复选框设置标签。
pub fn label(mut self, label: impl Into<Text>) -> Self { }
/// 为复选框设置点击处理程序。
///
/// `&bool` 参数表示点击后的新选中状态。
pub fn on_click(mut self, handler: impl Fn(&bool, &mut Window, &mut App) + 'static) -> Self { }
}
// 1. 外部 crate 导入
use std::rc::Rc;
// 2. Crate 导入
use crate::{
ActiveTheme, Disableable, FocusableExt, Icon, IconName,
Selectable, Sizable, Size, StyledExt,
};
// 3. GPUI 导入
use gpui::{
div, prelude::FluentBuilder as _, px, relative, rems,
AnyElement, App, Div, ElementId, InteractiveElement,
IntoElement, ParentElement, RenderOnce,
StatefulInteractiveElement, StyleRefinement, Styled, Window,
};
pub struct Component {
// 1. 标识
id: ElementId,
base: Div,
style: StyleRefinement,
// 2. 配置
size: Size,
disabled: bool,
selected: bool,
tab_stop: bool,
tab_index: isize,
// 3. 内容/子元素
label: Option<Text>,
children: Vec<AnyElement>,
prefix: Option<AnyElement>,
suffix: Option<AnyElement>,
// 4. 回调函数(最后)
on_click: Option<Rc<dyn Fn(Args, &mut Window, &mut App) + 'static>>,
}
pub fn prefix(mut self, prefix: impl IntoElement) -> Self {
self.prefix = Some(prefix.into_any_element());
self
}
// 模式 1: 事件参数在前
pub fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static) -> Self {
self.on_click = Some(Rc::new(handler));
self
}
// 模式 2: 状态参数
pub fn on_change(mut self, handler: impl Fn(&bool, &mut Window, &mut App) + 'static) -> Self {
self.on_change = Some(Rc::new(handler));
self
}
fn handle_click(
on_click: &Option<Rc<dyn Fn(&bool, &mut Window, &mut App) + 'static>>,
checked: bool,
window: &mut Window,
cx: &mut App,
) {
let new_checked = !checked;
if let Some(f) = on_click {
(f)(&new_checked, window, cx);
}
}
// 启用/禁用模式
pub fn cleanable(mut self, cleanable: bool) -> Self {
self.cleanable = cleanable;
self
}
// 切换方法(无参数)
pub fn mask_toggle(mut self) -> Self {
self.mask_toggle = true;
self
}
impl Sizable for Component {
fn with_size(mut self, size: impl Into<Size>) -> Self {
self.size = size.into();
self
}
}
组件通过 StyleSized trait 自动获得 .xsmall()、.small()、.medium()、.large() 方法。
impl RenderOnce for MyComponent {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let (width, height) = self.size.input_size();
self.base
.id(self.id)
.flex()
.items_center()
.gap(px(8.))
.min_w(width)
.h(height)
.when(self.disabled, |this| {
this.opacity(0.5).cursor_not_allowed()
})
.children(self.children)
}
}
// 访问主题颜色
cx.theme().surface
cx.theme().foreground
cx.theme().border
cx.theme().primary
cx.theme().transparent
// 在组件中使用
div()
.bg(cx.theme().surface)
.text_color(cx.theme().foreground)
.border_color(cx.theme().border)
组件示例:参见 component-examples.md
Trait 模式:参见 trait-patterns.md
在 crates/ui 中创建新组件时:
#[derive(IntoElement)]id: ElementId、base: Div、style: StyleRefinementInteractiveElement、StatefulInteractiveElement、StyledRenderOnce traitSizableSelectableDisableableRc<dyn Fn>Option<AnyElement>prelude::FluentBuilder as _cx.theme() 使用主题颜色每周安装量
140
仓库
GitHub 星标
10.7K
首次出现
2026 年 1 月 21 日
安全审计
安装于
opencode129
gemini-cli124
codex124
github-copilot117
cursor110
amp107
Code style guide derived from gpui-component implementation patterns.
Based on : Analysis of Button, Checkbox, Input, Select, and other components in crates/ui
use gpui::{
div, prelude::FluentBuilder as _, AnyElement, App, Div, ElementId,
InteractiveElement, IntoElement, ParentElement, RenderOnce,
StatefulInteractiveElement, StyleRefinement, Styled, Window,
};
#[derive(IntoElement)]
pub struct MyComponent {
id: ElementId,
base: Div,
style: StyleRefinement,
// Configuration fields
size: Size,
disabled: bool,
selected: bool,
// Content fields
label: Option<Text>,
children: Vec<AnyElement>,
// Callbacks (use Rc for Clone)
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,
}
impl MyComponent {
pub fn new(id: impl Into<ElementId>) -> Self {
Self {
id: id.into(),
base: div(),
style: StyleRefinement::default(),
size: Size::default(),
disabled: false,
selected: false,
label: None,
children: Vec::new(),
on_click: None,
}
}
// Builder methods
pub fn label(mut self, label: impl Into<Text>) -> Self {
self.label = Some(label.into());
self
}
pub fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static) -> Self {
self.on_click = Some(Rc::new(handler));
self
}
}
impl InteractiveElement for MyComponent {
fn interactivity(&mut self) -> &mut gpui::Interactivity {
self.base.interactivity()
}
}
impl StatefulInteractiveElement for MyComponent {}
impl Styled for MyComponent {
fn style(&mut self) -> &mut StyleRefinement {
&mut self.style
}
}
impl RenderOnce for MyComponent {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
// Implementation
self.base
}
}
#[derive(IntoElement)]
pub struct Select {
state: Entity<SelectState>,
style: StyleRefinement,
size: Size,
// ...
}
pub struct SelectState {
open: bool,
selected_index: Option<usize>,
// ...
}
impl Select {
pub fn new(state: &Entity<SelectState>) -> Self {
Self {
state: state.clone(),
size: Size::default(),
style: StyleRefinement::default(),
}
}
}
impl Sizable for MyComponent {
fn with_size(mut self, size: impl Into<Size>) -> Self {
self.size = size.into();
self
}
}
impl Selectable for MyComponent {
fn selected(mut self, selected: bool) -> Self {
self.selected = selected;
self
}
fn is_selected(&self) -> bool {
self.selected
}
}
impl Disableable for MyComponent {
fn disabled(mut self, disabled: bool) -> Self {
self.disabled = disabled;
self
}
fn is_disabled(&self) -> bool {
self.disabled
}
}
#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)]
pub enum ButtonVariant {
Primary,
#[default]
Secondary,
Danger,
Success,
Warning,
Ghost,
Link,
}
pub trait ButtonVariants: Sized {
fn with_variant(self, variant: ButtonVariant) -> Self;
/// With the primary style for the Button.
fn primary(self) -> Self {
self.with_variant(ButtonVariant::Primary)
}
/// With the danger style for the Button.
fn danger(self) -> Self {
self.with_variant(ButtonVariant::Danger)
}
// ... more variants
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct ButtonCustomVariant {
color: Hsla,
foreground: Hsla,
border: Hsla,
hover: Hsla,
active: Hsla,
shadow: bool,
}
impl ButtonCustomVariant {
pub fn new(cx: &App) -> Self {
Self {
color: cx.theme().transparent,
foreground: cx.theme().foreground,
// ...
shadow: false,
}
}
pub fn color(mut self, color: Hsla) -> Self {
self.color = color;
self
}
// ... more builder methods
}
const CONTEXT: &str = "Select";
pub(crate) fn init(cx: &mut App) {
cx.bind_keys([
KeyBinding::new("up", SelectUp, Some(CONTEXT)),
KeyBinding::new("down", SelectDown, Some(CONTEXT)),
KeyBinding::new("enter", Confirm { secondary: false }, Some(CONTEXT)),
KeyBinding::new("escape", Cancel, Some(CONTEXT)),
])
}
use crate::actions::{Cancel, Confirm, SelectDown, SelectUp};
div()
.key_context(CONTEXT)
.on_action(cx.listener(Self::on_action_select_up))
.on_action(cx.listener(Self::on_action_confirm))
pub trait SelectItem: Clone {
type Value: Clone;
fn title(&self) -> SharedString;
fn display_title(&self) -> Option<AnyElement> {
None
}
fn render(&self, _: &mut Window, _: &mut App) -> impl IntoElement {
self.title().into_element()
}
fn value(&self) -> &Self::Value;
fn matches(&self, query: &str) -> bool {
self.title().to_lowercase().contains(&query.to_lowercase())
}
}
impl SelectItem for String {
type Value = Self;
fn title(&self) -> SharedString {
SharedString::from(self.to_string())
}
fn value(&self) -> &Self::Value {
&self
}
}
impl SelectItem for SharedString { /* ... */ }
impl SelectItem for &'static str { /* ... */ }
pub trait IconNamed {
fn path(self) -> SharedString;
}
impl<T: IconNamed> From<T> for Icon {
fn from(value: T) -> Self {
Icon::build(value)
}
}
#[derive(IntoElement, Clone)]
pub enum IconName {
ArrowDown,
ArrowUp,
Check,
Close,
// ... all icon names
}
/// A Checkbox element.
#[derive(IntoElement)]
pub struct Checkbox { }
impl Checkbox {
/// Create a new Checkbox with the given id.
pub fn new(id: impl Into<ElementId>) -> Self { }
/// Set the label for the checkbox.
pub fn label(mut self, label: impl Into<Text>) -> Self { }
/// Set the click handler for the checkbox.
///
/// The `&bool` parameter indicates the new checked state after the click.
pub fn on_click(mut self, handler: impl Fn(&bool, &mut Window, &mut App) + 'static) -> Self { }
}
// 1. External crate imports
use std::rc::Rc;
// 2. Crate imports
use crate::{
ActiveTheme, Disableable, FocusableExt, Icon, IconName,
Selectable, Sizable, Size, StyledExt,
};
// 3. GPUI imports
use gpui::{
div, prelude::FluentBuilder as _, px, relative, rems,
AnyElement, App, Div, ElementId, InteractiveElement,
IntoElement, ParentElement, RenderOnce,
StatefulInteractiveElement, StyleRefinement, Styled, Window,
};
pub struct Component {
// 1. Identity
id: ElementId,
base: Div,
style: StyleRefinement,
// 2. Configuration
size: Size,
disabled: bool,
selected: bool,
tab_stop: bool,
tab_index: isize,
// 3. Content/children
label: Option<Text>,
children: Vec<AnyElement>,
prefix: Option<AnyElement>,
suffix: Option<AnyElement>,
// 4. Callbacks (last)
on_click: Option<Rc<dyn Fn(Args, &mut Window, &mut App) + 'static>>,
}
pub fn prefix(mut self, prefix: impl IntoElement) -> Self {
self.prefix = Some(prefix.into_any_element());
self
}
// Pattern 1: Event parameter first
pub fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static) -> Self {
self.on_click = Some(Rc::new(handler));
self
}
// Pattern 2: State parameter
pub fn on_change(mut self, handler: impl Fn(&bool, &mut Window, &mut App) + 'static) -> Self {
self.on_change = Some(Rc::new(handler));
self
}
fn handle_click(
on_click: &Option<Rc<dyn Fn(&bool, &mut Window, &mut App) + 'static>>,
checked: bool,
window: &mut Window,
cx: &mut App,
) {
let new_checked = !checked;
if let Some(f) = on_click {
(f)(&new_checked, window, cx);
}
}
// Enable/disable patterns
pub fn cleanable(mut self, cleanable: bool) -> Self {
self.cleanable = cleanable;
self
}
// Toggle methods (no parameter)
pub fn mask_toggle(mut self) -> Self {
self.mask_toggle = true;
self
}
impl Sizable for Component {
fn with_size(mut self, size: impl Into<Size>) -> Self {
self.size = size.into();
self
}
}
Components get .xsmall(), .small(), .medium(), .large() automatically via StyleSized trait.
impl RenderOnce for MyComponent {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let (width, height) = self.size.input_size();
self.base
.id(self.id)
.flex()
.items_center()
.gap(px(8.))
.min_w(width)
.h(height)
.when(self.disabled, |this| {
this.opacity(0.5).cursor_not_allowed()
})
.children(self.children)
}
}
// Access theme colors
cx.theme().surface
cx.theme().foreground
cx.theme().border
cx.theme().primary
cx.theme().transparent
// Use in components
div()
.bg(cx.theme().surface)
.text_color(cx.theme().foreground)
.border_color(cx.theme().border)
Component Examples : See component-examples.md
Trait Patterns : See trait-patterns.md
When creating a new component in crates/ui:
#[derive(IntoElement)] on structid: ElementId, base: Div, style: StyleRefinementInteractiveElement, StatefulInteractiveElement, StyledRenderOnce traitSizable if component has sizesSelectable if component can be selectedWeekly Installs
140
Repository
GitHub Stars
10.7K
First Seen
Jan 21, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode129
gemini-cli124
codex124
github-copilot117
cursor110
amp107
Kotlin 开发模式与最佳实践 | 构建健壮高效应用程序的惯用指南
1,100 周安装
App Store Connect 参考指南:崩溃分析、TestFlight反馈与性能指标导出
162 周安装
WordPress全站编辑(FSE)与区块编辑器指南:theme.json配置与站点编辑器实战
162 周安装
GPUI FocusHandle 焦点管理:Rust UI 键盘导航与焦点追踪组件库
163 周安装
API 设计指南:公共 API 兼容性与版本控制最佳实践
162 周安装
Flutter 3+ 跨平台开发专家 | Riverpod 状态管理、Impeller 渲染优化、原生集成
165 周安装
交互式小说诊断技能:解决分支叙事问题,平衡玩家选择与作者意图
165 周安装
Disableable if component can be disabledRc<dyn Fn> for callbacksOption<AnyElement> for optional child elementsprelude::FluentBuilder as _cx.theme()