feat(engine): UI scale setting — 90/100/115/130% (Phase K)
Settings -> Accessibility gains a UI Scale row cycling four steps, applied live through bevy::ui::UiScale: every menu, modal, and HUD element scales while the table itself stays window-fit via compute_layout. New Settings::ui_scale (serde default 1.0, sanitized clamp to [0.9, 1.3]). Safe-area anchors and modal scrim padding pre-divide physical insets by the UI scale so post-multiplication lands exactly on the system bars — without this, 90% would sink the bottom action bar into the Android gesture zone. The anchor systems also re-run on UiScale changes, not just inset changes. Deferred from Phase K (noted in the doc): seeding the setting from the Android system font scale on first run (needs JNI), and the 44px touch-target audit (on-device). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -736,3 +736,60 @@ fn scroll_clamps_offset_to_zero_at_top() {
|
||||
"scrolling past top must clamp to 0, got {offset}"
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Phase K: UI scale
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn ui_scale_steps_cycle_and_wrap() {
|
||||
assert_eq!(next_ui_scale(0.9), 1.0);
|
||||
assert_eq!(next_ui_scale(1.0), 1.15);
|
||||
assert_eq!(next_ui_scale(1.15), 1.3);
|
||||
assert_eq!(next_ui_scale(1.3), 0.9, "the cycle must wrap");
|
||||
// A hand-edited in-between value advances to the next larger step.
|
||||
assert_eq!(next_ui_scale(1.05), 1.15);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ui_scale_label_formats_as_percent() {
|
||||
assert_eq!(ui_scale_label(0.9), "90%");
|
||||
assert_eq!(ui_scale_label(1.0), "100%");
|
||||
assert_eq!(ui_scale_label(1.15), "115%");
|
||||
assert_eq!(ui_scale_label(1.3), "130%");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ui_scale_setting_syncs_the_bevy_resource() {
|
||||
let mut app = headless_app();
|
||||
app.insert_resource(UiScale(1.0));
|
||||
app.world_mut()
|
||||
.resource_mut::<SettingsResource>()
|
||||
.0
|
||||
.ui_scale = 1.3;
|
||||
app.update();
|
||||
|
||||
assert!(
|
||||
(app.world().resource::<UiScale>().0 - 1.3).abs() < f32::EPSILON,
|
||||
"Settings::ui_scale must drive bevy::ui::UiScale"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ui_scale_out_of_range_sanitizes_on_load() {
|
||||
use solitaire_data::settings::{UI_SCALE_MAX, UI_SCALE_MIN};
|
||||
|
||||
let wild = Settings {
|
||||
ui_scale: 5.0,
|
||||
..Settings::default()
|
||||
}
|
||||
.sanitized();
|
||||
assert_eq!(wild.ui_scale, UI_SCALE_MAX);
|
||||
|
||||
let tiny = Settings {
|
||||
ui_scale: 0.1,
|
||||
..Settings::default()
|
||||
}
|
||||
.sanitized();
|
||||
assert_eq!(tiny.ui_scale, UI_SCALE_MIN);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user