App 2.45.2: Halloween's song banner waves
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
This commit is contained in:
@@ -140,6 +140,7 @@ 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-11-02 — App 2.45.2: Halloween's song banner **waves** (owner: "Can it also be a bit wavy?"): waves run from the bat to the tail, still where the bat holds it and up to 7 units at the tail, and the text follows them (`drawTextOnPath`). Tested: emulator with the throwaway fake session, unit tests, lint. Not tried on the car.
|
||||||
- 2026-11-02 — App 2.45.1 published (`car-2.45.1`).
|
- 2026-11-02 — App 2.45.1 published (`car-2.45.1`).
|
||||||
- 2026-11-02 — App 2.45.1: **Halloween's song flies through the sky** (owner: "Instead of taking up screen space on the bottom, can we somehow include the title in the theme itself? ... have text fly through the air"). A bat tows a parchment banner with "♪ title — artist" from left to right across the strip of sky above the sign, behind the moon, one pass about every half minute, again and again while something plays (the title is cut first so the artist stays); the line at the foot of the hill is gone. While a banner flies the view draws about 30 frames a second, as it does while driving. Tested: emulator with the throwaway fake session (banner clear of the sign along the whole pass), unit tests, lint. Not tried on the car.
|
- 2026-11-02 — App 2.45.1: **Halloween's song flies through the sky** (owner: "Instead of taking up screen space on the bottom, can we somehow include the title in the theme itself? ... have text fly through the air"). A bat tows a parchment banner with "♪ title — artist" from left to right across the strip of sky above the sign, behind the moon, one pass about every half minute, again and again while something plays (the title is cut first so the artist stays); the line at the foot of the hill is gone. While a banner flies the view draws about 30 frames a second, as it does while driving. Tested: emulator with the throwaway fake session (banner clear of the sign along the whole pass), unit tests, lint. Not tried on the car.
|
||||||
- 2026-11-02 — App 2.45.0 published (`car-2.45.0`).
|
- 2026-11-02 — App 2.45.0 published (`car-2.45.0`).
|
||||||
|
|||||||
@@ -267,7 +267,7 @@ you drive the bats fly, the potion bubbles and the ghost floats (where there is
|
|||||||
this screen). With "Pumpkin light follows the music" on (Settings, shown with this style; off by
|
this screen). With "Pumpkin light follows the music" on (Settings, shown with this style; off by
|
||||||
default) and Bluetooth or CarPlay music playing, the pumpkin's glow flares and its carving
|
default) and Bluetooth or CarPlay music playing, the pumpkin's glow flares and its carving
|
||||||
brightens on each kick drum; the radio cannot be heard, so it burns as usual then. What plays
|
brightens on each kick drum; the radio cannot be heard, so it burns as usual then. What plays
|
||||||
(Bluetooth, CarPlay or the radio) flies across the top of the sky on a parchment banner that a
|
(Bluetooth, CarPlay or the radio) flies across the top of the sky on a waving parchment banner that a
|
||||||
bat tows, behind the moon, again and again while it plays. The app turns the
|
bat tows, behind the moon, again and again while it plays. The app turns the
|
||||||
pumpkin's orange with it.
|
pumpkin's orange with it.
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -34,8 +34,8 @@ android {
|
|||||||
applicationId "com.stinnesbeck.nilsmg4"
|
applicationId "com.stinnesbeck.nilsmg4"
|
||||||
minSdk 27
|
minSdk 27
|
||||||
targetSdk 36
|
targetSdk 36
|
||||||
versionCode 161
|
versionCode 162
|
||||||
versionName "2.45.1"
|
versionName "2.45.2"
|
||||||
|
|
||||||
// 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.
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import android.graphics.Canvas;
|
|||||||
import android.graphics.LinearGradient;
|
import android.graphics.LinearGradient;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
import android.graphics.Path;
|
import android.graphics.Path;
|
||||||
|
import android.graphics.PathMeasure;
|
||||||
import android.graphics.RadialGradient;
|
import android.graphics.RadialGradient;
|
||||||
import android.graphics.RectF;
|
import android.graphics.RectF;
|
||||||
import android.graphics.Shader;
|
import android.graphics.Shader;
|
||||||
@@ -50,6 +51,8 @@ final class HalloweenView extends ClusterCanvasView {
|
|||||||
private final Paint image = new Paint(Paint.FILTER_BITMAP_FLAG);
|
private final Paint image = new Paint(Paint.FILTER_BITMAP_FLAG);
|
||||||
private final Paint glow = new Paint(Paint.ANTI_ALIAS_FLAG);
|
private final Paint glow = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||||
private final Path path = new Path();
|
private final Path path = new Path();
|
||||||
|
private final Path baseline = new Path();
|
||||||
|
private final PathMeasure measure = new PathMeasure();
|
||||||
private final RectF r = new RectF();
|
private final RectF r = new RectF();
|
||||||
private final Typeface serif = Typeface.create("serif", Typeface.BOLD);
|
private final Typeface serif = Typeface.create("serif", Typeface.BOLD);
|
||||||
private final Typeface face = Typeface.create("sans-serif-condensed", Typeface.BOLD);
|
private final Typeface face = Typeface.create("sans-serif-condensed", Typeface.BOLD);
|
||||||
@@ -280,7 +283,7 @@ final class HalloweenView extends ClusterCanvasView {
|
|||||||
// ---------- What plays: a bat tows it across the sky on a banner ----------
|
// ---------- What plays: a bat tows it across the sky on a banner ----------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A bat pulls a parchment banner with the song from left to right, high in the sky, behind the
|
* A bat pulls a waving parchment banner with the song from left to right, high in the sky, behind the
|
||||||
* sign and the moon, again and again while something plays. Returns whether it is drawn, so
|
* sign and the moon, again and again while something plays. Returns whether it is drawn, so
|
||||||
* the view keeps animating for it.
|
* the view keeps animating for it.
|
||||||
*/
|
*/
|
||||||
@@ -300,20 +303,33 @@ final class HalloweenView extends ClusterCanvasView {
|
|||||||
// In the strip of sky above the sign, clear of it.
|
// In the strip of sky above the sign, clear of it.
|
||||||
float yc = 34f * u + (float) Math.sin(t * 1.3f) * 4f * u;
|
float yc = 34f * u + (float) Math.sin(t * 1.3f) * 4f * u;
|
||||||
float top = yc - bh / 2f, bottom = yc + bh / 2f, right = bx + bw, notch = 12f * u;
|
float top = yc - bh / 2f, bottom = yc + bh / 2f, right = bx + bw, notch = 12f * u;
|
||||||
// The ribbon, its trailing end cut as a swallowtail.
|
// The ribbon, waving in the wind: still where the bat holds it, rippling more towards its
|
||||||
|
// tail, which is cut as a swallowtail. The text follows the waves.
|
||||||
|
int steps = Math.max(8, Math.round(bw / (12f * u)));
|
||||||
path.reset();
|
path.reset();
|
||||||
path.moveTo(bx, top);
|
for (int i = 0; i <= steps; i++) {
|
||||||
path.lineTo(right, top);
|
float x = right - bw * i / steps;
|
||||||
path.lineTo(right, bottom);
|
if (i == 0) path.moveTo(x, top + wave(x, right, bw, t, u));
|
||||||
path.lineTo(bx, bottom);
|
else path.lineTo(x, top + wave(x, right, bw, t, u));
|
||||||
path.lineTo(bx + notch, yc);
|
}
|
||||||
|
path.lineTo(bx + notch, yc + wave(bx + notch, right, bw, t, u));
|
||||||
|
for (int i = steps; i >= 0; i--) path.lineTo(right - bw * i / steps, bottom + wave(right - bw * i / steps, right, bw, t, u));
|
||||||
path.close();
|
path.close();
|
||||||
fill.setColor(BONE);
|
fill.setColor(BONE);
|
||||||
c.drawPath(path, fill);
|
c.drawPath(path, fill);
|
||||||
line.setColor(0xFF8A6A3A);
|
line.setColor(0xFF8A6A3A);
|
||||||
line.setStrokeWidth(2f * u);
|
line.setStrokeWidth(2f * u);
|
||||||
c.drawPath(path, line);
|
c.drawPath(path, line);
|
||||||
label(c, s, bx + notch + (bw - notch) / 2f, yc + 7f * u, 19f * u, INK, face, Paint.Align.CENTER);
|
baseline.reset();
|
||||||
|
for (int i = 0; i <= steps; i++) {
|
||||||
|
float x = bx + notch + (bw - notch) * i / steps;
|
||||||
|
if (i == 0) baseline.moveTo(x, yc + wave(x, right, bw, t, u));
|
||||||
|
else baseline.lineTo(x, yc + wave(x, right, bw, t, u));
|
||||||
|
}
|
||||||
|
measure.setPath(baseline, false);
|
||||||
|
text.setColor(INK);
|
||||||
|
text.setTextAlign(Paint.Align.LEFT);
|
||||||
|
c.drawTextOnPath(s, baseline, Math.max(0f, (measure.getLength() - text.measureText(s)) / 2f), 7f * u, text);
|
||||||
// The tow lines, and the bat at their end.
|
// The tow lines, and the bat at their end.
|
||||||
float batX = right + tow, batY = yc - 10f * u;
|
float batX = right + tow, batY = yc - 10f * u;
|
||||||
line.setColor(BAT_EDGE);
|
line.setColor(BAT_EDGE);
|
||||||
@@ -324,6 +340,15 @@ final class HalloweenView extends ClusterCanvasView {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How far the banner bends at {@code x}: nothing at the bat's end ({@code right}), up to 7 units
|
||||||
|
* at the tail, in waves that run from the bat to the tail.
|
||||||
|
*/
|
||||||
|
private static float wave(float x, float right, float bw, float t, float u) {
|
||||||
|
float along = right - x;
|
||||||
|
return 7f * u * (along / bw) * (float) Math.sin(along / (170f * u) * 2f * Math.PI - t * 5f);
|
||||||
|
}
|
||||||
|
|
||||||
/** {@code s} cut with "…" to fit {@code room} in the text paint as set. */
|
/** {@code s} cut with "…" to fit {@code room} in the text paint as set. */
|
||||||
private String cut(String s, float room) {
|
private String cut(String s, float room) {
|
||||||
if (text.measureText(s) <= room) return s;
|
if (text.measureText(s) <= room) return s;
|
||||||
|
|||||||
Reference in New Issue
Block a user