开始使用
#安装
在 Cargo.toml 中添加依赖:
[dependencies]
gpui-kit = "0.6"
anyhow = "1.0"
#快速开始
下面是一个最小可运行示例:
use gpui_kit::component::button::*;
use gpui_kit::component::*;
use gpui_kit::*;
pub struct HelloWorld;
impl Render for HelloWorld {
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
div()
.v_flex()
.gap_2()
.size_full()
.items_center()
.justify_center()
.child("Hello, World!")
.child(
Button::new("ok")
.primary()
.label("Let's Go!")
.on_click(|_, _, _| println!("Clicked!")),
)
}
}
fn main() {
let app = gpui_kit::application().with_assets(gpui_kit::assets::Assets);
app.run(move |cx| {
gpui_kit::init(cx);
cx.spawn(async move |cx| {
cx.open_window(WindowOptions::default(), |window, cx| {
let view = cx.new(|_| HelloWorld);
cx.new(|cx| Root::new(view, window, cx))
})
.expect("Failed to open window");
})
.detach();
});
}
#有状态组件与完整示例
Input、List 和 DataTable 的状态由持有它们的视图保存。使用 &mut Window 创建 InputState,在 render 中通过 Input::new(&self.input) 渲染组件,不要每帧重新创建状态。事件订阅也必须保存在视图中,不能仅绑定到构造函数的局部变量。
每个窗口以 Root 包装,应用内容还需渲染所使用的 dialog、sheet 和 notification 图层。完整实现及验证命令见可执行应用示例。
use gpui_kit::component::{
ActiveTheme, IconName, Root, WindowExt,
button::Button,
checkbox::Checkbox,
form::{Field, Form},
input::{Input, InputEvent, InputState},
radio::RadioGroup,
switch::Switch,
};
use gpui_kit::{
AppContext as _, Context, Entity, IntoElement, ParentElement as _, Render, SharedString,
Styled as _, Subscription, Window, div,
};
pub struct Settings {
name: Entity<InputState>,
preview: SharedString,
changes: usize,
enabled: bool,
remember: bool,
delivery: Option<usize>,
_subscriptions: Vec<Subscription>,
}
impl Settings {
pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
let name = cx.new(|cx| InputState::new(window, cx).placeholder("Name"));
let subscription = cx.subscribe_in(&name, window, |this, state, event, _, cx| {
if matches!(event, InputEvent::Change) {
this.preview = state.read(cx).value().to_string().into();
this.changes += 1;
cx.notify();
}
});
Self {
name,
preview: "".into(),
changes: 0,
enabled: false,
remember: false,
delivery: Some(0),
_subscriptions: vec![subscription],
}
}
pub fn input(&self) -> Entity<InputState> {
self.name.clone()
}
pub fn preview(&self) -> &SharedString {
&self.preview
}
pub fn changes(&self) -> usize {
self.changes
}
}
impl Render for Settings {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
div()
.flex()
.flex_col()
.size_full()
.p_4()
.gap_3()
.bg(cx.theme().background)
.text_color(cx.theme().foreground)
.child("Profile")
.child(
Form::new()
.child(Field::new().label("Name").child(Input::new(&self.name)))
.child(Field::new().label("Preview").child(self.preview.clone()))
.child(
Field::new().label_indent(false).child(
Checkbox::new("remember")
.label("Remember name")
.checked(self.remember)
.on_change(cx.listener(|this, value, _, cx| {
this.remember = *value;
cx.notify();
})),
),
)
.child(
Field::new().label_indent(false).child(
Switch::new("enabled")
.label("Enable notifications")
.checked(self.enabled)
.on_change(cx.listener(|this, value, _, cx| {
this.enabled = *value;
cx.notify();
})),
),
)
.child(
Field::new().label("Delivery").child(
RadioGroup::new("delivery")
.children(["Immediately", "Daily summary"])
.selected_index(self.delivery)
.on_change(cx.listener(|this, value, _, cx| {
this.delivery = Some(*value);
cx.notify();
})),
),
)
.footer(
Button::new("about")
.label("About…")
.icon(IconName::Info)
.on_click(|_, window, cx| {
window.open_dialog(cx, |dialog, _, _| {
dialog.title("About").child("A complete GPUI Kit window")
});
}),
),
)
.children(Root::render_dialog_layer(window, cx))
.children(Root::render_sheet_layer(window, cx))
.children(Root::render_notification_layer(window, cx))
}
}