Split mouse.rs into page-specific handler files
Extract handle_artists_click and handle_playlists_click into mouse_artists.rs and mouse_playlists.rs respectively, reducing mouse.rs from ~530 to ~247 lines. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
196
src/app/mouse_artists.rs
Normal file
196
src/app/mouse_artists.rs
Normal file
@@ -0,0 +1,196 @@
|
||||
use tracing::error;
|
||||
|
||||
use crate::error::Error;
|
||||
|
||||
use super::*;
|
||||
|
||||
impl App {
|
||||
/// Handle click on artists page
|
||||
pub(super) async fn handle_artists_click(
|
||||
&mut self,
|
||||
x: u16,
|
||||
y: u16,
|
||||
layout: &LayoutAreas,
|
||||
) -> Result<(), Error> {
|
||||
use crate::ui::pages::artists::{build_tree_items, TreeItem};
|
||||
|
||||
let mut state = self.state.write().await;
|
||||
let left = layout.content_left.unwrap_or(layout.content);
|
||||
let right = layout.content_right.unwrap_or(layout.content);
|
||||
|
||||
if x >= left.x && x < left.x + left.width && y >= left.y && y < left.y + left.height {
|
||||
// Tree pane click — account for border (1 row top)
|
||||
let row_in_viewport = y.saturating_sub(left.y + 1) as usize;
|
||||
let item_index = state.artists.tree_scroll_offset + row_in_viewport;
|
||||
let tree_items = build_tree_items(&state);
|
||||
|
||||
if item_index < tree_items.len() {
|
||||
let was_selected = state.artists.selected_index == Some(item_index);
|
||||
state.artists.focus = 0;
|
||||
state.artists.selected_index = Some(item_index);
|
||||
|
||||
// Second click = activate (same as Enter)
|
||||
let is_second_click = was_selected
|
||||
&& self.last_click.is_some_and(|(lx, ly, t)| {
|
||||
lx == x && ly == y && t.elapsed().as_millis() < 500
|
||||
});
|
||||
|
||||
if is_second_click {
|
||||
// Activate: expand/collapse artist, or play album
|
||||
match &tree_items[item_index] {
|
||||
TreeItem::Artist { artist, expanded } => {
|
||||
let artist_id = artist.id.clone();
|
||||
let artist_name = artist.name.clone();
|
||||
let was_expanded = *expanded;
|
||||
|
||||
if was_expanded {
|
||||
state.artists.expanded.remove(&artist_id);
|
||||
} else if !state.artists.albums_cache.contains_key(&artist_id) {
|
||||
drop(state);
|
||||
if let Some(ref client) = self.subsonic {
|
||||
match client.get_artist(&artist_id).await {
|
||||
Ok((_artist, albums)) => {
|
||||
let mut state = self.state.write().await;
|
||||
let count = albums.len();
|
||||
state.artists.albums_cache.insert(artist_id.clone(), albums);
|
||||
state.artists.expanded.insert(artist_id);
|
||||
tracing::info!("Loaded {} albums for {}", count, artist_name);
|
||||
}
|
||||
Err(e) => {
|
||||
let mut state = self.state.write().await;
|
||||
state.notify_error(format!("Failed to load: {}", e));
|
||||
}
|
||||
}
|
||||
}
|
||||
self.last_click = Some((x, y, std::time::Instant::now()));
|
||||
return Ok(());
|
||||
} else {
|
||||
state.artists.expanded.insert(artist_id);
|
||||
}
|
||||
}
|
||||
TreeItem::Album { album } => {
|
||||
let album_id = album.id.clone();
|
||||
let album_name = album.name.clone();
|
||||
drop(state);
|
||||
|
||||
if let Some(ref client) = self.subsonic {
|
||||
match client.get_album(&album_id).await {
|
||||
Ok((_album, songs)) => {
|
||||
if songs.is_empty() {
|
||||
let mut state = self.state.write().await;
|
||||
state.notify_error("Album has no songs");
|
||||
self.last_click = Some((x, y, std::time::Instant::now()));
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let first_song = songs[0].clone();
|
||||
let stream_url = client.get_stream_url(&first_song.id);
|
||||
|
||||
let mut state = self.state.write().await;
|
||||
let count = songs.len();
|
||||
state.queue.clear();
|
||||
state.queue.extend(songs.clone());
|
||||
state.queue_position = Some(0);
|
||||
state.artists.songs = songs;
|
||||
state.artists.selected_song = Some(0);
|
||||
state.artists.focus = 1;
|
||||
state.now_playing.song = Some(first_song.clone());
|
||||
state.now_playing.state = PlaybackState::Playing;
|
||||
state.now_playing.position = 0.0;
|
||||
state.now_playing.duration = first_song.duration.unwrap_or(0) as f64;
|
||||
state.now_playing.sample_rate = None;
|
||||
state.now_playing.bit_depth = None;
|
||||
state.now_playing.format = None;
|
||||
state.now_playing.channels = None;
|
||||
state.notify(format!("Playing album: {} ({} songs)", album_name, count));
|
||||
drop(state);
|
||||
|
||||
if let Ok(url) = stream_url {
|
||||
if self.mpv.is_paused().unwrap_or(false) {
|
||||
let _ = self.mpv.resume();
|
||||
}
|
||||
if let Err(e) = self.mpv.loadfile(&url) {
|
||||
error!("Failed to play: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
let mut state = self.state.write().await;
|
||||
state.notify_error(format!("Failed to load album: {}", e));
|
||||
}
|
||||
}
|
||||
}
|
||||
self.last_click = Some((x, y, std::time::Instant::now()));
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// First click on album: preview songs in right pane
|
||||
if let TreeItem::Album { album } = &tree_items[item_index] {
|
||||
let album_id = album.id.clone();
|
||||
drop(state);
|
||||
if let Some(ref client) = self.subsonic {
|
||||
if let Ok((_album, songs)) = client.get_album(&album_id).await {
|
||||
let mut state = self.state.write().await;
|
||||
state.artists.songs = songs;
|
||||
state.artists.selected_song = Some(0);
|
||||
}
|
||||
}
|
||||
self.last_click = Some((x, y, std::time::Instant::now()));
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if x >= right.x && x < right.x + right.width && y >= right.y && y < right.y + right.height {
|
||||
// Songs pane click
|
||||
let row_in_viewport = y.saturating_sub(right.y + 1) as usize;
|
||||
let item_index = state.artists.song_scroll_offset + row_in_viewport;
|
||||
|
||||
if item_index < state.artists.songs.len() {
|
||||
let was_selected = state.artists.selected_song == Some(item_index);
|
||||
state.artists.focus = 1;
|
||||
state.artists.selected_song = Some(item_index);
|
||||
|
||||
let is_second_click = was_selected
|
||||
&& self.last_click.is_some_and(|(lx, ly, t)| {
|
||||
lx == x && ly == y && t.elapsed().as_millis() < 500
|
||||
});
|
||||
|
||||
if is_second_click {
|
||||
// Play selected song
|
||||
let song = state.artists.songs[item_index].clone();
|
||||
let songs = state.artists.songs.clone();
|
||||
state.queue.clear();
|
||||
state.queue.extend(songs);
|
||||
state.queue_position = Some(item_index);
|
||||
state.now_playing.song = Some(song.clone());
|
||||
state.now_playing.state = PlaybackState::Playing;
|
||||
state.now_playing.position = 0.0;
|
||||
state.now_playing.duration = song.duration.unwrap_or(0) as f64;
|
||||
state.now_playing.sample_rate = None;
|
||||
state.now_playing.bit_depth = None;
|
||||
state.now_playing.format = None;
|
||||
state.now_playing.channels = None;
|
||||
state.notify(format!("Playing: {}", song.title));
|
||||
drop(state);
|
||||
|
||||
if let Some(ref client) = self.subsonic {
|
||||
if let Ok(url) = client.get_stream_url(&song.id) {
|
||||
if self.mpv.is_paused().unwrap_or(false) {
|
||||
let _ = self.mpv.resume();
|
||||
}
|
||||
if let Err(e) = self.mpv.loadfile(&url) {
|
||||
error!("Failed to play: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.last_click = Some((x, y, std::time::Instant::now()));
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.last_click = Some((x, y, std::time::Instant::now()));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user