189 lines
7.0 KiB
Plaintext
189 lines
7.0 KiB
Plaintext
//
|
|
// Now playing — the marquee screen. Visualizer shader runs behind this in
|
|
// window.jai, so the foreground here is just track info, transport, and
|
|
// the seek/volume sliders.
|
|
//
|
|
// Artist backdrop (if available) renders full-screen behind everything with
|
|
// a dark tint overlay so the UI remains readable.
|
|
//
|
|
|
|
draw_now_playing_view :: () {
|
|
w := cast(float) app.window_width;
|
|
h := cast(float) app.window_height;
|
|
k := h * 0.05;
|
|
|
|
has_track := app.current_stream != null || app.current_track.id.count > 0;
|
|
|
|
//
|
|
// Full-screen artist backdrop (drawn first, behind everything).
|
|
// We tint it dark so the UI text remains readable.
|
|
//
|
|
if has_track && app.current_track.artist_id.count > 0 {
|
|
backdrop := image_request(app.current_track.artist_id, .BACKDROP);
|
|
// Draw backdrop full-screen
|
|
draw_image(0, 0, w, h, backdrop);
|
|
// Dark overlay: 60% black so the UI pops against busy backdrops
|
|
Simp.set_shader_for_color();
|
|
Simp.immediate_quad(0, 0, w, h, .{0.05, 0.02, 0.08, 0.60});
|
|
} else {
|
|
// No track playing — solid background
|
|
Simp.set_shader_for_color();
|
|
Simp.immediate_quad(0, 0, w, h, .{0.05, 0.02, 0.08, 1});
|
|
}
|
|
|
|
// Top-left: back to library.
|
|
{
|
|
button_theme := app.theme.button_theme;
|
|
button_theme.font = app.button_font;
|
|
button_theme.label_theme.alignment = .Center;
|
|
if button(get_rect(k * 0.6, k * 0.6, w * 0.16, k), "library", *button_theme) {
|
|
app.current_view = .LIBRARY;
|
|
}
|
|
}
|
|
|
|
// Layout: art on the left, info column on the right.
|
|
art_size := min(w * 0.32, h * 0.55);
|
|
art_x := w * 0.06;
|
|
art_y := h * 0.12;
|
|
|
|
info_x := art_x + art_size + w * 0.04;
|
|
info_w := w - info_x - w * 0.06;
|
|
|
|
if has_track {
|
|
img := image_request(app.current_track.album_id, .LARGE);
|
|
draw_image(art_x, art_y, art_size, art_size, img);
|
|
} else {
|
|
// Placeholder slot so the layout doesn't shift on first launch.
|
|
Simp.set_shader_for_color();
|
|
Simp.immediate_quad(art_x, art_y, art_x + art_size, art_y + art_size, .{0.10, 0.05, 0.18, 1});
|
|
}
|
|
|
|
// Track name (large).
|
|
text := ifx has_track app.current_track.name else "— silence —";
|
|
{
|
|
label_theme := app.theme.label_theme;
|
|
label_theme.font = app.title_font;
|
|
label_theme.alignment = .Left;
|
|
label_theme.text_color = .{1, 0.4, 0.8, 1};
|
|
r := get_rect(info_x, art_y, info_w, app.title_font.character_height * 1.4);
|
|
label(r, text, *label_theme);
|
|
}
|
|
|
|
if has_track {
|
|
label_theme := app.theme.label_theme;
|
|
label_theme.font = app.big_font;
|
|
label_theme.alignment = .Left;
|
|
label_theme.text_color = .{0.8, 0.8, 1, 1};
|
|
r := get_rect(info_x, art_y + app.title_font.character_height * 1.5, info_w, app.big_font.character_height * 1.4);
|
|
label(r, app.current_track.artist, *label_theme);
|
|
|
|
label_theme.font = app.body_font;
|
|
label_theme.text_color = .{0.6, 0.6, 0.8, 1};
|
|
r = get_rect(info_x, art_y + app.title_font.character_height * 1.5 + app.big_font.character_height * 1.6, info_w, app.body_font.character_height * 1.5);
|
|
label(r, app.current_track.album, *label_theme);
|
|
|
|
// Format chip.
|
|
label_theme.text_color = .{0.5, 0.9, 0.7, 1};
|
|
format_text := tprint("[%]", app.current_format);
|
|
r = get_rect(info_x, art_y + app.title_font.character_height * 1.5 + app.big_font.character_height * 1.6 + app.body_font.character_height * 1.6, info_w, app.body_font.character_height * 1.5);
|
|
label(r, format_text, *label_theme);
|
|
}
|
|
|
|
// Seek slider — replaces the old hand-drawn position strip.
|
|
if has_track && app.current_stream && app.current_stream.sound_data {
|
|
rate := cast(float) app.current_stream.sound_data.sampling_rate;
|
|
total := cast(float) app.current_track.duration_ticks / 10_000_000.0;
|
|
if total <= 0 total = max(cast(float) app.current_stream.play_cursor / rate + 1, 1);
|
|
|
|
// Each frame, mirror play_cursor → scrub_seconds before the slider
|
|
// draws. If the user drags, the slider mutates scrub_seconds in
|
|
// place; we read `changed` and seek.
|
|
app.scrub_seconds = clamp(cast(float) app.current_stream.play_cursor / rate, 0, total);
|
|
|
|
slider_theme := app.theme.slider_theme;
|
|
slider_theme.foreground.font = app.button_font;
|
|
|
|
slider_w := w * 0.7;
|
|
slider_h := k * 0.6;
|
|
slider_x := (w - slider_w) * 0.5;
|
|
slider_y := h * 0.66;
|
|
|
|
changed, _ := slider(
|
|
get_rect(slider_x, slider_y, slider_w, slider_h),
|
|
*app.scrub_seconds,
|
|
0.0, total, 1.0,
|
|
*slider_theme,
|
|
"", "s",
|
|
);
|
|
if changed {
|
|
audio_seek_seconds(app.scrub_seconds);
|
|
}
|
|
|
|
// Time labels under the slider.
|
|
time_theme := app.theme.label_theme;
|
|
time_theme.font = app.body_font;
|
|
time_theme.text_color = .{0.7, 0.7, 0.85, 1};
|
|
time_theme.alignment = .Left;
|
|
label(get_rect(slider_x, slider_y + slider_h, slider_w * 0.5, app.body_font.character_height * 1.3),
|
|
format_seconds(app.scrub_seconds), *time_theme);
|
|
time_theme.alignment = .Right;
|
|
label(get_rect(slider_x + slider_w * 0.5, slider_y + slider_h, slider_w * 0.5, app.body_font.character_height * 1.3),
|
|
format_seconds(total), *time_theme);
|
|
}
|
|
|
|
// Transport buttons cluster, centered.
|
|
button_theme := app.theme.button_theme;
|
|
button_theme.font = app.button_font;
|
|
button_theme.label_theme.alignment = .Center;
|
|
|
|
bw := k * 2.6;
|
|
bh := k * 1.6;
|
|
spacing := k * 0.4;
|
|
total_btn_w := bw * 3 + spacing * 2;
|
|
bx := (w - total_btn_w) * 0.5;
|
|
by := h * 0.78;
|
|
|
|
if button(get_rect(bx, by, bw, bh), "<<", *button_theme, identifier=1) queue_prev();
|
|
bx += bw + spacing;
|
|
pp_label := ifx audio_is_paused() then "PLAY" else "PAUSE";
|
|
if button(get_rect(bx, by, bw, bh), pp_label, *button_theme, identifier=2) audio_toggle_pause();
|
|
bx += bw + spacing;
|
|
if button(get_rect(bx, by, bw, bh), ">>", *button_theme, identifier=3) queue_next();
|
|
|
|
// Volume slider, bottom-right.
|
|
{
|
|
slider_theme := app.theme.slider_theme;
|
|
slider_theme.foreground.font = app.body_font;
|
|
|
|
vw := w * 0.18;
|
|
vh := k * 0.55;
|
|
vx := w - vw - k * 0.6;
|
|
vy := h - vh - k * 0.6;
|
|
|
|
before := app.master_volume;
|
|
slider(
|
|
get_rect(vx, vy, vw, vh),
|
|
*app.master_volume,
|
|
0.0, 1.0, 0.05,
|
|
*slider_theme,
|
|
"vol ", "",
|
|
identifier=99,
|
|
);
|
|
if before != app.master_volume {
|
|
// Persist on every change — config_save is cheap and the user
|
|
// expects volume to survive restarts.
|
|
config_save();
|
|
}
|
|
}
|
|
}
|
|
|
|
#scope_file
|
|
|
|
format_seconds :: (s: float) -> string {
|
|
if !(s >= 0) return "0:00"; // catches negatives and NaN
|
|
total := cast(int) s;
|
|
m := total / 60;
|
|
sec := total - m * 60;
|
|
return tprint("%:%", m, formatInt(sec, minimum_digits=2));
|
|
}
|