完整示例
一个完整的计数器——真实的 gpui 视图、投影出的窗口,两个驱动方始终保持同步。这就是 examples/gpui/counter,此处删去了注释。
rust
use gpui::*;
use gpui_component::{Root, button::{Button, ButtonVariants as _}, h_flex, v_flex};
use purview_gpui::{ActionError, Bridge, Outcome, PvFields, PvWindow, on_entity};
struct Counter {
count: i64,
field: PvFields, // the projection handle lives in the view
}
impl Counter {
/// The single mutation path: real state and projection move together.
fn bump(&mut self, delta: i64, cx: &mut Context<Self>) {
self.count += delta;
self.field.set("Count", self.count);
cx.notify();
}
}
impl Render for Counter {
fn render(&mut self, _w: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
v_flex().gap_4().size_full().items_center().justify_center()
.child(format!("Count: {}", self.count))
.child(h_flex().gap_2()
.child(Button::new("dec").label("-1")
.on_click(cx.listener(|this, _, _, cx| this.bump(-1, cx))))
.child(Button::new("inc").primary().label("+1")
.on_click(cx.listener(|this, _, _, cx| this.bump(1, cx)))))
}
}
fn register_actions(win: &PvWindow, counter: &Entity<Counter>) {
win.actions().add("increment", "Increment").on(on_entity(counter, |c, cx| {
c.bump(1, cx);
Ok::<_, ActionError>(Outcome::message(format!("count = {}", c.count)))
}));
win.actions().add("decrement", "Decrement").on(on_entity(counter, |c, cx| {
c.bump(-1, cx);
Ok::<_, ActionError>(Outcome::message(format!("count = {}", c.count)))
}));
}
fn main() {
Application::new()
.with_assets(gpui_component_assets::Assets)
.run(|cx: &mut App| {
gpui_component::init(cx);
let papp = Bridge::new()
.instructions_append("A counter. Use increment/decrement.")
.http()
.bind("127.0.0.1:8931")
.expect("bind the MCP port")
.install(cx);
let win = papp.open("Counter");
win.set_summary("A counter you can drive from the UI or an agent");
let field = win.blocks().fields([("Count", 0)]);
let options = WindowOptions {
window_bounds: Some(WindowBounds::centered(size(px(300.), px(180.)), cx)),
..Default::default()
};
cx.open_window(options, move |window, cx| {
window.set_window_title("Counter");
let counter = cx.new(|_| Counter { count: 0, field });
register_actions(&win, &counter);
cx.new(|cx| Root::new(counter, window, cx))
})
.expect("failed to open window");
});
}注意这里没有什么:没有 channel,没有排空循环,也没有需要你保活的服务端句柄。而 bump() 是唯一的写入方——点击按钮和调用 {winId}__increment 走的是同一条路径。