#![allow(dead_code)] use crossterm::event::MouseButton; use ratatui::widgets::ListState; use crate::application::app_state::App; use crate::data::{Artist, Track}; use crate::domain::conversions::{convert_artist, convert_track}; use crate::domain::navigation::Tab; use crate::grpc::GrpcResponse; use crate::ui::library::{LibraryFocus, LibraryState}; use crate::ui::notifications::NotifKind; impl App { pub fn handle_escape(&mut self) { if self.notifications_open { self.notifications_open = false; return; } if self.modal.is_some() { self.modal = None; } } pub fn handle_click(&mut self, x: u16, y: u16, button: MouseButton) { if button != MouseButton::Left { return; } if self.modal.is_some() { self.handle_modal_click(x, y); return; } if self.notifications_open { let in_dropdown = x >= self.notifications_dropdown_area.x && x < self.notifications_dropdown_area.x + self.notifications_dropdown_area.width && y >= self.notifications_dropdown_area.y && y < self.notifications_dropdown_area.y + self.notifications_dropdown_area.height; let in_btn = x >= self.notifications_btn_area.x && x < self.notifications_btn_area.x + self.notifications_btn_area.width && y == self.notifications_btn_area.y; if in_btn { self.notifications_open = false; self.notifications_expanded = None; } else if in_dropdown { self.handle_notification_click(x, y); } else { self.notifications_open = false; self.notifications_expanded = None; } return; } if y == self.topbar_area.y { self.handle_topbar_click(x); return; } if y >= self.main_area.y && y < self.main_area.y + self.main_area.height { self.handle_main_click(x, y); } } fn handle_modal_click(&mut self, _x: u16, _y: u16) { self.modal = None; } fn handle_topbar_click(&mut self, x: u16) { if x >= self.notifications_btn_area.x && x < self.notifications_btn_area.x + self.notifications_btn_area.width { self.notifications_open = !self.notifications_open; self.notifications_scroll = 0; self.notifications_expanded = None; return; } for (i, area) in self.tab_areas.iter().enumerate() { if x >= area.x && x < area.x + area.width { if let Some(tab) = Tab::ALL.get(i) { self.tab = *tab; } return; } } } fn handle_notification_click(&mut self, _x: u16, y: u16) { if self.notifications_expanded.is_some() { self.notifications_expanded = None; return; } let inner_y = self.notifications_dropdown_area.y + 1; let rel_y = y.saturating_sub(inner_y) as usize; let item_height = 3usize; let clicked_idx = rel_y / item_height; let history = self.notifications.history(); let actual_idx = self.notifications_scroll + clicked_idx; if let Some(notif) = history.iter().rev().nth(actual_idx) { self.notifications_expanded = Some(notif.id); } } fn handle_main_click(&mut self, x: u16, y: u16) { let rel_y = y.saturating_sub(self.main_area.y) as usize; match self.tab { Tab::Library => { self.handle_library_click(x, rel_y); } Tab::Wanted => { self.select_list_item(&mut self.wanted_state.clone(), self.wanted.len(), rel_y); if rel_y < self.wanted.len() { self.wanted_state.select(Some(rel_y)); } } Tab::Queue => { if rel_y < self.queue.len() { self.queue_state.select(Some(rel_y)); } } Tab::History => { if rel_y < self.history.len() { self.history_state.select(Some(rel_y)); } } Tab::Calendar | Tab::Settings => {} } } fn handle_library_click(&mut self, x: u16, rel_y: usize) { const ARTISTS_PANE_WIDTH: u16 = 32; const BORDER_TOP: usize = 1; const HEADER_HEIGHT: usize = 6; const DIVIDER_HEIGHT: usize = 1; const ALBUMS_START_ROW: usize = BORDER_TOP + HEADER_HEIGHT + DIVIDER_HEIGHT; if x < ARTISTS_PANE_WIDTH { if rel_y > 0 && rel_y <= self.library.artists.len() { self.library.artist_state.select(Some(rel_y - 1)); self.library.album_state.select(Some(0)); self.library.track_state.select(Some(0)); self.library.focus = LibraryFocus::Artists; } } else if rel_y >= ALBUMS_START_ROW { let album_row = rel_y - ALBUMS_START_ROW; let content_height = self.main_area.height.saturating_sub(10) as usize; let albums_section_height = (content_height * 40) / 100; let tracks_start_row = ALBUMS_START_ROW + albums_section_height + DIVIDER_HEIGHT; if rel_y < tracks_start_row { if let Some(artist) = self.library.selected_artist() && album_row < artist.albums.len() { self.library.album_state.select(Some(album_row)); self.library.track_state.select(Some(0)); self.library.focus = LibraryFocus::Albums; } } else { let track_row = rel_y - tracks_start_row; if let Some(album) = self.library.selected_album() && track_row < album.total as usize { self.library.track_state.select(Some(track_row)); self.library.focus = LibraryFocus::Tracks; } } } } fn select_list_item(&self, _state: &mut ListState, _len: usize, _rel_y: usize) {} pub fn handle_scroll(&mut self, delta: i32) { if self.modal.is_some() { return; } if self.notifications_open { let max_scroll = self.notifications.history_count().saturating_sub(8); if delta > 0 { self.notifications_scroll = (self.notifications_scroll + 1).min(max_scroll); } else { self.notifications_scroll = self.notifications_scroll.saturating_sub(1); } return; } match self.tab { Tab::Library => { self.scroll_library_list(delta); } Tab::Wanted => { let len = self.wanted.len(); scroll_list_state(&mut self.wanted_state, len, delta); } Tab::Queue => { let len = self.queue.len(); scroll_list_state(&mut self.queue_state, len, delta); } Tab::History => { let len = self.history.len(); scroll_list_state(&mut self.history_state, len, delta); } Tab::Calendar | Tab::Settings => {} } } fn scroll_library_list(&mut self, delta: i32) { match self.library.focus { LibraryFocus::Artists => { let len = self.library.artists.len(); if len == 0 { return; } let current = self.library.artist_state.selected().unwrap_or(0); let new_idx = if delta > 0 { (current + 1).min(len - 1) } else { current.saturating_sub(1) }; if new_idx != current { self.library.artist_state.select(Some(new_idx)); self.library.album_state.select(Some(0)); self.library.track_state.select(Some(0)); } } LibraryFocus::Albums => { if let Some(artist) = self.library.selected_artist() { let len = artist.albums.len(); if len == 0 { return; } let current = self.library.album_state.selected().unwrap_or(0); let new_idx = if delta > 0 { (current + 1).min(len - 1) } else { current.saturating_sub(1) }; if new_idx != current { self.library.album_state.select(Some(new_idx)); self.library.track_state.select(Some(0)); } } } LibraryFocus::Tracks => { if let Some(album) = self.library.selected_album() { let len = album.total as usize; if len == 0 { return; } let current = self.library.track_state.selected().unwrap_or(0); let new_idx = if delta > 0 { (current + 1).min(len - 1) } else { current.saturating_sub(1) }; self.library.track_state.select(Some(new_idx)); } } } } pub fn handle_tick(&mut self) { self.notifications.tick(); } pub fn set_error(&mut self, msg: String) { self.notifications .push("Error", Some(msg), NotifKind::Error, "✗"); } pub fn handle_grpc_response(&mut self, response: GrpcResponse) { match response { GrpcResponse::Artists(artists) => { let converted: Vec = artists.into_iter().map(convert_artist).collect(); let count = converted.len(); self.library = LibraryState::new(converted); self.notifications.push( "Library loaded", Some(format!("{} artists", count)), NotifKind::Success, "✓", ); } GrpcResponse::Album { album, tracks } => { let converted: Vec = tracks.into_iter().map(convert_track).collect(); self.library.cache_tracks(album.id, converted); } GrpcResponse::Error(msg) => { self.set_error(msg); } } } pub fn pending_album_fetch(&mut self) -> Option { self.library.needs_fetch() } } fn scroll_list_state(state: &mut ListState, len: usize, delta: i32) { if len == 0 { return; } let current = state.selected().unwrap_or(0); let new_idx = if delta > 0 { (current + 1).min(len - 1) } else { current.saturating_sub(1) }; state.select(Some(new_idx)); }