audioplayer/src/ui/views/now_playing_view.jai
2026-04-29 06:57:46 +03:00

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));
}