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