Krolyxon
Tag v0.1.0

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