Skip to content

Commit 49b8be6

Browse files
committed
Fix URL button layout and add URL history
1 parent 8e3c37a commit 49b8be6

7 files changed

Lines changed: 243 additions & 37 deletions

File tree

Cargo.lock

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ naga = { version = "29.0.3", features = ["wgsl-in"] }
5151
glam = "0.32.1"
5252
env_logger = "0.11.10"
5353
ureq = "3.3.0"
54+
directories = "6.0.0"
5455

5556
[build-dependencies]
5657
winres = "0.1.12"

src/audio.rs

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -999,7 +999,19 @@ impl AudioSource for FfmpegSource {
999999

10001000
fn get_num_channels(&mut self) -> i32 { self.channels as i32 }
10011001
fn get_current_channel_vu_mono(&mut self, channel: i32) -> f32 { self.channel_vus.get(channel as usize).cloned().unwrap_or(0.0) }
1002-
fn get_artist(&mut self) -> String { self.artist.clone() }
1002+
fn get_artist(&mut self) -> String {
1003+
if let Some(stream_title) = self.ictx.metadata().get("StreamTitle") {
1004+
if !stream_title.is_empty() {
1005+
return stream_title.to_string();
1006+
}
1007+
}
1008+
if let Some(icy_name) = self.ictx.metadata().get("icy-name") {
1009+
if !icy_name.is_empty() {
1010+
return icy_name.to_string();
1011+
}
1012+
}
1013+
self.artist.clone()
1014+
}
10031015
fn get_type(&mut self) -> String { self.ext_type.clone() }
10041016
fn get_tempo(&mut self) -> i32 { 0 }
10051017
fn get_speed(&mut self) -> i32 { 0 }
@@ -1191,6 +1203,8 @@ fn try_ffmpeg(file_path: &str, is_network: bool) -> Result<Box<dyn AudioSource>>
11911203
decoder.rate(),
11921204
).context("Failed to create resampler")?;
11931205

