Krolyxon
Commit 9c30356

rmptuisrcapp.rs

360 lines · 12.6 KB · Rust

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