feat(engine): reduce-transparency + high-contrast support for glass chrome

New Settings::reduce_transparency_mode (serde-default, Accessibility
toggle row) swaps every GlassSurface's gradients for a flat opaque
BG_ELEVATED fill; high-contrast mode now also boosts the glass fill
opacity and rim luminance to BORDER_SUBTLE_HC levels. Both applied by
settings_plugin::update_glass_surfaces, which retargets the gradients
in place on toggle or on newly spawned glass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
funman300
2026-07-15 15:16:32 -07:00
parent b405e54bb0
commit c2256582cd
6 changed files with 245 additions and 28 deletions
@@ -353,6 +353,14 @@ pub(super) fn handle_settings_buttons(
**t = on_off_label(settings.0.reduce_motion_mode);
}
}
SettingsButton::ToggleReduceTransparency => {
settings.0.reduce_transparency_mode = !settings.0.reduce_transparency_mode;
persist(&path, &settings.0);
changed.write(SettingsChangedEvent(settings.0.clone()));
// Text refreshed by `update_reduce_transparency_text`; the
// glass chrome itself follows via `update_glass_surfaces`
// next frame.
}
SettingsButton::ToggleTouchInputMode => {
use solitaire_data::settings::TouchInputMode;
settings.0.touch_input_mode = match settings.0.touch_input_mode {
+21 -2
View File
@@ -135,6 +135,10 @@ struct HighContrastText;
#[derive(Component, Debug)]
struct ReduceMotionText;
/// Marks the `Text` node showing the current reduce-transparency state.
#[derive(Component, Debug)]
struct ReduceTransparencyText;
/// Marks the `Text` node showing the current touch input mode state.
#[derive(Component, Debug)]
struct TouchInputModeText;
@@ -277,6 +281,10 @@ enum SettingsButton {
/// non-essential motion (card-slide animations become instant
/// snaps) per `design-system.md` §Accessibility (#3).
ToggleReduceMotion,
/// Toggle the [`Settings::reduce_transparency_mode`] flag — swaps
/// translucent glass chrome (the floating touch tab bar) for opaque
/// fills so table content never bleeds through UI text.
ToggleReduceTransparency,
/// Toggle [`Settings::touch_input_mode`] between `OneTap`
/// (auto-move on tap, default) and `TapToSelect` (first tap selects
/// a card/stack, second tap on a target pile moves it).
@@ -361,8 +369,9 @@ impl SettingsButton {
// run before continuing to the picker rows.
SettingsButton::ToggleHighContrast => 61,
SettingsButton::ToggleReduceMotion => 62,
SettingsButton::ToggleTouchInputMode => 63,
SettingsButton::CycleUiScale => 64,
SettingsButton::ToggleReduceTransparency => 63,
SettingsButton::ToggleTouchInputMode => 64,
SettingsButton::CycleUiScale => 65,
// Picker rows — every swatch in a row shares the row's
// priority so entity-index tiebreaking yields left → right.
SettingsButton::SelectCardBack(_) => 70,
@@ -514,6 +523,16 @@ impl Plugin for SettingsPlugin {
update_smart_default_size_text,
),
);
app.add_systems(
Update,
(
update_reduce_transparency_text,
// Guards internally on settings-change / newly added
// glass — `Added<GlassSurface>` can't be expressed as
// a resource run condition.
update_glass_surfaces,
),
);
app.add_systems(
Update,
(
@@ -317,6 +317,15 @@ fn spawn_accessibility_tab(
"Skips card-slide animations and other non-essential motion. Cards snap instantly to their target.",
font_res,
);
toggle_row(
body,
"Reduce Transparency",
ReduceTransparencyText,
on_off_label(settings.reduce_transparency_mode),
SettingsButton::ToggleReduceTransparency,
"Replaces the see-through glass toolbar with a solid panel so cards behind it never reduce readability.",
font_res,
);
toggle_row(
body,
"Touch Input Mode",
@@ -9,8 +9,10 @@ use crate::font_plugin::FontResource;
use crate::progress_plugin::ProgressResource;
use crate::resources::{SettingsScrollPos, SyncStatus, SyncStatusResource};
use crate::theme::ThemeThumbnailCache;
use crate::ui_glass::{GlassSurface, glass_decorations};
use crate::ui_modal::ModalScrim;
use crate::ui_theme::{BORDER_SUBTLE_HC, HighContrastBackground, HighContrastBorder};
use bevy::ui::{BackgroundGradient, BorderGradient};
/// Spawns the Settings panel when `SettingsScreen` becomes `true`;
/// despawns it when it becomes `false`.
@@ -330,6 +332,41 @@ pub(super) fn update_reduce_motion_text(
}
}
pub(super) fn update_reduce_transparency_text(
settings: Res<SettingsResource>,
mut text_nodes: Query<&mut Text, With<ReduceTransparencyText>>,
) {
if !settings.is_changed() {
return;
}
for mut text in &mut text_nodes {
**text = on_off_label(settings.0.reduce_transparency_mode);
}
}
/// Retargets every [`GlassSurface`]'s sheen + rim gradients when the
/// reduce-transparency or high-contrast toggles change. Runs on
/// `SettingsResource` change and on newly added surfaces (so glass
/// spawned after startup picks up an already-active toggle). Parallel to
/// [`update_high_contrast_borders`]: same trigger, different components.
pub(super) fn update_glass_surfaces(
settings: Res<SettingsResource>,
mut surfaces: Query<(&mut BackgroundGradient, &mut BorderGradient), With<GlassSurface>>,
added: Query<(), Added<GlassSurface>>,
) {
if !settings.is_changed() && added.is_empty() {
return;
}
let (sheen, rim) = glass_decorations(
settings.0.reduce_transparency_mode,
settings.0.high_contrast_mode,
);
for (mut s, mut r) in &mut surfaces {
*s = sheen.clone();
*r = rim.clone();
}
}
pub(super) fn update_touch_input_mode_text(
settings: Res<SettingsResource>,
mut text_nodes: Query<&mut Text, With<TouchInputModeText>>,