1206+
let initial_artist = ictx.metadata().get("artist").map(|s| s.to_string()).unwrap_or_else(|| "Unknown".to_string());
1207+
11941208
Ok(Box::new(FfmpegSource {
11951209
ictx,
11961210
decoder,
@@ -1202,7 +1216,7 @@ fn try_ffmpeg(file_path: &str, is_network: bool) -> Result<Box<dyn AudioSource>>
12021216
time_base: tb,
12031217
current_time: 0.0,
12041218
duration,
1205-
artist: "Unknown".to_string(),
1219+
artist: initial_artist,
12061220
ext_type: ext,
12071221
intrinsic_sample_rate: sample_rate,
12081222
video_info,
@@ -1852,7 +1866,18 @@ where
18521866
chunk.channel_vus.clear();
18531867
chunk.channel_vus.extend_from_slice(&channel_vus);
18541868

1869+
let current_artist = audio_source.get_artist();
18551870
if let Ok(mut state) = state_for_decoder.try_lock() {
1871+
if current_artist != "Unknown" && !current_artist.is_empty() {
1872+
if let Some((artist_part, title_part)) = current_artist.split_once(" - ") {
1873+
if state.artist != artist_part || state.song_title != title_part {
1874+
state.artist = artist_part.to_string();
1875+
state.song_title = title_part.to_string();
1876+
}
1877+
} else if state.artist != current_artist {
1878+
state.artist = current_artist.clone();
1879+
}
1880+
}
18561881
state.stats.decode_us = state.stats.decode_us * 0.9 + decode_elapsed * 0.1;
18571882
let fill_pct = (ready_rx_for_decoder.len() as f32 / pool_size as f32) * 100.0;
18581883
state.stats.audio_buffer_fill_pct = fill_pct;

src/engine.rs

Lines changed: 71 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ pub enum EngineAction {
6464
CloseUrlDialog,
6565
SetUrlInput(String),
6666
LoadUrl(String),
67+
EditUrl(String),
68+
ClearFocusUrlInput,
6769
}
6870

6971
#[repr(C)]
@@ -1812,27 +1814,56 @@ impl<'a> VulkanEngine<'a> {
18121814
egui::Window::new("Open Network Stream")
18131815
.collapsible(false)
18141816
.resizable(false)
1817+
.default_width(600.0)
18151818
.anchor(egui::Align2::CENTER_CENTER, [0.0, 0.0])
18161819
.show(ctx, |ui| {
18171820
ui.label("Enter Stream URL (e.g. .pls, .m3u, or direct stream link):");
1821+
ui.add_space(4.0);
18181822
let mut url_text = state.url_input_text.clone();
1819-
let text_resp = ui.add_sized([400.0, 30.0], egui::TextEdit::singleline(&mut url_text));
1823+
let text_resp = ui.add(
1824+
egui::TextEdit::singleline(&mut url_text)
1825+
.desired_width(f32::INFINITY)
1826+
.min_size(egui::vec2(0.0, 30.0))
1827+
);
1828+
if state.focus_url_input {
1829+
text_resp.request_focus();
1830+
engine_action = EngineAction::ClearFocusUrlInput;
1831+
}
18201832
if text_resp.changed() {
18211833
engine_action = EngineAction::SetUrlInput(url_text.clone());
18221834
}
18231835
ui.add_space(10.0);
18241836
ui.horizontal(|ui| {
1825-
ui.allocate_ui_with_layout(ui.available_size(), egui::Layout::centered_and_justified(egui::Direction::LeftToRight), |ui| {
1826-
if ui.button("Cancel").clicked() {
1827-
engine_action = EngineAction::CloseUrlDialog;
1828-
}
1829-
if ui.button("Open").clicked() || (text_resp.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter))) {
1837+
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
1838+
if ui.add_sized([80.0, 30.0], egui::Button::new("Open")).clicked() || (text_resp.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter))) {
18301839
if !url_text.is_empty() {
18311840
engine_action = EngineAction::LoadUrl(url_text);
18321841
}
18331842
}
1843+
if ui.add_sized([80.0, 30.0], egui::Button::new("Cancel")).clicked() {
1844+
engine_action = EngineAction::CloseUrlDialog;
1845+
}
18341846
});
18351847
});
1848+
1849+
if !state.url_history.is_empty() {
1850+
ui.separator();
1851+
ui.label(egui::RichText::new("Recent Streams:").strong());
1852+
egui::ScrollArea::vertical().max_height(150.0).show(ui, |ui| {
1853+
for (url, title) in state.url_history.iter().rev() {
1854+
ui.horizontal(|ui| {
1855+
let width = ui.available_width() - 38.0;
1856+
let btn = egui::Button::new(format!("{} ({})", title, url)).truncate();
1857+
if ui.add_sized([width.max(0.0), 30.0], btn).clicked() {
1858+
engine_action = EngineAction::LoadUrl(url.clone());
1859+
}
1860+
if ui.add_sized([30.0, 30.0], egui::Button::new("📝")).clicked() {
1861+
engine_action = EngineAction::EditUrl(url.clone());
1862+
}
1863+
});
1864+
}
1865+
});
1866+
}
18361867
});
18371868
}
18381869

@@ -2179,15 +2210,13 @@ impl<'a> VulkanEngine<'a> {
21792210
}
21802211

21812212
let mut append = state.append_to_playlist;
2182-
// Only update the file dialog UI when it's actually open to avoid layout overhead
2183-
if state.is_file_picker_open || *file_dialog.state() != egui_file_dialog::DialogState::Closed {
2184-
file_dialog.update_with_right_panel_ui(ctx, &mut |ui, _fd| {
2185-
ui.add_space(10.0);
2186-
ui.heading("Options");
2187-
ui.separator();
2188-
ui.checkbox(&mut append, "Add to Playlist instead of replacing");
2189-
});
2190-
}
2213+
file_dialog.update_with_right_panel_ui(ctx, &mut |ui, _fd| {
2214+
ui.add_space(10.0);
2215+
ui.heading("Options");
2216+
ui.separator();
2217+
ui.checkbox(&mut append, "Add to Playlist instead of replacing");
2218+
});
2219+
21912220
if append != state.append_to_playlist {
21922221
engine_action = EngineAction::SetAppendToPlaylist(append);
21932222
}
@@ -2388,7 +2417,6 @@ impl<'a> VulkanEngine<'a> {
23882417

23892418
egui::Area::new(egui::Id::new("splash_shortcuts_area"))
23902419
.anchor(egui::Align2::CENTER_BOTTOM, egui::vec2(0.0, -40.0))
2391-
.order(egui::Order::Foreground)
23922420
.show(ctx, |ui| {
23932421
egui::Frame::NONE
23942422
.fill(egui::Color32::from_black_alpha(200))
@@ -2595,13 +2623,12 @@ impl<'a> VulkanEngine<'a> {
25952623
}
25962624
ui.add_space(20.0);
25972625

2598-
if !is_game_mode {
25992626
egui::Frame::NONE
26002627
.shadow(egui::Shadow { offset: [0, 4], blur: 12, spread: 0, color: egui::Color32::from_black_alpha(200) })
26012628
.corner_radius(6.0)
26022629
.show(ui, |ui| {
26032630
ui.horizontal(|ui| {
2604-
let total_width = 300.0 + 20.0 + 250.0;
2631+
let total_width = 300.0 + 20.0 + 300.0;
26052632
ui.add_space((ui.available_width() - total_width) / 2.0);
26062633

26072634
let btn = egui::Button::new(
@@ -2613,7 +2640,11 @@ impl<'a> VulkanEngine<'a> {
26132640
.fill(egui::Color32::from_rgb(0, 100, 200))
26142641
.stroke(egui::Stroke::new(1.0, egui::Color32::from_rgb(80, 180, 255)));
26152642

2616-
if ui.add_sized([300.0, 60.0], btn).clicked() {
2643+
let resp1 = ui.add_sized([300.0, 60.0], btn);
2644+
if resp1.has_focus() {
2645+
ui.painter().rect(resp1.rect.expand(2.0), 6.0, egui::Color32::TRANSPARENT, egui::Stroke::new(3.0, egui::Color32::YELLOW), egui::StrokeKind::Outside);
2646+
}
2647+
if resp1.clicked() {
26172648
engine_action = EngineAction::OpenFile;
26182649
}
26192650

@@ -2628,14 +2659,17 @@ impl<'a> VulkanEngine<'a> {
26282659
.fill(egui::Color32::from_rgb(100, 50, 150))
26292660
.stroke(egui::Stroke::new(1.0, egui::Color32::from_rgb(180, 80, 255)));
26302661

2631-
if ui.add_sized([250.0, 60.0], url_btn).clicked() {
2662+
let resp2 = ui.add_sized([300.0, 60.0], url_btn);
2663+
if resp2.has_focus() {
2664+
ui.painter().rect(resp2.rect.expand(2.0), 6.0, egui::Color32::TRANSPARENT, egui::Stroke::new(3.0, egui::Color32::YELLOW), egui::StrokeKind::Outside);
2665+
}
2666+
if resp2.clicked() {
26322667
engine_action = EngineAction::OpenUrlDialog;
26332668
}
26342669
});
26352670
});
26362671

26372672
ui.add_space(30.0);
2638-
}
26392673

26402674
let mut force_stereo = state.force_stereo_downmix;
26412675
if ui.checkbox(&mut force_stereo, "Force Stereo Downmix (Fixes crackling on some devices)").changed() {
@@ -2745,10 +2779,16 @@ impl<'a> VulkanEngine<'a> {
27452779
format!("{:02}:{:02}.{}", m, s, f)
27462780
};
27472781

2782+
let time_text = if state.duration_seconds <= 0.0 {
2783+
format!("{} / LIVE", format_time(state.current_seconds))
2784+
} else {
2785+
format!("{} / {}", format_time(state.current_seconds), format_time(state.duration_seconds))
2786+
};
2787+
27482788
painter.text(
27492789
rect.center(),
27502790
egui::Align2::CENTER_CENTER,
2751-
format!("{} / {}", format_time(state.current_seconds), format_time(state.duration_seconds)),
2791+
time_text,
27522792
egui::FontId::proportional(11.0),
27532793
egui::Color32::WHITE,
27542794
);
@@ -2900,7 +2940,7 @@ impl<'a> VulkanEngine<'a> {
29002940
let title_path = std::path::Path::new(&state.song_title);
29012941
let file_name = title_path.file_name().unwrap_or_default().to_string_lossy().to_string();
29022942
let file_dir = title_path.parent().unwrap_or(std::path::Path::new("")).to_string_lossy().to_string();
2903-
columns[2].horizontal(|ui| { ui.label("File"); ui.label(&file_name); });
2943+
columns[2].horizontal(|ui| { ui.label("File/Title"); ui.label(&file_name); });
29042944
columns[2].horizontal(|ui| { ui.label("Artist"); ui.label(&state.artist); });
29052945
columns[2].horizontal(|ui| { ui.label("Path"); ui.label(&file_dir); });
29062946
if state.playlist.len() > 1 {
@@ -2954,7 +2994,14 @@ impl<'a> VulkanEngine<'a> {
29542994
}
29552995
});
29562996

2957-
columns[2].horizontal(|ui| { ui.label("Length"); ui.label(format!("{:.1}s", state.duration_seconds)); });
2997+
columns[2].horizontal(|ui| {
2998+
ui.label("Length");
2999+
if state.duration_seconds <= 0.0 {
3000+
ui.label("LIVE");
3001+
} else {
3002+
ui.label(format!("{:.1}s", state.duration_seconds));
3003+
}
3004+
});
29583005
out_track_info_rect = Some(columns[2].min_rect());
29593006
}
29603007
});

0 commit comments

Comments
 (0)