~/
rust-munich
·
crux + gpui
# Step 2 — counter UI Same app, now visible. We add a window, a number, and two buttons. File: `shell-gpui/src/main.rs`. The shell's job, in one sentence: > Take user input, push it into the core as an `Event`, drain the resulting > `Effect`s, and feed any results back. At Step 2 there is only one effect — `Render` — so the shell stays tiny. --- ## The `Shell` struct ```rust struct Shell { core: Arc
>, view_model: ViewModel, } ``` - `core` — crux's runtime wrapper around our `CounterApp`. Shared across any future async tasks, hence `Arc`. - `view_model` — the latest snapshot we got from the core. The render function reads from it and never touches the model directly. --- ## Dispatching an event When the user clicks `+`, we push that into the core and handle whatever effects come back: ```rust fn dispatch(&mut self, event: Event, cx: &mut Context
) { let effects = self.core.process_event(event); self.handle_effects(effects, cx); } ``` --- ## Handling effects — one arm so far Step 2's `handle_effects` has a single match arm: re-read the `ViewModel` and tell gpui to repaint. ```rust fn handle_effects(&mut self, effects: Vec
, cx: &mut Context
) { for effect in effects { match effect { Effect::Render(_) => { self.view_model = self.core.view(); cx.notify(); } } } } ``` `cx.notify()` is gpui's "I'm dirty, re-run my render" signal. --- ## Rendering with the Tailwind-flavoured DSL ```rust impl Render for Shell { fn render(&mut self, _w: &mut Window, cx: &mut Context
) -> impl IntoElement { let count = self.view_model.count; div() .flex() .flex_col() .items_center() .justify_center() .gap_6() .size_full() .bg(cx.theme().background) .text_color(cx.theme().foreground) .child(div().text_3xl().child(format!("{count}"))) .child( div() .flex() .flex_row() .gap_3() .child(button("dec", "−", cx.listener(|s, _, _, cx| s.dispatch(Event::Decrement, cx)))) .child(button("inc", "+", cx.listener(|s, _, _, cx| s.dispatch(Event::Increment, cx)))) ) } } ``` Read it like CSS: a centered column with a 24px gap, the big count on top, the two buttons in a row below. --- ## Wiring up the window ```rust fn main() { Application::new().run(|cx: &mut App| { gpui_component::init(cx); // theme, key bindings, tooltips let bounds = Bounds::centered(None, size(px(420.0), px(420.0)), cx); cx.open_window( WindowOptions { window_bounds: Some(WindowBounds::Windowed(bounds)), ..Default::default() }, |window, cx| { let shell = cx.new(|_| Shell::new()); cx.new(|cx| Root::new(shell, window, cx)) }, ).expect("open window"); cx.activate(true); }); } ``` `gpui_component::init(cx)` **must** come before any widget from the component kit is used — it registers the theme and global state. --- ## Run it ```bash cargo run -p shell-gpui ``` A 420×420 window with `0` and two buttons. Click `+`, `−`. The number updates. That's Step 2. Now we make it interesting.