//! Aspirational RUIN app API for composition, typed context providers, and app-wide services. //! //! Intentionally non-compiling; this is a design sketch. use ruin::prelude::*; #[ruin_runtime::async_main] async fn main() -> ruin::Result<()> { App::new() .window(Window::new().title("RUIN Workspace").size(1400.0, 900.0)) .mount::() .run() .await } #[component] fn WorkspaceRoot() -> impl View { let route = use_router(Route::Home); let session = use_resource(|| async { Session::restore().await }); let notifications = use_service::(); view! { provide::(notifications.clone()) { provide::(Theme::dark()) { match session.read() { Pending => view! { ProgressSpinner(label = "Restoring session...") {} }, Ready(session) => view! { provide::(session?) { WorkspaceShell(route = route) {} } }, } } } } } #[component] fn WorkspaceShell(route: Signal) -> impl View { let session = use_context::(); let notifications = use_context::(); use_effect(move || { if !session.is_authenticated() { notifications.info("Signed in as guest"); } }); view! { row(fill = true) { block( role = LandmarkRole::Navigation, width = 280, gap = 12, padding = 20, ) { BrandMark() {} NavLink(route = Route::Home, active = route.is(Route::Home)) { "Home" } NavLink(route = Route::Projects, active = route.is(Route::Projects)) { "Projects" } NavLink(route = Route::Settings, active = route.is(Route::Settings)) { "Settings" } } block(fill = true, padding = 20) { match route.get() { Route::Home => view! { HomeScreen() {} }, Route::Projects => view! { ProjectsScreen() {} }, Route::Settings => view! { SettingsScreen() {} }, } } } } } #[component] fn HomeScreen() -> impl View { let theme = use_context::(); view! { column(fill = true, gap = 16) { text(role = TextRole::Heading(1), size = 30, weight = FontWeight::Semibold) { "Workspace" } text(color = theme.muted_text()) { "Context should be keyed by provider identity, not only by value type. That allows \ multiple providers for the same `T` to coexist in the tree while descendants borrow \ the correct value by asking for a specific provider marker." } } } } #[component] fn ProjectsScreen() -> impl View { view! { EmptyState(title = "Projects go here") { "This screen exists only to show route switching and app-level composition." } } } #[component] fn SettingsScreen() -> impl View { view! { column(gap = 12) { text(role = TextRole::Heading(2), size = 24, weight = FontWeight::Semibold) { "Settings" } toggle(label = "Use reduced motion", value = Preferences::reduced_motion()) {} toggle(label = "Use system theme", value = Preferences::system_theme()) {} } } } #[context_provider(Theme)] struct ThemeContext; #[context_provider(Session)] struct SessionContext; #[context_provider(Notifications)] struct NotificationsContext; struct Session; struct Notifications; struct Theme; struct Preferences; #[derive(Clone, Copy)] enum Route { Home, Projects, Settings, } impl Session { async fn restore() -> ruin::Result { Ok(Self) } fn is_authenticated(&self) -> bool { false } } impl Notifications { fn info(&self, _message: &str) {} } impl Theme { fn dark() -> Self { Self } fn muted_text(&self) -> Color { Color::rgb(0x94, 0xA3, 0xB8) } } impl Preferences { fn reduced_motion() -> bool { false } fn system_theme() -> bool { true } }