Files
ruin/lib/ruin_app/example/00_bootstrap_and_counter_raw.rs
2026-03-21 20:23:33 -04:00

237 lines
6.8 KiB
Rust

use ruin_app::prelude::*;
// This is the aspirational author-facing source we eventually want to support:
//
// ```ignore
// #[ruin_runtime::async_main]
// async fn main() -> ruin::Result<()> {
// App::new()
// .window(
// Window::new()
// .title("RUIN Counter")
// .app_id("dev.ruin.counter")
// .size(960.0, 640.0),
// )
// .mount(view! { CounterApp(title = "RUIN Counter") {} })
// .run()
// .await
// }
//
// #[component]
// fn CounterApp(title: &'static str) -> impl View {
// let count = use_signal(|| 0);
// let doubled = use_memo(move || count.get() * 2);
//
// use_window_title(move || format!("{title} ({})", count.get()));
//
// view! {
// column(gap = 16, padding = 24) {
// text(role = TextRole::Heading(1), size = 32, weight = FontWeight::Semibold) {
// title
// }
//
// row(gap = 8) {
// button(on_press = move |_| count.update(|value| *value -= 1)) { "-1" }
// button(on_press = move |_| count.set(0)) { "Reset" }
// button(on_press = move |_| count.update(|value| *value += 1)) { "+1" }
// }
//
// block(padding = 16, gap = 8, background = surfaces::raised()) {
// text(size = 18) { "count = "; count }
// text(color = colors::muted()) { "double = "; doubled }
// }
// }
// }
// }
// ```
//
// The hand-written code below is the kind of lower-level shape the proc macro should expand to.
//
// Dev notes:
// - `mount` should accept any mountable root, including component instances and already-built
// views.
// - The component render function runs as-authored. Hook storage and event dispatch belong to the
// app runtime, not to ad hoc wrapper structs per component.
// - `view!` should lower component and primitive calls to builder invocations, with `children(...)`
// as the finalizing step.
// - The macro cannot ask rustc whether an arbitrary token names a primitive or a component. It has
// to decide syntactically. The workable rule is lowercase/intrinsic names (`row`, `text`,
// `button`) lower to primitive builders, while `UpperCamelCase` paths lower to component
// builders.
#[ruin_runtime::async_main]
async fn main() -> ruin_app::Result<()> {
let title = "RUIN Counter";
App::new()
.window(
Window::new()
.title(title)
.app_id("dev.ruin.counter")
.size(960.0, 640.0),
)
.mount(CounterApp::builder().title(title).children(()))
.run()
.await
}
struct CounterApp {
title: &'static str,
}
struct __CounterAppTitleMissing;
struct __CounterAppTitlePresent(&'static str);
struct __CounterAppBuilder<TitleState> {
title: TitleState,
}
impl __CounterAppBuilder<__CounterAppTitleMissing> {
fn title(self, title: &'static str) -> __CounterAppBuilder<__CounterAppTitlePresent> {
__CounterAppBuilder {
title: __CounterAppTitlePresent(title),
}
}
}
impl __CounterAppBuilder<__CounterAppTitlePresent> {
fn children(self, _children: ()) -> CounterApp {
CounterApp::from_builder(self)
}
}
impl CounterApp {
fn from_builder(builder: __CounterAppBuilder<__CounterAppTitlePresent>) -> Self {
Self {
title: builder.title.0,
}
}
}
impl Component for CounterApp {
type Builder = __CounterAppBuilder<__CounterAppTitleMissing>;
fn builder() -> Self::Builder {
__CounterAppBuilder {
title: __CounterAppTitleMissing,
}
}
fn render(&self) -> View {
__render_CounterApp(self.title)
}
}
struct CounterActions {
count: Signal<i32>,
}
struct __CounterActionsCountMissing;
struct __CounterActionsCountPresent(Signal<i32>);
struct __CounterActionsBuilder<CountState> {
count: CountState,
}
impl __CounterActionsBuilder<__CounterActionsCountMissing> {
fn count(self, count: Signal<i32>) -> __CounterActionsBuilder<__CounterActionsCountPresent> {
__CounterActionsBuilder {
count: __CounterActionsCountPresent(count),
}
}
}
impl __CounterActionsBuilder<__CounterActionsCountPresent> {
fn children(self, _children: ()) -> CounterActions {
CounterActions::from_builder(self)
}
}
impl CounterActions {
fn from_builder(builder: __CounterActionsBuilder<__CounterActionsCountPresent>) -> Self {
Self {
count: builder.count.0,
}
}
}
impl Component for CounterActions {
type Builder = __CounterActionsBuilder<__CounterActionsCountMissing>;
fn builder() -> Self::Builder {
__CounterActionsBuilder {
count: __CounterActionsCountMissing,
}
}
fn render(&self) -> View {
__render_CounterActions(self.count.clone())
}
}
#[allow(non_snake_case)]
fn __render_CounterApp(title: &'static str) -> View {
let count = use_signal(|| 0_i32);
let doubled = use_memo({
let count = count.clone();
move || count.get() * 2
});
use_window_title({
let count = count.clone();
move || format!("{title} ({})", count.get())
});
column().gap(16.0).padding(24.0).children((
text()
.role(TextRole::Heading(1))
.size(32.0)
.weight(FontWeight::Semibold)
.children(title),
// `CounterActions(...) {}` in `view!` would lower to a component builder invocation, not a
// primitive builder invocation. The resulting component instance is then rendered as a
// child view by the runtime.
CounterActions::builder().count(count.clone()).children(()),
block()
.padding(16.0)
.gap(8.0)
.background(surfaces::raised())
.border_radius(12.0)
.children((
text().size(18.0).children(("count = ", count.clone())),
text()
.color(colors::muted())
.children(("double = ", doubled.clone())),
)),
))
}
#[allow(non_snake_case)]
fn __render_CounterActions(count: Signal<i32>) -> View {
row().gap(8.0).children((
button()
.on_press({
let count = count.clone();
move |_| {
count.update(|value| *value -= 1);
}
})
.children("-1"),
button()
.on_press({
let count = count.clone();
move |_| {
let _ = count.set(0);
}
})
.children("Reset"),
button()
.on_press({
let count = count.clone();
move |_| {
count.update(|value| *value += 1);
}
})
.children("+1"),
))
}