app

Local-first trade for farms and co-ops
git clone https://radroots.dev/git/app.git
Log | Files | Refs | README | LICENSE

commit 13d1a073c1f0e2ac4c9fc42daef44bfd9c035816
parent 95c96717c1f100115a5e70bf7bcc729d60042df1
Author: triesap <tyson@radroots.org>
Date:   Fri, 17 Apr 2026 16:47:10 +0000

ui: add shared shell theme tokens

- split radroots_app_ui into theme, primitives, and placeholder modules
- move window sizing, surface colors, and typography into APP_UI_THEME
- render the placeholder shell through shared window and center-stage primitives
- point the desktop launcher at shared ui window tokens for startup sizing

Diffstat:
Mcrates/launchers/desktop/src/app.rs | 8++++----
Mcrates/shared/core/src/lib.rs | 11-----------
Mcrates/shared/ui/src/lib.rs | 41++++++++++++-----------------------------
Acrates/shared/ui/src/placeholder.rs | 21+++++++++++++++++++++
Acrates/shared/ui/src/primitives.rs | 93+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acrates/shared/ui/src/theme.rs | 97+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 227 insertions(+), 44 deletions(-)

diff --git a/crates/launchers/desktop/src/app.rs b/crates/launchers/desktop/src/app.rs @@ -1,6 +1,6 @@ use gpui::{AppContext, Application, WindowOptions, px, size}; -use radroots_app_core::{APP_ID, HOME_WINDOW_METRICS}; -use radroots_app_ui::{HOME_WINDOW_MIN_HEIGHT_PX, HOME_WINDOW_MIN_WIDTH_PX, PlaceholderView}; +use radroots_app_core::APP_ID; +use radroots_app_ui::{APP_UI_THEME, PlaceholderView}; fn titlebar_options() -> gpui::TitlebarOptions { gpui::TitlebarOptions { @@ -26,8 +26,8 @@ pub fn launch() { WindowOptions { app_id: Some(APP_ID.to_owned()), window_min_size: Some(size( - px(HOME_WINDOW_METRICS.min_width_px.max(HOME_WINDOW_MIN_WIDTH_PX)), - px(HOME_WINDOW_METRICS.min_height_px.max(HOME_WINDOW_MIN_HEIGHT_PX)), + px(APP_UI_THEME.windows.home_min_width_px), + px(APP_UI_THEME.windows.home_min_height_px), )), titlebar: Some(titlebar_options()), ..Default::default() diff --git a/crates/shared/core/src/lib.rs b/crates/shared/core/src/lib.rs @@ -2,14 +2,3 @@ pub const APP_ID: &str = "org.radroots.app"; pub const APP_NAME: &str = "radroots"; - -#[derive(Clone, Copy, Debug, PartialEq)] -pub struct AppWindowMetrics { - pub min_width_px: f32, - pub min_height_px: f32, -} - -pub const HOME_WINDOW_METRICS: AppWindowMetrics = AppWindowMetrics { - min_width_px: 640.0, - min_height_px: 480.0, -}; diff --git a/crates/shared/ui/src/lib.rs b/crates/shared/ui/src/lib.rs @@ -1,32 +1,15 @@ #![forbid(unsafe_code)] -use gpui::{ - Context, FontWeight, IntoElement, ParentElement, Render, Styled, Window, div, px, rgb, +mod placeholder; +mod primitives; +mod theme; + +pub use placeholder::PlaceholderView; +pub use primitives::{ + LabelValueRow, app_card, app_center_stage, app_window_shell, label_value_list, section_divider, + utility_title_row, +}; +pub use theme::{ + APP_UI_THEME, AppLayoutTokens, AppSurfaceTokens, AppTextTokens, AppTypographyTokens, + AppUiTheme, AppWindowTokens, }; -use radroots_app_i18n::{AppTextKey, app_text}; - -pub const HOME_WINDOW_MIN_WIDTH_PX: f32 = 640.0; -pub const HOME_WINDOW_MIN_HEIGHT_PX: f32 = 480.0; -pub const WINDOW_BACKGROUND: u32 = 0xF5F1E8; -pub const TEXT_PRIMARY: u32 = 0x1F2C23; -pub const BRAND_TEXT_SIZE_PX: f32 = 20.0; - -pub struct PlaceholderView; - -impl Render for PlaceholderView { - fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement { - div() - .size_full() - .flex() - .items_center() - .justify_center() - .bg(rgb(WINDOW_BACKGROUND)) - .child( - div() - .text_size(px(BRAND_TEXT_SIZE_PX)) - .font_weight(FontWeight::SEMIBOLD) - .text_color(rgb(TEXT_PRIMARY)) - .child(app_text(AppTextKey::Brand)), - ) - } -} diff --git a/crates/shared/ui/src/placeholder.rs b/crates/shared/ui/src/placeholder.rs @@ -0,0 +1,21 @@ +use gpui::{Context, FontWeight, IntoElement, ParentElement, Render, Styled, Window, div, px, rgb}; +use radroots_app_i18n::{AppTextKey, app_text}; + +use crate::{APP_UI_THEME, app_center_stage, app_window_shell}; + +pub struct PlaceholderView; + +impl Render for PlaceholderView { + fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement { + app_window_shell( + APP_UI_THEME.surfaces.window_background, + app_center_stage( + div() + .text_size(px(APP_UI_THEME.typography.brand_text_px)) + .font_weight(FontWeight::SEMIBOLD) + .text_color(rgb(APP_UI_THEME.text.primary)) + .child(app_text(AppTextKey::Brand)), + ), + ) + } +} diff --git a/crates/shared/ui/src/primitives.rs b/crates/shared/ui/src/primitives.rs @@ -0,0 +1,93 @@ +use gpui::{IntoElement, ParentElement, SharedString, Styled, div, px, rgb}; + +use crate::APP_UI_THEME; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct LabelValueRow { + pub label: SharedString, + pub value: SharedString, +} + +impl LabelValueRow { + pub fn new(label: impl Into<SharedString>, value: impl Into<SharedString>) -> Self { + Self { + label: label.into(), + value: value.into(), + } + } +} + +pub fn app_window_shell(background: u32, content: impl IntoElement) -> impl IntoElement { + div() + .size_full() + .overflow_hidden() + .bg(rgb(background)) + .text_color(rgb(APP_UI_THEME.text.primary)) + .child(content) +} + +pub fn app_center_stage(content: impl IntoElement) -> impl IntoElement { + div() + .size_full() + .flex() + .items_center() + .justify_center() + .p(px(APP_UI_THEME.layout.home_window_padding_px)) + .child(content) +} + +pub fn app_card(content: impl IntoElement) -> impl IntoElement { + div() + .w_full() + .max_w(px(APP_UI_THEME.layout.home_card_max_width_px)) + .mx_auto() + .bg(rgb(APP_UI_THEME.surfaces.card_background)) + .overflow_hidden() + .child( + div() + .w_full() + .p(px(APP_UI_THEME.layout.home_card_padding_px)) + .child(content), + ) +} + +pub fn section_divider() -> impl IntoElement { + div() + .w_full() + .h(px(APP_UI_THEME.layout.divider_thickness_px)) + .bg(rgb(APP_UI_THEME.surfaces.divider)) +} + +pub fn utility_title_row(title: impl Into<SharedString>) -> impl IntoElement { + div() + .w_full() + .h(px(APP_UI_THEME.layout.utility_title_row_height_px)) + .flex() + .justify_center() + .items_center() + .text_size(px(APP_UI_THEME.typography.utility_title_text_px)) + .font_weight(gpui::FontWeight::BOLD) + .text_color(rgb(APP_UI_THEME.text.primary)) + .child(title.into()) +} + +pub fn label_value_list(rows: impl IntoIterator<Item = LabelValueRow>) -> impl IntoElement { + let rows = rows + .into_iter() + .map(|row| { + let line = format!("{}: {}", row.label, row.value); + div() + .w_full() + .text_size(px(APP_UI_THEME.typography.body_text_px)) + .text_color(rgb(APP_UI_THEME.text.secondary)) + .child(line) + }) + .collect::<Vec<_>>(); + + div() + .w_full() + .flex() + .flex_col() + .gap(px(APP_UI_THEME.layout.metadata_row_gap_px)) + .children(rows) +} diff --git a/crates/shared/ui/src/theme.rs b/crates/shared/ui/src/theme.rs @@ -0,0 +1,97 @@ +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct AppUiTheme { + pub windows: AppWindowTokens, + pub surfaces: AppSurfaceTokens, + pub text: AppTextTokens, + pub typography: AppTypographyTokens, + pub layout: AppLayoutTokens, +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct AppWindowTokens { + pub home_min_width_px: f32, + pub home_min_height_px: f32, + pub settings_width_px: f32, + pub settings_height_px: f32, +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct AppSurfaceTokens { + pub window_background: u32, + pub chrome_background: u32, + pub panel_background: u32, + pub card_background: u32, + pub divider: u32, +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct AppTextTokens { + pub primary: u32, + pub secondary: u32, + pub accent: u32, +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct AppTypographyTokens { + pub utility_title_text_px: f32, + pub body_text_px: f32, + pub brand_text_px: f32, + pub settings_row_text_px: f32, +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct AppLayoutTokens { + pub divider_thickness_px: f32, + pub home_window_padding_px: f32, + pub home_card_max_width_px: f32, + pub home_card_padding_px: f32, + pub home_stack_gap_px: f32, + pub metadata_row_gap_px: f32, + pub utility_title_row_height_px: f32, + pub settings_chrome_height_px: f32, + pub settings_section_gap_px: f32, + pub settings_navigation_row_padding_px: f32, + pub settings_navigation_row_gap_px: f32, + pub settings_content_padding_px: f32, +} + +pub const APP_UI_THEME: AppUiTheme = AppUiTheme { + windows: AppWindowTokens { + home_min_width_px: 640.0, + home_min_height_px: 480.0, + settings_width_px: 600.0, + settings_height_px: 540.0, + }, + surfaces: AppSurfaceTokens { + window_background: 0xF5F1E8, + chrome_background: 0xEAE5D8, + panel_background: 0xF8F4EC, + card_background: 0xEFE8D8, + divider: 0xD4CCBA, + }, + text: AppTextTokens { + primary: 0x1F2C23, + secondary: 0x5D665B, + accent: 0x3B6A3E, + }, + typography: AppTypographyTokens { + utility_title_text_px: 12.0, + body_text_px: 14.0, + brand_text_px: 20.0, + settings_row_text_px: 13.0, + }, + layout: AppLayoutTokens { + divider_thickness_px: 1.0, + home_window_padding_px: 24.0, + home_card_max_width_px: 960.0, + home_card_padding_px: 24.0, + home_stack_gap_px: 12.0, + metadata_row_gap_px: 12.0, + utility_title_row_height_px: 24.0, + settings_chrome_height_px: 88.0, + settings_section_gap_px: 8.0, + settings_navigation_row_padding_px: 8.0, + settings_navigation_row_gap_px: 8.0, + settings_content_padding_px: 24.0, + }, +};