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 { 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, } struct __CounterActionsCountMissing; struct __CounterActionsCountPresent(Signal); struct __CounterActionsBuilder { count: CountState, } impl __CounterActionsBuilder<__CounterActionsCountMissing> { fn count(self, count: Signal) -> __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) -> 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"), )) }