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:
@@ -154,6 +154,14 @@ pub struct Settings {
|
|||||||
/// `#[serde(default)]`.
|
/// `#[serde(default)]`.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub reduce_motion_mode: bool,
|
pub reduce_motion_mode: bool,
|
||||||
|
/// When `true`, replaces translucent "glass" chrome (the floating
|
||||||
|
/// touch tab bar) with opaque fills so table content behind it can
|
||||||
|
/// never reduce label/icon contrast. The accessibility counterpart
|
||||||
|
/// of iOS "Reduce Transparency". Older `settings.json` files written
|
||||||
|
/// before this field existed deserialize cleanly to `false` thanks
|
||||||
|
/// to `#[serde(default)]`.
|
||||||
|
#[serde(default)]
|
||||||
|
pub reduce_transparency_mode: bool,
|
||||||
/// Window size and screen position to restore on next launch. `None`
|
/// Window size and screen position to restore on next launch. `None`
|
||||||
/// means "use platform defaults" — set on first run, then populated
|
/// means "use platform defaults" — set on first run, then populated
|
||||||
/// as the player resizes / moves the window. Older `settings.json`
|
/// as the player resizes / moves the window. Older `settings.json`
|
||||||
@@ -451,6 +459,7 @@ impl Default for Settings {
|
|||||||
color_blind_mode: false,
|
color_blind_mode: false,
|
||||||
high_contrast_mode: false,
|
high_contrast_mode: false,
|
||||||
reduce_motion_mode: false,
|
reduce_motion_mode: false,
|
||||||
|
reduce_transparency_mode: false,
|
||||||
window_geometry: None,
|
window_geometry: None,
|
||||||
selected_theme_id: default_theme_id(),
|
selected_theme_id: default_theme_id(),
|
||||||
shown_achievement_onboarding: false,
|
shown_achievement_onboarding: false,
|
||||||
|
|||||||
@@ -353,6 +353,14 @@ pub(super) fn handle_settings_buttons(
|
|||||||
**t = on_off_label(settings.0.reduce_motion_mode);
|
**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 => {
|
SettingsButton::ToggleTouchInputMode => {
|
||||||
use solitaire_data::settings::TouchInputMode;
|
use solitaire_data::settings::TouchInputMode;
|
||||||
settings.0.touch_input_mode = match settings.0.touch_input_mode {
|
settings.0.touch_input_mode = match settings.0.touch_input_mode {
|
||||||
|
|||||||
@@ -135,6 +135,10 @@ struct HighContrastText;
|
|||||||
#[derive(Component, Debug)]
|
#[derive(Component, Debug)]
|
||||||
struct ReduceMotionText;
|
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.
|
/// Marks the `Text` node showing the current touch input mode state.
|
||||||
#[derive(Component, Debug)]
|
#[derive(Component, Debug)]
|
||||||
struct TouchInputModeText;
|
struct TouchInputModeText;
|
||||||
@@ -277,6 +281,10 @@ enum SettingsButton {
|
|||||||
/// non-essential motion (card-slide animations become instant
|
/// non-essential motion (card-slide animations become instant
|
||||||
/// snaps) per `design-system.md` §Accessibility (#3).
|
/// snaps) per `design-system.md` §Accessibility (#3).
|
||||||
ToggleReduceMotion,
|
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`
|
/// Toggle [`Settings::touch_input_mode`] between `OneTap`
|
||||||
/// (auto-move on tap, default) and `TapToSelect` (first tap selects
|
/// (auto-move on tap, default) and `TapToSelect` (first tap selects
|
||||||
/// a card/stack, second tap on a target pile moves it).
|
/// a card/stack, second tap on a target pile moves it).
|
||||||
@@ -361,8 +369,9 @@ impl SettingsButton {
|
|||||||
// run before continuing to the picker rows.
|
// run before continuing to the picker rows.
|
||||||
SettingsButton::ToggleHighContrast => 61,
|
SettingsButton::ToggleHighContrast => 61,
|
||||||
SettingsButton::ToggleReduceMotion => 62,
|
SettingsButton::ToggleReduceMotion => 62,
|
||||||
SettingsButton::ToggleTouchInputMode => 63,
|
SettingsButton::ToggleReduceTransparency => 63,
|
||||||
SettingsButton::CycleUiScale => 64,
|
SettingsButton::ToggleTouchInputMode => 64,
|
||||||
|
SettingsButton::CycleUiScale => 65,
|
||||||
// Picker rows — every swatch in a row shares the row's
|
// Picker rows — every swatch in a row shares the row's
|
||||||
// priority so entity-index tiebreaking yields left → right.
|
// priority so entity-index tiebreaking yields left → right.
|
||||||
SettingsButton::SelectCardBack(_) => 70,
|
SettingsButton::SelectCardBack(_) => 70,
|
||||||
@@ -514,6 +523,16 @@ impl Plugin for SettingsPlugin {
|
|||||||
update_smart_default_size_text,
|
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(
|
app.add_systems(
|
||||||
Update,
|
Update,
|
||||||
(
|
(
|
||||||
|
|||||||
@@ -317,6 +317,15 @@ fn spawn_accessibility_tab(
|
|||||||
"Skips card-slide animations and other non-essential motion. Cards snap instantly to their target.",
|
"Skips card-slide animations and other non-essential motion. Cards snap instantly to their target.",
|
||||||
font_res,
|
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(
|
toggle_row(
|
||||||
body,
|
body,
|
||||||
"Touch Input Mode",
|
"Touch Input Mode",
|
||||||
|
|||||||
@@ -9,8 +9,10 @@ use crate::font_plugin::FontResource;
|
|||||||
use crate::progress_plugin::ProgressResource;
|
use crate::progress_plugin::ProgressResource;
|
||||||
use crate::resources::{SettingsScrollPos, SyncStatus, SyncStatusResource};
|
use crate::resources::{SettingsScrollPos, SyncStatus, SyncStatusResource};
|
||||||
use crate::theme::ThemeThumbnailCache;
|
use crate::theme::ThemeThumbnailCache;
|
||||||
|
use crate::ui_glass::{GlassSurface, glass_decorations};
|
||||||
use crate::ui_modal::ModalScrim;
|
use crate::ui_modal::ModalScrim;
|
||||||
use crate::ui_theme::{BORDER_SUBTLE_HC, HighContrastBackground, HighContrastBorder};
|
use crate::ui_theme::{BORDER_SUBTLE_HC, HighContrastBackground, HighContrastBorder};
|
||||||
|
use bevy::ui::{BackgroundGradient, BorderGradient};
|
||||||
|
|
||||||
/// Spawns the Settings panel when `SettingsScreen` becomes `true`;
|
/// Spawns the Settings panel when `SettingsScreen` becomes `true`;
|
||||||
/// despawns it when it becomes `false`.
|
/// 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(
|
pub(super) fn update_touch_input_mode_text(
|
||||||
settings: Res<SettingsResource>,
|
settings: Res<SettingsResource>,
|
||||||
mut text_nodes: Query<&mut Text, With<TouchInputModeText>>,
|
mut text_nodes: Query<&mut Text, With<TouchInputModeText>>,
|
||||||
|
|||||||
@@ -30,10 +30,19 @@ use bevy::ui::{
|
|||||||
BackgroundGradient, BorderGradient, BoxShadow, ColorStop, LinearGradient, ShadowStyle,
|
BackgroundGradient, BorderGradient, BoxShadow, ColorStop, LinearGradient, ShadowStyle,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::ui_theme::{BG_ELEVATED, BORDER_SUBTLE, BORDER_SUBTLE_HC};
|
||||||
|
|
||||||
/// Border width every glass surface must reserve in its `Node::border` so
|
/// Border width every glass surface must reserve in its `Node::border` so
|
||||||
/// the rim gradient has a strip to paint into.
|
/// the rim gradient has a strip to paint into.
|
||||||
pub const GLASS_BORDER_PX: f32 = 1.0;
|
pub const GLASS_BORDER_PX: f32 = 1.0;
|
||||||
|
|
||||||
|
/// Marker on every node that received [`glass_surface`]. The settings
|
||||||
|
/// plugin's `update_glass_surfaces` retargets these when the player
|
||||||
|
/// toggles reduce-transparency or high-contrast, swapping the gradients
|
||||||
|
/// in place via [`glass_decorations`].
|
||||||
|
#[derive(Component, Debug)]
|
||||||
|
pub struct GlassSurface;
|
||||||
|
|
||||||
/// Base fill at the *bottom* of the glass sheet — near-black at ~62%
|
/// Base fill at the *bottom* of the glass sheet — near-black at ~62%
|
||||||
/// opacity so the felt and cards remain visible through the bar while
|
/// opacity so the felt and cards remain visible through the bar while
|
||||||
/// keeping icon/label contrast comfortable.
|
/// keeping icon/label contrast comfortable.
|
||||||
@@ -58,6 +67,22 @@ const GLASS_RIM_BOTTOM: Color = Color::srgba(1.0, 1.0, 1.0, 0.16);
|
|||||||
/// Drop shadow under the floating surface.
|
/// Drop shadow under the floating surface.
|
||||||
const GLASS_SHADOW: Color = Color::srgba(0.0, 0.0, 0.0, 0.35);
|
const GLASS_SHADOW: Color = Color::srgba(0.0, 0.0, 0.0, 0.35);
|
||||||
|
|
||||||
|
/// High-contrast fill — same glass, far less see-through, so text and
|
||||||
|
/// icons keep contrast over any card art. Per `design-system.md`
|
||||||
|
/// §Accessibility the HC toggle trades aesthetics for legibility.
|
||||||
|
const GLASS_FILL_TOP_HC: Color = Color::srgba(0.20, 0.21, 0.23, 0.90);
|
||||||
|
/// High-contrast fill at the bottom of the sheet.
|
||||||
|
const GLASS_FILL_BOTTOM_HC: Color = Color::srgba(0.055, 0.055, 0.063, 0.88);
|
||||||
|
|
||||||
|
/// High-contrast rim at the top edge — matches the luminance of
|
||||||
|
/// `BORDER_SUBTLE_HC` so the bar's outline reads as strongly as every
|
||||||
|
/// other HC-boosted border.
|
||||||
|
const GLASS_RIM_TOP_HC: Color = Color::srgba(1.0, 1.0, 1.0, 0.75);
|
||||||
|
/// High-contrast rim at the midpoint.
|
||||||
|
const GLASS_RIM_MID_HC: Color = Color::srgba(1.0, 1.0, 1.0, 0.35);
|
||||||
|
/// High-contrast rim at the bottom edge.
|
||||||
|
const GLASS_RIM_BOTTOM_HC: Color = Color::srgba(1.0, 1.0, 1.0, 0.50);
|
||||||
|
|
||||||
/// Visual components for a floating glass surface.
|
/// Visual components for a floating glass surface.
|
||||||
///
|
///
|
||||||
/// The caller owns the `Node` and must set two fields for the material to
|
/// The caller owns the `Node` and must set two fields for the material to
|
||||||
@@ -69,33 +94,11 @@ const GLASS_SHADOW: Color = Color::srgba(0.0, 0.0, 0.0, 0.35);
|
|||||||
/// Everything here is fragment-shader work inside Bevy's stock UI pipeline,
|
/// Everything here is fragment-shader work inside Bevy's stock UI pipeline,
|
||||||
/// so it is safe on WebGL2 and adds no per-frame cost beyond ordinary nodes.
|
/// so it is safe on WebGL2 and adds no per-frame cost beyond ordinary nodes.
|
||||||
pub fn glass_surface() -> impl Bundle {
|
pub fn glass_surface() -> impl Bundle {
|
||||||
|
let (sheen, rim) = glass_decorations(false, false);
|
||||||
(
|
(
|
||||||
// Sheen: one top-to-bottom linear gradient carries both the fill and
|
GlassSurface,
|
||||||
// the lighting so there is a single source of truth for the surface
|
sheen,
|
||||||
// colour (a separate `BackgroundColor` would just be painted over).
|
rim,
|
||||||
BackgroundGradient(vec![
|
|
||||||
LinearGradient::new(
|
|
||||||
LinearGradient::TO_BOTTOM,
|
|
||||||
vec![
|
|
||||||
ColorStop::new(GLASS_FILL_TOP, Val::Percent(0.0)),
|
|
||||||
ColorStop::new(GLASS_FILL_BOTTOM, Val::Percent(60.0)),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
.into(),
|
|
||||||
]),
|
|
||||||
// Specular rim: bright at the top edge, fading out through the
|
|
||||||
// sides, with a faint return at the bottom.
|
|
||||||
BorderGradient(vec![
|
|
||||||
LinearGradient::new(
|
|
||||||
LinearGradient::TO_BOTTOM,
|
|
||||||
vec![
|
|
||||||
ColorStop::new(GLASS_RIM_TOP, Val::Percent(0.0)),
|
|
||||||
ColorStop::new(GLASS_RIM_MID, Val::Percent(55.0)),
|
|
||||||
ColorStop::new(GLASS_RIM_BOTTOM, Val::Percent(100.0)),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
.into(),
|
|
||||||
]),
|
|
||||||
// Soft shadow underneath sells the "floating above the table" read.
|
// Soft shadow underneath sells the "floating above the table" read.
|
||||||
BoxShadow(vec![ShadowStyle {
|
BoxShadow(vec![ShadowStyle {
|
||||||
color: GLASS_SHADOW,
|
color: GLASS_SHADOW,
|
||||||
@@ -107,9 +110,98 @@ pub fn glass_surface() -> impl Bundle {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The sheen + rim pair for the requested accessibility state. The
|
||||||
|
/// settings plugin overwrites every [`GlassSurface`]'s components with
|
||||||
|
/// these when the relevant toggles change.
|
||||||
|
///
|
||||||
|
/// - Default: translucent fill with a top-lit sheen and a specular rim.
|
||||||
|
/// - `high_contrast`: same shape, near-opaque fill, and a rim boosted to
|
||||||
|
/// `BORDER_SUBTLE_HC` luminance so the outline stays legible.
|
||||||
|
/// - `reduce_transparency`: flat opaque `BG_ELEVATED` fill and a flat
|
||||||
|
/// border — no see-through at all. Combined with `high_contrast` the
|
||||||
|
/// flat border brightens to `BORDER_SUBTLE_HC`.
|
||||||
|
pub fn glass_decorations(
|
||||||
|
reduce_transparency: bool,
|
||||||
|
high_contrast: bool,
|
||||||
|
) -> (BackgroundGradient, BorderGradient) {
|
||||||
|
if reduce_transparency {
|
||||||
|
// "Gradients" with a single stop render as flat fills — reusing the
|
||||||
|
// same component types means the settings toggle swaps values, not
|
||||||
|
// component sets.
|
||||||
|
let border = if high_contrast {
|
||||||
|
BORDER_SUBTLE_HC
|
||||||
|
} else {
|
||||||
|
BORDER_SUBTLE
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
BackgroundGradient(vec![
|
||||||
|
LinearGradient::new(
|
||||||
|
LinearGradient::TO_BOTTOM,
|
||||||
|
vec![ColorStop::new(BG_ELEVATED, Val::Percent(0.0))],
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
]),
|
||||||
|
BorderGradient(vec![
|
||||||
|
LinearGradient::new(
|
||||||
|
LinearGradient::TO_BOTTOM,
|
||||||
|
vec![ColorStop::new(border, Val::Percent(0.0))],
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let (fill_top, fill_bottom, rim_top, rim_mid, rim_bottom) = if high_contrast {
|
||||||
|
(
|
||||||
|
GLASS_FILL_TOP_HC,
|
||||||
|
GLASS_FILL_BOTTOM_HC,
|
||||||
|
GLASS_RIM_TOP_HC,
|
||||||
|
GLASS_RIM_MID_HC,
|
||||||
|
GLASS_RIM_BOTTOM_HC,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
GLASS_FILL_TOP,
|
||||||
|
GLASS_FILL_BOTTOM,
|
||||||
|
GLASS_RIM_TOP,
|
||||||
|
GLASS_RIM_MID,
|
||||||
|
GLASS_RIM_BOTTOM,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
(
|
||||||
|
// Sheen: one top-to-bottom linear gradient carries both the fill and
|
||||||
|
// the lighting so there is a single source of truth for the surface
|
||||||
|
// colour (a separate `BackgroundColor` would just be painted over).
|
||||||
|
BackgroundGradient(vec![
|
||||||
|
LinearGradient::new(
|
||||||
|
LinearGradient::TO_BOTTOM,
|
||||||
|
vec![
|
||||||
|
ColorStop::new(fill_top, Val::Percent(0.0)),
|
||||||
|
ColorStop::new(fill_bottom, Val::Percent(60.0)),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
]),
|
||||||
|
// Specular rim: bright at the top edge, fading out through the
|
||||||
|
// sides, with a faint return at the bottom.
|
||||||
|
BorderGradient(vec![
|
||||||
|
LinearGradient::new(
|
||||||
|
LinearGradient::TO_BOTTOM,
|
||||||
|
vec![
|
||||||
|
ColorStop::new(rim_top, Val::Percent(0.0)),
|
||||||
|
ColorStop::new(rim_mid, Val::Percent(55.0)),
|
||||||
|
ColorStop::new(rim_bottom, Val::Percent(100.0)),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use bevy::ui::Gradient;
|
||||||
|
|
||||||
/// The bundle must insert all three visual components — a regression
|
/// The bundle must insert all three visual components — a regression
|
||||||
/// here (e.g. a refactor dropping the rim) would silently flatten the
|
/// here (e.g. a refactor dropping the rim) would silently flatten the
|
||||||
@@ -142,5 +234,48 @@ mod tests {
|
|||||||
fn rim_highlight_peaks_at_top() {
|
fn rim_highlight_peaks_at_top() {
|
||||||
assert!(GLASS_RIM_TOP.alpha() > GLASS_RIM_BOTTOM.alpha());
|
assert!(GLASS_RIM_TOP.alpha() > GLASS_RIM_BOTTOM.alpha());
|
||||||
assert!(GLASS_RIM_BOTTOM.alpha() > GLASS_RIM_MID.alpha());
|
assert!(GLASS_RIM_BOTTOM.alpha() > GLASS_RIM_MID.alpha());
|
||||||
|
assert!(GLASS_RIM_TOP_HC.alpha() > GLASS_RIM_BOTTOM_HC.alpha());
|
||||||
|
assert!(GLASS_RIM_BOTTOM_HC.alpha() > GLASS_RIM_MID_HC.alpha());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reduce-transparency must produce fully opaque fills — the entire
|
||||||
|
/// point of the toggle is that nothing shows through.
|
||||||
|
#[test]
|
||||||
|
fn reduce_transparency_is_fully_opaque() {
|
||||||
|
for high_contrast in [false, true] {
|
||||||
|
let (sheen, _) = glass_decorations(true, high_contrast);
|
||||||
|
for gradient in &sheen.0 {
|
||||||
|
let Gradient::Linear(linear) = gradient else {
|
||||||
|
panic!("reduce-transparency sheen must stay linear");
|
||||||
|
};
|
||||||
|
for stop in &linear.stops {
|
||||||
|
assert_eq!(
|
||||||
|
stop.color.alpha(),
|
||||||
|
1.0,
|
||||||
|
"opaque variant leaked translucency (hc={high_contrast})"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// High-contrast glass must be meaningfully less transparent than the
|
||||||
|
/// default, and its rim meaningfully brighter — otherwise the toggle
|
||||||
|
/// does nothing perceptible on this surface.
|
||||||
|
#[test]
|
||||||
|
fn high_contrast_boosts_fill_and_rim() {
|
||||||
|
assert!(GLASS_FILL_TOP_HC.alpha() >= GLASS_FILL_TOP.alpha() + 0.15);
|
||||||
|
assert!(GLASS_FILL_BOTTOM_HC.alpha() >= GLASS_FILL_BOTTOM.alpha() + 0.15);
|
||||||
|
assert!(GLASS_RIM_TOP_HC.alpha() >= GLASS_RIM_TOP.alpha() + 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `glass_surface()` must carry the marker the settings applier
|
||||||
|
/// retargets — without it the accessibility toggles silently skip
|
||||||
|
/// the bar.
|
||||||
|
#[test]
|
||||||
|
fn glass_surface_carries_retarget_marker() {
|
||||||
|
let mut world = World::new();
|
||||||
|
let e = world.spawn(glass_surface()).id();
|
||||||
|
assert!(world.get::<GlassSurface>(e).is_some());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user