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").size(960.0, 640.0)) // .app_id("dev.ruin.counter") // .mount(view! { CounterApp {} }) // #[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` needs to take _any_ component. It should work with an arbitrary expansion of `view!`, not just a // specially-designated root component. // - The `RootComponent` trait is awkward and unnecessary. // - The component render function needs to run as-defined. Hook lifecycles will be managed by runtime context. We don't // want to get into the business of significantly transforming the execution semantics of the component function. // - The logic is that the syntax inside of `view!` expands to an instantiation of a struct that implements `Component`, // by calling the builder associated functions. Each parameter to the render function becomes a method of the builder, // with `children` as a special finalizing method and reserved property name. This is the only way to stay sane. #[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, } const DECREMENT_BUTTON_ID: ElementId = ElementId::new(1); const RESET_BUTTON_ID: ElementId = ElementId::new(2); const INCREMENT_BUTTON_ID: ElementId = ElementId::new(3); #[derive(Default)] struct __CounterAppBuilder { title: Option<&'static str>, } impl __CounterAppBuilder { fn title(&mut self, title: &'static str) -> &self { self.title = Some(title); self } fn children(self, _children: ()) -> Result { CounterApp::from_builder(self) } } impl Component for CounterApp { type Builder = __CounterAppBuilder; fn builder() -> Self::Builder { __CounterAppBuilder::default() } fn from_builder(builder: Self::Builder) -> Result { Ok(Self { title: builder .title .ok_or_else(|| anyhow::anyhow!("Missing required property `title`"))?, }) } fn render(&self) -> View { __render_CounterApp(self.title) } } #[allow(non_snake_case)] fn __render_CounterApp(title: &'static str) -> View { // The state will be managed by the runtime, like how it's done in react. When rerunning the render function, // use_signal and use_memo will return the same state/memo instances as the previous run, so the state is preserved // across renders as appropriate. This will require correlating the state/memo instances to call traces using // thread-local state on the UI thread. let count = use_signal(|| 0); let doubled = use_memo(move || count.get() * 2); use_window_title(move || format!("{title} ({})", count.get())); // Expansion of view macro to return a concrete view goes here. // ... // ... }