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 <noreply@anthropic.com>
This commit is contained in:
funman300
2026-07-07 16:52:31 -07:00
parent 50d6d41d85
commit 0c69d6859d
2 changed files with 54 additions and 32 deletions
+11 -29
View File
@@ -18,6 +18,7 @@ use crate::theme::{ImportError, import_theme, refresh_registry};
use crate::ui_focus::FocusRow; use crate::ui_focus::FocusRow;
use crate::ui_modal::{ use crate::ui_modal::{
ButtonVariant, spawn_modal, spawn_modal_actions, spawn_modal_button, spawn_modal_header, ButtonVariant, spawn_modal, spawn_modal_actions, spawn_modal_button, spawn_modal_header,
spawn_tab_chip,
}; };
use crate::ui_theme::{ use crate::ui_theme::{
BG_BASE, BG_ELEVATED, BG_ELEVATED_HI, BORDER_SUBTLE, HighContrastBorder, RADIUS_SM, 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 /// One Settings tab chip — thin wrapper over the shared
/// chips are quiet outlines. /// [`spawn_tab_chip`] widget so Settings and the You hub stay visually
/// identical.
fn tab_chip( fn tab_chip(
parent: &mut ChildSpawnerCommands, parent: &mut ChildSpawnerCommands,
tab: SettingsTab, tab: SettingsTab,
active: bool, active: bool,
font_res: Option<&FontResource>, font_res: Option<&FontResource>,
) { ) {
let font = TextFont { spawn_tab_chip(
font: font_res.map(|f| f.0.clone()).unwrap_or_default(), parent,
font_size: TYPE_CAPTION, SettingsTabButton(tab),
..default() tab.label(),
}; active,
parent font_res,
.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 }),
));
});
} }
/// Audio tab: the two volume rows. /// Audio tab: the two volume rows.
+43 -3
View File
@@ -60,9 +60,9 @@ use crate::settings_plugin::SettingsResource;
use crate::ui_theme::{ use crate::ui_theme::{
ACCENT_PRIMARY, ACCENT_PRIMARY_HOVER, ACCENT_SECONDARY, BG_BASE, BG_ELEVATED, BG_ELEVATED_HI, 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, 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, MOTION_MODAL_SECS, RADIUS_LG, RADIUS_MD, RADIUS_SM, SCRIM, STATE_SUCCESS, TEXT_PRIMARY,
TYPE_CAPTION, TYPE_HEADLINE, VAL_SPACE_2, VAL_SPACE_3, VAL_SPACE_4, VAL_SPACE_5, TEXT_SECONDARY, TYPE_BODY_LG, TYPE_CAPTION, TYPE_HEADLINE, VAL_SPACE_2, VAL_SPACE_3,
scaled_duration, VAL_SPACE_4, VAL_SPACE_5, scaled_duration,
}; };
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -402,6 +402,46 @@ pub fn spawn_modal_button<M: Component>(
}); });
} }
/// 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<M: Component>(
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 // Generic touch-scroll helper
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------