Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,9 @@ bottom.](docs/images/player-booth.png)
optional signed name + status polling).
- **Signal + sleep** — timed `/api/health` probe → latency readout while
playing; sleep timer (15–90 min) from the tray or the back panel.
- **Mini player** — a compact secondary window (420×168) toggled from the tray
or Cmd/Ctrl+Shift+M. One player surface at a time: opening it minimizes the
- **Mini player** — a compact secondary window (420×168) toggled from the
masthead button, the tray, or Cmd/Ctrl+Shift+M. One player surface at a
time: opening it minimizes the
full player, and EXPAND — or closing the mini window — brings the full
player back.
- **Menu-bar extra** — macOS `NSStatusItem` and the Windows tray (the GTK host
Expand Down
1 change: 1 addition & 0 deletions src/icons/mini.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 5 additions & 2 deletions src/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const disc_icon = canvas.svg_icon.parseComptime(@embedFile("icons/disc.svg"));
const heart_icon = canvas.svg_icon.parseComptime(@embedFile("icons/heart.svg"));
const heart_fill_icon = canvas.svg_icon.parseComptime(@embedFile("icons/heart-fill.svg"));
const logo_icon = canvas.svg_icon.parseComptime(@embedFile("icons/logo.svg"));
const mini_icon = canvas.svg_icon.parseComptime(@embedFile("icons/mini.svg"));
const power_icon = canvas.svg_icon.parseComptime(@embedFile("icons/power.svg"));
const radio_icon = canvas.svg_icon.parseComptime(@embedFile("icons/radio.svg"));
const spark_icon = canvas.svg_icon.parseComptime(@embedFile("icons/spark.svg"));
Expand All @@ -49,6 +50,7 @@ pub const app_icons = [_]canvas.icons.Entry{
.{ .name = "heart", .icon = &heart_icon },
.{ .name = "heart-fill", .icon = &heart_fill_icon },
.{ .name = "logo", .icon = &logo_icon },
.{ .name = "mini", .icon = &mini_icon },
.{ .name = "power", .icon = &power_icon },
.{ .name = "radio", .icon = &radio_icon },
.{ .name = "spark", .icon = &spark_icon },
Expand All @@ -59,8 +61,9 @@ pub const app_icons = [_]canvas.icons.Entry{
// (typing in the request/station fields is never stolen). Space = tune toggle,
// arrows = volume, M = mute, cmd/ctrl+K = stations, cmd/ctrl+shift+M = mini
// player (plain cmd+M is the macOS system minimize), Esc = back to LIVE,
// 1–5 = dial stops. The shortcut is also the only way into mini mode on
// Linux, where the GTK host has no tray to offer the menu item.
// 1–5 = dial stops. The masthead's mini button and the tray item reach mini
// mode too; on Linux the button and the shortcut are the only routes, since
// the GTK host has no tray to offer the menu item.
fn onKey(keyboard: canvas.WidgetKeyboardEvent) ?Msg {
if (keyboard.phase != .key_down) return null;
const mods = keyboard.modifiers;
Expand Down
40 changes: 33 additions & 7 deletions src/views/mini.native
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,41 @@
</if>
<spacer grow="1"/>
<button variant="ghost" size="sm" icon="external-link" label="Open full player" on-press="expand_mini">EXPAND</button>
<!-- Both edges, same rule as the masthead: the window-control cluster
leads on macOS and trails on Windows, so EXPAND needs clearance on the
trailing side too. Zero on macOS, so this costs nothing there. -->
<spacer width="{chrome_trailing}"/>
</row>
<row gap="12" cross="center" padding="10" grow="1">
<card padding="6" label="Cover art sleeve">
<avatar image="{cover_id}" width="46" height="46" label="Cover art">{initials}</avatar>
</card>
<column gap="2" grow="1">
<text><span weight="bold">{title}</span></text>
<text size="sm" foreground="text_muted"><span foreground="text">{artist}</span><span foreground="text_muted">{album_line}</span></text>
<text size="sm" foreground="text_muted">{np_head}</text>
<!-- `card` stretches to fill the row, which crushed the text column to a
single glyph wide; a `panel` hugs its explicitly-sized child the way
the LIVE stage's sleeve does. -->
<panel padding="3" background="surface" border-color="border" radius="sm" label="Cover art sleeve">
<!-- `<image>`, not `<avatar>`: avatar renders a disc, and a record sleeve
is square. Same shape as the LIVE stage — `<image>` draws nothing for
id 0, so the initials tile is the explicit fallback while the cover
loads or after it fails. -->
<if test="{has_cover}">
<image image="{cover_id}" width="46" height="46" label="Cover art"/>
</if>
<else>
<panel width="46" height="46" background="surface_subtle">
<column grow="1" main="center" cross="center">
<text size="sm" foreground="text_muted">{initials}</text>
</column>
</panel>
</else>
</panel>
<!-- min-width 0 lets the metadata elide instead of forcing the row wider
than the fixed 420pt window. -->
<column gap="2" grow="1" min-width="0" main="center">
<!-- No <span> on these two: a span paragraph always word-wraps and
reserves the wrapped height, which is what overflowed the row. The
window is fixed at 168pt, so single-line elision beats the bold
weight and the ink/muted split — size carries the hierarchy. -->
<text wrap="false" overflow="ellipsis">{title}</text>
<text size="sm" foreground="text_muted" wrap="false" overflow="ellipsis">{artist}{album_line}</text>
<text size="sm" foreground="text_muted" wrap="false" overflow="ellipsis">{np_head}</text>
</column>
<if test="{like_available}">
<stack>
Expand Down
2 changes: 1 addition & 1 deletion src/views/player-stage.native
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<!-- Record sleeve: field card around SQUARE art. Pressing it opens the
timeline. `<image>` draws nothing for id 0, so the initials disc is
the explicit fallback while the cover loads (or after it fails). -->
<panel padding="12" background="surface" border-color="border" radius="lg"
<panel padding="5" background="surface" border-color="border" radius="sm"
on-press="open_timeline" label="Open timeline">
<!-- Metadata, not flow: right-click on the sleeve. The copy item drops
out entirely while nothing is playing, which is how a menu declares
Expand Down
9 changes: 8 additions & 1 deletion src/views/player-top.native
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,14 @@
<icon name="circle-dot" width="10" height="10" foreground="accent"/>
</if>
<stack>
<button variant="ghost" icon="settings" label="Back panel" on-press="open_panel"/>
<button variant="ghost" icon="app:mini" label="Mini player" on-press="toggle_mini"/>
<tooltip anchor="below">Mini player · Cmd/Ctrl+Shift+M</tooltip>
</stack>
<!-- `settings` read as preferences-only; the back panel is SHOWS,
TIMELINE, BOOTH and REQUEST as well, and it opens as a right-hand
column — which is what panel-right names. -->
<stack>
<button variant="ghost" icon="panel-right" label="Back panel" on-press="open_panel"/>
<tooltip anchor="below">Back panel</tooltip>
</stack>
<!-- Windows: clears the DWM min/max/close cluster, which overlays this
Expand Down
Loading