skunky example
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
use ruin_app::{Component, prelude::*};
|
use ruin_app::prelude::*;
|
||||||
|
|
||||||
// This is the aspirational author-facing source we eventually want to support:
|
// This is the aspirational author-facing source we eventually want to support:
|
||||||
//
|
//
|
||||||
@@ -10,16 +10,16 @@ use ruin_app::{Component, prelude::*};
|
|||||||
// .app_id("dev.ruin.counter")
|
// .app_id("dev.ruin.counter")
|
||||||
// .mount(view! { CounterApp {} })
|
// .mount(view! { CounterApp {} })
|
||||||
// #[component]
|
// #[component]
|
||||||
// fn CounterApp() -> impl View {
|
// fn CounterApp(title: &'static str) -> impl View {
|
||||||
// let count = use_signal(|| 0);
|
// let count = use_signal(|| 0);
|
||||||
// let doubled = use_memo(move || count.get() * 2);
|
// let doubled = use_memo(move || count.get() * 2);
|
||||||
//
|
//
|
||||||
// use_window_title(move || format!("RUIN Counter ({})", count.get()));
|
// use_window_title(move || format!("{title} ({})", count.get()));
|
||||||
//
|
//
|
||||||
// view! {
|
// view! {
|
||||||
// column(gap = 16, padding = 24) {
|
// column(gap = 16, padding = 24) {
|
||||||
// text(role = TextRole::Heading(1), size = 32, weight = FontWeight::Semibold) {
|
// text(role = TextRole::Heading(1), size = 32, weight = FontWeight::Semibold) {
|
||||||
// "RUIN counter"
|
// title
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// row(gap = 8) {
|
// row(gap = 8) {
|
||||||
@@ -38,35 +38,53 @@ use ruin_app::{Component, prelude::*};
|
|||||||
// ```
|
// ```
|
||||||
//
|
//
|
||||||
// The hand-written code below is the kind of lower-level shape the proc macro should expand to.
|
// 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]
|
#[ruin_runtime::async_main]
|
||||||
async fn main() -> ruin_app::Result<()> {
|
async fn main() -> ruin_app::Result<()> {
|
||||||
|
let title = "RUIN Counter";
|
||||||
App::new()
|
App::new()
|
||||||
.window(
|
.window(
|
||||||
Window::new()
|
Window::new()
|
||||||
.title("RUIN Counter")
|
.title(title)
|
||||||
.app_id("dev.ruin.counter")
|
.app_id("dev.ruin.counter")
|
||||||
.size(960.0, 640.0),
|
.size(960.0, 640.0),
|
||||||
)
|
)
|
||||||
.mount(CounterApp::builder().children(()))
|
.mount(CounterApp::builder().title(title).children(()))
|
||||||
.run()
|
.run()
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CounterApp {
|
struct CounterApp {
|
||||||
count: Cell<i32>,
|
title: &'static str,
|
||||||
doubled: Memo<i32>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const DECREMENT_BUTTON_ID: ElementId = ElementId::new(1);
|
const DECREMENT_BUTTON_ID: ElementId = ElementId::new(1);
|
||||||
const RESET_BUTTON_ID: ElementId = ElementId::new(2);
|
const RESET_BUTTON_ID: ElementId = ElementId::new(2);
|
||||||
const INCREMENT_BUTTON_ID: ElementId = ElementId::new(3);
|
const INCREMENT_BUTTON_ID: ElementId = ElementId::new(3);
|
||||||
|
|
||||||
struct __CounterAppBuilder;
|
#[derive(Default)]
|
||||||
|
struct __CounterAppBuilder {
|
||||||
|
title: Option<&'static str>,
|
||||||
|
}
|
||||||
|
|
||||||
impl __CounterAppBuilder {
|
impl __CounterAppBuilder {
|
||||||
fn children(self, _children: ()) -> CounterApp {
|
fn title(&mut self, title: &'static str) -> &self {
|
||||||
CounterApp::new()
|
self.title = Some(title);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn children(self, _children: ()) -> Result<CounterApp, anyhow::Error> {
|
||||||
|
CounterApp::from_builder(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,119 +92,34 @@ impl Component for CounterApp {
|
|||||||
type Builder = __CounterAppBuilder;
|
type Builder = __CounterAppBuilder;
|
||||||
|
|
||||||
fn builder() -> Self::Builder {
|
fn builder() -> Self::Builder {
|
||||||
__CounterAppBuilder
|
__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`"))?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(&self) -> View {
|
||||||
|
__render_CounterApp(self.title)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CounterApp {
|
#[allow(non_snake_case)]
|
||||||
fn new() -> Self {
|
fn __render_CounterApp(title: &'static str) -> View {
|
||||||
let count = cell(0_i32);
|
// The state will be managed by the runtime, like how it's done in react. When rerunning the render function,
|
||||||
let doubled = memo({
|
// use_signal and use_memo will return the same state/memo instances as the previous run, so the state is preserved
|
||||||
let count = count.clone();
|
// across renders as appropriate. This will require correlating the state/memo instances to call traces using
|
||||||
move || count.get() * 2
|
// thread-local state on the UI thread.
|
||||||
});
|
let count = use_signal(|| 0);
|
||||||
Self { count, doubled }
|
let doubled = use_memo(move || count.get() * 2);
|
||||||
}
|
|
||||||
|
|
||||||
fn button(&self, label: &str, id: ElementId, fill: Color) -> Element {
|
use_window_title(move || format!("{title} ({})", count.get()));
|
||||||
Element::column()
|
|
||||||
.padding(Edges::symmetric(14.0, 10.0))
|
|
||||||
.background(fill)
|
|
||||||
.corner_radius(10.0)
|
|
||||||
.cursor(CursorIcon::Pointer)
|
|
||||||
.focusable(true)
|
|
||||||
.id(id)
|
|
||||||
.child(
|
|
||||||
Element::text(label, body_text())
|
|
||||||
.pointer_events(false)
|
|
||||||
.focusable(false),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RootComponent for CounterApp {
|
// Expansion of view macro to return a concrete view goes here.
|
||||||
fn build(&self, cx: &BuildCx) -> View {
|
// ...
|
||||||
let count = self.count.get();
|
// ...
|
||||||
let doubled = self.doubled.get();
|
|
||||||
|
|
||||||
Element::column()
|
|
||||||
.width(cx.viewport.width)
|
|
||||||
.height(cx.viewport.height)
|
|
||||||
.padding(Edges::all(24.0))
|
|
||||||
.gap(16.0)
|
|
||||||
.background(Color::rgb(0x0F, 0x16, 0x25))
|
|
||||||
.child(
|
|
||||||
Element::text("RUIN counter", title_text())
|
|
||||||
.pointer_events(false)
|
|
||||||
.focusable(false),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
Element::paragraph(
|
|
||||||
"This is the raw, hand-written shape a future proc macro should lower the \
|
|
||||||
ergonomic counter component into.",
|
|
||||||
body_text().with_wrap(TextWrap::Word),
|
|
||||||
)
|
|
||||||
.pointer_events(false)
|
|
||||||
.focusable(false),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
Element::row()
|
|
||||||
.gap(8.0)
|
|
||||||
.child(self.button("-1", DECREMENT_BUTTON_ID, Color::rgb(0x2B, 0x3A, 0x67)))
|
|
||||||
.child(self.button("Reset", RESET_BUTTON_ID, Color::rgb(0x3D, 0x4B, 0x72)))
|
|
||||||
.child(self.button("+1", INCREMENT_BUTTON_ID, Color::rgb(0x2B, 0x3A, 0x67))),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
Element::column()
|
|
||||||
.padding(Edges::all(16.0))
|
|
||||||
.gap(8.0)
|
|
||||||
.background(Color::rgb(0x1B, 0x26, 0x3D))
|
|
||||||
.corner_radius(12.0)
|
|
||||||
.child(
|
|
||||||
Element::text(format!("count = {count}"), body_text())
|
|
||||||
.pointer_events(false)
|
|
||||||
.focusable(false),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
Element::text(
|
|
||||||
format!("double = {doubled}"),
|
|
||||||
body_text().with_font_family(TextFontFamily::Monospace),
|
|
||||||
)
|
|
||||||
.pointer_events(false)
|
|
||||||
.focusable(false),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_pointer(&self, event: &ruin_ui::RoutedPointerEvent) {
|
|
||||||
if !matches!(
|
|
||||||
event.kind,
|
|
||||||
ruin_ui::RoutedPointerEventKind::Up {
|
|
||||||
button: PointerButton::Primary
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
match event.target.element_id {
|
|
||||||
Some(DECREMENT_BUTTON_ID) => {
|
|
||||||
self.count.update(|value| *value -= 1);
|
|
||||||
}
|
|
||||||
Some(RESET_BUTTON_ID) => {
|
|
||||||
let _ = self.count.set(0);
|
|
||||||
}
|
|
||||||
Some(INCREMENT_BUTTON_ID) => {
|
|
||||||
self.count.update(|value| *value += 1);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn title_text() -> TextStyle {
|
|
||||||
TextStyle::new(32.0, Color::rgb(0xFF, 0xFF, 0xFF))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn body_text() -> TextStyle {
|
|
||||||
TextStyle::new(18.0, Color::rgb(0xDF, 0xE8, 0xF6)).with_line_height(24.0)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ impl App {
|
|||||||
pub fn mount<R: RootComponent>(self, root: R) -> MountedApp<R> {
|
pub fn mount<R: RootComponent>(self, root: R) -> MountedApp<R> {
|
||||||
MountedApp {
|
MountedApp {
|
||||||
window: self.window,
|
window: self.window,
|
||||||
root,
|
root: Rc::new(root),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -100,9 +100,9 @@ impl Default for App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct MountedApp<R> {
|
pub struct MountedApp<R: RootComponent> {
|
||||||
window: Window,
|
window: Window,
|
||||||
root: R,
|
root: Rc<R>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: RootComponent> MountedApp<R> {
|
impl<R: RootComponent> MountedApp<R> {
|
||||||
@@ -122,7 +122,7 @@ impl<R: RootComponent> MountedApp<R> {
|
|||||||
let scene_version = StdCell::new(0_u64);
|
let scene_version = StdCell::new(0_u64);
|
||||||
let text_system = Rc::new(RefCell::new(TextSystem::new()));
|
let text_system = Rc::new(RefCell::new(TextSystem::new()));
|
||||||
let interaction_tree = Rc::new(RefCell::new(None::<InteractionTree>));
|
let interaction_tree = Rc::new(RefCell::new(None::<InteractionTree>));
|
||||||
let root = Rc::new(root);
|
let root = Rc::clone(&root);
|
||||||
let mut pointer_router = PointerRouter::new();
|
let mut pointer_router = PointerRouter::new();
|
||||||
let mut current_cursor = CursorIcon::Default;
|
let mut current_cursor = CursorIcon::Default;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user