use ruin_app::prelude::*; #[derive(Clone)] struct Project { id: u64, name: &'static str, status: &'static str, summary: &'static str, } #[ruin_runtime::async_main] async fn main() -> ruin_app::Result<()> { App::new() .window( Window::new() .title("RUIN Children and Slots") .app_id("dev.ruin.children-and-slots") .size(1180.0, 820.0), ) .mount(view! { ChildrenAndSlotsExample() {} }) .run() .await } #[component] fn ChildrenAndSlotsExample() -> impl IntoView { let active_project = use_signal(|| 1_u64); let dialog_open = use_signal(|| false); let projects = [ Project { id: 1, name: "Renderer clip recovery", status: "Ready for validation", summary: "Nested clip propagation now preserves empty intersections all the way down the tree.", }, Project { id: 2, name: "Runtime I/O dashboard", status: "Shipped", summary: "Filesystem snapshots and local TCP-backed endpoint previews now update immediately after async work completes.", }, Project { id: 3, name: "Children contract support", status: "In progress", summary: "Custom components can now accept unnamed child content and typed child-like slot props without faking overlay APIs.", }, ]; let active = projects .iter() .find(|project| project.id == active_project.get()) .cloned() .unwrap_or_else(|| projects[0].clone()); view! { column(gap = 18.0, padding = 22.0, background = surfaces::canvas()) { text(role = TextRole::Heading(1), size = 30.0, weight = FontWeight::Semibold) { "Children and slots" } text(color = colors::muted(), wrap = TextWrap::Word) { "This honest slice implements unnamed child contracts plus typed child-like slot props. It does not pretend modal overlays or keyed list primitives exist yet; the dialog below is rendered inline on purpose." } SplitLayout( sidebar = vec![ view! { text(role = TextRole::Heading(2), size = 22.0, weight = FontWeight::Semibold) { "Views" } }, IntoView::into_view(FilterChip::builder().selected(active.id == 1).children("Rendering")), IntoView::into_view(FilterChip::builder().selected(active.id == 2).children("Runtime I/O")), IntoView::into_view(FilterChip::builder().selected(active.id == 3).children("Framework")), view! { text(color = colors::muted(), wrap = TextWrap::Word) { "The sidebar itself is passed as a typed child-view slot, while the chips use a text-only child contract." } }, ] ) { column(gap = 16.0) { CardFrame( title = view! { text(role = TextRole::Heading(2), size = 24.0, weight = FontWeight::Semibold) { "Workspace focus" } }, toolbar = vec![ view! { button(on_press = { let dialog_open = dialog_open.clone(); move |_| { let _ = dialog_open.set(true); } }) { "Open review dialog" } }, view! { button(on_press = { let active_project = active_project.clone(); move |_| { let next = if active_project.get() == 3 { 1 } else { active_project.get() + 1 }; let _ = active_project.set(next); } }) { "Cycle project" } }, ] ) { text(color = colors::muted()) { ("Active project: ", active.name) } text(color = colors::muted(), wrap = TextWrap::Word) { active.summary } column(gap = 10.0) { button(on_press = { let active_project = active_project.clone(); move |_| { let _ = active_project.set(1); } }) { "Show rendering work" } button(on_press = { let active_project = active_project.clone(); move |_| { let _ = active_project.set(2); } }) { "Show runtime I/O work" } button(on_press = { let active_project = active_project.clone(); move |_| { let _ = active_project.set(3); } }) { "Show framework work" } } } InlineDialog( open = dialog_open.get(), title = view! { text(role = TextRole::Heading(2), size = 22.0, weight = FontWeight::Semibold) { "Inline review dialog" } }, actions = vec![ view! { button(on_press = { let dialog_open = dialog_open.clone(); move |_| { let _ = dialog_open.set(false); } }) { "Dismiss" } }, view! { button(on_press = { let dialog_open = dialog_open.clone(); move |_| { eprintln!("example07: accepted review for {}", active.name); let _ = dialog_open.set(false); } }) { "Accept" } }, ] ) { text(color = colors::muted(), wrap = TextWrap::Word) { "This body arrives through the unnamed child contract. The title and action row are separate typed slot props." } text(color = colors::muted()) { ("Selected item status: ", active.status) } } } } } } } #[component] fn SplitLayout(sidebar: ChildViews, children: ChildViews) -> impl IntoView { view! { row(gap = 18.0) { block( width = 280.0, gap = 12.0, padding = 16.0, background = surfaces::raised(), border_radius = 16.0, ) { sidebar } block( flex = 1.0, gap = 16.0, padding = 16.0, background = surfaces::raised(), border_radius = 16.0, ) { children } } } } #[component] fn FilterChip(selected: bool, children: TextValue) -> impl IntoView { let background = if selected { surfaces::interactive() } else { surfaces::interactive_muted() }; let border = if selected { colors::text() } else { colors::muted() }; view! { block( padding = Edges::symmetric(14.0, 10.0), background = background, border = (1.0, border), border_radius = 999.0, ) { text(weight = FontWeight::Medium) { children } } } } #[component] fn CardFrame(title: View, toolbar: ChildViews, children: ChildViews) -> impl IntoView { let header = view! { row(gap = 12.0) { block(flex = 1.0) { title } row(gap = 10.0) { toolbar } } }; let mut body = vec![header]; body.extend(children.into_vec()); block() .gap(14.0) .padding(18.0) .background(surfaces::canvas()) .border_radius(16.0) .children(body) } #[component] fn InlineDialog(open: bool, title: View, actions: ChildViews, children: ChildViews) -> impl IntoView { if open { let actions_row = view! { row(gap = 10.0) { actions } }; let mut body = vec![title]; body.extend(children.into_vec()); body.push(actions_row); block() .gap(14.0) .padding(18.0) .background(surfaces::raised()) .border((1.0, colors::text())) .border_radius(16.0) .children(body) } else { column().children(()) } }