App 2.47.0: now playing in Quest, Empire, Terminal and Sci-fi
Quest: a dragon tows a scroll. Empire: an eagle flies one. Terminal: a PLAYING row. Sci-fi: the song in black on the top bar. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
This commit is contained in:
+2
-2
@@ -34,8 +34,8 @@ android {
|
||||
applicationId "com.stinnesbeck.nilsmg4"
|
||||
minSdk 27
|
||||
targetSdk 36
|
||||
versionCode 163
|
||||
versionName "2.46.0"
|
||||
versionCode 164
|
||||
versionName "2.47.0"
|
||||
|
||||
// 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.
|
||||
|
||||
@@ -38,6 +38,7 @@ final class EmpireView extends ClusterCanvasView {
|
||||
private final Paint fill = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private final Paint text = 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(Typeface.SERIF, Typeface.BOLD);
|
||||
private final Typeface sans = Typeface.create("sans-serif-condensed", Typeface.BOLD);
|
||||
@@ -61,10 +62,38 @@ final class EmpireView extends ClusterCanvasView {
|
||||
advance(moving);
|
||||
float top = 46f * u, bottom = h - 186f * u;
|
||||
drawWorld(c, w, top, bottom, u);
|
||||
boolean song = drawSong(c, w, top, u);
|
||||
drawSpeed(c, w, top, u);
|
||||
drawResources(c, w, top, u);
|
||||
drawPanel(c, w, h, bottom, u);
|
||||
keepAnimating(moving, 33);
|
||||
keepAnimating(moving || song, 33);
|
||||
}
|
||||
|
||||
// ---------- What plays: an eagle brings it on a scroll ----------
|
||||
|
||||
/**
|
||||
* An eagle flies a waving scroll with the song across the top of the world, behind the speed,
|
||||
* again and again while something plays. Returns whether it is drawn, so the view keeps animating.
|
||||
*/
|
||||
private boolean drawSong(Canvas c, float w, float top, float u) {
|
||||
if (v.songTitle == null) return false;
|
||||
text.setTypeface(serif);
|
||||
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, size = 58f * u, tow = size * 1.0f;
|
||||
float bx = SongBanner.left(t, Math.round(w), bw, tow, u);
|
||||
float yc = top + 30f * u + (float) Math.sin(t * 1.3f) * 3f * u;
|
||||
banner.draw(c, fill, line, text, s, bx, yc, bw, bh, t, u, 0f, 0xFFEFE0B8, WOOD_DARK, 0xFF2A1A08);
|
||||
float right = bx + bw, cx = right + tow, cy = yc - 10f * u;
|
||||
line.setColor(0xCCFFFFFF);
|
||||
line.setStrokeWidth(1.5f * u);
|
||||
float ax = cx + SongCarriers.eagleTail[0] * size, ay = cy + SongCarriers.eagleTail[1] * size;
|
||||
c.drawLine(right, yc - bh / 2f + 3f * u, ax, ay, line);
|
||||
c.drawLine(right, yc + bh / 2f - 3f * u, ax, ay, line);
|
||||
SongCarriers.eagle(c, fill, line, path, r, cx, cy, size, (float) Math.sin(t * 7f), t, u);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void advance(boolean moving) {
|
||||
|
||||
@@ -1786,7 +1786,8 @@ public class MainActivity extends AppCompatActivity {
|
||||
/** The styles that show what plays. */
|
||||
private static boolean showsNowPlaying(OverviewStyle style) {
|
||||
return style == OverviewStyle.TILES || style == OverviewStyle.HALLOWEEN
|
||||
|| style == OverviewStyle.MUERTOS;
|
||||
|| style == OverviewStyle.MUERTOS || style == OverviewStyle.QUEST || style == OverviewStyle.EMPIRE
|
||||
|| style == OverviewStyle.TERMINAL || style == OverviewStyle.SCIFI;
|
||||
}
|
||||
|
||||
/** The media sessions are watched only while what plays can be seen, and the switch is on. */
|
||||
|
||||
@@ -37,6 +37,7 @@ final class QuestView extends ClusterCanvasView {
|
||||
private final Paint text = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private final Paint skyPaint = new Paint();
|
||||
private final Path path = new Path();
|
||||
private final SongBanner banner = new SongBanner();
|
||||
private final RectF r = new RectF();
|
||||
private final Typeface serif = Typeface.create(Typeface.SERIF, Typeface.BOLD);
|
||||
private final Typeface sans = Typeface.create("sans-serif-condensed", Typeface.BOLD);
|
||||
@@ -54,6 +55,7 @@ final class QuestView extends ClusterCanvasView {
|
||||
if (w <= 0 || h <= 0) return;
|
||||
float u = Math.min(h / 640f, w / 1100f);
|
||||
drawLandscape(c, w, h);
|
||||
boolean song = drawSong(c, w, u);
|
||||
drawPlayer(c, u);
|
||||
drawMinimap(c, w, u);
|
||||
drawSpeed(c, w, u);
|
||||
@@ -62,6 +64,35 @@ final class QuestView extends ClusterCanvasView {
|
||||
drawChat(c, u);
|
||||
drawActionBar(c, w, h, u);
|
||||
drawXp(c, w, h, u);
|
||||
keepAnimating(song, 33);
|
||||
}
|
||||
|
||||
// ---------- What plays: a dragon tows it on a scroll ----------
|
||||
|
||||
/**
|
||||
* A dragon tows a waving scroll with the song across the top of the sky, behind the frames, again
|
||||
* and again while something plays. Returns whether it is drawn, so the view keeps animating.
|
||||
*/
|
||||
private boolean drawSong(Canvas c, float w, float u) {
|
||||
if (v.songTitle == null) return false;
|
||||
text.setTypeface(serif);
|
||||
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, size = 58f * u, tow = size * 1.3f;
|
||||
int wi = Math.round(w);
|
||||
float bx = SongBanner.left(t, wi, bw, tow, u);
|
||||
float yc = 72f * u + (float) Math.sin(t * 1.3f) * 3f * u;
|
||||
banner.draw(c, fill, line, text, s, bx, yc, bw, bh, t, u, 0f, 0xFFE8D5A8, GOLD_DARK, 0xFF2A1A08);
|
||||
float right = bx + bw, cx = right + tow, cy = yc - 8f * u;
|
||||
line.setColor(0xCCFFFFFF);
|
||||
line.setStrokeWidth(1.5f * u);
|
||||
float ax = cx + SongCarriers.dragonTail[0] * size, ay = cy + SongCarriers.dragonTail[1] * size;
|
||||
c.drawLine(right, yc - bh / 2f + 3f * u, ax, ay, line);
|
||||
c.drawLine(right, yc + bh / 2f - 3f * u, ax, ay, line);
|
||||
SongCarriers.dragon(c, fill, line, path, r, cx, cy, size, (float) Math.sin(t * 6f), t, u);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---------- World ----------
|
||||
|
||||
@@ -67,6 +67,13 @@ public final class SciFiView extends View {
|
||||
label(c, redAlert ? "RED ALERT · LOW RESERVES" : "PROPULSION STATUS", w * 0.61f + 10f * d,
|
||||
bar * 0.86f, bar * 1.05f, redAlert ? alert : orange, Paint.Align.LEFT);
|
||||
pill(c, w * 0.94f, 0, w, bar, frame2);
|
||||
if (v.songTitle != null) {
|
||||
// What plays, in black on the top bar, cut to its length.
|
||||
float x = side + 16f * d, size = bar * 0.8f;
|
||||
text.setTextSize(size);
|
||||
String s = SongBanner.line(text, v.songTitle, v.songArtist, w * 0.60f - x - 16f * d);
|
||||
label(c, s, x, bar * 0.78f, size, 0xFF000000, Paint.Align.LEFT);
|
||||
}
|
||||
// The bottom bar's end: odometer and time.
|
||||
String odo = v.odometerKm == null ? "—" : String.format(Locale.US, "%06.0f", v.odometerKm);
|
||||
label(c, "ODO " + odo + " · " + (v.clock.isEmpty() ? "—" : v.clock),
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
package com.evsuite.abrp;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
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).
|
||||
* {@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}.
|
||||
*/
|
||||
final class SongCarriers {
|
||||
|
||||
static final float[] dragonTail = { -1.0f, -0.25f }, eagleTail = { -0.55f, 0.05f };
|
||||
|
||||
private SongCarriers() {}
|
||||
|
||||
static void dragon(Canvas c, Paint fill, Paint line, Path path, RectF r, float x, float y, float s,
|
||||
float flap, float t, float u) {
|
||||
final int red = 0xFFB3262E, dark = 0xFF6E1219, belly = 0xFFE8B84A;
|
||||
c.save();
|
||||
c.translate(x, y);
|
||||
// The wing behind the body: a membrane between three fingers, lifted and dropped by the flap.
|
||||
c.save();
|
||||
c.translate(-0.05f * s, -0.2f * s);
|
||||
c.rotate(-120f + flap * 30f);
|
||||
path.reset();
|
||||
path.moveTo(0, 0);
|
||||
path.lineTo(0.95f * s, -0.05f * s);
|
||||
path.quadTo(0.8f * s, 0.15f * s, 0.62f * s, 0.12f * s);
|
||||
path.quadTo(0.5f * s, 0.3f * s, 0.3f * s, 0.22f * s);
|
||||
path.quadTo(0.2f * s, 0.35f * s, 0, 0.15f * s);
|
||||
path.close();
|
||||
fill.setColor(dark);
|
||||
c.drawPath(path, fill);
|
||||
line.setColor(0xFF2A0508);
|
||||
line.setStrokeWidth(2f * u);
|
||||
c.drawPath(path, line);
|
||||
c.drawLine(0, 0.05f * s, 0.62f * s, 0.12f * s, line);
|
||||
c.restore();
|
||||
// The tail, ending in a spade.
|
||||
path.reset();
|
||||
path.moveTo(-0.4f * s, 0f);
|
||||
path.cubicTo(-0.9f * s, 0.1f * s, -0.8f * s, -0.3f * s, -1.0f * s, -0.25f * s);
|
||||
line.setColor(red);
|
||||
line.setStrokeWidth(0.13f * s);
|
||||
line.setStrokeCap(Paint.Cap.ROUND);
|
||||
c.drawPath(path, line);
|
||||
line.setStrokeCap(Paint.Cap.BUTT);
|
||||
// Legs.
|
||||
line.setColor(dark);
|
||||
line.setStrokeWidth(3f * u);
|
||||
c.drawLine(-0.25f * s, 0.12f * s, -0.3f * s, 0.34f * s, line);
|
||||
c.drawLine(0.1f * s, 0.12f * s, 0.18f * s, 0.34f * s, line);
|
||||
// Body with a pale belly.
|
||||
r.set(-0.45f * s, -0.17f * s, 0.3f * s, 0.17f * s);
|
||||
fill.setColor(red);
|
||||
c.drawOval(r, fill);
|
||||
r.set(-0.4f * s, 0.0f, 0.25f * s, 0.15f * s);
|
||||
fill.setColor(belly);
|
||||
c.drawOval(r, fill);
|
||||
// Neck and head, a horn, an eye, and a small flame at the mouth.
|
||||
path.reset();
|
||||
path.moveTo(0.2f * s, -0.05f * s);
|
||||
path.quadTo(0.4f * s, -0.05f * s, 0.5f * s, -0.3f * s);
|
||||
line.setColor(red);
|
||||
line.setStrokeWidth(0.15f * s);
|
||||
c.drawPath(path, line);
|
||||
r.set(0.46f * s, -0.45f * s, 0.86f * s, -0.24f * s);
|
||||
fill.setColor(red);
|
||||
c.drawOval(r, fill);
|
||||
line.setColor(belly);
|
||||
line.setStrokeWidth(3f * u);
|
||||
c.drawLine(0.5f * s, -0.44f * s, 0.38f * s, -0.62f * s, line);
|
||||
fill.setColor(0xFFFFE04A);
|
||||
c.drawCircle(0.62f * s, -0.37f * s, 0.035f * s, fill);
|
||||
fill.setColor(0xFF2A0508);
|
||||
c.drawCircle(0.63f * s, -0.37f * s, 0.016f * s, fill);
|
||||
float flick = 0.8f + 0.2f * (float) Math.sin(t * 25f);
|
||||
fill.setColor(0xFFFF8A1F);
|
||||
c.drawCircle(0.93f * s, -0.33f * s, 0.07f * s * flick, fill);
|
||||
fill.setColor(0xFFFFE04A);
|
||||
c.drawCircle(0.95f * s, -0.33f * s, 0.035f * s * flick, fill);
|
||||
c.restore();
|
||||
}
|
||||
|
||||
static void eagle(Canvas c, Paint fill, Paint line, Path path, RectF r, float x, float y, float s,
|
||||
float flap, float t, float u) {
|
||||
final int brown = 0xFF6B4423, dark = 0xFF3E2610, white = 0xFFF4EAD2, beak = 0xFFF2B21B;
|
||||
c.save();
|
||||
c.translate(x, y);
|
||||
// The tail, a short fan.
|
||||
path.reset();
|
||||
path.moveTo(-0.3f * s, 0f);
|
||||
path.lineTo(-0.58f * s, -0.1f * s);
|
||||
path.lineTo(-0.6f * s, 0.02f * s);
|
||||
path.lineTo(-0.55f * s, 0.14f * s);
|
||||
path.close();
|
||||
fill.setColor(dark);
|
||||
c.drawPath(path, fill);
|
||||
// The far wing, then the near one: feathers fanned from the shoulder, lifted by the flap.
|
||||
for (int wing = 0; wing < 2; wing++) {
|
||||
for (int i = 0; i < 5; i++) {
|
||||
c.save();
|
||||
c.translate(0.02f * s, -0.08f * s);
|
||||
c.rotate(-130f + flap * 32f + i * 13f + (wing == 0 ? 8f : 0f));
|
||||
r.set(0, -0.05f * s, (0.95f - 0.08f * Math.abs(i - 2)) * s, 0.05f * s);
|
||||
fill.setColor(wing == 0 ? dark : (i % 2 == 0 ? brown : 0xFF84552E));
|
||||
c.drawOval(r, fill);
|
||||
c.restore();
|
||||
}
|
||||
}
|
||||
// Body, white head, hooked beak, eye, talons.
|
||||
r.set(-0.35f * s, -0.14f * s, 0.3f * s, 0.14f * s);
|
||||
fill.setColor(brown);
|
||||
c.drawOval(r, fill);
|
||||
fill.setColor(white);
|
||||
c.drawCircle(0.34f * s, -0.12f * s, 0.12f * s, fill);
|
||||
path.reset();
|
||||
path.moveTo(0.42f * s, -0.16f * s);
|
||||
path.quadTo(0.6f * s, -0.15f * s, 0.55f * s, -0.04f * s);
|
||||
path.lineTo(0.43f * s, -0.08f * s);
|
||||
path.close();
|
||||
fill.setColor(beak);
|
||||
c.drawPath(path, fill);
|
||||
fill.setColor(0xFF1A1008);
|
||||
c.drawCircle(0.38f * s, -0.15f * s, 0.022f * s, fill);
|
||||
line.setColor(beak);
|
||||
line.setStrokeWidth(2.5f * u);
|
||||
c.drawLine(-0.1f * s, 0.13f * s, -0.14f * s, 0.26f * s, line);
|
||||
c.drawLine(0.05f * s, 0.13f * s, 0.02f * s, 0.26f * s, line);
|
||||
c.restore();
|
||||
}
|
||||
}
|
||||
@@ -84,6 +84,14 @@ final class TerminalView extends ClusterCanvasView {
|
||||
y += line;
|
||||
print(c, row("TYRES", "FL " + v.tyreFl + " FR " + v.tyreFr + " RL " + v.tyreRl + " RR " + v.tyreRr), x, y, green);
|
||||
y += line * 1.4f;
|
||||
if (v.songTitle != null) {
|
||||
// What plays, cut to the row's width.
|
||||
String label = String.format(Locale.US, "%-9s", "PLAYING");
|
||||
String s = v.songTitle + (v.songArtist == null ? "" : " — " + v.songArtist);
|
||||
int fits = text.breakText(s, true, Math.max(0f, getWidth() - x - text.measureText(label)), null);
|
||||
print(c, label + (fits < s.length() ? s.substring(0, Math.max(0, fits - 1)).trim() + "…" : s), x, y, green);
|
||||
y += line;
|
||||
}
|
||||
boolean cursor = (SystemClock.uptimeMillis() / 500) % 2 == 0;
|
||||
print(c, "[" + (v.clock.isEmpty() ? "--:--" : v.clock) + "] nils@mg4:~$ " + (cursor ? "█" : ""), x, y, green);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user