From 0c69d6859d01d3f9ffa9e0fa81ce6365f1a6cb7c Mon Sep 17 00:00:00 2001 From: funman300 Date: Tue, 7 Jul 2026 16:52:31 -0700 Subject: [PATCH] refactor(engine): extract shared spawn_tab_chip widget into ui_modal Settings' tab_chip becomes a thin wrapper; the You hub (Phase E) will reuse the same widget so tabbed modals stay visually identical. Co-Authored-By: Claude Fable 5 --- solitaire_engine/src/settings_plugin/ui.rs | 40 ++++++------------- solitaire_engine/src/ui_modal.rs | 46 ++++++++++++++++++++-- 2 files changed, 54 insertions(+), 32 deletions(-) diff --git a/solitaire_engine/src/settings_plugin/ui.rs b/solitaire_engine/src/settings_plugin/ui.rs index 9d125e4..2b7d068 100644 --- a/solitaire_engine/src/settings_plugin/ui.rs +++ b/solitaire_engine/src/settings_plugin/ui.rs @@ -18,6 +18,7 @@ use crate::theme::{ImportError, import_theme, refresh_registry}; use crate::ui_focus::FocusRow; use crate::ui_modal::{ ButtonVariant, spawn_modal, spawn_modal_actions, spawn_modal_button, spawn_modal_header, + spawn_tab_chip, }; use crate::ui_theme::{ BG_BASE, BG_ELEVATED, BG_ELEVATED_HI, BORDER_SUBTLE, HighContrastBorder, RADIUS_SM, @@ -117,41 +118,22 @@ pub(super) fn spawn_settings_panel( }); } -/// One Settings tab chip. The active chip is filled + bright; inactive -/// chips are quiet outlines. +/// One Settings tab chip — thin wrapper over the shared +/// [`spawn_tab_chip`] widget so Settings and the You hub stay visually +/// identical. fn tab_chip( parent: &mut ChildSpawnerCommands, tab: SettingsTab, active: bool, font_res: Option<&FontResource>, ) { - let font = TextFont { - font: font_res.map(|f| f.0.clone()).unwrap_or_default(), - font_size: TYPE_CAPTION, - ..default() - }; - parent - .spawn(( - SettingsTabButton(tab), - Button, - Node { - padding: UiRect::axes(VAL_SPACE_3, VAL_SPACE_2), - justify_content: JustifyContent::Center, - border: UiRect::all(Val::Px(1.0)), - border_radius: BorderRadius::all(Val::Px(RADIUS_SM)), - ..default() - }, - BackgroundColor(if active { BG_ELEVATED_HI } else { BG_BASE }), - BorderColor::all(if active { STATE_SUCCESS } else { BORDER_SUBTLE }), - HighContrastBorder::with_default(if active { STATE_SUCCESS } else { BORDER_SUBTLE }), - )) - .with_children(|b| { - b.spawn(( - Text::new(tab.label()), - font, - TextColor(if active { TEXT_PRIMARY } else { TEXT_SECONDARY }), - )); - }); + spawn_tab_chip( + parent, + SettingsTabButton(tab), + tab.label(), + active, + font_res, + ); } /// Audio tab: the two volume rows. diff --git a/solitaire_engine/src/ui_modal.rs b/solitaire_engine/src/ui_modal.rs index cb94708..004d1fc 100644 --- a/solitaire_engine/src/ui_modal.rs +++ b/solitaire_engine/src/ui_modal.rs @@ -60,9 +60,9 @@ use crate::settings_plugin::SettingsResource; use crate::ui_theme::{ ACCENT_PRIMARY, ACCENT_PRIMARY_HOVER, ACCENT_SECONDARY, BG_BASE, BG_ELEVATED, BG_ELEVATED_HI, BG_ELEVATED_PRESSED, BG_ELEVATED_TOP, BORDER_STRONG, BORDER_SUBTLE, HighContrastBorder, - MOTION_MODAL_SECS, RADIUS_LG, RADIUS_MD, SCRIM, TEXT_PRIMARY, TEXT_SECONDARY, TYPE_BODY_LG, - TYPE_CAPTION, TYPE_HEADLINE, VAL_SPACE_2, VAL_SPACE_3, VAL_SPACE_4, VAL_SPACE_5, - scaled_duration, + MOTION_MODAL_SECS, RADIUS_LG, RADIUS_MD, RADIUS_SM, SCRIM, STATE_SUCCESS, TEXT_PRIMARY, + TEXT_SECONDARY, TYPE_BODY_LG, TYPE_CAPTION, TYPE_HEADLINE, VAL_SPACE_2, VAL_SPACE_3, + VAL_SPACE_4, VAL_SPACE_5, scaled_duration, }; // --------------------------------------------------------------------------- @@ -402,6 +402,46 @@ pub fn spawn_modal_button( }); } +/// One tab chip for a tabbed modal (Settings, the You hub). The active +/// chip is filled + bright with a success-green border; inactive chips +/// are quiet outlines. `marker` is the plugin's click-target component +/// carrying which tab the chip selects. +pub fn spawn_tab_chip( + parent: &mut ChildSpawnerCommands, + marker: M, + label: &str, + active: bool, + font_res: Option<&FontResource>, +) { + let font = TextFont { + font: font_res.map(|f| f.0.clone()).unwrap_or_default(), + font_size: TYPE_CAPTION, + ..default() + }; + parent + .spawn(( + marker, + Button, + Node { + padding: UiRect::axes(VAL_SPACE_3, VAL_SPACE_2), + justify_content: JustifyContent::Center, + border: UiRect::all(Val::Px(1.0)), + border_radius: BorderRadius::all(Val::Px(RADIUS_SM)), + ..default() + }, + BackgroundColor(if active { BG_ELEVATED_HI } else { BG_BASE }), + BorderColor::all(if active { STATE_SUCCESS } else { BORDER_SUBTLE }), + HighContrastBorder::with_default(if active { STATE_SUCCESS } else { BORDER_SUBTLE }), + )) + .with_children(|b| { + b.spawn(( + Text::new(label), + font, + TextColor(if active { TEXT_PRIMARY } else { TEXT_SECONDARY }), + )); + }); +} + // --------------------------------------------------------------------------- // Generic touch-scroll helper // ---------------------------------------------------------------------------