App 2.48.0: the Christmas style
A tree with a bauble for the speed, fairy lights for the power, a mug of cocoa for the battery, a wreath clock and candy canes for the range. Its lights flare on the beat with the music light, now "Light follows the music" for Halloween and Christmas. A sleigh or an angel tows the song. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
This commit is contained in:
@@ -0,0 +1,616 @@
|
||||
package com.evsuite.abrp;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.DashPathEffect;
|
||||
import android.graphics.LinearGradient;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.RadialGradient;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.Shader;
|
||||
import android.graphics.Typeface;
|
||||
import android.os.SystemClock;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* The Christmas Overview style: a snowy village night, warm and festive.
|
||||
*
|
||||
* In the middle a Christmas tree with a big red bauble in front of it: the speed. Fairy lights
|
||||
* arch over the tree: the power, left to right in warm gold while drawing, ice blue and right to
|
||||
* left while regenerating. On the left a glass mug of hot cocoa filling with the charge, cold blue
|
||||
* with "BATTERY LOW" blinking below 20 %, under gift tags with trip, usage, climate and odometer.
|
||||
* On the right a wreath holds the clock, and a snowy signboard reads the range, with a candy cane
|
||||
* per 50 km; P R N D are four baubles, the one engaged lit. The snow drift along the bottom is the
|
||||
* last two minutes of power, rising with it, in gold or, where it regenerated, ice blue. Unread
|
||||
* values show "—" and light nothing. With music playing, the tree's lights and the fairy lights
|
||||
* flare on the beat ({@link MusicTap}); with no music they twinkle as before. What plays crosses
|
||||
* the sky on a banner of wrapping paper, towed by Santa's sleigh and, on the next pass, an angel
|
||||
* ({@link NowPlaying}).
|
||||
*
|
||||
* The night, the hills and the stars are drawn once into a bitmap per size; only the rest goes over
|
||||
* it. Snow falls and the lights twinkle (about 30 frames a second) only while the car moves and the
|
||||
* view is on screen; the lights' pulse and the song keep it drawing while there are any.
|
||||
*/
|
||||
final class ChristmasView extends ClusterCanvasView {
|
||||
|
||||
private static final int BULBS = 25, FLAKES = 70, CANES = 8;
|
||||
private static final int SKY_TOP = 0xFF06142B, SKY_LOW = 0xFF173A5E, SKY_END = 0xFF2A5A7A;
|
||||
private static final int SNOW = 0xFFE9F2FF, PINE = 0xFF1F7A3C, PINE_DARK = 0xFF0F4A24;
|
||||
private static final int RED = 0xFFE53946, GOLD = 0xFFFFD04A, BONE = 0xFFF6F0E4, INK = 0xFF1B0F12;
|
||||
private static final int COCOA = 0xFF7B3F22, COLD = 0xFF4A8AD0, ICE = 0xFF8FD8FF, WOOD = 0xFF4A2E1A;
|
||||
private static final int[] LIGHTS = { RED, GOLD, ICE, 0xFFFFFFFF, 0xFF5CE08A };
|
||||
private static final int[] PAPER = { 0xFFC62828, 0xFF1E7B3C, 0xFF2B5FA8, 0xFFD9A62E, 0xFFF1F1F1 };
|
||||
|
||||
private final Paint fill = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private final Paint line = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private final Paint text = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private final Paint image = new Paint(Paint.FILTER_BITMAP_FLAG);
|
||||
private final Paint glow = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private final Path path = new Path();
|
||||
private final SongBanner banner = new SongBanner();
|
||||
private final RectF r = new RectF();
|
||||
private final Typeface serif = Typeface.create("serif", Typeface.BOLD);
|
||||
private final Typeface face = Typeface.create("sans-serif-condensed", Typeface.BOLD);
|
||||
private final Typeface thin = Typeface.create("sans-serif-condensed", Typeface.NORMAL);
|
||||
|
||||
private Bitmap sky;
|
||||
private int skyW, skyH;
|
||||
private float time;
|
||||
private long lastFrame;
|
||||
private MusicTap music;
|
||||
|
||||
ChristmasView(Context context) {
|
||||
super(context);
|
||||
line.setStyle(Paint.Style.STROKE);
|
||||
line.setStrokeCap(Paint.Cap.ROUND);
|
||||
}
|
||||
|
||||
/** Where the lights hear the music; without it, they do not follow any. */
|
||||
void setMusic(MusicTap tap) {
|
||||
music = tap;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
sky = null;
|
||||
skyW = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas c) {
|
||||
super.onDraw(c);
|
||||
int w = getWidth(), h = getHeight();
|
||||
if (w <= 0 || h <= 0) return;
|
||||
float u = Math.min(h / 640f, w / 1100f);
|
||||
boolean moving = v.speedKmh != null && v.speedKmh > 0.5f;
|
||||
if (moving) {
|
||||
long now = SystemClock.uptimeMillis();
|
||||
time += lastFrame == 0 ? 0f : Math.min(0.2f, (now - lastFrame) / 1000f);
|
||||
lastFrame = now;
|
||||
} else {
|
||||
lastFrame = 0;
|
||||
}
|
||||
if (sky == null || skyW != w || skyH != h) buildSky(w, h, u);
|
||||
c.drawBitmap(sky, 0, 0, image);
|
||||
boolean song = drawSong(c, w, u);
|
||||
drawSnow(c, w, h, u);
|
||||
drawDrift(c, w, h, u);
|
||||
float pulse = music == null ? 0f : music.pulse();
|
||||
drawTree(c, w, u, pulse);
|
||||
drawArch(c, w, u, pulse);
|
||||
drawBauble(c, w, u, pulse);
|
||||
drawWreath(c, w, u);
|
||||
drawTags(c, u);
|
||||
drawMug(c, u);
|
||||
drawBoard(c, w, u);
|
||||
drawGears(c, w, u);
|
||||
keepAnimating(moving || pulse > 0f || song, 33);
|
||||
}
|
||||
|
||||
private void label(Canvas c, String s, float x, float y, float size, int colour, Typeface tf, Paint.Align a) {
|
||||
text.setTypeface(tf);
|
||||
text.setTextSize(size);
|
||||
text.setTextAlign(a);
|
||||
text.setColor(colour);
|
||||
c.drawText(s, x, y, text);
|
||||
}
|
||||
|
||||
/** Text with a dark outline, to read over whatever is behind it. */
|
||||
private void outlined(Canvas c, String s, float x, float y, float size, int colour, Typeface tf, Paint.Align a, float u) {
|
||||
text.setStyle(Paint.Style.STROKE);
|
||||
text.setStrokeWidth(Math.max(3f * u, size * 0.12f));
|
||||
text.setStrokeJoin(Paint.Join.ROUND);
|
||||
label(c, s, x, y, size, 0xDD0A0608, tf, a);
|
||||
text.setStyle(Paint.Style.FILL);
|
||||
label(c, s, x, y, size, colour, tf, a);
|
||||
}
|
||||
|
||||
private boolean regen() {
|
||||
return v.powerKw != null && v.powerKw < -0.05f;
|
||||
}
|
||||
|
||||
// ---------- Night sky and hills ----------
|
||||
|
||||
private void buildSky(int w, int h, float u) {
|
||||
sky = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
|
||||
skyW = w;
|
||||
skyH = h;
|
||||
Canvas c = new Canvas(sky);
|
||||
Paint p = new Paint();
|
||||
p.setShader(new LinearGradient(0, 0, 0, h, new int[] { SKY_TOP, SKY_LOW, SKY_END },
|
||||
new float[] { 0f, 0.7f, 1f }, Shader.TileMode.CLAMP));
|
||||
c.drawRect(0, 0, w, h, p);
|
||||
Paint star = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
star.setColor(0x99FFFFFF);
|
||||
for (int i = 0; i < 90; i++) {
|
||||
float x = ((i * 191 + 17) % 1009) / 1009f * w, y = ((i * 83 + 5) % 421) * u;
|
||||
c.drawCircle(x, y, (i % 4 == 0 ? 1.7f : 0.9f) * u, star);
|
||||
}
|
||||
// Two ranges of snowy hills, with pines on the far one.
|
||||
hills(c, w, h, h - 150f * u, 0xFF28496B, 0.8f, u);
|
||||
Paint pine = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
pine.setColor(0xFF0F3A2A);
|
||||
for (int i = 0; i < 14; i++) {
|
||||
float x = ((i * 233 + 41) % 997) / 997f * w, tall = (36f + (i * 17) % 30) * u;
|
||||
float base = h - 150f * u + 8f * u;
|
||||
Path tri = new Path();
|
||||
tri.moveTo(x, base - tall);
|
||||
tri.lineTo(x + tall * 0.32f, base);
|
||||
tri.lineTo(x - tall * 0.32f, base);
|
||||
tri.close();
|
||||
c.drawPath(tri, pine);
|
||||
}
|
||||
hills(c, w, h, h - 100f * u, 0xFF3C6286, 1.5f, u);
|
||||
}
|
||||
|
||||
private void hills(Canvas c, int w, int h, float base, int colour, float wave, float u) {
|
||||
Path hill = new Path();
|
||||
hill.moveTo(0, h);
|
||||
for (int i = 0; i <= 24; i++) {
|
||||
float x = w * i / 24f;
|
||||
hill.lineTo(x, base - (float) (Math.sin(i * wave) * 0.5 + Math.sin(i * wave * 0.37 + 1) * 0.5) * 22f * u);
|
||||
}
|
||||
hill.lineTo(w, h);
|
||||
hill.close();
|
||||
Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
p.setColor(colour);
|
||||
c.drawPath(hill, p);
|
||||
}
|
||||
|
||||
private void drawSnow(Canvas c, int w, int h, float u) {
|
||||
for (int i = 0; i < FLAKES; i++) {
|
||||
float fall = (30f + (i % 5) * 12f) * u;
|
||||
float y = (((i * 53) % 600) * u + time * fall) % h;
|
||||
float x = ((i * 97 + 13) % 1000) / 1000f * w + (float) Math.sin(time * 0.8f + i) * 12f * u;
|
||||
fill.setColor(i % 3 == 0 ? 0xE0FFFFFF : 0x90FFFFFF);
|
||||
c.drawCircle(x, y, (1f + (i % 3) * 0.8f) * u, fill);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- The drift: the last two minutes of power ----------
|
||||
|
||||
private void drawDrift(Canvas c, int w, int h, float u) {
|
||||
float base = h - 44f * u, amp = 36f * u;
|
||||
float[] trail = v.powerTrail;
|
||||
path.reset();
|
||||
path.moveTo(0, h);
|
||||
int n = trail == null ? 0 : trail.length;
|
||||
if (n == 0) {
|
||||
path.lineTo(0, base);
|
||||
path.lineTo(w, base);
|
||||
} else {
|
||||
for (int i = 0; i < n; i++) {
|
||||
float p = trail[i];
|
||||
float height = Float.isNaN(p) ? 0f : Math.min(1f, Math.abs(p) / (p < 0 ? 40f : 60f));
|
||||
path.lineTo(i / (n - 1f) * w, base - height * amp);
|
||||
}
|
||||
}
|
||||
path.lineTo(w, h);
|
||||
path.close();
|
||||
fill.setColor(SNOW);
|
||||
c.drawPath(path, fill);
|
||||
if (n > 1) {
|
||||
line.setStrokeWidth(3f * u);
|
||||
for (int i = 1; i < n; i++) {
|
||||
float a = trail[i - 1], b = trail[i];
|
||||
if (Float.isNaN(a) || Float.isNaN(b)) continue;
|
||||
float ha = Math.min(1f, Math.abs(a) / (a < 0 ? 40f : 60f));
|
||||
float hb = Math.min(1f, Math.abs(b) / (b < 0 ? 40f : 60f));
|
||||
line.setColor(b < 0 ? ICE : GOLD);
|
||||
c.drawLine((i - 1f) / (n - 1f) * w, base - ha * amp, i / (n - 1f) * w, base - hb * amp, line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- The tree ----------
|
||||
|
||||
private void drawTree(Canvas c, int w, float u, float pulse) {
|
||||
float cx = w / 2f;
|
||||
// The trunk, then three tiers, each with a darker underside.
|
||||
r.set(cx - 22f * u, 480f * u, cx + 22f * u, 526f * u);
|
||||
fill.setColor(WOOD);
|
||||
c.drawRect(r, fill);
|
||||
tier(c, cx, 118f * u, 238f * u, 96f * u, u);
|
||||
tier(c, cx, 190f * u, 358f * u, 136f * u, u);
|
||||
tier(c, cx, 270f * u, 492f * u, 182f * u, u);
|
||||
// The star on top, flaring with the music.
|
||||
float sy = 104f * u, flare = 1f + pulse * 0.5f;
|
||||
glow.setShader(new RadialGradient(cx, sy, 60f * u * flare,
|
||||
new int[] { AccentColor.withAlpha(GOLD, 0x55 + (int) (pulse * 0x80)), AccentColor.withAlpha(GOLD, 0) }, null,
|
||||
Shader.TileMode.CLAMP));
|
||||
c.drawCircle(cx, sy, 60f * u * flare, glow);
|
||||
path.reset();
|
||||
for (int i = 0; i < 10; i++) {
|
||||
double a = -Math.PI / 2 + Math.PI * i / 5;
|
||||
float rad = (i % 2 == 0 ? 24f : 10f) * u;
|
||||
float x = cx + (float) Math.cos(a) * rad, y = sy + (float) Math.sin(a) * rad;
|
||||
if (i == 0) path.moveTo(x, y);
|
||||
else path.lineTo(x, y);
|
||||
}
|
||||
path.close();
|
||||
fill.setColor(GOLD);
|
||||
c.drawPath(path, fill);
|
||||
// The lights: strung along each tier's lower edge, steady, twinkling a little while driving,
|
||||
// flaring together on the beat.
|
||||
float[][] strings = { { 238f, 96f, 5 }, { 358f, 136f, 7 }, { 492f, 182f, 10 } };
|
||||
int i = 0;
|
||||
for (float[] st : strings) {
|
||||
int n = (int) st[2];
|
||||
for (int k = 0; k < n; k++, i++) {
|
||||
float x = cx - st[1] * 0.85f * u + k * (1.7f * st[1] * u) / (n - 1);
|
||||
float y = (st[0] - 16f + (k % 2) * 9f) * u;
|
||||
int colour = LIGHTS[i % LIGHTS.length];
|
||||
float twinkle = 0.5f + 0.5f * (float) Math.sin(time * 3f + i * 1.7f);
|
||||
int alpha = Math.min(0xFF, 0x58 + (int) (twinkle * 0x38) + (int) (pulse * 0x90));
|
||||
fill.setColor(AccentColor.withAlpha(colour, alpha));
|
||||
c.drawCircle(x, y, (11f + pulse * 9f) * u, fill);
|
||||
fill.setColor(brighter(colour, pulse * 0.4f));
|
||||
c.drawCircle(x, y, 4.8f * u * (1f + pulse * 0.35f), fill);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void tier(Canvas c, float cx, float apex, float base, float half, float u) {
|
||||
for (int pass = 0; pass < 2; pass++) {
|
||||
float dy = pass == 0 ? 7f * u : 0f;
|
||||
path.reset();
|
||||
path.moveTo(cx, apex + dy);
|
||||
path.lineTo(cx + half, base - 6f * u + dy);
|
||||
int scallops = 5;
|
||||
float sw = half * 2f / scallops;
|
||||
for (int i = scallops; i > 0; i--) {
|
||||
float x = cx - half + i * sw;
|
||||
path.quadTo(x - sw / 2f, base + 12f * u + dy, x - sw, base - 6f * u + dy);
|
||||
}
|
||||
path.close();
|
||||
fill.setColor(pass == 0 ? PINE_DARK : PINE);
|
||||
c.drawPath(path, fill);
|
||||
}
|
||||
}
|
||||
|
||||
/** The colour moved {@code share} of the way to white. */
|
||||
private static int brighter(int colour, float share) {
|
||||
int r = (colour >> 16) & 0xFF, g = (colour >> 8) & 0xFF, b = colour & 0xFF;
|
||||
return 0xFF000000 | (r + (int) ((255 - r) * share)) << 16 | (g + (int) ((255 - g) * share)) << 8
|
||||
| (b + (int) ((255 - b) * share));
|
||||
}
|
||||
|
||||
// ---------- The fairy lights over the tree: the power ----------
|
||||
|
||||
private void drawArch(Canvas c, int w, float u, float pulse) {
|
||||
float cx = w / 2f, cy = 348f * u, radX = 272f * u, radY = 244f * u;
|
||||
int lit = Tally.ofPower(v.powerKw, 150f, 50f, BULBS);
|
||||
boolean regen = regen();
|
||||
// The wire.
|
||||
line.setColor(PINE_DARK);
|
||||
line.setStrokeWidth(3f * u);
|
||||
path.reset();
|
||||
for (int i = 0; i <= 48; i++) {
|
||||
double a = Math.PI + Math.PI * i / 48.0;
|
||||
float x = cx + (float) Math.cos(a) * radX, y = cy + (float) Math.sin(a) * radY;
|
||||
if (i == 0) path.moveTo(x, y);
|
||||
else path.lineTo(x, y);
|
||||
}
|
||||
c.drawPath(path, line);
|
||||
for (int i = 0; i < BULBS; i++) {
|
||||
// Left to right over the top; regeneration comes back from the right.
|
||||
int k = regen ? BULBS - 1 - i : i;
|
||||
double a = Math.PI + Math.PI * k / (BULBS - 1);
|
||||
float x = cx + (float) Math.cos(a) * radX, y = cy + (float) Math.sin(a) * radY;
|
||||
boolean on = i < lit;
|
||||
if (on) {
|
||||
fill.setColor(AccentColor.withAlpha(regen ? ICE : GOLD, 0x44 + (int) (pulse * 0x50)));
|
||||
c.drawCircle(x, y, (15f + pulse * 6f) * u, fill);
|
||||
}
|
||||
fill.setColor(on ? brighter(regen ? ICE : GOLD, pulse * 0.4f) : 0x38FFFFFF);
|
||||
c.drawCircle(x, y, 7.5f * u, fill);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- The big bauble: the speed ----------
|
||||
|
||||
private void drawBauble(Canvas c, int w, float u, float pulse) {
|
||||
float cx = w / 2f, cy = 372f * u, rad = 88f * u;
|
||||
glow.setShader(new RadialGradient(cx, cy, rad * (1.6f + pulse * 0.2f),
|
||||
new int[] { AccentColor.withAlpha(RED, 0x40 + (int) (pulse * 0x50)), AccentColor.withAlpha(RED, 0) }, null,
|
||||
Shader.TileMode.CLAMP));
|
||||
c.drawCircle(cx, cy, rad * (1.6f + pulse * 0.2f), glow);
|
||||
// The cap and its loop.
|
||||
line.setColor(GOLD);
|
||||
line.setStrokeWidth(3f * u);
|
||||
r.set(cx - 12f * u, cy - rad - 30f * u, cx + 12f * u, cy - rad - 6f * u);
|
||||
c.drawOval(r, line);
|
||||
glow.setShader(new RadialGradient(cx - rad * 0.3f, cy - rad * 0.35f, rad * 1.3f,
|
||||
new int[] { 0xFFFF7A82, RED, 0xFF8E1623 }, new float[] { 0f, 0.5f, 1f }, Shader.TileMode.CLAMP));
|
||||
c.drawCircle(cx, cy, rad, glow);
|
||||
r.set(cx - 16f * u, cy - rad - 12f * u, cx + 16f * u, cy - rad + 8f * u);
|
||||
fill.setColor(GOLD);
|
||||
c.drawRect(r, fill);
|
||||
// A gold band round the middle's lower half, and a highlight.
|
||||
line.setColor(0x88FFD04A);
|
||||
line.setStrokeWidth(3f * u);
|
||||
r.set(cx - rad + 6f * u, cy + 34f * u, cx + rad - 6f * u, cy + rad - 4f * u);
|
||||
c.drawArc(r, 20f, 140f, false, line);
|
||||
fill.setColor(0x55FFFFFF);
|
||||
r.set(cx - rad * 0.62f, cy - rad * 0.72f, cx - rad * 0.2f, cy - rad * 0.42f);
|
||||
c.drawOval(r, fill);
|
||||
label(c, fmt(v.speedKmh, "%.0f", "—"), cx, cy + 20f * u, 84f * u, BONE, serif, Paint.Align.CENTER);
|
||||
label(c, "KM/H", cx, cy + 50f * u, 16f * u, 0xCCFFE9EA, face, Paint.Align.CENTER);
|
||||
// The power under the tree, above the drift.
|
||||
boolean regen = regen();
|
||||
String power = v.powerKw == null ? "— kW" : String.format(Locale.US, regen ? "+%.1f kW" : "%.1f kW",
|
||||
Math.abs(v.powerKw));
|
||||
outlined(c, power, cx, 556f * u, 34f * u, regen ? ICE : BONE, face, Paint.Align.CENTER, u);
|
||||
}
|
||||
|
||||
// ---------- The wreath: the clock ----------
|
||||
|
||||
private void drawWreath(Canvas c, int w, float u) {
|
||||
float rad = 74f * u, cx = w - 100f * u - rad * 0.2f, cy = 96f * u;
|
||||
line.setColor(PINE);
|
||||
line.setStrokeWidth(24f * u);
|
||||
c.drawCircle(cx, cy, rad, line);
|
||||
line.setColor(PINE_DARK);
|
||||
line.setStrokeWidth(5f * u);
|
||||
c.drawCircle(cx, cy, rad - 8f * u, line);
|
||||
fill.setColor(0xFF2E9C52);
|
||||
for (int i = 0; i < 16; i++) {
|
||||
double a = Math.PI * 2 * i / 16;
|
||||
c.drawCircle(cx + (float) Math.cos(a) * rad, cy + (float) Math.sin(a) * rad, 8f * u, fill);
|
||||
}
|
||||
fill.setColor(RED);
|
||||
for (int i = 0; i < 9; i++) {
|
||||
double a = Math.PI * 2 * i / 9 + 0.3;
|
||||
c.drawCircle(cx + (float) Math.cos(a) * rad, cy + (float) Math.sin(a) * rad, 4.5f * u, fill);
|
||||
}
|
||||
// The bow at the foot.
|
||||
path.reset();
|
||||
float bx = cx, by = cy + rad;
|
||||
path.moveTo(bx, by);
|
||||
path.lineTo(bx - 28f * u, by - 14f * u);
|
||||
path.lineTo(bx - 28f * u, by + 14f * u);
|
||||
path.close();
|
||||
path.moveTo(bx, by);
|
||||
path.lineTo(bx + 28f * u, by - 14f * u);
|
||||
path.lineTo(bx + 28f * u, by + 14f * u);
|
||||
path.close();
|
||||
fill.setColor(RED);
|
||||
c.drawPath(path, fill);
|
||||
c.drawCircle(bx, by, 7f * u, fill);
|
||||
// The clock face inside.
|
||||
fill.setColor(0xFFFFF6E0);
|
||||
c.drawCircle(cx, cy, rad - 18f * u, fill);
|
||||
line.setColor(GOLD);
|
||||
line.setStrokeWidth(3f * u);
|
||||
c.drawCircle(cx, cy, rad - 18f * u, line);
|
||||
label(c, v.clock.isEmpty() ? "—" : v.clock, cx, cy + 14f * u, 40f * u, INK, serif, Paint.Align.CENTER);
|
||||
}
|
||||
|
||||
// ---------- Gift tags: trip, usage, climate, odometer ----------
|
||||
|
||||
private void drawTags(Canvas c, float u) {
|
||||
float l = 28f * u, rr = l + 300f * u, t = 44f * u, tag = 34f * u, step = 38f * u;
|
||||
// The garland they hang from.
|
||||
line.setColor(PINE);
|
||||
line.setStrokeWidth(8f * u);
|
||||
c.drawLine(l + 10f * u, 20f * u, rr - 10f * u, 20f * u, line);
|
||||
fill.setColor(RED);
|
||||
for (int i = 0; i < 7; i++) c.drawCircle(l + 24f * u + i * 44f * u, 20f * u, 4f * u, fill);
|
||||
String[][] rows = {
|
||||
{ "TRIP", fmt(v.tripKm, "%.1f km", "—") },
|
||||
{ "5 MIN", fmt(v.usage5Min, "%.1f kWh/100", "—") },
|
||||
{ "SINCE START", fmt(v.usageTrip, "%.1f kWh/100", "—") },
|
||||
{ "CLIMATE", fmt(v.climateCelsius, "%.0f °C", "—") },
|
||||
{ "ODOMETER", fmt(v.odometerKm, "%,.0f km", "—") },
|
||||
};
|
||||
for (int i = 0; i < rows.length; i++) {
|
||||
float top = t + i * step;
|
||||
// A tag with a clipped corner and a hole, hung on a thread from the one above.
|
||||
path.reset();
|
||||
path.moveTo(l + 18f * u, top);
|
||||
path.lineTo(rr, top);
|
||||
path.lineTo(rr, top + tag);
|
||||
path.lineTo(l + 18f * u, top + tag);
|
||||
path.lineTo(l, top + tag / 2f);
|
||||
path.close();
|
||||
fill.setColor(i % 2 == 0 ? 0xFFF6EBD0 : 0xFFEAD9B6);
|
||||
c.drawPath(path, fill);
|
||||
line.setColor(RED);
|
||||
line.setStrokeWidth(2f * u);
|
||||
c.drawPath(path, line);
|
||||
line.setColor(0xCCFFFFFF);
|
||||
line.setStrokeWidth(1.5f * u);
|
||||
c.drawLine(l + 12f * u, top - (i == 0 ? 20f * u - 0f : step - tag), l + 12f * u, top + tag / 2f, line);
|
||||
fill.setColor(0xFF2A1A10);
|
||||
c.drawCircle(l + 12f * u, top + tag / 2f, 3f * u, fill);
|
||||
label(c, rows[i][0], l + 28f * u, top + 23f * u, 17f * u, 0xFF8E1623, thin, Paint.Align.LEFT);
|
||||
label(c, rows[i][1], rr - 12f * u, top + 25f * u, 22f * u, INK, face, Paint.Align.RIGHT);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- The mug of cocoa: the battery ----------
|
||||
|
||||
private void drawMug(Canvas c, float u) {
|
||||
float cx = 178f * u, l = cx - 92f * u, rr = cx + 92f * u, top = 330f * u, bottom = 560f * u;
|
||||
boolean low = v.socPercent != null && v.socPercent < 20f;
|
||||
boolean blinkOn = (SystemClock.uptimeMillis() / 500) % 2 == 0;
|
||||
float corner = 22f * u, inner = 6f * u;
|
||||
// The handle.
|
||||
line.setColor(0xFFCFE6F0);
|
||||
line.setStrokeWidth(14f * u);
|
||||
r.set(rr - 26f * u, top + 50f * u, rr + 40f * u, top + 170f * u);
|
||||
c.drawArc(r, -90f, 180f, false, line);
|
||||
// The glass.
|
||||
r.set(l, top, rr, bottom);
|
||||
fill.setColor(0x30CFE6F0);
|
||||
c.drawRoundRect(r, corner, corner, fill);
|
||||
if (v.socPercent != null) {
|
||||
float share = Math.max(0f, Math.min(1f, v.socPercent / 100f));
|
||||
float level = bottom - inner - share * (bottom - top - 2f * inner);
|
||||
c.save();
|
||||
r.set(l + inner, top + inner, rr - inner, bottom - inner);
|
||||
path.reset();
|
||||
path.addRoundRect(r, corner - inner, corner - inner, Path.Direction.CW);
|
||||
c.clipPath(path);
|
||||
fill.setColor(low ? COLD : COCOA);
|
||||
c.drawRect(l, level, rr, bottom, fill);
|
||||
fill.setColor(0x33FFFFFF);
|
||||
c.drawRect(l, level, rr, level + 4f * u, fill);
|
||||
c.restore();
|
||||
// Marshmallows float on top, bobbing while driving.
|
||||
for (int i = 0; i < 3; i++) {
|
||||
float mx = cx + (i - 1) * 50f * u, my = level - 8f * u + (float) Math.sin(time * 2f + i * 2f) * 2f * u;
|
||||
r.set(mx - 14f * u, my - 12f * u, mx + 14f * u, my + 8f * u);
|
||||
fill.setColor(BONE);
|
||||
c.drawRoundRect(r, 6f * u, 6f * u, fill);
|
||||
}
|
||||
// Steam.
|
||||
line.setColor(0x66FFFFFF);
|
||||
line.setStrokeWidth(4f * u);
|
||||
for (int i = 0; i < 3; i++) {
|
||||
float sx = cx + (i - 1) * 44f * u;
|
||||
path.reset();
|
||||
path.moveTo(sx, level - 24f * u);
|
||||
for (int k = 1; k <= 6; k++) {
|
||||
path.lineTo(sx + (float) Math.sin(time * 2.5f + i + k * 0.9f) * 9f * u, level - 24f * u - k * 11f * u);
|
||||
}
|
||||
c.drawPath(path, line);
|
||||
}
|
||||
}
|
||||
line.setColor(0xFFCFE6F0);
|
||||
line.setStrokeWidth(4f * u);
|
||||
r.set(l, top, rr, bottom);
|
||||
c.drawRoundRect(r, corner, corner, line);
|
||||
String number = fmt(v.socPercent, "%.0f %%", "—");
|
||||
outlined(c, number, cx, 470f * u, 58f * u, low ? 0xFFFF9AA3 : BONE, serif, Paint.Align.CENTER, u);
|
||||
if (low && blinkOn) outlined(c, "BATTERY LOW", cx, 508f * u, 21f * u, BONE, face, Paint.Align.CENTER, u);
|
||||
else if (!low) outlined(c, "BATTERY", cx, 508f * u, 19f * u, GOLD, face, Paint.Align.CENTER, u);
|
||||
}
|
||||
|
||||
// ---------- The signboard and candy canes: the range; baubles: the gears ----------
|
||||
|
||||
private void drawBoard(Canvas c, int w, float u) {
|
||||
float rr = w - 40f * u, l = rr - 250f * u, t = 214f * u, b = 394f * u, mid = (l + rr) / 2f;
|
||||
r.set(l, t, rr, b);
|
||||
fill.setColor(WOOD);
|
||||
c.drawRoundRect(r, 10f * u, 10f * u, fill);
|
||||
line.setColor(0xFF8A5A33);
|
||||
line.setStrokeWidth(3f * u);
|
||||
c.drawRoundRect(r, 10f * u, 10f * u, line);
|
||||
// Snow on top.
|
||||
path.reset();
|
||||
path.moveTo(l - 4f * u, t + 4f * u);
|
||||
path.quadTo(l + 40f * u, t - 24f * u, l + 90f * u, t - 4f * u);
|
||||
path.quadTo(l + 140f * u, t - 28f * u, l + 190f * u, t - 6f * u);
|
||||
path.quadTo(l + 230f * u, t - 16f * u, rr + 4f * u, t + 4f * u);
|
||||
path.lineTo(rr + 4f * u, t + 12f * u);
|
||||
path.lineTo(l - 4f * u, t + 12f * u);
|
||||
path.close();
|
||||
fill.setColor(SNOW);
|
||||
c.drawPath(path, fill);
|
||||
label(c, "RANGE", mid, t + 52f * u, 20f * u, GOLD, face, Paint.Align.CENTER);
|
||||
label(c, fmt(v.rangeKm, "%.0f", "—"), mid, t + 128f * u, 80f * u, BONE, serif, Paint.Align.CENTER);
|
||||
label(c, "KM", mid, t + 160f * u, 20f * u, GOLD, face, Paint.Align.CENTER);
|
||||
// A candy cane per 50 km in the snow below.
|
||||
int lit = Tally.lit(v.rangeKm, 50f, CANES);
|
||||
for (int i = 0; i < CANES; i++) {
|
||||
cane(c, l + 18f * u + i * 30f * u, 488f * u, u, i < lit);
|
||||
}
|
||||
}
|
||||
|
||||
private void cane(Canvas c, float x, float foot, float u, boolean on) {
|
||||
path.reset();
|
||||
path.moveTo(x, foot);
|
||||
path.lineTo(x, foot - 58f * u);
|
||||
path.cubicTo(x, foot - 78f * u, x + 18f * u, foot - 78f * u, x + 18f * u, foot - 62f * u);
|
||||
line.setStrokeWidth(9f * u);
|
||||
line.setColor(on ? 0xFFFFFFFF : 0x40FFFFFF);
|
||||
c.drawPath(path, line);
|
||||
line.setColor(on ? RED : 0x40E53946);
|
||||
line.setPathEffect(new DashPathEffect(new float[] { 7f * u, 7f * u }, 0f));
|
||||
c.drawPath(path, line);
|
||||
line.setPathEffect(null);
|
||||
}
|
||||
|
||||
private void drawGears(Canvas c, int w, float u) {
|
||||
float rr = w - 40f * u, l = rr - 250f * u, cy = 540f * u, rad = 25f * u;
|
||||
String[] gears = { "P", "R", "N", "D" };
|
||||
String now = Gear.label(v.gear);
|
||||
float step = (rr - l) / 4f;
|
||||
for (int i = 0; i < gears.length; i++) {
|
||||
float mid = l + (i + 0.5f) * step;
|
||||
boolean on = gears[i].equals(now);
|
||||
if (on) {
|
||||
fill.setColor(0x55FFD04A);
|
||||
c.drawCircle(mid, cy, 42f * u, fill);
|
||||
}
|
||||
fill.setColor(GOLD);
|
||||
r.set(mid - 7f * u, cy - rad - 9f * u, mid + 7f * u, cy - rad + 3f * u);
|
||||
c.drawRect(r, fill);
|
||||
fill.setColor(on ? GOLD : 0xFF7A1F2A);
|
||||
c.drawCircle(mid, cy, rad, fill);
|
||||
line.setColor(on ? 0xFFFFF0B0 : 0xFFB03A48);
|
||||
line.setStrokeWidth(2f * u);
|
||||
c.drawCircle(mid, cy, rad, line);
|
||||
label(c, gears[i], mid, cy + 11f * u, 30f * u, on ? INK : 0xB0FFFFFF, serif, Paint.Align.CENTER);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- What plays: a sleigh or an angel tows it on wrapping paper ----------
|
||||
|
||||
/**
|
||||
* Santa's sleigh and an angel take turns towing a waving strip of wrapping paper with the song
|
||||
* across the sky above the tree, behind the wreath and the tags, again and again while
|
||||
* something plays. The paper's colour is fixed by the song. Returns whether it is drawn, so the
|
||||
* view keeps animating for it.
|
||||
*/
|
||||
private boolean drawSong(Canvas c, int w, float u) {
|
||||
if (v.songTitle == null) return false;
|
||||
text.setTypeface(face);
|
||||
text.setTextSize(19f * u);
|
||||
text.setStyle(Paint.Style.FILL);
|
||||
String s = SongBanner.line(text, v.songTitle, v.songArtist, Math.min(560f * u, w * 0.45f));
|
||||
float t = SystemClock.uptimeMillis() / 1000f;
|
||||
float bw = text.measureText(s) + 40f * u, bh = 28f * u, tow = 80f * u;
|
||||
float bx = SongBanner.left(t, w, bw, tow, u);
|
||||
float yc = 66f * u + (float) Math.sin(t * 1.3f) * 3f * u;
|
||||
int paper = PAPER[Math.floorMod((v.songTitle + "\n" + v.songArtist).hashCode(), PAPER.length)];
|
||||
boolean darkInk = paper == PAPER[3] || paper == PAPER[4];
|
||||
banner.draw(c, fill, line, text, s, bx, yc, bw, bh, t, u, 0f, paper, 0xCCFFFFFF, darkInk ? INK : 0xFFFFFFFF);
|
||||
boolean sleigh = SongBanner.pass(t, w, bw, tow, u) % 2 == 0;
|
||||
float right = bx + bw, cx = right + tow, cy = yc - 6f * u, size = (sleigh ? 70f : 56f) * u;
|
||||
float[] tail = sleigh ? SongCarriers.sleighTail : SongCarriers.angelTail;
|
||||
float ax = cx + tail[0] * size, ay = cy + tail[1] * size;
|
||||
line.setColor(0xCCFFFFFF);
|
||||
line.setStrokeWidth(1.5f * u);
|
||||
c.drawLine(right, yc - bh / 2f + 3f * u, ax, ay, line);
|
||||
c.drawLine(right, yc + bh / 2f - 3f * u, ax, ay, line);
|
||||
if (sleigh) SongCarriers.sleigh(c, fill, line, path, r, cx, cy, size, t, u);
|
||||
else SongCarriers.angel(c, fill, line, path, r, cx, cy, size, (float) Math.sin(t * 7f), u);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -217,9 +217,9 @@ public class MainActivity extends AppCompatActivity {
|
||||
|
||||
/** Whether the Overview hides the navigation (the button in the navigation's header). */
|
||||
static final String PREF_OVERVIEW_FULLSCREEN = "overview_fullscreen";
|
||||
/** The Overview shows what plays (Tiles, Halloween); on by default. */
|
||||
/** The Overview shows what plays (the styles that have a place for it); on by default. */
|
||||
static final String PREF_NOW_PLAYING = "now_playing";
|
||||
/** The Halloween pumpkin's light follows the music; off by default, so the analyser stays off. */
|
||||
/** The Halloween pumpkin's and the Christmas tree's light follows the music; off by default, so the analyser stays off. */
|
||||
static final String PREF_MUSIC_LIGHT = "music_light";
|
||||
|
||||
/** True while the Overview is full screen: the navigation hidden, the edge swipe armed. */
|
||||
@@ -364,10 +364,13 @@ public class MainActivity extends AppCompatActivity {
|
||||
HalloweenView halloweenView = new HalloweenView(this);
|
||||
musicTap = new MusicTap(this);
|
||||
nowPlaying = new NowPlayingWatcher(this);
|
||||
ChristmasView christmasView = new ChristmasView(this);
|
||||
halloweenView.setMusic(musicTap);
|
||||
christmasView.setMusic(musicTap);
|
||||
java.util.Collections.addAll(canvasStyles, new MinimalView(this), terminalView,
|
||||
new NixieView(this), new ChronographView(this), gridView, gridMapView, new TimeCircuitsView(this),
|
||||
new QuestView(this), visorView, new EmpireView(this), new MuertosView(this), halloweenView);
|
||||
new QuestView(this), visorView, new EmpireView(this), new MuertosView(this), halloweenView,
|
||||
christmasView);
|
||||
View[] canvasPanes = new View[canvasStyles.size()];
|
||||
for (int i = 0; i < canvasPanes.length; i++) canvasPanes[i] = drawnPane(canvasStyles.get(i));
|
||||
// Grid with map: its map layer goes under it, in the same pane.
|
||||
@@ -1787,7 +1790,8 @@ public class MainActivity extends AppCompatActivity {
|
||||
private static boolean showsNowPlaying(OverviewStyle style) {
|
||||
return style == OverviewStyle.TILES || style == OverviewStyle.HALLOWEEN
|
||||
|| style == OverviewStyle.MUERTOS || style == OverviewStyle.QUEST || style == OverviewStyle.EMPIRE
|
||||
|| style == OverviewStyle.TERMINAL || style == OverviewStyle.SCIFI;
|
||||
|| style == OverviewStyle.TERMINAL || style == OverviewStyle.SCIFI
|
||||
|| style == OverviewStyle.CHRISTMAS;
|
||||
}
|
||||
|
||||
/** The media sessions are watched only while what plays can be seen, and the switch is on. */
|
||||
@@ -1835,13 +1839,18 @@ public class MainActivity extends AppCompatActivity {
|
||||
if (!android.text.TextUtils.equals(view.getText(), text)) view.setText(text);
|
||||
}
|
||||
|
||||
/** The styles with a light that can follow the music: Halloween's pumpkin and Christmas's tree. */
|
||||
private static boolean hasMusicLight(OverviewStyle style) {
|
||||
return style == OverviewStyle.HALLOWEEN || style == OverviewStyle.CHRISTMAS;
|
||||
}
|
||||
|
||||
/**
|
||||
* The pumpkin listens only while it can be seen (the app in front, on the Overview, in
|
||||
* Halloween) and only when "Pumpkin light follows the music" is on.
|
||||
* The light listens only while it can be seen (the app in front, on the Overview, in
|
||||
* Halloween or Christmas) and only when "Light follows the music" is on.
|
||||
*/
|
||||
private void updateMusicTap() {
|
||||
ViewPager2 pager = findViewById(R.id.content);
|
||||
musicTap.setRunning(inFront && overviewStyle() == OverviewStyle.HALLOWEEN
|
||||
musicTap.setRunning(inFront && hasMusicLight(overviewStyle())
|
||||
&& prefs.getBoolean(PREF_MUSIC_LIGHT, false)
|
||||
&& pager != null && pager.getCurrentItem() == 0);
|
||||
}
|
||||
@@ -1909,7 +1918,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
int playing = showsNowPlaying(style) ? View.VISIBLE : View.GONE;
|
||||
settingsPane.findViewById(R.id.now_playing_switch).setVisibility(playing);
|
||||
settingsPane.findViewById(R.id.now_playing_hint).setVisibility(playing);
|
||||
int music = style == OverviewStyle.HALLOWEEN ? View.VISIBLE : View.GONE;
|
||||
int music = hasMusicLight(style) ? View.VISIBLE : View.GONE;
|
||||
settingsPane.findViewById(R.id.music_light_switch).setVisibility(music);
|
||||
settingsPane.findViewById(R.id.music_light_hint).setVisibility(music);
|
||||
}
|
||||
@@ -1927,7 +1936,8 @@ public class MainActivity extends AppCompatActivity {
|
||||
getString(R.string.overview_style_grid_map),
|
||||
getString(R.string.overview_style_time_circuits), getString(R.string.overview_style_quest),
|
||||
getString(R.string.overview_style_visor), getString(R.string.overview_style_empire),
|
||||
getString(R.string.overview_style_muertos), getString(R.string.overview_style_halloween) };
|
||||
getString(R.string.overview_style_muertos), getString(R.string.overview_style_halloween),
|
||||
getString(R.string.overview_style_christmas) };
|
||||
}
|
||||
|
||||
/** The style drop-down; every style is fed on every reading, only the chosen one is shown. */
|
||||
|
||||
@@ -33,7 +33,8 @@ enum OverviewStyle {
|
||||
VISOR(true, true),
|
||||
EMPIRE(false, false),
|
||||
MUERTOS(false, false),
|
||||
HALLOWEEN(false, false);
|
||||
HALLOWEEN(false, false),
|
||||
CHRISTMAS(false, false);
|
||||
|
||||
static final String PREF_KEY = "overview_style";
|
||||
|
||||
|
||||
@@ -7,14 +7,16 @@ import android.graphics.RectF;
|
||||
|
||||
/**
|
||||
* The creatures that tow a {@link SongBanner} in the styles with a sky for it, drawn flying right
|
||||
* with their centre at (x, y) and about {@code s} wide: a dragon (Quest) and an eagle (Empire).
|
||||
* with their centre at (x, y) and about {@code s} wide: a dragon (Quest), an eagle (Empire), Santa's
|
||||
* sleigh with a reindeer and an angel (Christmas).
|
||||
* {@code flap} runs from −1 to 1 with the wing beat, {@code t} is the time in seconds. The caller
|
||||
* passes its own paints and scratch objects; the paints' colour and width are set here. Where the
|
||||
* tow lines tie on is {@link #dragonTail} and {@link #eagleTail}, as offsets in units of {@code s}.
|
||||
* tow lines tie on is {@link #dragonTail}, {@link #eagleTail}, {@link #sleighTail} and {@link #angelTail}, as offsets in units of {@code s}.
|
||||
*/
|
||||
final class SongCarriers {
|
||||
|
||||
static final float[] dragonTail = { -1.0f, -0.25f }, eagleTail = { -0.55f, 0.05f };
|
||||
static final float[] dragonTail = { -1.0f, -0.25f }, eagleTail = { -0.55f, 0.05f },
|
||||
sleighTail = { -0.62f, 0f }, angelTail = { -0.45f, 0.1f };
|
||||
|
||||
private SongCarriers() {}
|
||||
|
||||
@@ -134,4 +136,132 @@ final class SongCarriers {
|
||||
c.drawLine(0.05f * s, 0.13f * s, 0.02f * s, 0.26f * s, line);
|
||||
c.restore();
|
||||
}
|
||||
|
||||
/** A reindeer with a glowing nose pulling Santa's sleigh, drawn about 1.7 s wide. */
|
||||
static void sleigh(Canvas c, Paint fill, Paint line, Path path, RectF r, float x, float y, float s,
|
||||
float t, float u) {
|
||||
final int brown = 0xFF8A5A33, red = 0xFFC62828, gold = 0xFFFFD04A;
|
||||
c.save();
|
||||
c.translate(x, y);
|
||||
// The reindeer: four legs trotting, a body, a neck, a head with antlers and a red nose.
|
||||
float swing = (float) Math.sin(t * 8f) * 0.18f;
|
||||
line.setColor(brown);
|
||||
line.setStrokeWidth(3f * u);
|
||||
c.drawLine(0.72f * s, 0.05f * s, (0.72f + swing) * s, 0.3f * s, line);
|
||||
c.drawLine(0.66f * s, 0.05f * s, (0.66f - swing) * s, 0.3f * s, line);
|
||||
c.drawLine(0.44f * s, 0.05f * s, (0.44f - swing) * s, 0.3f * s, line);
|
||||
c.drawLine(0.38f * s, 0.05f * s, (0.38f + swing) * s, 0.3f * s, line);
|
||||
r.set(0.32f * s, -0.15f * s, 0.8f * s, 0.1f * s);
|
||||
fill.setColor(brown);
|
||||
c.drawOval(r, fill);
|
||||
line.setStrokeWidth(0.1f * s);
|
||||
c.drawLine(0.76f * s, -0.08f * s, 0.86f * s, -0.2f * s, line);
|
||||
c.drawCircle(0.88f * s, -0.22f * s, 0.075f * s, fill);
|
||||
line.setColor(0xFFD9C08A);
|
||||
line.setStrokeWidth(2f * u);
|
||||
c.drawLine(0.85f * s, -0.28f * s, 0.78f * s, -0.46f * s, line);
|
||||
c.drawLine(0.82f * s, -0.37f * s, 0.9f * s, -0.4f * s, line);
|
||||
c.drawLine(0.9f * s, -0.28f * s, 0.97f * s, -0.46f * s, line);
|
||||
c.drawLine(0.94f * s, -0.37f * s, 0.86f * s, -0.42f * s, line);
|
||||
float glow = 0.75f + 0.25f * (float) Math.sin(t * 6f);
|
||||
fill.setColor(0x55FF3B30);
|
||||
c.drawCircle(0.97f * s, -0.2f * s, 0.1f * s * glow, fill);
|
||||
fill.setColor(0xFFFF3B30);
|
||||
c.drawCircle(0.96f * s, -0.2f * s, 0.04f * s, fill);
|
||||
// The harness, and the sleigh: a red body with a curled front on a gold runner.
|
||||
line.setColor(gold);
|
||||
line.setStrokeWidth(1.5f * u);
|
||||
c.drawLine(0.36f * s, -0.02f * s, 0.12f * s, -0.02f * s, line);
|
||||
path.reset();
|
||||
path.moveTo(-0.55f * s, -0.12f * s);
|
||||
path.lineTo(0.05f * s, -0.12f * s);
|
||||
path.quadTo(0.22f * s, -0.12f * s, 0.2f * s, -0.28f * s);
|
||||
path.lineTo(0.12f * s, 0.1f * s);
|
||||
path.lineTo(-0.5f * s, 0.1f * s);
|
||||
path.quadTo(-0.64f * s, 0.08f * s, -0.6f * s, -0.12f * s);
|
||||
path.close();
|
||||
fill.setColor(red);
|
||||
c.drawPath(path, fill);
|
||||
line.setColor(gold);
|
||||
line.setStrokeWidth(2f * u);
|
||||
c.drawPath(path, line);
|
||||
path.reset();
|
||||
path.moveTo(-0.62f * s, 0.19f * s);
|
||||
path.lineTo(0.18f * s, 0.19f * s);
|
||||
path.quadTo(0.32f * s, 0.19f * s, 0.3f * s, 0.07f * s);
|
||||
line.setStrokeWidth(3f * u);
|
||||
c.drawPath(path, line);
|
||||
c.drawLine(-0.4f * s, 0.1f * s, -0.4f * s, 0.19f * s, line);
|
||||
c.drawLine(0.0f, 0.1f * s, 0.0f, 0.19f * s, line);
|
||||
// Santa with his sack.
|
||||
fill.setColor(0xFF7A4A22);
|
||||
c.drawCircle(-0.5f * s, -0.18f * s, 0.11f * s, fill);
|
||||
fill.setColor(red);
|
||||
c.drawCircle(-0.25f * s, -0.2f * s, 0.12f * s, fill);
|
||||
fill.setColor(0xFFF2CFA0);
|
||||
c.drawCircle(-0.22f * s, -0.36f * s, 0.07f * s, fill);
|
||||
fill.setColor(0xFFFFFFFF);
|
||||
r.set(-0.28f * s, -0.37f * s, -0.15f * s, -0.28f * s);
|
||||
c.drawOval(r, fill);
|
||||
path.reset();
|
||||
path.moveTo(-0.29f * s, -0.4f * s);
|
||||
path.lineTo(-0.14f * s, -0.4f * s);
|
||||
path.lineTo(-0.3f * s, -0.56f * s);
|
||||
path.close();
|
||||
fill.setColor(red);
|
||||
c.drawPath(path, fill);
|
||||
fill.setColor(0xFFFFFFFF);
|
||||
c.drawCircle(-0.3f * s, -0.57f * s, 0.035f * s, fill);
|
||||
c.restore();
|
||||
}
|
||||
|
||||
/** A small angel flying right with a trumpet, wings beating. */
|
||||
static void angel(Canvas c, Paint fill, Paint line, Path path, RectF r, float x, float y, float s,
|
||||
float flap, float u) {
|
||||
final int white = 0xFFF4F8FF, gold = 0xFFFFD04A;
|
||||
c.save();
|
||||
c.translate(x, y);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
c.save();
|
||||
c.translate(-0.05f * s, -0.12f * s);
|
||||
c.rotate(-125f + flap * 28f + i * 14f);
|
||||
r.set(0, -0.06f * s, (0.85f - 0.07f * i) * s, 0.06f * s);
|
||||
fill.setColor(white);
|
||||
c.drawOval(r, fill);
|
||||
line.setColor(0xFFB9C6DC);
|
||||
line.setStrokeWidth(1.5f * u);
|
||||
c.drawOval(r, line);
|
||||
c.restore();
|
||||
}
|
||||
// The robe trails behind, with a gold sash.
|
||||
path.reset();
|
||||
path.moveTo(0.2f * s, -0.14f * s);
|
||||
path.quadTo(0.28f * s, 0.05f * s, 0.05f * s, 0.12f * s);
|
||||
path.lineTo(-0.5f * s, 0.22f * s);
|
||||
path.quadTo(-0.35f * s, -0.02f * s, -0.1f * s, -0.14f * s);
|
||||
path.close();
|
||||
fill.setColor(white);
|
||||
c.drawPath(path, fill);
|
||||
line.setColor(0xFFB9C6DC);
|
||||
line.setStrokeWidth(1.5f * u);
|
||||
c.drawPath(path, line);
|
||||
line.setColor(gold);
|
||||
line.setStrokeWidth(3f * u);
|
||||
c.drawLine(0.02f * s, -0.12f * s, 0.12f * s, 0.06f * s, line);
|
||||
// The head with a halo, and the trumpet held out in front.
|
||||
fill.setColor(0xFFF2CFA0);
|
||||
c.drawCircle(0.3f * s, -0.2f * s, 0.1f * s, fill);
|
||||
fill.setColor(gold);
|
||||
r.set(0.2f * s, -0.4f * s, 0.4f * s, -0.33f * s);
|
||||
c.drawOval(r, line);
|
||||
line.setStrokeWidth(2.5f * u);
|
||||
c.drawLine(0.3f * s, -0.04f * s, 0.7f * s, -0.1f * s, line);
|
||||
path.reset();
|
||||
path.moveTo(0.66f * s, -0.1f * s);
|
||||
path.lineTo(0.9f * s, -0.22f * s);
|
||||
path.lineTo(0.9f * s, 0.02f * s);
|
||||
path.close();
|
||||
c.drawPath(path, fill);
|
||||
c.restore();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ final class StylePalette {
|
||||
case EMPIRE: return R.style.ThemeOverlay_EV_Style_Empire;
|
||||
case MUERTOS: return R.style.ThemeOverlay_EV_Style_Muertos;
|
||||
case HALLOWEEN: return R.style.ThemeOverlay_EV_Style_Halloween;
|
||||
case CHRISTMAS: return R.style.ThemeOverlay_EV_Style_Christmas;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user