Добавить доступный интерфейс этапа 3
This commit is contained in:
@@ -14,13 +14,16 @@ import android.util.SparseArray;
|
||||
import android.view.HapticFeedbackConstants;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A dependency-free 2D puzzle-platformer renderer and deterministic physics loop.
|
||||
@@ -164,6 +167,7 @@ public final class GameView extends View implements Runnable {
|
||||
private final ControlSettingsStore controlSettingsStore;
|
||||
private final OnboardingStore onboardingStore;
|
||||
private final GameLanguageStore languageStore;
|
||||
private final AccessibilitySettingsStore accessibilitySettingsStore;
|
||||
private final SparseArray<PointF> pointers = new SparseArray<>();
|
||||
private final Player player = new Player();
|
||||
private final ArrayList<Corpse> corpses = new ArrayList<>();
|
||||
@@ -194,6 +198,8 @@ public final class GameView extends View implements Runnable {
|
||||
private CompletionResult completionResult;
|
||||
private GameLanguage language;
|
||||
private GameStrings strings;
|
||||
private AccessibilitySettings accessibilitySettings;
|
||||
private String lastAccessibilityStateKey = "";
|
||||
|
||||
public GameView(Context context) {
|
||||
super(context);
|
||||
@@ -205,12 +211,14 @@ public final class GameView extends View implements Runnable {
|
||||
languageStore = new GameLanguageStore(context);
|
||||
language = languageStore.load();
|
||||
strings = GameStrings.forLanguage(language);
|
||||
accessibilitySettingsStore = new AccessibilitySettingsStore(context);
|
||||
accessibilitySettings = accessibilitySettingsStore.load();
|
||||
paint.setTypeface(android.graphics.Typeface.create("sans", android.graphics.Typeface.NORMAL));
|
||||
stroke.setStyle(Paint.Style.STROKE);
|
||||
stroke.setStrokeCap(Paint.Cap.ROUND);
|
||||
setFocusable(true);
|
||||
setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
|
||||
setContentDescription(strings.appContentDescription());
|
||||
refreshAccessibilityDescription(false);
|
||||
running = true;
|
||||
postOnAnimation(this);
|
||||
}
|
||||
@@ -239,12 +247,14 @@ public final class GameView extends View implements Runnable {
|
||||
pointers.clear();
|
||||
deriveControls();
|
||||
lastFrameNanos = System.nanoTime();
|
||||
refreshAccessibilityDescription(false);
|
||||
}
|
||||
|
||||
public boolean handleBack() {
|
||||
if (screen == Screen.LEVELS) return false;
|
||||
if (screen == Screen.SETTINGS) {
|
||||
screen = Screen.LEVELS;
|
||||
refreshAccessibilityDescription(true);
|
||||
return true;
|
||||
}
|
||||
if (screen == Screen.COMPLETE) {
|
||||
@@ -258,6 +268,7 @@ public final class GameView extends View implements Runnable {
|
||||
}
|
||||
pointers.clear();
|
||||
deriveControls();
|
||||
refreshAccessibilityDescription(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -281,6 +292,7 @@ public final class GameView extends View implements Runnable {
|
||||
for (LevelDefinition.Platform item : level.platforms) platformStates.add(new PlatformState(item));
|
||||
spawnPlayer();
|
||||
lastFrameNanos = System.nanoTime();
|
||||
refreshAccessibilityDescription(true);
|
||||
}
|
||||
|
||||
private void restartLevel() {
|
||||
@@ -320,6 +332,8 @@ public final class GameView extends View implements Runnable {
|
||||
checkGoal();
|
||||
}
|
||||
|
||||
refreshAccessibilityDescriptionIfStateChanged();
|
||||
|
||||
float targetCamera = player.alive ? player.x - 610f + player.facing * 110f : cameraX;
|
||||
targetCamera = GameMath.clamp(targetCamera, 0f, Math.max(0f, level.worldWidth - VIEW_WIDTH));
|
||||
cameraX += (targetCamera - cameraX) * Math.min(1f, dt * 4.5f);
|
||||
@@ -656,6 +670,7 @@ public final class GameView extends View implements Runnable {
|
||||
pointers.clear();
|
||||
deriveControls();
|
||||
emitHaptic(HapticFeedbackConstants.LONG_PRESS);
|
||||
refreshAccessibilityDescription(true);
|
||||
}
|
||||
|
||||
private RectF playerRect() {
|
||||
@@ -673,9 +688,7 @@ public final class GameView extends View implements Runnable {
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
canvasScale = Math.min(getWidth() / VIEW_WIDTH, getHeight() / VIEW_HEIGHT);
|
||||
canvasOffsetX = (getWidth() - VIEW_WIDTH * canvasScale) * 0.5f;
|
||||
canvasOffsetY = (getHeight() - VIEW_HEIGHT * canvasScale) * 0.5f;
|
||||
updateViewport(getWidth(), getHeight());
|
||||
|
||||
paint.setShader(outsideGradient);
|
||||
canvas.drawRect(0, 0, getWidth(), getHeight(), paint);
|
||||
@@ -714,6 +727,12 @@ public final class GameView extends View implements Runnable {
|
||||
}
|
||||
|
||||
private void drawSky(Canvas canvas) {
|
||||
if (accessibilitySettings.highContrastEnabled()) {
|
||||
canvas.drawColor(Color.BLACK);
|
||||
paint.setColor(Color.rgb(255, 214, 77));
|
||||
canvas.drawCircle(1560, 170, 88, paint);
|
||||
return;
|
||||
}
|
||||
paint.setShader(skyGradient);
|
||||
canvas.drawRect(0, 0, VIEW_WIDTH, VIEW_HEIGHT, paint);
|
||||
paint.setShader(null);
|
||||
@@ -745,10 +764,19 @@ public final class GameView extends View implements Runnable {
|
||||
for (PlatformState state : platformStates) {
|
||||
RectF box = state.rect();
|
||||
boolean moving = state.definition.isMoving();
|
||||
paint.setColor(moving ? Color.rgb(172, 126, 55) : Color.rgb(69, 74, 91));
|
||||
paint.setColor(accessibilitySettings.highContrastEnabled()
|
||||
? (moving ? Color.rgb(255, 191, 0) : Color.rgb(56, 56, 56))
|
||||
: (moving ? Color.rgb(172, 126, 55) : Color.rgb(69, 74, 91)));
|
||||
canvas.drawRoundRect(box, 10, 10, paint);
|
||||
paint.setColor(moving ? Color.rgb(242, 195, 92) : Color.rgb(117, 121, 137));
|
||||
paint.setColor(accessibilitySettings.highContrastEnabled()
|
||||
? Color.WHITE
|
||||
: (moving ? Color.rgb(242, 195, 92) : Color.rgb(117, 121, 137)));
|
||||
canvas.drawRoundRect(new RectF(box.left, box.top, box.right, box.top + Math.min(14f, box.height())), 8, 8, paint);
|
||||
if (accessibilitySettings.highContrastEnabled()) {
|
||||
stroke.setColor(Color.WHITE);
|
||||
stroke.setStrokeWidth(5f);
|
||||
canvas.drawRoundRect(box, 10, 10, stroke);
|
||||
}
|
||||
if (moving) {
|
||||
stroke.setColor(Color.argb(120, 244, 214, 137));
|
||||
stroke.setStrokeWidth(5f);
|
||||
@@ -771,6 +799,7 @@ public final class GameView extends View implements Runnable {
|
||||
canvas.drawRoundRect(new RectF(box.left - 8, box.top + 6, box.right + 8, box.bottom + 12), 6, 6, paint);
|
||||
paint.setColor(active ? Color.rgb(250, 194, 76) : Color.rgb(174, 126, 55));
|
||||
canvas.drawRoundRect(box, 7, 7, paint);
|
||||
drawWorldLabel(canvas, active ? "DOWN" : "UP", box.centerX(), box.top - 18);
|
||||
} else {
|
||||
paint.setColor(Color.rgb(50, 56, 70));
|
||||
canvas.drawRoundRect(box, 12, 12, paint);
|
||||
@@ -785,6 +814,7 @@ public final class GameView extends View implements Runnable {
|
||||
Float remaining = timedSignals.get(item.signalId);
|
||||
drawWorldLabel(canvas, String.format(Locale.US, "%.1f", remaining == null ? 0f : remaining), box.centerX(), box.top - 52);
|
||||
}
|
||||
drawWorldLabel(canvas, active ? "ON" : "OFF", box.centerX(), box.bottom + 32);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -877,6 +907,7 @@ public final class GameView extends View implements Runnable {
|
||||
paint.setColor(Color.rgb(239, 195, 92));
|
||||
canvas.drawCircle(box.centerX(), box.centerY(), 15, paint);
|
||||
}
|
||||
drawWorldLabel(canvas, open ? "OPEN" : "LOCKED", box.centerX(), box.top - 20);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -949,13 +980,24 @@ public final class GameView extends View implements Runnable {
|
||||
private void drawHud(Canvas canvas) {
|
||||
paint.setColor(Color.argb(178, 15, 20, 34));
|
||||
canvas.drawRoundRect(new RectF(28, 26, 940, 138), 24, 24, paint);
|
||||
canvas.save();
|
||||
canvas.clipRect(40, 30, 925, 135);
|
||||
drawText(canvas, String.format(Locale.US, "%02d %s", level.number,
|
||||
strings.levelTitle(level.number)), 56, 72, 34,
|
||||
Color.rgb(248, 220, 151), false);
|
||||
drawFittedText(canvas, strings.levelHint(level.number), 56, 116, 25, 16,
|
||||
850, Color.rgb(231, 231, 235), false);
|
||||
canvas.restore();
|
||||
canvas.save();
|
||||
canvas.clipRect(990, 28, 1590, 130);
|
||||
drawText(canvas, strings.hudStats(deaths, (long) (levelElapsed * 1000)),
|
||||
1010, 82, 31, Color.WHITE, false);
|
||||
canvas.restore();
|
||||
paint.setColor(accessibilitySettings.highContrastEnabled()
|
||||
? Color.BLACK : Color.argb(178, 15, 20, 34));
|
||||
canvas.drawRoundRect(new RectF(590, 139, 1330, 185), 18, 18, paint);
|
||||
drawCenteredText(canvas, criticalStatusText(), VIEW_WIDTH * 0.5f, 170, 22,
|
||||
accessibilitySettings.highContrastEnabled() ? Color.WHITE : Color.rgb(218, 221, 230));
|
||||
|
||||
drawHudButton(canvas, new RectF(1608, 26, 1770, 112), strings.restart(), false);
|
||||
drawHudButton(canvas, new RectF(1790, 26, 1892, 112), "II", false);
|
||||
@@ -973,11 +1015,15 @@ public final class GameView extends View implements Runnable {
|
||||
private void drawControlCircle(Canvas canvas, float x, float y, float radius, String label, boolean active) {
|
||||
int inactiveFillAlpha = Math.round(190f * controlSettings.controlOpacity());
|
||||
int inactiveStrokeAlpha = Math.round(230f * controlSettings.controlOpacity());
|
||||
paint.setColor(active ? Color.argb(220, 245, 190, 74)
|
||||
: Color.argb(inactiveFillAlpha, 22, 28, 43));
|
||||
paint.setColor(accessibilitySettings.highContrastEnabled()
|
||||
? (active ? Color.rgb(255, 214, 77) : Color.BLACK)
|
||||
: (active ? Color.argb(220, 245, 190, 74)
|
||||
: Color.argb(inactiveFillAlpha, 22, 28, 43)));
|
||||
canvas.drawCircle(x, y, radius, paint);
|
||||
stroke.setColor(active ? Color.rgb(255, 232, 169)
|
||||
: Color.argb(inactiveStrokeAlpha, 240, 240, 245));
|
||||
stroke.setColor(accessibilitySettings.highContrastEnabled()
|
||||
? Color.WHITE
|
||||
: (active ? Color.rgb(255, 232, 169)
|
||||
: Color.argb(inactiveStrokeAlpha, 240, 240, 245)));
|
||||
stroke.setStrokeWidth(5f);
|
||||
canvas.drawCircle(x, y, radius - 5, stroke);
|
||||
drawCenteredText(canvas, label, x, y + 21, 68 * controlSettings.controlScale(),
|
||||
@@ -1003,12 +1049,17 @@ public final class GameView extends View implements Runnable {
|
||||
RectF card = new RectF(left, top, left + cardW, top + cardH);
|
||||
boolean enabled = i < unlocked;
|
||||
boolean complete = progress.isCompleted(i + 1);
|
||||
paint.setColor(enabled ? Color.argb(220, 38, 45, 65) : Color.argb(185, 25, 29, 42));
|
||||
paint.setColor(accessibilitySettings.highContrastEnabled()
|
||||
? Color.BLACK
|
||||
: (enabled ? Color.argb(220, 38, 45, 65) : Color.argb(185, 25, 29, 42)));
|
||||
canvas.drawRoundRect(card, 22, 22, paint);
|
||||
stroke.setColor(complete ? Color.rgb(109, 222, 143) : (enabled ? Color.rgb(205, 164, 83) : Color.rgb(73, 77, 91)));
|
||||
stroke.setStrokeWidth(5f);
|
||||
canvas.drawRoundRect(card, 22, 22, stroke);
|
||||
drawCenteredText(canvas, enabled ? String.format(Locale.US, "%02d", i + 1) : strings.locked(), card.centerX(), top + 72, enabled ? 48 : 27,
|
||||
canvas.save();
|
||||
canvas.clipRect(card.left + 10, card.top + 8, card.right - 10, card.bottom - 8);
|
||||
drawCenteredText(canvas, enabled ? String.format(Locale.US, "%02d", i + 1)
|
||||
: strings.locked(), card.centerX(), top + 72, enabled ? 48 : 27,
|
||||
enabled ? Color.rgb(248, 210, 118) : Color.rgb(120, 123, 135));
|
||||
LevelDefinition item = LevelCatalog.get(i);
|
||||
drawCenteredFittedText(canvas, strings.levelTitle(item.number), card.centerX(),
|
||||
@@ -1024,6 +1075,7 @@ public final class GameView extends View implements Runnable {
|
||||
drawCenteredText(canvas, strings.tapToEnter(), card.centerX(), top + 190,
|
||||
22, Color.rgb(202, 204, 214));
|
||||
}
|
||||
canvas.restore();
|
||||
}
|
||||
drawHudButton(canvas, new RectF(1560, 70, 1850, 160), strings.settingsButton(), false);
|
||||
drawCenteredText(canvas, strings.levelSelectFooter(), VIEW_WIDTH * 0.5f, 980, 24,
|
||||
@@ -1044,18 +1096,47 @@ public final class GameView extends View implements Runnable {
|
||||
strings.controlOpacityValue(controlSettings.controlOpacityIndex()));
|
||||
drawSettingRow(canvas, new RectF(420, 620, 1500, 750), strings.vibrationSetting(),
|
||||
strings.hapticsValue(controlSettings.hapticsEnabled()));
|
||||
drawCenteredText(canvas, strings.accessibilityTitle(), 220, 240, 25,
|
||||
Color.rgb(248, 212, 126));
|
||||
drawCompactSettingRow(canvas, new RectF(55, 280, 385, 410),
|
||||
strings.textSizeSetting(),
|
||||
strings.textSizeValue(accessibilitySettings.textScale()));
|
||||
drawCompactSettingRow(canvas, new RectF(55, 450, 385, 580),
|
||||
strings.highContrastSetting(),
|
||||
strings.highContrastValue(accessibilitySettings.highContrastEnabled()));
|
||||
drawHudButton(canvas, new RectF(710, 850, 1210, 960), strings.back(), true);
|
||||
}
|
||||
|
||||
private void drawSettingRow(Canvas canvas, RectF box, String label, String value) {
|
||||
paint.setColor(Color.argb(220, 38, 45, 65));
|
||||
private void drawCompactSettingRow(Canvas canvas, RectF box, String label, String value) {
|
||||
paint.setColor(accessibilitySettings.highContrastEnabled()
|
||||
? Color.BLACK : Color.argb(220, 38, 45, 65));
|
||||
canvas.drawRoundRect(box, 22, 22, paint);
|
||||
stroke.setColor(Color.rgb(139, 145, 160));
|
||||
stroke.setColor(accessibilitySettings.highContrastEnabled()
|
||||
? Color.WHITE : Color.rgb(139, 145, 160));
|
||||
stroke.setStrokeWidth(4f);
|
||||
canvas.drawRoundRect(box, 22, 22, stroke);
|
||||
canvas.save();
|
||||
canvas.clipRect(box.left + 8f, box.top + 8f, box.right - 8f, box.bottom - 8f);
|
||||
drawCenteredText(canvas, label, box.centerX(), box.top + 47f, 24f, Color.WHITE);
|
||||
drawCenteredText(canvas, value, box.centerX(), box.top + 98f, 28f,
|
||||
Color.rgb(248, 212, 126));
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
private void drawSettingRow(Canvas canvas, RectF box, String label, String value) {
|
||||
paint.setColor(accessibilitySettings.highContrastEnabled()
|
||||
? Color.BLACK : Color.argb(220, 38, 45, 65));
|
||||
canvas.drawRoundRect(box, 22, 22, paint);
|
||||
stroke.setColor(accessibilitySettings.highContrastEnabled()
|
||||
? Color.WHITE : Color.rgb(139, 145, 160));
|
||||
stroke.setStrokeWidth(4f);
|
||||
canvas.drawRoundRect(box, 22, 22, stroke);
|
||||
canvas.save();
|
||||
canvas.clipRect(box.left + 10f, box.top + 8f, box.right - 10f, box.bottom - 8f);
|
||||
drawText(canvas, label, box.left + 55f, box.centerY() + 11f, 32f, Color.WHITE, true);
|
||||
drawHudButton(canvas, new RectF(1_090f, box.top + 24f, 1_445f, box.bottom - 24f),
|
||||
value, false);
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
private void drawComplete(Canvas canvas) {
|
||||
@@ -1176,12 +1257,19 @@ public final class GameView extends View implements Runnable {
|
||||
}
|
||||
|
||||
private void drawHudButton(Canvas canvas, RectF box, String label, boolean primary) {
|
||||
paint.setColor(primary ? Color.rgb(226, 169, 63) : Color.argb(210, 45, 52, 71));
|
||||
paint.setColor(accessibilitySettings.highContrastEnabled()
|
||||
? (primary ? Color.rgb(255, 214, 77) : Color.BLACK)
|
||||
: (primary ? Color.rgb(226, 169, 63) : Color.argb(210, 45, 52, 71)));
|
||||
canvas.drawRoundRect(box, 22, 22, paint);
|
||||
stroke.setColor(primary ? Color.rgb(255, 224, 146) : Color.rgb(139, 145, 160));
|
||||
stroke.setColor(accessibilitySettings.highContrastEnabled()
|
||||
? Color.WHITE
|
||||
: (primary ? Color.rgb(255, 224, 146) : Color.rgb(139, 145, 160)));
|
||||
stroke.setStrokeWidth(4f);
|
||||
canvas.drawRoundRect(box, 22, 22, stroke);
|
||||
canvas.save();
|
||||
canvas.clipRect(box.left + 8f, box.top + 5f, box.right - 8f, box.bottom - 5f);
|
||||
drawCenteredText(canvas, label, box.centerX(), box.centerY() + 10, Math.min(31, box.height() * 0.34f), primary ? Color.rgb(35, 36, 43) : Color.WHITE);
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
private void drawWorldLabel(Canvas canvas, String label, float x, float y) {
|
||||
@@ -1191,8 +1279,8 @@ public final class GameView extends View implements Runnable {
|
||||
private void drawText(Canvas canvas, String text, float x, float baseline, float size, int color, boolean bold) {
|
||||
paint.setShader(null);
|
||||
paint.setStyle(Paint.Style.FILL);
|
||||
paint.setTextSize(size);
|
||||
paint.setColor(color);
|
||||
paint.setTextSize(accessibilitySettings.canvasTextSize(size));
|
||||
paint.setColor(accessibleTextColor(color));
|
||||
paint.setTextAlign(Paint.Align.LEFT);
|
||||
paint.setTypeface(android.graphics.Typeface.create("sans", bold ? android.graphics.Typeface.BOLD : android.graphics.Typeface.NORMAL));
|
||||
canvas.drawText(text, x, baseline, paint);
|
||||
@@ -1225,8 +1313,8 @@ public final class GameView extends View implements Runnable {
|
||||
private void drawCenteredText(Canvas canvas, String text, float x, float baseline, float size, int color) {
|
||||
paint.setShader(null);
|
||||
paint.setStyle(Paint.Style.FILL);
|
||||
paint.setTextSize(size);
|
||||
paint.setColor(color);
|
||||
paint.setTextSize(accessibilitySettings.canvasTextSize(size));
|
||||
paint.setColor(accessibleTextColor(color));
|
||||
paint.setTextAlign(Paint.Align.CENTER);
|
||||
paint.setTypeface(android.graphics.Typeface.create("sans", android.graphics.Typeface.BOLD));
|
||||
canvas.drawText(text, x, baseline, paint);
|
||||
@@ -1236,6 +1324,16 @@ public final class GameView extends View implements Runnable {
|
||||
return Color.argb((int) (255 * alpha), Color.red(color), Color.green(color), Color.blue(color));
|
||||
}
|
||||
|
||||
private int accessibleTextColor(int color) {
|
||||
if (!accessibilitySettings.highContrastEnabled()) return color;
|
||||
int red = Color.red(color);
|
||||
int green = Color.green(color);
|
||||
int blue = Color.blue(color);
|
||||
if (red < 90 && green < 90 && blue < 90) return Color.BLACK;
|
||||
if (red > 180 && green > 140 && blue < 170) return Color.rgb(255, 214, 77);
|
||||
return Color.WHITE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
PointF point = toCanvasPoint(event.getX(event.getActionIndex()), event.getY(event.getActionIndex()));
|
||||
@@ -1287,6 +1385,7 @@ public final class GameView extends View implements Runnable {
|
||||
userPaused = true;
|
||||
pointers.clear();
|
||||
deriveControls();
|
||||
refreshAccessibilityDescription(true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1334,6 +1433,7 @@ public final class GameView extends View implements Runnable {
|
||||
if (contains(1560, 70, 1850, 160, x, y)) {
|
||||
screen = Screen.SETTINGS;
|
||||
emitHaptic(HapticFeedbackConstants.KEYBOARD_TAP);
|
||||
refreshAccessibilityDescription(true);
|
||||
return;
|
||||
}
|
||||
float startX = 110f;
|
||||
@@ -1384,6 +1484,7 @@ public final class GameView extends View implements Runnable {
|
||||
screen = Screen.LEVELS;
|
||||
pointers.clear();
|
||||
deriveControls();
|
||||
refreshAccessibilityDescription(true);
|
||||
}
|
||||
|
||||
private void replayFromResult() {
|
||||
@@ -1402,10 +1503,17 @@ public final class GameView extends View implements Runnable {
|
||||
userPaused = false;
|
||||
screen = Screen.LEVELS;
|
||||
}
|
||||
refreshAccessibilityDescription(true);
|
||||
}
|
||||
|
||||
private void handleSettingsTap(float x, float y) {
|
||||
if (contains(1580, 70, 1850, 160, x, y)) {
|
||||
if (contains(55, 280, 385, 410, x, y)) {
|
||||
updateAccessibilitySettings(accessibilitySettings.nextTextSize());
|
||||
emitHaptic(HapticFeedbackConstants.KEYBOARD_TAP);
|
||||
} else if (contains(55, 450, 385, 580, x, y)) {
|
||||
updateAccessibilitySettings(accessibilitySettings.toggleHighContrast());
|
||||
emitHaptic(HapticFeedbackConstants.KEYBOARD_TAP);
|
||||
} else if (contains(1580, 70, 1850, 160, x, y)) {
|
||||
updateLanguage(language.next());
|
||||
emitHaptic(HapticFeedbackConstants.KEYBOARD_TAP);
|
||||
} else if (contains(420, 280, 1500, 410, x, y)) {
|
||||
@@ -1420,6 +1528,7 @@ public final class GameView extends View implements Runnable {
|
||||
} else if (contains(710, 850, 1210, 960, x, y)) {
|
||||
screen = Screen.LEVELS;
|
||||
emitHaptic(HapticFeedbackConstants.KEYBOARD_TAP);
|
||||
refreshAccessibilityDescription(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1431,6 +1540,12 @@ public final class GameView extends View implements Runnable {
|
||||
return new PointF((x - canvasOffsetX) / Math.max(0.001f, canvasScale), (y - canvasOffsetY) / Math.max(0.001f, canvasScale));
|
||||
}
|
||||
|
||||
private void updateViewport(float width, float height) {
|
||||
canvasScale = Math.min(width / VIEW_WIDTH, height / VIEW_HEIGHT);
|
||||
canvasOffsetX = (width - VIEW_WIDTH * canvasScale) * 0.5f;
|
||||
canvasOffsetY = (height - VIEW_HEIGHT * canvasScale) * 0.5f;
|
||||
}
|
||||
|
||||
private static boolean contains(float left, float top, float right, float bottom, float x, float y) {
|
||||
return x >= left && x <= right && y >= top && y <= bottom;
|
||||
}
|
||||
@@ -1581,6 +1696,11 @@ public final class GameView extends View implements Runnable {
|
||||
visible.add(strings.controlOpacityValue(controlSettings.controlOpacityIndex()));
|
||||
visible.add(strings.vibrationSetting());
|
||||
visible.add(strings.hapticsValue(controlSettings.hapticsEnabled()));
|
||||
visible.add(strings.accessibilityTitle());
|
||||
visible.add(strings.textSizeSetting());
|
||||
visible.add(strings.textSizeValue(accessibilitySettings.textScale()));
|
||||
visible.add(strings.highContrastSetting());
|
||||
visible.add(strings.highContrastValue(accessibilitySettings.highContrastEnabled()));
|
||||
visible.add(strings.back());
|
||||
} else if (level != null) {
|
||||
visible.add(String.format(Locale.US, "%02d %s", level.number,
|
||||
@@ -1623,18 +1743,145 @@ public final class GameView extends View implements Runnable {
|
||||
language = updated == null ? GameLanguage.EN : updated;
|
||||
strings = GameStrings.forLanguage(language);
|
||||
languageStore.save(language);
|
||||
setContentDescription(strings.appContentDescription());
|
||||
refreshAccessibilityDescription(false);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public AccessibilitySettings accessibilitySettingsForAutomation() {
|
||||
return accessibilitySettings;
|
||||
}
|
||||
|
||||
public void resetAccessibilitySettingsForAutomation() {
|
||||
updateAccessibilitySettings(AccessibilitySettings.defaults());
|
||||
}
|
||||
|
||||
public void selectNextTextSizeForAutomation() {
|
||||
updateAccessibilitySettings(accessibilitySettings.nextTextSize());
|
||||
}
|
||||
|
||||
public void toggleHighContrastForAutomation() {
|
||||
updateAccessibilitySettings(accessibilitySettings.toggleHighContrast());
|
||||
}
|
||||
|
||||
public float textScaleForAutomation() {
|
||||
return accessibilitySettings.textScale();
|
||||
}
|
||||
|
||||
public boolean highContrastForAutomation() {
|
||||
return accessibilitySettings.highContrastEnabled();
|
||||
}
|
||||
|
||||
public String screenDescriptionForAutomation() {
|
||||
return currentAccessibilityDescription();
|
||||
}
|
||||
|
||||
public String criticalVisibleStatusForAutomation() {
|
||||
return criticalStatusText();
|
||||
}
|
||||
|
||||
public void openSettingsForAutomation() {
|
||||
screen = Screen.SETTINGS;
|
||||
refreshAccessibilityDescription(false);
|
||||
}
|
||||
|
||||
public void pauseForAutomation() {
|
||||
if (screen == Screen.PLAYING) {
|
||||
userPaused = true;
|
||||
pointers.clear();
|
||||
deriveControls();
|
||||
refreshAccessibilityDescription(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void showCompleteForAutomation() {
|
||||
if (level != null) {
|
||||
screen = Screen.COMPLETE;
|
||||
pointers.clear();
|
||||
deriveControls();
|
||||
refreshAccessibilityDescription(false);
|
||||
}
|
||||
}
|
||||
|
||||
public float[] safeViewportForAutomation() {
|
||||
float width = getWidth();
|
||||
float height = getHeight();
|
||||
if (width <= 0f || height <= 0f) return new float[] {0f, 0f, 0f, 0f};
|
||||
float scale = Math.min(width / VIEW_WIDTH, height / VIEW_HEIGHT);
|
||||
float left = (width - VIEW_WIDTH * scale) * 0.5f;
|
||||
float top = (height - VIEW_HEIGHT * scale) * 0.5f;
|
||||
return new float[] {left, top, left + VIEW_WIDTH * scale, top + VIEW_HEIGHT * scale};
|
||||
}
|
||||
|
||||
private void updateControlSettings(ControlSettings updated) {
|
||||
controlSettings = updated;
|
||||
controlSettingsStore.save(updated);
|
||||
pointers.clear();
|
||||
deriveControls();
|
||||
refreshAccessibilityDescription(false);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
private void updateAccessibilitySettings(AccessibilitySettings updated) {
|
||||
accessibilitySettings = updated;
|
||||
accessibilitySettingsStore.save(updated);
|
||||
refreshAccessibilityDescription(false);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
private String criticalStatusText() {
|
||||
if (level == null) return AccessibilityStatus.describe(strings, 0, 0, 0, 0, true);
|
||||
Set<String> signalIds = new HashSet<>();
|
||||
int activeSignals = 0;
|
||||
for (LevelDefinition.Switch item : level.switches) signalIds.add(item.signalId);
|
||||
for (String signalId : signalIds) if (signalActive(signalId)) activeSignals++;
|
||||
int openDoors = 0;
|
||||
for (LevelDefinition.Door door : level.doors) if (doorOpen(door)) openDoors++;
|
||||
return AccessibilityStatus.describe(strings, activeSignals, signalIds.size(), openDoors,
|
||||
level.doors.size(), player.alive);
|
||||
}
|
||||
|
||||
private String currentAccessibilityDescription() {
|
||||
if (screen == Screen.LEVELS) {
|
||||
return AccessibilityScreenDescription.levelSelect(strings, progress.unlockedCount(),
|
||||
LevelCatalog.all().size());
|
||||
}
|
||||
if (screen == Screen.SETTINGS) {
|
||||
return AccessibilityScreenDescription.settings(strings, controlSettings,
|
||||
accessibilitySettings);
|
||||
}
|
||||
if (screen == Screen.COMPLETE) {
|
||||
return AccessibilityScreenDescription.complete(strings, level, deaths,
|
||||
GameMath.formatTime((long) (levelElapsed * 1000f)),
|
||||
levelIndex + 1 < LevelCatalog.all().size());
|
||||
}
|
||||
return AccessibilityScreenDescription.playing(strings, level, deaths,
|
||||
GameMath.formatTime((long) (levelElapsed * 1000f)), criticalStatusText(),
|
||||
userPaused);
|
||||
}
|
||||
|
||||
private String accessibilityStateKey() {
|
||||
return screen + "|" + userPaused + "|" + deaths + "|" + criticalStatusText()
|
||||
+ "|" + controlSettings.controlSizeLabel()
|
||||
+ "|" + controlSettings.controlOpacityLabel()
|
||||
+ "|" + controlSettings.hapticsEnabled()
|
||||
+ "|" + accessibilitySettings.textSizeLabel()
|
||||
+ "|" + accessibilitySettings.highContrastEnabled()
|
||||
+ "|" + language;
|
||||
}
|
||||
|
||||
private void refreshAccessibilityDescriptionIfStateChanged() {
|
||||
String stateKey = accessibilityStateKey();
|
||||
if (!stateKey.equals(lastAccessibilityStateKey)) refreshAccessibilityDescription(false);
|
||||
}
|
||||
|
||||
private void refreshAccessibilityDescription(boolean announce) {
|
||||
lastAccessibilityStateKey = accessibilityStateKey();
|
||||
setContentDescription(currentAccessibilityDescription());
|
||||
if (announce && isAttachedToWindow()) {
|
||||
sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
|
||||
}
|
||||
}
|
||||
|
||||
public void setAutomationInput(boolean moveLeft, boolean moveRight, boolean jump) {
|
||||
boolean jumpWasPressed = jumpPressed;
|
||||
leftPressed = moveLeft;
|
||||
|
||||
Reference in New Issue
Block a user