237 lines
6.8 KiB
Rust
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"),
|
|
))
|
|
}
|