f13d8ad0ed
The card behind a media tile shows the album cover, the title, the artist and the album, a progress bar that runs while the track plays with the times at its ends, round keys for previous, play or pause and next, and a volume row with a mute key, a wide slider and the percentage. Keys the player lacks stay faded, an off player shows one power key, a stream without a length shows no bar, and the CYD shows the player's icon in the cover's place. A media tile over the whole page is the same card under the tile's head. media_card.h holds the geometry, tests/test_media_card.cpp checks every shape. The cover comes the way camera images do: the screen asks with the size and the colour behind the corners, the app fetches the picture from Home Assistant, cuts it square, rounds the corners over that colour and serves it on port 8098; the Guition loads it once and asks again only when the picture's mark in the state changes. Firmware 0.2.64. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
104 lines
6.2 KiB
C++
104 lines
6.2 KiB
C++
// clang++ -std=c++17 -Wall -Wextra -Werror -I. tests/test_media_card.cpp -o /tmp/test_media_card && /tmp/test_media_card
|
|
#include "../components/smart_display/media_card.h"
|
|
#include <cassert>
|
|
#include <cstdio>
|
|
|
|
using namespace media_card;
|
|
|
|
static bool inside(const Rect &r, int width, int height) { return r.x >= 0 && r.y >= 0 && r.right() <= width && r.bottom() <= height; }
|
|
static bool apart(const Rect &a, const Rect &b) { return a.right() <= b.x || b.right() <= a.x || a.bottom() <= b.y || b.bottom() <= a.y; }
|
|
static bool above(const Rect &a, const Rect &b) { return a.bottom() <= b.y; }
|
|
|
|
// Every part inside the area, nothing over anything else, the keys in one row and the volume row at the bottom.
|
|
static void sound(const Layout &l, int width, int height) {
|
|
const Rect *parts[] = {&l.art, &l.title, &l.bar, &l.prev, &l.play, &l.next, &l.mute, &l.volume, &l.percent};
|
|
for (const Rect *r : parts) assert(r->w > 0 && r->h > 0 && inside(*r, width, height));
|
|
if (l.artist) assert(l.artist_line.h > 0 && inside(l.artist_line, width, height));
|
|
if (l.times) assert(l.elapsed.h > 0 && l.total.h > 0 && inside(l.elapsed, width, height) && inside(l.total, width, height));
|
|
// The keys share a centre line, the play key is the biggest, previous and next are alike.
|
|
assert(l.prev.cy() == l.play.cy() && l.next.cy() == l.play.cy());
|
|
assert(l.play.w > l.prev.w && l.prev.w == l.next.w && l.prev.w == l.prev.h);
|
|
assert(l.prev.right() < l.play.x && l.play.right() < l.next.x);
|
|
// The volume row runs along the bottom under everything else: the mute key, the slider, the percentage.
|
|
assert(l.mute.right() < l.volume.x && l.volume.right() < l.percent.x);
|
|
for (const Rect *r : {&l.art, &l.title, &l.bar, &l.prev, &l.play, &l.next}) assert(above(*r, l.volume) && above(*r, l.mute));
|
|
// Nothing overlaps.
|
|
const Rect *rects[] = {&l.art, &l.title, &l.artist_line, &l.bar, &l.elapsed, &l.total, &l.prev, &l.play, &l.next, &l.mute, &l.volume, &l.percent};
|
|
for (const Rect *a : rects) for (const Rect *b : rects) if (a != b && a->w && b->w) assert(apart(*a, *b));
|
|
// The art is square and its corner follows its size.
|
|
assert(l.art.w == l.art.h && l.art_radius == radius_for(l.art.w));
|
|
}
|
|
|
|
int main() {
|
|
// The Guition's card: 480 wide under its top bar, a tall area, so everything stacks under the art.
|
|
{
|
|
Metrics m; Layout l = layout(m, 480, 396);
|
|
sound(l, 480, 396);
|
|
assert(!l.wide && l.times && l.artist);
|
|
assert(l.art.w >= 160 && l.art.cx() == 240);
|
|
assert(above(l.art, l.title) && above(l.title, l.artist_line) && above(l.artist_line, l.bar) && above(l.bar, l.play));
|
|
assert(l.play.cx() == 240 && l.title.x == 24 && l.title.w == 432);
|
|
// The times sit at the ends of the bar, on its line.
|
|
assert(l.elapsed.cy() == l.bar.cy() && l.total.cy() == l.bar.cy() && l.elapsed.right() <= l.bar.x && l.bar.right() <= l.total.x);
|
|
assert(l.elapsed.x == l.title.x && l.total.right() == l.title.right());
|
|
// The keys are big enough for a thumb.
|
|
assert(l.play.w >= 64 && l.prev.w >= 52);
|
|
printf("guition card: art %d at %d,%d keys y=%d volume y=%d\n", l.art.w, l.art.x, l.art.y, l.play.y, l.volume.y);
|
|
}
|
|
// The CYD's card: 320 wide, 192 tall under its bar: the art at the left, the column beside it, times included.
|
|
{
|
|
Metrics m; m.large = false; m.title_h = 21; m.artist_h = 17; m.small_h = 13;
|
|
Layout l = layout(m, 320, 192);
|
|
sound(l, 320, 192);
|
|
assert(l.wide && l.times && l.artist && l.art.w == 120);
|
|
assert(l.title.x > l.art.right() && l.title.y >= l.art.y && l.play.bottom() <= l.art.bottom());
|
|
assert(l.title.right() == 310 && l.play.cx() == l.title.cx());
|
|
printf("cyd card: art %d column x=%d w=%d keys y=%d\n", l.art.w, l.title.x, l.title.w, l.play.y);
|
|
}
|
|
// A Guition tile over the whole page under its head: wide, everything fits, the keys never wider than the column.
|
|
{
|
|
Metrics m; Layout l = layout(m, 448, 236);
|
|
sound(l, 448, 236);
|
|
assert(l.wide && l.times && l.artist && l.art.w >= 180);
|
|
// The head of a real tile leaves 202 px: the artist line still fits beside a smaller cover.
|
|
Layout r = layout(m, 448, 202);
|
|
sound(r, 448, 202);
|
|
assert(r.artist && r.times && r.art.w >= 150);
|
|
assert(l.prev.x >= l.title.x && l.next.right() <= l.title.right());
|
|
printf("guition full: art %d column w=%d gap=%d\n", l.art.w, l.title.w, l.play.x - l.prev.right());
|
|
}
|
|
// A CYD tile over the whole page under its head: little room, so the artist line goes and the art shrinks.
|
|
{
|
|
Metrics m; m.large = false; m.title_h = 21; m.artist_h = 17; m.small_h = 13;
|
|
Layout l = layout(m, 302, 108);
|
|
sound(l, 302, 108);
|
|
assert(l.wide && !l.artist && l.art.w < 120 && l.art.w >= 60);
|
|
assert(l.play.bottom() <= 108 - 22 - 6 && l.bar.h == 4);
|
|
printf("cyd full: art %d keys y=%d bottom=%d\n", l.art.w, l.play.y, l.play.bottom());
|
|
}
|
|
// A very short tall area still holds every part (times gone, a small cover).
|
|
{
|
|
Metrics m; Layout l = layout(m, 300, 300);
|
|
sound(l, 300, 300);
|
|
assert(!l.wide);
|
|
}
|
|
// Progress: the position runs on while playing, stands still when paused, never beyond the track.
|
|
assert(progress(0, 0, 0, true, 0) == -1);
|
|
assert(progress(60, 1000, 1000, true, 240) == 250);
|
|
assert(progress(60, 1000, 1060, true, 240) == 500);
|
|
assert(progress(60, 1000, 1060, false, 240) == 250);
|
|
assert(progress(60, 1000, 9000, true, 240) == 1000);
|
|
assert(progress(60, 0, 9000, true, 240) == 250); // no report time: the position as it was
|
|
assert(elapsed_seconds(60, 1000, 1030, true, 240) == 90 && elapsed_seconds(300, 0, 0, false, 240) == 240);
|
|
// Times as every player writes them.
|
|
assert(clock_text(0) == "0:00" && clock_text(187) == "3:07" && clock_text(3600) == "1:00:00" && clock_text(3765) == "1:02:45");
|
|
// The second line and the idle words.
|
|
assert(subtitle("KATZROAR", "Invocation") == "KATZROAR · Invocation");
|
|
assert(subtitle("KATZROAR", "") == "KATZROAR" && subtitle("", "Invocation") == "Invocation" && subtitle("", "").empty());
|
|
assert(std::string(idle_text("off")) == "Off" && std::string(idle_text("idle")) == "Not playing" && std::string(idle_text("standby")) == "Standby");
|
|
assert(playing("playing") && playing("buffering") && !playing("paused"));
|
|
assert(has_track("paused") && !has_track("idle") && !has_track("off"));
|
|
printf("test_media_card: ok\n");
|
|
return 0;
|
|
}
|