use ruin_app::{Component, 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() -> impl View { // let count = use_signal(|| 0); // let doubled = use_memo(move || count.get() * 2); // // use_window_title(move || format!("RUIN Counter ({})", count.get())); // // view! { // column(gap = 16, padding = 24) { // text(role = TextRole::Heading(1), size = 32, weight = FontWeight::Semibold) { // "RUIN counter" // } // // 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. #[ruin_runtime::async_main] async fn main() -> ruin_app::Result<()> { App::new() .window( Window::new() .title("RUIN Counter") .app_id("dev.ruin.counter") .size(960.0, 640.0), ) .mount(CounterApp::builder().children(())) .run() .await } struct CounterApp { count: Cell, doubled: Memo, } 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 __CounterAppBuilder; impl __CounterAppBuilder { fn children(self, _children: ()) -> CounterApp { CounterApp::new() } } impl Component for CounterApp { type Builder = __CounterAppBuilder; fn builder() -> Self::Builder { __CounterAppBuilder } } impl CounterApp { fn new() -> Self { let count = cell(0_i32); let doubled = memo({ let count = count.clone(); move || count.get() * 2 }); Self { count, doubled } } fn button(&self, label: &str, id: ElementId, fill: Color) -> Element { 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 { 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) }