App 2.30.0: a chibi fox as the third Mascot
FoxFigure draws a sitting fox with a scarf in the accent colour whose ears and tail act the mood with the face: wagging, stiff, drooping, laid back. The chibi's shared parts (frame, eyes, mouths, limbs by mood, effects) moved into ChibiBase, which both figures extend. Settings → Mascot: Classic / Chibi / Fox. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
This commit is contained in:
@@ -69,7 +69,7 @@ Repository: `ssh://git@git.stinnesbeck.com:2222/nils/mg4.git` (remote `origin`),
|
|||||||
|
|
||||||
| Tab | Contents |
|
| Tab | Contents |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Overview | value tiles (battery, range, range/usage/energy since start, 5-min averages of range, usage, power, speed), vehicle info (odometer, lease allowance and the odometer against it), tyre pressures; read every 0.2 s on a background thread. Styles (Settings → Overview style): Tiles, Gauges (`GaugeView` needles), Cockpit (`RingView` rings, `TraceView` power trace), Retro (`RetroView`, seven-segment VFD), Futuristic (`FlowView`, animated energy flow), Sci-fi (`SciFiView`, starship console), Cartoon (`CartoonView`), Blueprint (`BlueprintView`, top-view drawing with tyre pressures), MG (`OctagonView`, octagonal dials after the classic MG dashboards), and on `ClusterCanvasView`: Minimal, Terminal, Aviation, Nixie tubes, Chronograph, Mascot (`MascotView`, an anime co-pilot whose face follows `MascotMood`; classic or `ChibiFigure`, `MascotCharacter`); the drawn styles share `ClusterValues`. A button in the top-right corner hides the app's navigation for a full-width Overview (`PREF_OVERVIEW_FULLSCREEN`) |
|
| Overview | value tiles (battery, range, range/usage/energy since start, 5-min averages of range, usage, power, speed), vehicle info (odometer, lease allowance and the odometer against it), tyre pressures; read every 0.2 s on a background thread. Styles (Settings → Overview style): Tiles, Gauges (`GaugeView` needles), Cockpit (`RingView` rings, `TraceView` power trace), Retro (`RetroView`, seven-segment VFD), Futuristic (`FlowView`, animated energy flow), Sci-fi (`SciFiView`, starship console), Cartoon (`CartoonView`), Blueprint (`BlueprintView`, top-view drawing with tyre pressures), MG (`OctagonView`, octagonal dials after the classic MG dashboards), and on `ClusterCanvasView`: Minimal, Terminal, Aviation, Nixie tubes, Chronograph, Mascot (`MascotView`, an anime co-pilot whose face follows `MascotMood`; classic, `ChibiFigure` or `FoxFigure` on `ChibiBase`, chosen by `MascotCharacter`); the drawn styles share `ClusterValues`. A button in the top-right corner hides the app's navigation for a full-width Overview (`PREF_OVERVIEW_FULLSCREEN`) |
|
||||||
| Consumption / Charging | period totals, daily log, charging sessions and curve |
|
| Consumption / Charging | period totals, daily log, charging sessions and curve |
|
||||||
| Live data | on/off, ABRP API key and user token, upload frequency |
|
| Live data | on/off, ABRP API key and user token, upload frequency |
|
||||||
| Server | URL, pairing by QR, ingest token, installation ID (read-only), config import |
|
| Server | URL, pairing by QR, ingest token, installation ID (read-only), config import |
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ with the server using a token of its own, obtained by pairing with a phone (belo
|
|||||||
|
|
||||||
| Tab | What it shows |
|
| Tab | What it shows |
|
||||||
|---|---|
|
|---|---|
|
||||||
| **Overview** | Battery, range, power and speed; range, usage and energy since start; 5-minute averages of range, usage, power and speed; vehicle info (odometer, climate setpoint) and tyre pressures. Read five times a second. Looks to choose from in Settings → Overview style: Tiles, Gauges, Cockpit, Retro, Futuristic, Sci-fi, Cartoon, Blueprint, MG, Minimal, Terminal, Aviation, Nixie tubes, Chronograph and Mascot (an anime co-pilot who reacts to the drive, classic or chibi). The small button in the top-right corner hides the app's navigation for a full-width Overview; tap it again to bring it back |
|
| **Overview** | Battery, range, power and speed; range, usage and energy since start; 5-minute averages of range, usage, power and speed; vehicle info (odometer, climate setpoint) and tyre pressures. Read five times a second. Looks to choose from in Settings → Overview style: Tiles, Gauges, Cockpit, Retro, Futuristic, Sci-fi, Cartoon, Blueprint, MG, Minimal, Terminal, Aviation, Nixie tubes, Chronograph and Mascot (an anime co-pilot who reacts to the drive, classic, chibi or a chibi fox). The small button in the top-right corner hides the app's navigation for a full-width Overview; tap it again to bring it back |
|
||||||
| **Consumption** | Lifetime, last 7 and 30 days, Trip A and B (distance, energy, average consumption and speed, SOC change), and a daily log of the last four months |
|
| **Consumption** | Lifetime, last 7 and 30 days, Trip A and B (distance, energy, average consumption and speed, SOC change), and a daily log of the last four months |
|
||||||
| **Charging** | The current session (SOC, voltage, current, power, energy into the battery and from the grid, time, cost) and its SOC/power curve |
|
| **Charging** | The current session (SOC, voltage, current, power, energy into the battery and from the grid, time, cost) and its SOC/power curve |
|
||||||
| **Live data** | Live data on/off, ABRP credentials for sending directly from the car, upload frequency |
|
| **Live data** | Live data on/off, ABRP credentials for sending directly from the car, upload frequency |
|
||||||
|
|||||||
@@ -131,6 +131,16 @@ tyre pressures, GPS (lat, lon, elevation, heading) and the is_* flags. It does *
|
|||||||
confirmed car facts, app structure, UI conventions, build/test/ship, documents. It replaces
|
confirmed car facts, app structure, UI conventions, build/test/ship, documents. It replaces
|
||||||
upstream's EVABRPUploader text. The test emulator is now in the flake: `nix run .#car-emulator`
|
upstream's EVABRPUploader text. The test emulator is now in the flake: `nix run .#car-emulator`
|
||||||
(Android 9 AOSP at 1920×720, 160 dpi; the car build installs as the system user — checked).
|
(Android 9 AOSP at 1920×720, 160 dpi; the car build installs as the system user — checked).
|
||||||
|
- 2026-10-24 — App 2.30.0 (branch `styles-retro-future`): a third Mascot, the **chibi fox**
|
||||||
|
(`FoxFigure`): a sitting fox with a big head, pointed ears, cream cheeks, chest and tail tip,
|
||||||
|
dark paws and a scarf in the accent colour. Ears and tail act the mood with the face: the
|
||||||
|
tail wags while content and fast while cheering, stands stiff when determined and hangs low
|
||||||
|
when worried or asleep; ears droop when worried or asleep and lie back when fast, when the
|
||||||
|
scarf also flies. Its own lines (`fox_*`, "Kon!"). The chibi's shared parts (frame, eyes,
|
||||||
|
mouths, limbs by mood, effects) moved into `ChibiBase`, which both figures extend. Settings →
|
||||||
|
Mascot: Classic / Chibi / Fox. Emulator: all moods checked with sample values in a throwaway
|
||||||
|
build; the tail's swing is kept to about 18° so it stays clear of the values. Not tried on the
|
||||||
|
car.
|
||||||
- 2026-10-24 — App 2.29.0 (branch `styles-retro-future`): a second Mascot, the **chibi**
|
- 2026-10-24 — App 2.29.0 (branch `styles-retro-future`): a second Mascot, the **chibi**
|
||||||
(`ChibiFigure`): small and full-figure, a big round head with a coral bob and a star clip,
|
(`ChibiFigure`): small and full-figure, a big round head with a coral bob and a star clip,
|
||||||
a hoodie in the accent colour, dark outlines like a sticker; an adult character in chibi
|
a hoodie in the accent colour, dark outlines like a sticker; an adult character in chibi
|
||||||
|
|||||||
+2
-2
@@ -34,8 +34,8 @@ android {
|
|||||||
applicationId "com.stinnesbeck.nilsmg4"
|
applicationId "com.stinnesbeck.nilsmg4"
|
||||||
minSdk 27
|
minSdk 27
|
||||||
targetSdk 36
|
targetSdk 36
|
||||||
versionCode 126
|
versionCode 127
|
||||||
versionName "2.29.0"
|
versionName "2.30.0"
|
||||||
|
|
||||||
// Nothing about the server ships in the APK: its URL and ingest token are entered in
|
// Nothing about the server ships in the APK: its URL and ingest token are entered in
|
||||||
// the app's Server tab (ServerConfig), and the database is only reachable by the server.
|
// the app's Server tab (ServerConfig), and the database is only reachable by the server.
|
||||||
|
|||||||
@@ -0,0 +1,316 @@
|
|||||||
|
package com.evsuite.abrp;
|
||||||
|
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Path;
|
||||||
|
import android.graphics.RectF;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* What the chibi Mascot figures ({@link ChibiFigure}, {@link FoxFigure}) share: the frame
|
||||||
|
* (scaled down and moved left so the big head stays clear of the speed, a shadow, a hop while
|
||||||
|
* cheering, a lean), their big eyes and mouths per {@link MascotMood}, limbs placed by the mood,
|
||||||
|
* and the effects around them (Zs, sparkles, wind, "?", drops). Sticker style: dark outlines.
|
||||||
|
*
|
||||||
|
* Draws on {@link MascotView}'s 100 × 100 grid; holds no state.
|
||||||
|
*/
|
||||||
|
abstract class ChibiBase {
|
||||||
|
|
||||||
|
protected final Paint fill = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||||
|
protected final Paint line = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||||
|
protected final Paint text = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||||
|
protected final Path p = new Path();
|
||||||
|
protected final Path q = new Path();
|
||||||
|
protected final RectF r = new RectF();
|
||||||
|
protected final int accent, accentLight, regen;
|
||||||
|
protected final int ink = 0xFF3A2A33, blush = 0x66FF7A8A, mouth = 0xFF8E3A48, tongue = 0xFFE88A95;
|
||||||
|
/** The eyes' colours, and what a narrowed lid is filled with (skin or fur). */
|
||||||
|
private final int iris, irisLight, lid;
|
||||||
|
/** How far left the figure moves, in grid units. */
|
||||||
|
private final float shiftX;
|
||||||
|
|
||||||
|
ChibiBase(int accent, int accentLight, int regen, int iris, int irisLight, int lid, float shiftX) {
|
||||||
|
this.accent = accent;
|
||||||
|
this.accentLight = accentLight;
|
||||||
|
this.regen = regen;
|
||||||
|
this.iris = iris;
|
||||||
|
this.irisLight = irisLight;
|
||||||
|
this.lid = lid;
|
||||||
|
this.shiftX = shiftX;
|
||||||
|
line.setStyle(Paint.Style.STROKE);
|
||||||
|
line.setStrokeCap(Paint.Cap.ROUND);
|
||||||
|
line.setStrokeJoin(Paint.Join.ROUND);
|
||||||
|
line.setColor(ink);
|
||||||
|
text.setTextAlign(Paint.Align.CENTER);
|
||||||
|
text.setFakeBoldText(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Where the speech bubble's tail points, on the grid. */
|
||||||
|
abstract float tipX();
|
||||||
|
|
||||||
|
abstract float tipY();
|
||||||
|
|
||||||
|
/** The figure itself, standing or sitting on y = 96, centred on x = 50. */
|
||||||
|
protected abstract void drawFigure(Canvas c, MascotMood mood, long t, boolean blink);
|
||||||
|
|
||||||
|
final void draw(Canvas c, MascotMood mood, long t, boolean blink) {
|
||||||
|
c.save();
|
||||||
|
c.translate(-shiftX, 0f);
|
||||||
|
c.scale(0.92f, 0.92f, 50f, 100f);
|
||||||
|
// A hop while cheering.
|
||||||
|
float hop = mood == MascotMood.CHEERING ? -3.5f * (float) Math.abs(Math.sin(t / 180.0)) : 0f;
|
||||||
|
fill.setColor(0x40000000);
|
||||||
|
float shadow = 18f + hop * 0.8f;
|
||||||
|
c.drawOval(oval(50f - shadow, 96.5f, 50f + shadow, 100f), fill);
|
||||||
|
|
||||||
|
c.save();
|
||||||
|
c.translate(0f, hop);
|
||||||
|
float lean = mood == MascotMood.FAST ? 5f : mood == MascotMood.SLEEPING ? -6f : 0f;
|
||||||
|
c.rotate(lean, 50f, 96f);
|
||||||
|
drawFigure(c, mood, t, blink);
|
||||||
|
c.restore();
|
||||||
|
|
||||||
|
switch (mood) {
|
||||||
|
case SLEEPING: zs(c, t); break;
|
||||||
|
case CHEERING: sparkles(c, t); break;
|
||||||
|
case FAST: wind(c, t); break;
|
||||||
|
case WAITING: question(c, t); break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
c.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Head tilt per mood, degrees. */
|
||||||
|
static float tilt(MascotMood mood) {
|
||||||
|
return mood == MascotMood.REVERSE ? -10f : mood == MascotMood.SLEEPING ? 10f
|
||||||
|
: mood == MascotMood.WAITING ? 6f : 0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Hands on the cheeks and a fist at the chest are drawn in front of the head. */
|
||||||
|
static boolean handsInFront(MascotMood mood) {
|
||||||
|
return mood == MascotMood.WORRIED || mood == MascotMood.DETERMINED;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Left and right hand positions for the mood: {lx, ly, rx, ry}. */
|
||||||
|
static float[] hands(MascotMood mood, long t) {
|
||||||
|
float wave = (float) Math.sin(t / 220.0) * 3f;
|
||||||
|
switch (mood) {
|
||||||
|
case CONTENT: return new float[] { 33f, 82f, 82f + wave * 0.5f, 64f + wave };
|
||||||
|
case CHEERING: return new float[] { 16f, 56f, 84f, 56f };
|
||||||
|
case DETERMINED: return new float[] { 33f, 82f, 57f, 72f };
|
||||||
|
case WORRIED: return new float[] { 34f, 55f, 66f, 55f };
|
||||||
|
// Pointing back.
|
||||||
|
case REVERSE: return new float[] { 20f, 66f, 67f, 82f };
|
||||||
|
case FAST: return new float[] { 30f, 76f, 70f, 76f };
|
||||||
|
default: return new float[] { 33f, 82f, 67f, 82f };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A limb from the shoulder to a round hand or paw, outlined. */
|
||||||
|
protected void limb(Canvas c, float sx, float sy, float hx, float hy, int sleeve, int hand) {
|
||||||
|
line.setStrokeWidth(7.4f);
|
||||||
|
c.drawLine(sx, sy, hx, hy, line);
|
||||||
|
line.setColor(sleeve);
|
||||||
|
line.setStrokeWidth(5f);
|
||||||
|
c.drawLine(sx, sy, hx, hy, line);
|
||||||
|
line.setColor(ink);
|
||||||
|
fill.setColor(hand);
|
||||||
|
line.setStrokeWidth(1.1f);
|
||||||
|
c.drawCircle(hx, hy, 3.2f, fill);
|
||||||
|
c.drawCircle(hx, hy, 3.2f, line);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Cheeks, eyes at y 44, brows, mouth around y 55 and the worried drops. */
|
||||||
|
protected void face(Canvas c, MascotMood mood, long t, boolean blink) {
|
||||||
|
if (mood == MascotMood.CHEERING || mood == MascotMood.FAST || mood == MascotMood.CONTENT
|
||||||
|
|| mood == MascotMood.SLEEPING || mood == MascotMood.WORRIED) {
|
||||||
|
fill.setColor(blush);
|
||||||
|
c.drawOval(oval(29.5f, 50f, 37f, 54f), fill);
|
||||||
|
c.drawOval(oval(63f, 50f, 70.5f, 54f), fill);
|
||||||
|
}
|
||||||
|
eye(c, 39f, 44f, mood, blink);
|
||||||
|
eye(c, 61f, 44f, mood, blink);
|
||||||
|
brows(c, mood);
|
||||||
|
mouth(c, mood, t);
|
||||||
|
if (mood == MascotMood.WORRIED) {
|
||||||
|
fill.setColor(0xFF8FD3FF);
|
||||||
|
drop(c, 72f, 30f, 3f);
|
||||||
|
drop(c, 34.5f, 52f, 1.8f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void eye(Canvas c, float cx, float cy, MascotMood mood, boolean blink) {
|
||||||
|
line.setColor(ink);
|
||||||
|
line.setStrokeWidth(1.5f);
|
||||||
|
if (mood == MascotMood.SLEEPING || (blink && mood != MascotMood.CHEERING && mood != MascotMood.FAST)) {
|
||||||
|
c.drawArc(oval(cx - 4.5f, cy - 3f, cx + 4.5f, cy + 2f), 20f, 140f, false, line);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mood == MascotMood.FAST) {
|
||||||
|
// ^ ^
|
||||||
|
c.drawArc(oval(cx - 4.5f, cy - 2f, cx + 4.5f, cy + 5f), 200f, 140f, false, line);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
float look = mood == MascotMood.REVERSE ? -2f : 0f;
|
||||||
|
oval(cx - 4.6f, cy - 5.6f, cx + 4.6f, cy + 5.6f);
|
||||||
|
if (mood == MascotMood.REVERSE) {
|
||||||
|
fill.setColor(Color.WHITE);
|
||||||
|
c.drawOval(r, fill);
|
||||||
|
fill.setColor(iris);
|
||||||
|
c.drawOval(oval(cx - 3f + look, cy - 4f, cx + 3f + look, cy + 4.5f), fill);
|
||||||
|
} else {
|
||||||
|
fill.setColor(iris);
|
||||||
|
c.drawOval(r, fill);
|
||||||
|
fill.setColor(irisLight);
|
||||||
|
c.drawOval(oval(cx - 3.6f, cy + 0.5f, cx + 3.6f, cy + 5f), fill);
|
||||||
|
}
|
||||||
|
fill.setColor(Color.WHITE);
|
||||||
|
if (mood == MascotMood.CHEERING) {
|
||||||
|
star(c, cx, cy - 0.5f, 3.6f);
|
||||||
|
} else {
|
||||||
|
c.drawCircle(cx - 1.6f + look, cy - 2.4f, 2f, fill);
|
||||||
|
c.drawCircle(cx + 1.8f + look, cy + 2.4f, 0.9f, fill);
|
||||||
|
}
|
||||||
|
if (mood == MascotMood.DETERMINED) {
|
||||||
|
// A flat lid over the top: focused.
|
||||||
|
fill.setColor(lid);
|
||||||
|
c.drawRect(cx - 5.2f, cy - 6.4f, cx + 5.2f, cy - 2f, fill);
|
||||||
|
line.setStrokeWidth(1.6f);
|
||||||
|
c.drawLine(cx - 5f, cy - 2f, cx + 5f, cy - 2f, line);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
line.setStrokeWidth(1.6f);
|
||||||
|
c.drawArc(oval(cx - 5f, cy - 5.9f, cx + 5f, cy + 5.6f), 200f, 140f, false, line);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void brows(Canvas c, MascotMood mood) {
|
||||||
|
line.setColor(ink);
|
||||||
|
line.setStrokeWidth(1.4f);
|
||||||
|
if (mood == MascotMood.DETERMINED) {
|
||||||
|
c.drawLine(34f, 33.5f, 43.5f, 36.5f, line);
|
||||||
|
c.drawLine(56.5f, 36.5f, 66f, 33.5f, line);
|
||||||
|
} else if (mood == MascotMood.WORRIED) {
|
||||||
|
c.drawLine(34f, 36f, 43f, 33f, line);
|
||||||
|
c.drawLine(57f, 33f, 66f, 36f, line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void mouth(Canvas c, MascotMood mood, long t) {
|
||||||
|
line.setColor(ink);
|
||||||
|
line.setStrokeWidth(1.2f);
|
||||||
|
switch (mood) {
|
||||||
|
case CHEERING:
|
||||||
|
case FAST:
|
||||||
|
p.reset();
|
||||||
|
p.moveTo(45f, 53.5f);
|
||||||
|
p.quadTo(50f, 61.5f, 55f, 53.5f);
|
||||||
|
p.close();
|
||||||
|
fill.setColor(mouth);
|
||||||
|
c.drawPath(p, fill);
|
||||||
|
fill.setColor(tongue);
|
||||||
|
c.drawOval(oval(47.5f, 56.4f, 52.5f, 58.4f), fill);
|
||||||
|
c.drawPath(p, line);
|
||||||
|
break;
|
||||||
|
case DETERMINED:
|
||||||
|
fill.setColor(Color.WHITE);
|
||||||
|
oval(44.5f, 53f, 55.5f, 57f);
|
||||||
|
c.drawRoundRect(r, 1.5f, 1.5f, fill);
|
||||||
|
c.drawRoundRect(r, 1.5f, 1.5f, line);
|
||||||
|
c.drawLine(44.8f, 55f, 55.2f, 55f, line);
|
||||||
|
break;
|
||||||
|
case WORRIED:
|
||||||
|
p.reset();
|
||||||
|
p.moveTo(45.5f, 56f);
|
||||||
|
p.quadTo(47f, 54.3f, 48.5f, 56f);
|
||||||
|
p.quadTo(50f, 57.7f, 51.5f, 56f);
|
||||||
|
p.quadTo(53f, 54.3f, 54.5f, 56f);
|
||||||
|
c.drawPath(p, line);
|
||||||
|
break;
|
||||||
|
case SLEEPING: {
|
||||||
|
fill.setColor(mouth);
|
||||||
|
c.drawOval(oval(48.6f, 54.5f, 51.4f, 57f), fill);
|
||||||
|
// A nose bubble that swells and pops.
|
||||||
|
float grow = (t % 2400) / 2400f;
|
||||||
|
fill.setColor(0x7090D8FF);
|
||||||
|
c.drawCircle(53.5f, 51f, 1f + grow * 4f, fill);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case REVERSE:
|
||||||
|
fill.setColor(mouth);
|
||||||
|
c.drawOval(oval(48.6f, 54f, 51.4f, 57f), fill);
|
||||||
|
break;
|
||||||
|
case WAITING:
|
||||||
|
c.drawLine(47.5f, 55.5f, 52.5f, 55.5f, line);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// A cat smile: w.
|
||||||
|
p.reset();
|
||||||
|
p.moveTo(45.5f, 54f);
|
||||||
|
p.quadTo(47.75f, 57.5f, 50f, 54.5f);
|
||||||
|
p.quadTo(52.25f, 57.5f, 54.5f, 54f);
|
||||||
|
c.drawPath(p, line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void zs(Canvas c, long t) {
|
||||||
|
float phase = (t % 3000) / 3000f;
|
||||||
|
float[][] zs = { { 20f, 24f, 6f }, { 13f, 14f, 7.5f }, { 5f, 4f, 9f } };
|
||||||
|
for (float[] z : zs) {
|
||||||
|
text.setTextSize(z[2]);
|
||||||
|
text.setColor(AccentColor.withAlpha(accentLight, Math.round(255 * (1f - phase * 0.5f))));
|
||||||
|
c.drawText("Z", z[0] - phase * 2f, z[1] + z[2] - phase * 3f, text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sparkles(Canvas c, long t) {
|
||||||
|
float[][] at = { { 14f, 30f }, { 86f, 26f }, { 12f, 62f }, { 88f, 60f }, { 50f, 1.5f } };
|
||||||
|
for (int i = 0; i < at.length; i++) {
|
||||||
|
float s = 2.4f + 1.8f * (float) Math.abs(Math.sin(t / 260.0 + i * 1.3));
|
||||||
|
fill.setColor(i % 2 == 0 ? regen : Color.WHITE);
|
||||||
|
star(c, at[i][0], at[i][1], s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void wind(Canvas c, long t) {
|
||||||
|
line.setStrokeWidth(1f);
|
||||||
|
line.setColor(0x88FFFFFF);
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
float x = 18f - ((t / 6f + i * 37f) % 20f);
|
||||||
|
float y = 20f + i * 14f;
|
||||||
|
c.drawLine(x - 10f, y, x, y, line);
|
||||||
|
}
|
||||||
|
line.setColor(ink);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void question(Canvas c, long t) {
|
||||||
|
text.setTextSize(14f);
|
||||||
|
text.setColor(accentLight);
|
||||||
|
float bob = (float) Math.sin(t / 400.0) * 1.2f;
|
||||||
|
c.drawText("?", 16f, 18f + bob, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void drop(Canvas c, float x, float y, float s) {
|
||||||
|
p.reset();
|
||||||
|
p.moveTo(x, y - s * 1.6f);
|
||||||
|
p.quadTo(x + s * 1.2f, y + s * 0.2f, x, y + s);
|
||||||
|
p.quadTo(x - s * 1.2f, y + s * 0.2f, x, y - s * 1.6f);
|
||||||
|
c.drawPath(p, fill);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A four-pointed sparkle; leaves its outline in {@link #p}. */
|
||||||
|
protected void star(Canvas c, float x, float y, float s) {
|
||||||
|
p.reset();
|
||||||
|
p.moveTo(x, y - s);
|
||||||
|
p.quadTo(x, y, x + s, y);
|
||||||
|
p.quadTo(x, y, x, y + s);
|
||||||
|
p.quadTo(x, y, x - s, y);
|
||||||
|
p.quadTo(x, y, x, y - s);
|
||||||
|
p.close();
|
||||||
|
c.drawPath(p, fill);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected RectF oval(float left, float top, float right, float bottom) {
|
||||||
|
r.set(left, top, right, bottom);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,9 +2,7 @@ package com.evsuite.abrp;
|
|||||||
|
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.Paint;
|
|
||||||
import android.graphics.Path;
|
import android.graphics.Path;
|
||||||
import android.graphics.RectF;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The chibi co-pilot for the Mascot Overview style ({@link MascotCharacter#CHIBI}): the small,
|
* The chibi co-pilot for the Mascot Overview style ({@link MascotCharacter#CHIBI}): the small,
|
||||||
@@ -14,71 +12,35 @@ import android.graphics.RectF;
|
|||||||
* Her whole body acts the mood: waves while content, throws both arms up and hops while
|
* Her whole body acts the mood: waves while content, throws both arms up and hops while
|
||||||
* cheering (star eyes), pumps a fist when determined, holds her cheeks with a tear when worried,
|
* cheering (star eyes), pumps a fist when determined, holds her cheeks with a tear when worried,
|
||||||
* sleeps with a nose bubble in P, glances and points back in R, leans into the wind when
|
* sleeps with a nose bubble in P, glances and points back in R, leans into the wind when
|
||||||
* fast, and wonders "?" while waiting.
|
* fast, and wonders "?" while waiting. The shared parts are in {@link ChibiBase}.
|
||||||
*
|
|
||||||
* Draws on the same 100 × 100 grid as {@link MascotView}'s classic figure; it holds no state.
|
|
||||||
*/
|
*/
|
||||||
final class ChibiFigure {
|
final class ChibiFigure extends ChibiBase {
|
||||||
|
|
||||||
private final Paint fill = new Paint(Paint.ANTI_ALIAS_FLAG);
|
private final int hair = 0xFFF0855C, hairLight = 0xFFFFB08A, skin = 0xFFFFE6D6, trousers = 0xFF2D3142,
|
||||||
private final Paint line = new Paint(Paint.ANTI_ALIAS_FLAG);
|
shoe = 0xFFF6F6F8;
|
||||||
private final Paint text = new Paint(Paint.ANTI_ALIAS_FLAG);
|
|
||||||
private final Path p = new Path();
|
|
||||||
private final Path q = new Path();
|
|
||||||
private final RectF r = new RectF();
|
|
||||||
private final int accent, accentLight, regen;
|
|
||||||
private final int ink = 0xFF3A2A33, hair = 0xFFF0855C, hairLight = 0xFFFFB08A, skin = 0xFFFFE6D6,
|
|
||||||
iris = 0xFF6E45B8, irisLight = 0xFFA27BE6, blush = 0x66FF7A8A, trousers = 0xFF2D3142,
|
|
||||||
shoe = 0xFFF6F6F8, mouth = 0xFF8E3A48, tongue = 0xFFE88A95;
|
|
||||||
|
|
||||||
ChibiFigure(int accent, int accentLight, int regen) {
|
ChibiFigure(int accent, int accentLight, int regen) {
|
||||||
this.accent = accent;
|
super(accent, accentLight, regen, 0xFF6E45B8, 0xFFA27BE6, 0xFFFFE6D6, 6f);
|
||||||
this.accentLight = accentLight;
|
|
||||||
this.regen = regen;
|
|
||||||
line.setStyle(Paint.Style.STROKE);
|
|
||||||
line.setStrokeCap(Paint.Cap.ROUND);
|
|
||||||
line.setStrokeJoin(Paint.Join.ROUND);
|
|
||||||
line.setColor(ink);
|
|
||||||
text.setTextAlign(Paint.Align.CENTER);
|
|
||||||
text.setFakeBoldText(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Where the speech bubble's tail points, on the grid. */
|
@Override
|
||||||
static final float TIP_X = 70f, TIP_Y = 34f;
|
float tipX() {
|
||||||
|
return 70f;
|
||||||
|
}
|
||||||
|
|
||||||
void draw(Canvas c, MascotMood mood, long t, boolean blink) {
|
@Override
|
||||||
// Smaller and further left than the grid, so her big head stays clear of the speed.
|
float tipY() {
|
||||||
c.save();
|
return 34f;
|
||||||
c.translate(-6f, 0f);
|
}
|
||||||
c.scale(0.92f, 0.92f, 50f, 100f);
|
|
||||||
// A hop while cheering.
|
|
||||||
float hop = mood == MascotMood.CHEERING ? -3.5f * (float) Math.abs(Math.sin(t / 180.0)) : 0f;
|
|
||||||
fill.setColor(0x40000000);
|
|
||||||
float shadow = 18f + hop * 0.8f;
|
|
||||||
c.drawOval(oval(50f - shadow, 96.5f, 50f + shadow, 100f), fill);
|
|
||||||
|
|
||||||
c.save();
|
|
||||||
c.translate(0f, hop);
|
|
||||||
float lean = mood == MascotMood.FAST ? 5f : mood == MascotMood.SLEEPING ? -6f : 0f;
|
|
||||||
c.rotate(lean, 50f, 96f);
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void drawFigure(Canvas c, MascotMood mood, long t, boolean blink) {
|
||||||
drawLegs(c);
|
drawLegs(c);
|
||||||
drawBody(c);
|
drawBody(c);
|
||||||
// Hands on her cheeks and the fist at her chest are in front of the head.
|
boolean inFront = handsInFront(mood);
|
||||||
boolean inFront = mood == MascotMood.WORRIED || mood == MascotMood.DETERMINED;
|
|
||||||
if (!inFront) drawArms(c, mood, t);
|
if (!inFront) drawArms(c, mood, t);
|
||||||
drawHead(c, mood, t, blink);
|
drawHead(c, mood, t, blink);
|
||||||
if (inFront) drawArms(c, mood, t);
|
if (inFront) drawArms(c, mood, t);
|
||||||
c.restore();
|
|
||||||
|
|
||||||
switch (mood) {
|
|
||||||
case SLEEPING: zs(c, t); break;
|
|
||||||
case CHEERING: sparkles(c, t); break;
|
|
||||||
case FAST: wind(c, t); break;
|
|
||||||
case WAITING: question(c, t); break;
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
c.restore();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void drawLegs(Canvas c) {
|
private void drawLegs(Canvas c) {
|
||||||
@@ -124,42 +86,15 @@ final class ChibiFigure {
|
|||||||
line.setColor(ink);
|
line.setColor(ink);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Two arms from the shoulders to hands placed by the mood. */
|
|
||||||
private void drawArms(Canvas c, MascotMood mood, long t) {
|
private void drawArms(Canvas c, MascotMood mood, long t) {
|
||||||
float wave = (float) Math.sin(t / 220.0) * 3f;
|
float[] h = hands(mood, t);
|
||||||
float lx, ly, rx, ry;
|
limb(c, 39.5f, 69f, h[0], h[1], accent, skin);
|
||||||
switch (mood) {
|
limb(c, 60.5f, 69f, h[2], h[3], accent, skin);
|
||||||
case CONTENT: lx = 33f; ly = 82f; rx = 82f + wave * 0.5f; ry = 64f + wave; break;
|
|
||||||
case CHEERING: lx = 16f; ly = 56f; rx = 84f; ry = 56f; break;
|
|
||||||
case DETERMINED: lx = 33f; ly = 82f; rx = 57f; ry = 72f; break;
|
|
||||||
case WORRIED: lx = 34f; ly = 55f; rx = 66f; ry = 55f; break;
|
|
||||||
// Pointing back.
|
|
||||||
case REVERSE: lx = 20f; ly = 66f; rx = 67f; ry = 82f; break;
|
|
||||||
case FAST: lx = 30f; ly = 76f; rx = 70f; ry = 76f; break;
|
|
||||||
default: lx = 33f; ly = 82f; rx = 67f; ry = 82f;
|
|
||||||
}
|
|
||||||
arm(c, 39.5f, 69f, lx, ly);
|
|
||||||
arm(c, 60.5f, 69f, rx, ry);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void arm(Canvas c, float sx, float sy, float hx, float hy) {
|
|
||||||
line.setStrokeWidth(7.4f);
|
|
||||||
c.drawLine(sx, sy, hx, hy, line);
|
|
||||||
line.setColor(accent);
|
|
||||||
line.setStrokeWidth(5f);
|
|
||||||
c.drawLine(sx, sy, hx, hy, line);
|
|
||||||
line.setColor(ink);
|
|
||||||
fill.setColor(skin);
|
|
||||||
line.setStrokeWidth(1.1f);
|
|
||||||
c.drawCircle(hx, hy, 3.2f, fill);
|
|
||||||
c.drawCircle(hx, hy, 3.2f, line);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void drawHead(Canvas c, MascotMood mood, long t, boolean blink) {
|
private void drawHead(Canvas c, MascotMood mood, long t, boolean blink) {
|
||||||
float tilt = mood == MascotMood.REVERSE ? -10f : mood == MascotMood.SLEEPING ? 10f
|
|
||||||
: mood == MascotMood.WAITING ? 6f : 0f;
|
|
||||||
c.save();
|
c.save();
|
||||||
c.rotate(tilt, 50f, 62f);
|
c.rotate(tilt(mood), 50f, 62f);
|
||||||
// The bob behind the face.
|
// The bob behind the face.
|
||||||
fill.setColor(hair);
|
fill.setColor(hair);
|
||||||
line.setStrokeWidth(1.3f);
|
line.setStrokeWidth(1.3f);
|
||||||
@@ -201,197 +136,7 @@ final class ChibiFigure {
|
|||||||
line.setStrokeWidth(0.8f);
|
line.setStrokeWidth(0.8f);
|
||||||
c.drawPath(p, line);
|
c.drawPath(p, line);
|
||||||
|
|
||||||
if (mood == MascotMood.CHEERING || mood == MascotMood.FAST || mood == MascotMood.CONTENT
|
face(c, mood, t, blink);
|
||||||
|| mood == MascotMood.SLEEPING || mood == MascotMood.WORRIED) {
|
|
||||||
fill.setColor(blush);
|
|
||||||
c.drawOval(oval(29.5f, 50f, 37f, 54f), fill);
|
|
||||||
c.drawOval(oval(63f, 50f, 70.5f, 54f), fill);
|
|
||||||
}
|
|
||||||
eye(c, 39f, 44f, mood, blink, t);
|
|
||||||
eye(c, 61f, 44f, mood, blink, t);
|
|
||||||
brows(c, mood);
|
|
||||||
mouth(c, mood, t);
|
|
||||||
if (mood == MascotMood.WORRIED) {
|
|
||||||
fill.setColor(0xFF8FD3FF);
|
|
||||||
drop(c, 72f, 30f, 3f);
|
|
||||||
drop(c, 34.5f, 52f, 1.8f);
|
|
||||||
}
|
|
||||||
c.restore();
|
c.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void eye(Canvas c, float cx, float cy, MascotMood mood, boolean blink, long t) {
|
|
||||||
line.setColor(ink);
|
|
||||||
line.setStrokeWidth(1.5f);
|
|
||||||
if (mood == MascotMood.SLEEPING || (blink && mood != MascotMood.CHEERING && mood != MascotMood.FAST)) {
|
|
||||||
c.drawArc(oval(cx - 4.5f, cy - 3f, cx + 4.5f, cy + 2f), 20f, 140f, false, line);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (mood == MascotMood.FAST) {
|
|
||||||
// ^ ^
|
|
||||||
c.drawArc(oval(cx - 4.5f, cy - 2f, cx + 4.5f, cy + 5f), 200f, 140f, false, line);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
float look = mood == MascotMood.REVERSE ? -2f : 0f;
|
|
||||||
oval(cx - 4.6f, cy - 5.6f, cx + 4.6f, cy + 5.6f);
|
|
||||||
if (mood == MascotMood.REVERSE) {
|
|
||||||
fill.setColor(Color.WHITE);
|
|
||||||
c.drawOval(r, fill);
|
|
||||||
fill.setColor(iris);
|
|
||||||
c.drawOval(oval(cx - 3f + look, cy - 4f, cx + 3f + look, cy + 4.5f), fill);
|
|
||||||
} else {
|
|
||||||
fill.setColor(iris);
|
|
||||||
c.drawOval(r, fill);
|
|
||||||
fill.setColor(irisLight);
|
|
||||||
c.drawOval(oval(cx - 3.6f, cy + 0.5f, cx + 3.6f, cy + 5f), fill);
|
|
||||||
}
|
|
||||||
if (mood == MascotMood.CHEERING) {
|
|
||||||
fill.setColor(Color.WHITE);
|
|
||||||
star(c, cx, cy - 0.5f, 3.6f);
|
|
||||||
} else {
|
|
||||||
fill.setColor(Color.WHITE);
|
|
||||||
c.drawCircle(cx - 1.6f + look, cy - 2.4f, 2f, fill);
|
|
||||||
c.drawCircle(cx + 1.8f + look, cy + 2.4f, 0.9f, fill);
|
|
||||||
}
|
|
||||||
if (mood == MascotMood.DETERMINED) {
|
|
||||||
// A flat lid over the top: focused.
|
|
||||||
fill.setColor(skin);
|
|
||||||
c.drawRect(cx - 5.2f, cy - 6.4f, cx + 5.2f, cy - 2f, fill);
|
|
||||||
line.setStrokeWidth(1.6f);
|
|
||||||
c.drawLine(cx - 5f, cy - 2f, cx + 5f, cy - 2f, line);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
line.setStrokeWidth(1.6f);
|
|
||||||
c.drawArc(oval(cx - 5f, cy - 5.9f, cx + 5f, cy + 5.6f), 200f, 140f, false, line);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void brows(Canvas c, MascotMood mood) {
|
|
||||||
line.setColor(ink);
|
|
||||||
line.setStrokeWidth(1.4f);
|
|
||||||
if (mood == MascotMood.DETERMINED) {
|
|
||||||
c.drawLine(34f, 33.5f, 43.5f, 36.5f, line);
|
|
||||||
c.drawLine(56.5f, 36.5f, 66f, 33.5f, line);
|
|
||||||
} else if (mood == MascotMood.WORRIED) {
|
|
||||||
c.drawLine(34f, 36f, 43f, 33f, line);
|
|
||||||
c.drawLine(57f, 33f, 66f, 36f, line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void mouth(Canvas c, MascotMood mood, long t) {
|
|
||||||
line.setColor(ink);
|
|
||||||
line.setStrokeWidth(1.2f);
|
|
||||||
switch (mood) {
|
|
||||||
case CHEERING:
|
|
||||||
case FAST:
|
|
||||||
p.reset();
|
|
||||||
p.moveTo(45f, 53.5f);
|
|
||||||
p.quadTo(50f, 61.5f, 55f, 53.5f);
|
|
||||||
p.close();
|
|
||||||
fill.setColor(mouth);
|
|
||||||
c.drawPath(p, fill);
|
|
||||||
fill.setColor(tongue);
|
|
||||||
c.drawOval(oval(47.5f, 56.4f, 52.5f, 58.4f), fill);
|
|
||||||
c.drawPath(p, line);
|
|
||||||
break;
|
|
||||||
case DETERMINED:
|
|
||||||
fill.setColor(Color.WHITE);
|
|
||||||
oval(44.5f, 53f, 55.5f, 57f);
|
|
||||||
c.drawRoundRect(r, 1.5f, 1.5f, fill);
|
|
||||||
c.drawRoundRect(r, 1.5f, 1.5f, line);
|
|
||||||
c.drawLine(44.8f, 55f, 55.2f, 55f, line);
|
|
||||||
break;
|
|
||||||
case WORRIED:
|
|
||||||
p.reset();
|
|
||||||
p.moveTo(45.5f, 56f);
|
|
||||||
p.quadTo(47f, 54.3f, 48.5f, 56f);
|
|
||||||
p.quadTo(50f, 57.7f, 51.5f, 56f);
|
|
||||||
p.quadTo(53f, 54.3f, 54.5f, 56f);
|
|
||||||
c.drawPath(p, line);
|
|
||||||
break;
|
|
||||||
case SLEEPING: {
|
|
||||||
fill.setColor(mouth);
|
|
||||||
c.drawOval(oval(48.6f, 54.5f, 51.4f, 57f), fill);
|
|
||||||
// A nose bubble that swells and pops.
|
|
||||||
float grow = (t % 2400) / 2400f;
|
|
||||||
fill.setColor(0x7090D8FF);
|
|
||||||
c.drawCircle(53.5f, 51f, 1f + grow * 4f, fill);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case REVERSE:
|
|
||||||
fill.setColor(mouth);
|
|
||||||
c.drawOval(oval(48.6f, 54f, 51.4f, 57f), fill);
|
|
||||||
break;
|
|
||||||
case WAITING:
|
|
||||||
c.drawLine(47.5f, 55.5f, 52.5f, 55.5f, line);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// A cat smile: w.
|
|
||||||
p.reset();
|
|
||||||
p.moveTo(45.5f, 54f);
|
|
||||||
p.quadTo(47.75f, 57.5f, 50f, 54.5f);
|
|
||||||
p.quadTo(52.25f, 57.5f, 54.5f, 54f);
|
|
||||||
c.drawPath(p, line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void zs(Canvas c, long t) {
|
|
||||||
float phase = (t % 3000) / 3000f;
|
|
||||||
float[][] zs = { { 20f, 24f, 6f }, { 13f, 14f, 7.5f }, { 5f, 4f, 9f } };
|
|
||||||
for (float[] z : zs) {
|
|
||||||
text.setTextSize(z[2]);
|
|
||||||
text.setColor(AccentColor.withAlpha(accentLight, Math.round(255 * (1f - phase * 0.5f))));
|
|
||||||
c.drawText("Z", z[0] - phase * 2f, z[1] + z[2] - phase * 3f, text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sparkles(Canvas c, long t) {
|
|
||||||
float[][] at = { { 14f, 30f }, { 86f, 26f }, { 12f, 62f }, { 88f, 60f }, { 50f, 1.5f } };
|
|
||||||
for (int i = 0; i < at.length; i++) {
|
|
||||||
float s = 2.4f + 1.8f * (float) Math.abs(Math.sin(t / 260.0 + i * 1.3));
|
|
||||||
fill.setColor(i % 2 == 0 ? regen : Color.WHITE);
|
|
||||||
star(c, at[i][0], at[i][1], s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void wind(Canvas c, long t) {
|
|
||||||
line.setStrokeWidth(1f);
|
|
||||||
line.setColor(0x88FFFFFF);
|
|
||||||
for (int i = 0; i < 5; i++) {
|
|
||||||
float x = 18f - ((t / 6f + i * 37f) % 20f);
|
|
||||||
float y = 20f + i * 14f;
|
|
||||||
c.drawLine(x - 10f, y, x, y, line);
|
|
||||||
}
|
|
||||||
line.setColor(ink);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void question(Canvas c, long t) {
|
|
||||||
text.setTextSize(14f);
|
|
||||||
text.setColor(accentLight);
|
|
||||||
float bob = (float) Math.sin(t / 400.0) * 1.2f;
|
|
||||||
c.drawText("?", 16f, 18f + bob, text);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void drop(Canvas c, float x, float y, float s) {
|
|
||||||
p.reset();
|
|
||||||
p.moveTo(x, y - s * 1.6f);
|
|
||||||
p.quadTo(x + s * 1.2f, y + s * 0.2f, x, y + s);
|
|
||||||
p.quadTo(x - s * 1.2f, y + s * 0.2f, x, y - s * 1.6f);
|
|
||||||
c.drawPath(p, fill);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** A four-pointed sparkle; leaves its outline in {@link #p}. */
|
|
||||||
private void star(Canvas c, float x, float y, float s) {
|
|
||||||
p.reset();
|
|
||||||
p.moveTo(x, y - s);
|
|
||||||
p.quadTo(x, y, x + s, y);
|
|
||||||
p.quadTo(x, y, x, y + s);
|
|
||||||
p.quadTo(x, y, x - s, y);
|
|
||||||
p.quadTo(x, y, x, y - s);
|
|
||||||
p.close();
|
|
||||||
c.drawPath(p, fill);
|
|
||||||
}
|
|
||||||
|
|
||||||
private RectF oval(float left, float top, float right, float bottom) {
|
|
||||||
r.set(left, top, right, bottom);
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,206 @@
|
|||||||
|
package com.evsuite.abrp;
|
||||||
|
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Path;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The chibi fox for the Mascot Overview style ({@link MascotCharacter#FOX}): a sitting fox with
|
||||||
|
* a big head, pointed ears, cream cheeks and chest, dark paws, a scarf in the accent colour and
|
||||||
|
* a large brush tail with a cream tip.
|
||||||
|
*
|
||||||
|
* Ears and tail act the mood with the face ({@link ChibiBase}): the tail wags while content and
|
||||||
|
* fast while cheering (paws up, star eyes), stands stiff when determined (paw at the chest),
|
||||||
|
* hangs low with drooping ears when worried (paws on the cheeks), lies flat in P (ears down,
|
||||||
|
* nose bubble); ears go back and the scarf flies when fast; a paw points back in R.
|
||||||
|
*/
|
||||||
|
final class FoxFigure extends ChibiBase {
|
||||||
|
|
||||||
|
private final int fur = 0xFFF08A3C, furLight = 0xFFFFB46E, cream = 0xFFFFF4E6, paw = 0xFF4A3028,
|
||||||
|
earInside = 0xFF5A3A30;
|
||||||
|
|
||||||
|
FoxFigure(int accent, int accentLight, int regen) {
|
||||||
|
super(accent, accentLight, regen, 0xFF9A5A1A, 0xFFE0A040, 0xFFF08A3C, 10f);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
float tipX() {
|
||||||
|
return 66f;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
float tipY() {
|
||||||
|
return 38f;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void drawFigure(Canvas c, MascotMood mood, long t, boolean blink) {
|
||||||
|
drawTail(c, mood, t);
|
||||||
|
drawBody(c);
|
||||||
|
drawScarf(c, mood, t);
|
||||||
|
boolean inFront = handsInFront(mood);
|
||||||
|
if (!inFront) drawPaws(c, mood, t);
|
||||||
|
drawHead(c, mood, t, blink);
|
||||||
|
if (inFront) drawPaws(c, mood, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The brush, rotated about its root by the mood; wagging is a swing over time. */
|
||||||
|
private void drawTail(Canvas c, MascotMood mood, long t) {
|
||||||
|
// Kept within about 18° to the right, so the tail stays clear of the values.
|
||||||
|
float angle;
|
||||||
|
switch (mood) {
|
||||||
|
case CONTENT: angle = (float) Math.sin(t / 250.0) * 8f; break;
|
||||||
|
case CHEERING: angle = (float) Math.sin(t / 110.0) * 14f; break;
|
||||||
|
case DETERMINED: angle = -8f; break;
|
||||||
|
case WORRIED: angle = 15f; break;
|
||||||
|
case SLEEPING: angle = 18f; break;
|
||||||
|
case FAST: angle = 16f; break;
|
||||||
|
case REVERSE: angle = (float) Math.sin(t / 400.0) * 5f; break;
|
||||||
|
default: angle = (float) Math.sin(t / 600.0) * 4f;
|
||||||
|
}
|
||||||
|
c.save();
|
||||||
|
c.rotate(angle, 60f, 90f);
|
||||||
|
p.reset();
|
||||||
|
p.moveTo(57f, 92f);
|
||||||
|
p.cubicTo(80f, 96f, 94f, 78f, 88f, 56f);
|
||||||
|
p.cubicTo(86f, 46f, 78f, 40f, 73f, 44f);
|
||||||
|
p.cubicTo(78f, 56f, 76f, 74f, 57f, 86f);
|
||||||
|
p.close();
|
||||||
|
fill.setColor(fur);
|
||||||
|
c.drawPath(p, fill);
|
||||||
|
c.save();
|
||||||
|
c.clipPath(p);
|
||||||
|
fill.setColor(cream);
|
||||||
|
c.drawCircle(83f, 46f, 10f, fill);
|
||||||
|
c.restore();
|
||||||
|
line.setStrokeWidth(1.3f);
|
||||||
|
c.drawPath(p, line);
|
||||||
|
c.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawBody(Canvas c) {
|
||||||
|
p.reset();
|
||||||
|
p.moveTo(37f, 93f);
|
||||||
|
p.cubicTo(33f, 80f, 40f, 66f, 50f, 66f);
|
||||||
|
p.cubicTo(60f, 66f, 67f, 80f, 63f, 93f);
|
||||||
|
p.quadTo(50f, 96f, 37f, 93f);
|
||||||
|
p.close();
|
||||||
|
fill.setColor(fur);
|
||||||
|
c.drawPath(p, fill);
|
||||||
|
line.setStrokeWidth(1.2f);
|
||||||
|
c.drawPath(p, line);
|
||||||
|
fill.setColor(cream);
|
||||||
|
c.drawOval(oval(43f, 71f, 57f, 91f), fill);
|
||||||
|
// Hind paws.
|
||||||
|
fill.setColor(paw);
|
||||||
|
for (float x : new float[] { 37.5f, 52.5f }) {
|
||||||
|
oval(x, 90f, x + 10f, 97.5f);
|
||||||
|
c.drawOval(r, fill);
|
||||||
|
c.drawOval(r, line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The scarf round the neck; its end hangs, or flies back when fast. */
|
||||||
|
private void drawScarf(Canvas c, MascotMood mood, long t) {
|
||||||
|
fill.setColor(accent);
|
||||||
|
line.setStrokeWidth(1.1f);
|
||||||
|
if (mood == MascotMood.FAST) {
|
||||||
|
float flap = (float) Math.sin(t / 90.0) * 2f;
|
||||||
|
p.reset();
|
||||||
|
p.moveTo(40f, 64f);
|
||||||
|
p.quadTo(30f, 60f + flap, 20f, 63f - flap);
|
||||||
|
p.lineTo(21f, 69f - flap);
|
||||||
|
p.quadTo(30f, 67f + flap, 40f, 70f);
|
||||||
|
p.close();
|
||||||
|
} else {
|
||||||
|
p.reset();
|
||||||
|
p.addRoundRect(oval(54f, 65f, 61f, 80f), 2f, 2f, Path.Direction.CW);
|
||||||
|
}
|
||||||
|
c.drawPath(p, fill);
|
||||||
|
c.drawPath(p, line);
|
||||||
|
oval(36f, 62f, 64f, 70f);
|
||||||
|
c.drawRoundRect(r, 4f, 4f, fill);
|
||||||
|
c.drawRoundRect(r, 4f, 4f, line);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawPaws(Canvas c, MascotMood mood, long t) {
|
||||||
|
float[] h = hands(mood, t);
|
||||||
|
limb(c, 41f, 72f, h[0], h[1], fur, paw);
|
||||||
|
limb(c, 59f, 72f, h[2], h[3], fur, paw);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawHead(Canvas c, MascotMood mood, long t, boolean blink) {
|
||||||
|
c.save();
|
||||||
|
c.rotate(tilt(mood), 50f, 62f);
|
||||||
|
// Ears behind the head, drooping or laid back by the mood; the right one mirrored.
|
||||||
|
float droop = mood == MascotMood.WORRIED ? 38f : mood == MascotMood.SLEEPING ? 26f
|
||||||
|
: mood == MascotMood.FAST ? 22f : mood == MascotMood.CONTENT ? (float) Math.sin(t / 900.0) * 3f : 0f;
|
||||||
|
ear(c, droop);
|
||||||
|
c.save();
|
||||||
|
c.scale(-1f, 1f, 50f, 0f);
|
||||||
|
ear(c, droop);
|
||||||
|
c.restore();
|
||||||
|
|
||||||
|
// The head: round on top, a tuft at each cheek.
|
||||||
|
p.reset();
|
||||||
|
p.moveTo(50f, 16f);
|
||||||
|
p.cubicTo(66f, 16f, 78f, 26f, 79f, 40f);
|
||||||
|
p.lineTo(86f, 52f);
|
||||||
|
p.lineTo(77f, 53f);
|
||||||
|
p.cubicTo(70f, 63f, 60f, 66f, 50f, 66f);
|
||||||
|
p.cubicTo(40f, 66f, 30f, 63f, 23f, 53f);
|
||||||
|
p.lineTo(14f, 52f);
|
||||||
|
p.lineTo(21f, 40f);
|
||||||
|
p.cubicTo(22f, 26f, 34f, 16f, 50f, 16f);
|
||||||
|
p.close();
|
||||||
|
fill.setColor(fur);
|
||||||
|
c.drawPath(p, fill);
|
||||||
|
// Cream cheeks and muzzle, inside the head.
|
||||||
|
c.save();
|
||||||
|
c.clipPath(p);
|
||||||
|
fill.setColor(cream);
|
||||||
|
c.drawOval(oval(22f, 47f, 78f, 84f), fill);
|
||||||
|
c.drawOval(oval(12f, 45f, 34f, 60f), fill);
|
||||||
|
c.drawOval(oval(66f, 45f, 88f, 60f), fill);
|
||||||
|
c.restore();
|
||||||
|
line.setStrokeWidth(1.3f);
|
||||||
|
c.drawPath(p, line);
|
||||||
|
line.setColor(furLight);
|
||||||
|
line.setStrokeWidth(1.6f);
|
||||||
|
c.drawArc(oval(30f, 19f, 56f, 36f), 200f, 60f, false, line);
|
||||||
|
line.setColor(ink);
|
||||||
|
|
||||||
|
// Whiskers, then the nose and the face.
|
||||||
|
line.setStrokeWidth(0.6f);
|
||||||
|
c.drawLine(28f, 53f, 19f, 51f, line);
|
||||||
|
c.drawLine(28f, 55.5f, 19f, 56.5f, line);
|
||||||
|
c.drawLine(72f, 53f, 81f, 51f, line);
|
||||||
|
c.drawLine(72f, 55.5f, 81f, 56.5f, line);
|
||||||
|
face(c, mood, t, blink);
|
||||||
|
fill.setColor(ink);
|
||||||
|
c.drawOval(oval(47.6f, 49f, 52.4f, 52f), fill);
|
||||||
|
c.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The left ear, rotated outwards about its root by {@code droop} degrees. */
|
||||||
|
private void ear(Canvas c, float droop) {
|
||||||
|
c.save();
|
||||||
|
c.rotate(-droop, 35f, 23f);
|
||||||
|
p.reset();
|
||||||
|
p.moveTo(23f, 34f);
|
||||||
|
p.lineTo(26f, 3f);
|
||||||
|
p.lineTo(47f, 20f);
|
||||||
|
p.close();
|
||||||
|
fill.setColor(fur);
|
||||||
|
c.drawPath(p, fill);
|
||||||
|
line.setStrokeWidth(1.3f);
|
||||||
|
c.drawPath(p, line);
|
||||||
|
p.reset();
|
||||||
|
p.moveTo(27.5f, 27f);
|
||||||
|
p.lineTo(29f, 10f);
|
||||||
|
p.lineTo(40f, 20f);
|
||||||
|
p.close();
|
||||||
|
fill.setColor(earInside);
|
||||||
|
c.drawPath(p, fill);
|
||||||
|
c.restore();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1267,7 +1267,8 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
/** Who sits in the Mascot style; the drop-down shows only while that style is chosen. */
|
/** Who sits in the Mascot style; the drop-down shows only while that style is chosen. */
|
||||||
private void bindMascotCharacter() {
|
private void bindMascotCharacter() {
|
||||||
MascotCharacter[] characters = MascotCharacter.values();
|
MascotCharacter[] characters = MascotCharacter.values();
|
||||||
String[] labels = { getString(R.string.mascot_character_classic), getString(R.string.mascot_character_chibi) };
|
String[] labels = { getString(R.string.mascot_character_classic), getString(R.string.mascot_character_chibi),
|
||||||
|
getString(R.string.mascot_character_fox) };
|
||||||
int current = MascotCharacter.fromPref(prefs.getString(MascotCharacter.PREF_KEY, null)).ordinal();
|
int current = MascotCharacter.fromPref(prefs.getString(MascotCharacter.PREF_KEY, null)).ordinal();
|
||||||
bindChoices(settingsPane.findViewById(R.id.mascot_choices), labels, current, position -> {
|
bindChoices(settingsPane.findViewById(R.id.mascot_choices), labels, current, position -> {
|
||||||
prefs.edit().putString(MascotCharacter.PREF_KEY, characters[position].name()).apply();
|
prefs.edit().putString(MascotCharacter.PREF_KEY, characters[position].name()).apply();
|
||||||
|
|||||||
@@ -2,13 +2,15 @@ package com.evsuite.abrp;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Who sits in the Mascot Overview style, in the order of the Settings drop-down: the classic
|
* Who sits in the Mascot Overview style, in the order of the Settings drop-down: the classic
|
||||||
* co-pilot (head and shoulders, headset, racing jacket) or the chibi (small, full figure, big
|
* co-pilot (head and shoulders, headset, racing jacket), the chibi (small, full figure, big
|
||||||
* head, hoodie, bubbly). Each has her own drawing and lines; both follow {@link MascotMood}.
|
* head, hoodie, bubbly) or the chibi fox (sitting, scarf, a tail that wags). Each has their
|
||||||
|
* own drawing and lines; all follow {@link MascotMood}.
|
||||||
* Pure Java, unit-tested.
|
* Pure Java, unit-tested.
|
||||||
*/
|
*/
|
||||||
enum MascotCharacter {
|
enum MascotCharacter {
|
||||||
CLASSIC,
|
CLASSIC,
|
||||||
CHIBI;
|
CHIBI,
|
||||||
|
FOX;
|
||||||
|
|
||||||
static final String PREF_KEY = "mascot_character";
|
static final String PREF_KEY = "mascot_character";
|
||||||
|
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ import android.os.SystemClock;
|
|||||||
* power, 5-minute usage and the trip; the clock top right, left of the full-screen button.
|
* power, 5-minute usage and the trip; the clock top right, left of the full-screen button.
|
||||||
* Unread values show "—".
|
* Unread values show "—".
|
||||||
*
|
*
|
||||||
* Two characters ({@link MascotCharacter}): this classic co-pilot, or the chibi drawn by
|
* Three characters ({@link MascotCharacter}): this classic co-pilot, the chibi
|
||||||
* {@link ChibiFigure}, each with her own lines.
|
* ({@link ChibiFigure}) or the chibi fox ({@link FoxFigure}), each with their own lines.
|
||||||
*
|
*
|
||||||
* Drawn on a 100 × 100 grid scaled to the pane. Nothing animates on its own: blinking, the Zs,
|
* Drawn on a 100 × 100 grid scaled to the pane. Nothing animates on its own: blinking, the Zs,
|
||||||
* sparkles and wind move because readings redraw the view every 0.2 s.
|
* sparkles and wind move because readings redraw the view every 0.2 s.
|
||||||
@@ -37,7 +37,9 @@ final class MascotView extends ClusterCanvasView {
|
|||||||
{ R.array.mascot_waiting, R.array.mascot_reverse, R.array.mascot_worried, R.array.mascot_sleeping,
|
{ R.array.mascot_waiting, R.array.mascot_reverse, R.array.mascot_worried, R.array.mascot_sleeping,
|
||||||
R.array.mascot_cheering, R.array.mascot_determined, R.array.mascot_fast, R.array.mascot_content },
|
R.array.mascot_cheering, R.array.mascot_determined, R.array.mascot_fast, R.array.mascot_content },
|
||||||
{ R.array.chibi_waiting, R.array.chibi_reverse, R.array.chibi_worried, R.array.chibi_sleeping,
|
{ R.array.chibi_waiting, R.array.chibi_reverse, R.array.chibi_worried, R.array.chibi_sleeping,
|
||||||
R.array.chibi_cheering, R.array.chibi_determined, R.array.chibi_fast, R.array.chibi_content } };
|
R.array.chibi_cheering, R.array.chibi_determined, R.array.chibi_fast, R.array.chibi_content },
|
||||||
|
{ R.array.fox_waiting, R.array.fox_reverse, R.array.fox_worried, R.array.fox_sleeping,
|
||||||
|
R.array.fox_cheering, R.array.fox_determined, R.array.fox_fast, R.array.fox_content } };
|
||||||
|
|
||||||
private final Paint fill = new Paint(Paint.ANTI_ALIAS_FLAG);
|
private final Paint fill = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||||
private final Paint stroke = new Paint(Paint.ANTI_ALIAS_FLAG);
|
private final Paint stroke = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||||
@@ -46,7 +48,8 @@ final class MascotView extends ClusterCanvasView {
|
|||||||
private final RectF r = new RectF();
|
private final RectF r = new RectF();
|
||||||
private final MascotMood.Holder holder = new MascotMood.Holder();
|
private final MascotMood.Holder holder = new MascotMood.Holder();
|
||||||
private final String[][][] lines = new String[LINES.length][][];
|
private final String[][][] lines = new String[LINES.length][][];
|
||||||
private final ChibiFigure chibi;
|
/** The drawn figures by {@link MascotCharacter} ordinal; null for the classic one, drawn here. */
|
||||||
|
private final ChibiBase[] figures;
|
||||||
private MascotCharacter character = MascotCharacter.CLASSIC;
|
private MascotCharacter character = MascotCharacter.CLASSIC;
|
||||||
private final int accent, accentDark, accentLight, regen, warn, white, grey, track;
|
private final int accent, accentDark, accentLight, regen, warn, white, grey, track;
|
||||||
private final int hair = 0xFF2E2440, hairLight = 0xFF5A4880, skin = 0xFFFBE2D0, skinShade = 0xFFE9BFA9,
|
private final int hair = 0xFF2E2440, hairLight = 0xFF5A4880, skin = 0xFFFBE2D0, skinShade = 0xFFE9BFA9,
|
||||||
@@ -68,7 +71,8 @@ final class MascotView extends ClusterCanvasView {
|
|||||||
lines[i] = new String[LINES[i].length][];
|
lines[i] = new String[LINES[i].length][];
|
||||||
for (int j = 0; j < LINES[i].length; j++) lines[i][j] = context.getResources().getStringArray(LINES[i][j]);
|
for (int j = 0; j < LINES[i].length; j++) lines[i][j] = context.getResources().getStringArray(LINES[i][j]);
|
||||||
}
|
}
|
||||||
chibi = new ChibiFigure(accent, accentLight, regen);
|
figures = new ChibiBase[] { null, new ChibiFigure(accent, accentLight, regen),
|
||||||
|
new FoxFigure(accent, accentLight, regen) };
|
||||||
stroke.setStyle(Paint.Style.STROKE);
|
stroke.setStyle(Paint.Style.STROKE);
|
||||||
stroke.setStrokeCap(Paint.Cap.ROUND);
|
stroke.setStrokeCap(Paint.Cap.ROUND);
|
||||||
stroke.setStrokeJoin(Paint.Join.ROUND);
|
stroke.setStrokeJoin(Paint.Join.ROUND);
|
||||||
@@ -98,13 +102,13 @@ final class MascotView extends ClusterCanvasView {
|
|||||||
c.save();
|
c.save();
|
||||||
c.translate(ox, oy);
|
c.translate(ox, oy);
|
||||||
c.scale(k, k);
|
c.scale(k, k);
|
||||||
boolean chibiShown = character == MascotCharacter.CHIBI;
|
ChibiBase figure = figures[character.ordinal()];
|
||||||
if (chibiShown) chibi.draw(c, mood, t, t % 4200 < 260);
|
if (figure != null) figure.draw(c, mood, t, t % 4200 < 260);
|
||||||
else drawCharacter(c, mood, t);
|
else drawCharacter(c, mood, t);
|
||||||
c.restore();
|
c.restore();
|
||||||
|
|
||||||
float x0 = ox + 78f * k;
|
float x0 = ox + 78f * k;
|
||||||
float tipX = chibiShown ? ChibiFigure.TIP_X : 70f, tipY = chibiShown ? ChibiFigure.TIP_Y : 46f;
|
float tipX = figure != null ? figure.tipX() : 70f, tipY = figure != null ? figure.tipY() : 46f;
|
||||||
drawBubble(c, mood, t, x0, h, ox + tipX * k, oy + tipY * k, w);
|
drawBubble(c, mood, t, x0, h, ox + tipX * k, oy + tipY * k, w);
|
||||||
drawValues(c, x0, ox + 93f * k, w, h);
|
drawValues(c, x0, ox + 93f * k, w, h);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -143,6 +143,41 @@
|
|||||||
<string name="label_mascot_character">Mascot</string>
|
<string name="label_mascot_character">Mascot</string>
|
||||||
<string name="mascot_character_classic">Classic</string>
|
<string name="mascot_character_classic">Classic</string>
|
||||||
<string name="mascot_character_chibi">Chibi</string>
|
<string name="mascot_character_chibi">Chibi</string>
|
||||||
|
<string name="mascot_character_fox">Fox</string>
|
||||||
|
<!-- The fox's lines, per mood like mascot_*. -->
|
||||||
|
<string-array name="fox_waiting">
|
||||||
|
<item>Kon? Anyone there?</item>
|
||||||
|
<item>Sniff sniff… no car yet.</item>
|
||||||
|
</string-array>
|
||||||
|
<string-array name="fox_reverse">
|
||||||
|
<item>Backing up, ears up!</item>
|
||||||
|
<item>Mind my tail!</item>
|
||||||
|
</string-array>
|
||||||
|
<string-array name="fox_worried">
|
||||||
|
<item>Battery\'s low, kon…</item>
|
||||||
|
<item>Find a charger, please!</item>
|
||||||
|
</string-array>
|
||||||
|
<string-array name="fox_sleeping">
|
||||||
|
<item>Curled up in P… zzz</item>
|
||||||
|
<item>Fox nap time.</item>
|
||||||
|
</string-array>
|
||||||
|
<string-array name="fox_cheering">
|
||||||
|
<item>Kon kon! Free energy!</item>
|
||||||
|
<item>Tail-wagging regen!</item>
|
||||||
|
</string-array>
|
||||||
|
<string-array name="fox_determined">
|
||||||
|
<item>Pounce!</item>
|
||||||
|
<item>Full speed, kon!</item>
|
||||||
|
</string-array>
|
||||||
|
<string-array name="fox_fast">
|
||||||
|
<item>Ears back, here we go!</item>
|
||||||
|
<item>Zoomies!</item>
|
||||||
|
</string-array>
|
||||||
|
<string-array name="fox_content">
|
||||||
|
<item>Kon! Nice drive.</item>
|
||||||
|
<item>Happy tail, happy fox.</item>
|
||||||
|
<item>Sniffing the road ahead.</item>
|
||||||
|
</string-array>
|
||||||
<!-- The chibi's lines, per mood like mascot_*. -->
|
<!-- The chibi's lines, per mood like mascot_*. -->
|
||||||
<string-array name="chibi_waiting">
|
<string-array name="chibi_waiting">
|
||||||
<item>Hellooo? Car?</item>
|
<item>Hellooo? Car?</item>
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ public class MascotMoodTest {
|
|||||||
@Test
|
@Test
|
||||||
public void characterFromPref() {
|
public void characterFromPref() {
|
||||||
assertEquals(MascotCharacter.CHIBI, MascotCharacter.fromPref("CHIBI"));
|
assertEquals(MascotCharacter.CHIBI, MascotCharacter.fromPref("CHIBI"));
|
||||||
|
assertEquals(MascotCharacter.FOX, MascotCharacter.fromPref("FOX"));
|
||||||
assertEquals(MascotCharacter.CLASSIC, MascotCharacter.fromPref("CLASSIC"));
|
assertEquals(MascotCharacter.CLASSIC, MascotCharacter.fromPref("CLASSIC"));
|
||||||
assertEquals(MascotCharacter.CLASSIC, MascotCharacter.fromPref(null));
|
assertEquals(MascotCharacter.CLASSIC, MascotCharacter.fromPref(null));
|
||||||
assertEquals(MascotCharacter.CLASSIC, MascotCharacter.fromPref("NOPE"));
|
assertEquals(MascotCharacter.CLASSIC, MascotCharacter.fromPref("NOPE"));
|
||||||
|
|||||||
Reference in New Issue
Block a user