1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
use crate::app::{
Cell, Config, DirtyFlags, KeyBinds, LazyLibrary, ListState, MenuMode, PanelFocus, PlayState,
SongInfo,
};
#[derive(Debug, Clone)]
pub struct StatusMessage {
pub text: String,
pub created_at: std::time::Instant,
pub message_type: MessageType,
}
#[derive(Debug, Clone)]
pub enum MessageType {
InProgress,
Success,
Error,
}
/// The main application which holds the state and logic of the application.
#[derive(Debug)]
pub struct App {
/// Is the application running?
pub running: bool,
/// Current song information
pub current_song: Option<SongInfo>,
/// MPD queue information
pub queue: Vec<SongInfo>,
/// Currently selected queue item index
pub selected_queue_index: Option<usize>,
/// List state for the queue widget
pub queue_list_state: ListState,
/// List states for Artists navigation
pub artist_list_state: ListState,
pub album_list_state: ListState,
pub album_display_list_state: ListState, // For handling expanded album navigation
/// List states for Albums mode navigation (separate from Artists mode)
pub all_albums_list_state: ListState, // For navigating all_albums in Albums mode
pub album_tracks_list_state: ListState, // For navigating tracks within an album in Albums mode
/// For navigating the year list
pub year_list_state: ListState,
/// For navigating albums within a year
pub year_album_list_state: ListState,
/// Cached panel focus for Years mode
pub years_panel_focus: PanelFocus,
/// Configuration loaded from TOML file
pub genre_list_state: ListState,
pub genre_album_list_state: ListState,
pub genre_panel_focus: PanelFocus,
pub config: Config,
/// Current menu mode
pub menu_mode: MenuMode,
/// Current panel focus in Artists mode
pub panel_focus: PanelFocus,
/// Cached panel focus for Artists mode (restored when switching back)
pub artists_panel_focus: PanelFocus,
/// Cached panel focus for Albums mode (restored when switching back)
pub albums_panel_focus: PanelFocus,
/// Music library (lazy-loaded)
pub library: Option<LazyLibrary>,
/// Expanded albums (tracks which albums are currently expanded)
pub expanded_albums: std::collections::HashSet<(String, String)>, // (artist_name, album_name)
/// Expanded albums in Years mode (tracks which albums are currently expanded in Years mode)
pub expanded_albums_years: std::collections::HashSet<(String, String)>, // (artist_name, album_name)
/// Display list state for albums within a year (used in Years mode when an album is expanded)
pub expanded_albums_genres: std::collections::HashSet<(String, String)>, // (artist_name, album_name)
pub year_album_display_list_state: ListState,
pub genre_album_display_list_state: ListState,
/// Current MPD status information
pub mpd_status: Option<mpd_client::responses::Status>,
/// Key bindings handler
pub key_binds: KeyBinds,
/// Bit-perfect mode enabled (PipeWire sample rate matching)
pub bit_perfect_enabled: bool,
/// Flag to force immediate MPD status update (set after user actions)
pub force_update: bool,
/// Config warnings to display in popup
pub config_warnings: Vec<String>,
/// Whether the config warnings popup is currently showing
pub show_config_warnings_popup: bool,
/// Last play state for PipeWire rate tracking (used to detect state changes)
pub last_play_state: Option<PlayState>,
/// Last sample rate for PipeWire rate tracking (used to detect song changes)
pub last_sample_rate: Option<u32>,
/// Last known queue/playlist version from MPD (for differential updates)
pub last_playlist_version: Option<u32>,
/// Last known song ID from MPD (to skip refetching same song)
pub last_song_id: Option<mpd_client::commands::SongId>,
/// Dirty flags for optimized rendering (tracks which UI regions need redraw)
pub dirty: DirtyFlags,
/// Flag to indicate library reload is needed
pub library_reload_pending: bool,
/// Previous artist index to restore after library reload
pub pending_artist_index: Option<String>,
/// Library reload status message
pub status_message: Option<StatusMessage>,
/// Track if update is in progress to avoid overlapping updates
pub update_in_progress: bool,
/// Track last rendered animation frame for status messages
pub last_animation_frame: Cell<u64>,
/// Track if current reload was user-initiated (for status messages)
pub user_initiated_reload: bool,
pub vol_before_mute: u8,
}
impl App {
pub fn set_status_message(&mut self, message: StatusMessage) {
self.status_message = Some(message);
self.dirty.mark_status_message();
}
pub fn clear_status_message(&mut self) {
self.status_message = None;
self.dirty.mark_status_message();
}
pub fn check_status_message_expiry(&mut self) {
if let Some(msg) = &self.status_message {
let duration = std::time::Duration::from_secs(2);
if msg.created_at.elapsed() >= duration {
self.clear_status_message();
}
}
}
pub fn check_animation_updates(&mut self) {
if let Some(msg) = &self.status_message
&& matches!(msg.message_type, MessageType::InProgress)
{
let elapsed_ms = msg.created_at.elapsed().as_millis() as u64;
let current_frame = (elapsed_ms / 500) % 3;
if self.last_animation_frame.get() != current_frame {
self.last_animation_frame.set(current_frame);
self.dirty.mark_status_message();
}
}
}
}