Krolyxon
Commit bb5bcea

rmptuisrcapp.rs

350 lines · 12.3 KB · Rust

1use std::time::Duration;
2
3use crate::browser::FileBrowser;
4use crate::connection::Connection;
5use crate::list::ContentList;
6use crate::ui::InputMode;
7use mpd::{Client, Song};
8
9// Application result type
10pub type AppResult<T> = std::result::Result<T, Box<dyn std::error::Error>>;
11
12/// Application
13#[derive(Debug)]
14pub struct App {
15 pub running: bool, // Check if app is running
16 pub conn: Connection, // Connection
17 pub browser: FileBrowser, // Directory browser
18 pub queue_list: ContentList<Song>, // Stores the current playing queue
19 pub pl_list: ContentList<String>, // Stores list of playlists
20 pub selected_tab: SelectedTab, // Used to switch between tabs
21
22 // Search
23 pub inputmode: InputMode, // Defines input mode, Normal or Search
24 pub search_input: String, // Stores the userinput to be searched
25 pub search_cursor_pos: usize, // Stores the cursor position for searching
26 pub pl_newname_input: String, // Stores the new name of the playlist
27 pub pl_cursor_pos: usize, // Stores the cursor position for renaming playlist
28
29 // playlist variables
30 // used to show playlist popup
31 pub playlist_popup: bool,
32 pub append_list: ContentList<String>,
33}
34
35#[derive(Debug, PartialEq, Clone)]
36pub enum SelectedTab {
37 DirectoryBrowser,
38 Queue,
39 Playlists,
40}
41
42impl App {
43 pub fn builder(addrs: &str) -> AppResult<Self> {
44 let mut conn = Connection::new(addrs).unwrap();
45 let mut queue_list = ContentList::new();
46 let mut pl_list = ContentList::new();
47 pl_list.list = Self::get_playlist(&mut conn.conn)?;
48
49 let append_list = Self::get_append_list(&mut conn.conn)?;
50 Self::get_queue(&mut conn, &mut queue_list.list);
51
52 let browser = FileBrowser::new();
53
54 Ok(Self {
55 running: true,
56 conn,
57 queue_list,
58 pl_list,
59 selected_tab: SelectedTab::Queue,
60 browser,
61 inputmode: InputMode::Normal,
62 search_input: String::new(),
63 pl_newname_input: String::new(),
64 search_cursor_pos: 0,
65 pl_cursor_pos: 0,
66 playlist_popup: false,
67 append_list,
68 })
69 }
70
71 pub fn tick(&mut self) {
72 self.conn.update_status();
73 self.update_queue();
74 }
75
76 pub fn quit(&mut self) {
77 self.running = false;
78 }
79
80 pub fn get_queue(conn: &mut Connection, vec: &mut Vec<Song>) {
81 // conn.conn.queue().unwrap().into_iter().for_each(|x| {
82 // if let Some(title) = x.title {
83 // if let Some(artist) = x.artist {
84 // vec.push(format!("{} - {}", artist, title));
85 // } else {
86 // vec.push(title)
87 // }
88 // } else {
89 // vec.push(x.file)
90 // }
91 // });
92 conn.conn.queue().unwrap().into_iter().for_each(|x| {
93 vec.push(x);
94 });
95 }
96
97 // Rescan the queue into queue_list
98 pub fn update_queue(&mut self) {
99 self.queue_list.list.clear();
100 Self::get_queue(&mut self.conn, &mut self.queue_list.list);
101 }
102
103 pub fn get_playlist(conn: &mut Client) -> AppResult<Vec<String>> {
104 let list: Vec<String> = conn.playlists()?.iter().map(|p| p.clone().name).collect();
105 Ok(list)
106 }
107
108 pub fn get_append_list(conn: &mut Client) -> AppResult<ContentList<String>> {
109 let mut list = ContentList::new();
110 list.list.push("Current Playlist".to_string());
111 for item in Self::get_playlist(conn)? {
112 list.list.push(item.to_string());
113 }
114
115 Ok(list)
116 }
117
118 /// Handles the <Space> event key
119 pub fn handle_add_or_remove_from_current_playlist(&mut self) -> AppResult<()> {
120 match self.selected_tab {
121 SelectedTab::DirectoryBrowser => {
122 let (_, file) = self.browser.filetree.get(self.browser.selected).unwrap();
123
124 let mut status = false;
125 for (i, song) in self.queue_list.list.clone().iter().enumerate() {
126 if song.file.contains(file) {
127 self.conn.conn.delete(i as u32).unwrap();
128 status = true;
129 }
130 }
131
132 if !status {
133 if let Some(full_path) = &self.conn.get_full_path(file) {
134 let song = self.conn.get_song_with_only_filename(full_path);
135 self.conn.conn.push(&song)?;
136 }
137 }
138
139 if self.browser.selected != self.browser.filetree.len() - 1 {
140 self.browser.selected += 1;
141 }
142 }
143
144 SelectedTab::Queue => {
145 if self.queue_list.list.is_empty() {
146 return Ok(());
147 }
148 let file = self
149 .queue_list
150 .list
151 .get(self.queue_list.index)
152 .unwrap()
153 .file
154 .to_string();
155
156 for (i, song) in self.queue_list.list.clone().iter().enumerate() {
157 if song.file.contains(&file) {
158 self.conn.conn.delete(i as u32).unwrap();
159 if self.queue_list.index == self.queue_list.list.len() - 1
160 && self.queue_list.index != 0
161 {
162 self.queue_list.index -= 1;
163 }
164 }
165 }
166 }
167
168 _ => {}
169 }
170
171 self.update_queue();
172 Ok(())
173 }
174
175 /// Cycle through tabs
176 pub fn cycle_tabls(&mut self) {
177 self.selected_tab = match self.selected_tab {
178 SelectedTab::Queue => SelectedTab::DirectoryBrowser,
179 SelectedTab::DirectoryBrowser => SelectedTab::Playlists,
180 SelectedTab::Playlists => SelectedTab::DirectoryBrowser,
181 };
182 }
183
184 /// handles the Enter event on the directory browser
185 pub fn handle_enter(&mut self) -> AppResult<()> {
186 let browser = &mut self.browser;
187 let (t, path) = browser.filetree.get(browser.selected).unwrap();
188 if t == "directory" {
189 if path != "." {
190 browser.prev_path = browser.path.clone();
191 browser.path = browser.prev_path.clone() + "/" + path;
192 browser.update_directory(&mut self.conn)?;
193 browser.prev_selected = browser.selected;
194 browser.selected = 0;
195 }
196 } else {
197 let index = self
198 .queue_list
199 .list
200 .iter()
201 .position(|x| x.file.contains(path));
202
203 if index.is_some() {
204 self.conn.conn.switch(index.unwrap() as u32)?;
205 } else {
206 let mut filename = format!("{}/{}", browser.path, path);
207
208 // Remove "./" from the beginning of filename
209 filename.remove(0);
210 filename.remove(0);
211
212 let song = self.conn.get_song_with_only_filename(&filename);
213 self.conn.push(&song)?;
214
215 // updating queue, to avoid multiple pushes of the same songs if we enter multiple times before the queue gets updated
216 self.update_queue();
217 }
218 }
219 Ok(())
220 }
221
222 // Cursor movements
223 pub fn move_cursor_left(&mut self) {
224 match self.inputmode {
225 InputMode::PlaylistRename => {
226 let cursor_moved_left = self.pl_cursor_pos.saturating_sub(1);
227 self.pl_cursor_pos = self.clamp_cursor(cursor_moved_left);
228 }
229 InputMode::Editing => {
230 let cursor_moved_left = self.search_cursor_pos.saturating_sub(1);
231 self.search_cursor_pos = self.clamp_cursor(cursor_moved_left);
232 }
233 _ => {}
234 }
235 }
236
237 pub fn move_cursor_right(&mut self) {
238 match self.inputmode {
239 InputMode::PlaylistRename => {
240 let cursor_moved_right = self.pl_cursor_pos.saturating_add(1);
241 self.pl_cursor_pos = self.clamp_cursor(cursor_moved_right);
242 }
243 InputMode::Editing => {
244 let cursor_moved_right = self.search_cursor_pos.saturating_add(1);
245 self.search_cursor_pos = self.clamp_cursor(cursor_moved_right);
246 }
247 _ => {}
248 }
249 }
250
251 pub fn enter_char(&mut self, new_char: char) {
252 match self.inputmode {
253 InputMode::PlaylistRename => {
254 self.pl_newname_input.insert(self.pl_cursor_pos, new_char);
255 self.move_cursor_right();
256 }
257 InputMode::Editing => {
258 self.search_input.insert(self.search_cursor_pos, new_char);
259 self.move_cursor_right();
260 }
261 _ => {}
262 }
263 }
264
265 pub fn delete_char(&mut self) {
266 let is_not_cursor_leftmost = match self.inputmode {
267 InputMode::PlaylistRename => self.pl_cursor_pos != 0,
268 InputMode::Editing => self.search_cursor_pos != 0,
269 _ => false,
270 };
271
272 if is_not_cursor_leftmost {
273 // Method "remove" is not used on the saved text for deleting the selected char.
274 // Reason: Using remove on String works on bytes instead of the chars.
275 // Using remove would require special care because of char boundaries.
276
277 let current_index = match self.inputmode {
278 InputMode::Editing => self.search_cursor_pos,
279 InputMode::PlaylistRename => self.pl_cursor_pos,
280 _ => 0,
281 };
282
283 let from_left_to_current_index = current_index - 1;
284
285 if self.inputmode == InputMode::PlaylistRename {
286 // Getting all characters before the selected character.
287 let before_char_to_delete = self
288 .pl_newname_input
289 .chars()
290 .take(from_left_to_current_index);
291 // Getting all characters after selected character.
292 let after_char_to_delete = self.pl_newname_input.chars().skip(current_index);
293 // Put all characters together except the selected one.
294 // By leaving the selected one out, it is forgotten and therefore deleted.
295 self.pl_newname_input = before_char_to_delete.chain(after_char_to_delete).collect();
296 self.move_cursor_left();
297 } else if self.inputmode == InputMode::Editing {
298 // Getting all characters before the selected character.
299 let before_char_to_delete =
300 self.search_input.chars().take(from_left_to_current_index);
301 // Getting all characters after selected character.
302 let after_char_to_delete = self.search_input.chars().skip(current_index);
303 // Put all characters together except the selected one.
304 // By leaving the selected one out, it is forgotten and therefore deleted.
305 self.search_input = before_char_to_delete.chain(after_char_to_delete).collect();
306 self.move_cursor_left();
307 }
308 }
309 }
310
311 pub fn clamp_cursor(&self, new_cursor_pos: usize) -> usize {
312 match self.inputmode {
313 InputMode::PlaylistRename => new_cursor_pos.clamp(0, self.pl_newname_input.len()),
314 InputMode::Editing => new_cursor_pos.clamp(0, self.search_input.len()),
315 _ => 0,
316 }
317 }
318
319 pub fn reset_cursor(&mut self) {
320 match self.inputmode {
321 InputMode::Editing => {
322 self.search_cursor_pos = 0;
323 }
324 InputMode::PlaylistRename => {
325 self.pl_cursor_pos = 0;
326 }
327 _ => {}
328 }
329 }
330
331 /// Given time in seconds, convert it to hh:mm:ss
332 pub fn format_time(time: Duration) -> String {
333 let time = time.as_secs();
334 let h = time / 3600;
335 let m = (time % 3600) / 60;
336 let s = (time % 3600) % 60;
337 if h == 0 {
338 format!("{:02}:{:02}", m, s)
339 } else {
340 format!("{:02}:{:02}:{:02}", h, m, s)
341 }
342 }
343
344 pub fn change_playlist_name(&mut self) -> AppResult<()> {
345 if self.selected_tab == SelectedTab::Playlists {
346 self.inputmode = InputMode::PlaylistRename;
347 }
348 Ok(())
349 }
350}