unskunk the examples
This commit is contained in:
@@ -6,9 +6,17 @@ use ruin_app::prelude::*;
|
||||
// #[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 {} })
|
||||
// .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);
|
||||
@@ -40,14 +48,16 @@ use ruin_app::prelude::*;
|
||||
// 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.
|
||||
// - `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<()> {
|
||||
@@ -68,39 +78,42 @@ 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);
|
||||
struct __CounterAppTitleMissing;
|
||||
struct __CounterAppTitlePresent(&'static str);
|
||||
|
||||
#[derive(Default)]
|
||||
struct __CounterAppBuilder {
|
||||
title: Option<&'static str>,
|
||||
struct __CounterAppBuilder<TitleState> {
|
||||
title: TitleState,
|
||||
}
|
||||
|
||||
impl __CounterAppBuilder {
|
||||
fn title(&mut self, title: &'static str) -> &self {
|
||||
self.title = Some(title);
|
||||
self
|
||||
impl __CounterAppBuilder<__CounterAppTitleMissing> {
|
||||
fn title(self, title: &'static str) -> __CounterAppBuilder<__CounterAppTitlePresent> {
|
||||
__CounterAppBuilder {
|
||||
title: __CounterAppTitlePresent(title),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn children(self, _children: ()) -> Result<CounterApp, anyhow::Error> {
|
||||
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;
|
||||
type Builder = __CounterAppBuilder<__CounterAppTitleMissing>;
|
||||
|
||||
fn builder() -> Self::Builder {
|
||||
__CounterAppBuilder::default()
|
||||
}
|
||||
|
||||
fn from_builder(builder: Self::Builder) -> Result<Self, anyhow::Error> {
|
||||
Ok(Self {
|
||||
title: builder
|
||||
.title
|
||||
.ok_or_else(|| anyhow::anyhow!("Missing required property `title`"))?,
|
||||
})
|
||||
__CounterAppBuilder {
|
||||
title: __CounterAppTitleMissing,
|
||||
}
|
||||
}
|
||||
|
||||
fn render(&self) -> View {
|
||||
@@ -108,18 +121,116 @@ impl Component for CounterApp {
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
// 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);
|
||||
let count = use_signal(|| 0_i32);
|
||||
let doubled = use_memo({
|
||||
let count = count.clone();
|
||||
move || count.get() * 2
|
||||
});
|
||||
|
||||
use_window_title(move || format!("{title} ({})", count.get()));
|
||||
use_window_title({
|
||||
let count = count.clone();
|
||||
move || format!("{title} ({})", count.get())
|
||||
});
|
||||
|
||||
// Expansion of view macro to return a concrete view goes here.
|
||||
// ...
|
||||
// ...
|
||||
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"),
|
||||
))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user