Krolyxon
Commit 828c0a5

rmptuisrcui.rs

278 lines · 8.9 KB · Rust

1use crate::app::{App, SelectedTab};
2use ratatui::{
3 prelude::*,
4 widgets::{block::Title, *},
5};
6
7#[derive(Debug, PartialEq)]
8pub enum InputMode {
9 Editing,
10 Normal,
11}
12
13impl InputMode {
14 pub fn toggle_editing_states(state: &InputMode) -> InputMode {
15 match state {
16 InputMode::Editing => return InputMode::Normal,
17 InputMode::Normal => return InputMode::Editing,
18 };
19 }
20}
21
22/// Renders the user interface widgets
23pub fn render(app: &mut App, frame: &mut Frame) {
24 // Layout
25 let layout = Layout::default()
26 .direction(Direction::Vertical)
27 .constraints(vec![Constraint::Percentage(93), Constraint::Min(3)])
28 .split(frame.size());
29
30 match app.selected_tab {
31 SelectedTab::Queue => draw_queue(frame, app, layout[0]),
32 SelectedTab::Playlists => draw_playlists(frame, app, layout[0]),
33 SelectedTab::DirectoryBrowser => draw_directory_browser(frame, app, layout[0]),
34 }
35
36 match app.inputmode {
37 InputMode::Normal => {
38 draw_progress_bar(frame, app, layout[1]);
39 }
40 InputMode::Editing => {
41 draw_search_bar(frame, app, layout[1]);
42 }
43 }
44
45 if app.playlist_popup {
46 draw_add_to_playlist(frame, app, layout[0]);
47 }
48}
49
50/// Draws the file tree browser
51fn draw_directory_browser(frame: &mut Frame, app: &mut App, size: Rect) {
52 let mut song_state = ListState::default();
53 let total_songs = app.conn.conn.stats().unwrap().songs.to_string();
54 let mut list: Vec<String> = vec![];
55 for (t, s) in app.browser.filetree.iter() {
56 if t == "file" {
57 list.push(s.to_string());
58 } else {
59 list.push(format!("[{}]", *s));
60 }
61 }
62 let list = List::new(list)
63 .block(
64 Block::default()
65 .title(format!("File Browser: {}", app.browser.path.clone()).bold())
66 .title(
67 Title::from(format!("Total Songs: {}", total_songs).bold().green())
68 .alignment(Alignment::Center),
69 )
70 .title(
71 Title::from(format!("Volume: {}%", app.conn.volume).bold().green())
72 .alignment(Alignment::Right),
73 )
74 .borders(Borders::ALL),
75 )
76 .highlight_style(
77 Style::new()
78 .fg(Color::Cyan)
79 .bg(Color::Black)
80 .add_modifier(Modifier::BOLD)
81 .add_modifier(Modifier::REVERSED),
82 )
83 .highlight_symbol(">>")
84 .repeat_highlight_symbol(true)
85 .scroll_padding(20);
86
87 song_state.select(Some(app.browser.selected));
88 frame.render_stateful_widget(list, size, &mut song_state);
89}
90
91/// draws playing queue
92fn draw_queue(frame: &mut Frame, app: &mut App, size: Rect) {
93 let mut queue_state = ListState::default();
94 let title = Block::default()
95 .title(Title::from("Play Queue".green().bold()))
96 .title(
97 Title::from(format!("Volume: {}%", app.conn.volume).bold().green())
98 .alignment(Alignment::Right),
99 );
100 let list = List::new(app.queue_list.list.clone())
101 .block(title.borders(Borders::ALL))
102 .highlight_style(
103 Style::new()
104 .fg(Color::Cyan)
105 .bg(Color::Black)
106 .add_modifier(Modifier::BOLD)
107 .add_modifier(Modifier::REVERSED),
108 )
109 .highlight_symbol(">>");
110
111 queue_state.select(Some(app.queue_list.index));
112 frame.render_stateful_widget(list, size, &mut queue_state);
113}
114
115/// draws all playlists
116fn draw_playlists(frame: &mut Frame, app: &mut App, size: Rect) {
117 let mut state = ListState::default();
118
119 let title = Block::default()
120 .title(Title::from("Playlist".green().bold()))
121 .title(
122 Title::from(format!("Volume: {}%", app.conn.volume).bold().green())
123 .alignment(Alignment::Right),
124 );
125
126 let list = List::new(app.pl_list.list.clone())
127 .block(title.borders(Borders::ALL))
128 .highlight_style(
129 Style::new()
130 .fg(Color::Cyan)
131 .bg(Color::Black)
132 .add_modifier(Modifier::BOLD)
133 .add_modifier(Modifier::REVERSED),
134 )
135 .highlight_symbol(">>")
136 .repeat_highlight_symbol(true);
137
138 state.select(Some(app.pl_list.index));
139 frame.render_stateful_widget(list, size, &mut state);
140}
141
142// Draw search bar
143fn draw_search_bar(frame: &mut Frame, app: &mut App, size: Rect) {
144 match app.inputmode {
145 InputMode::Normal =>
146 // Hide the cursor. `Frame` does this by default, so we don't need to do anything here
147 {}
148
149 InputMode::Editing => {
150 // Make the cursor visible and ask ratatui to put it at the specified coordinates after
151 // rendering
152 #[allow(clippy::cast_possible_truncation)]
153 frame.set_cursor(
154 // Draw the cursor at the current position in the input field.
155 // This position is can be controlled via the left and right arrow key
156 size.x + app.cursor_position as u16 + 2,
157 // Move one line down, from the border to the input line
158 size.y + 1,
159 );
160 }
161 }
162
163 let input = Paragraph::new("/".to_string() + &app.search_input)
164 .style(Style::default())
165 .block(
166 Block::default()
167 .borders(Borders::ALL)
168 .title("Search Forward: ".bold().green()),
169 );
170 frame.render_widget(input, size);
171}
172
173/// Draws Progress Bar
174fn draw_progress_bar(frame: &mut Frame, app: &mut App, size: Rect) {
175 // Get the current playing song
176 let song = app
177 .conn
178 .now_playing()
179 .unwrap()
180 .unwrap_or_else(|| "No Title Found".to_string());
181
182 // Get the current playing state
183 let mut state: String = String::new();
184 if !app.queue_list.list.is_empty() {
185 state = app.conn.state.clone();
186 state.push(':');
187 }
188
189 // Get the current modes
190 let mut modes_bottom: String = String::new();
191 // we do this to check if at least one mode is enabled so we can push "[]"
192 if app.conn.repeat | app.conn.random {
193 modes_bottom.push('r');
194 }
195
196 if !modes_bottom.is_empty() {
197 modes_bottom.clear();
198 modes_bottom.push('[');
199 if app.conn.repeat {
200 modes_bottom.push('r');
201 }
202 if app.conn.random {
203 modes_bottom.push('z');
204 }
205 modes_bottom.push(']');
206 };
207
208 // get the duration
209 let duration = if app.conn.total_duration.as_secs() != 0 {
210 format!(
211 "[{}/{}]",
212 humantime::format_duration(app.conn.elapsed),
213 humantime::format_duration(app.conn.total_duration)
214 )
215 } else {
216 "".to_string()
217 };
218
219 // Define the title
220 let title = Block::default()
221 .title(Title::from(format!("{}", state).red().bold()))
222 .title(Title::from(song.green().bold()))
223 .title(Title::from(duration.cyan().bold()).alignment(Alignment::Right))
224 .title(Title::from(format!("{}", modes_bottom)).position(block::Position::Bottom))
225 .borders(Borders::ALL);
226
227 let progress_bar = LineGauge::default()
228 .block(title.borders(Borders::ALL))
229 .gauge_style(
230 Style::default()
231 .fg(Color::Blue)
232 .bg(Color::Black)
233 .add_modifier(Modifier::BOLD),
234 )
235 .line_set(symbols::line::THICK)
236 .ratio(app.conn.get_progress_ratio());
237
238 frame.render_widget(progress_bar, size);
239}
240
241fn draw_add_to_playlist(frame: &mut Frame, app: &mut App, area: Rect) {
242 let area = centered_rect(40, 50, area);
243 let mut state = ListState::default();
244 let title = Block::default()
245 .title(Title::from("Add Selected Item to: "))
246 .title(Title::from("<Esc> to Cancel".green().bold()).alignment(Alignment::Right));
247 let list = List::new(app.append_list.list.clone())
248 .block(title.borders(Borders::ALL))
249 .highlight_style(
250 Style::new()
251 .fg(Color::Cyan)
252 .bg(Color::Black)
253 .add_modifier(Modifier::BOLD)
254 .add_modifier(Modifier::REVERSED),
255 )
256 .highlight_symbol(">>")
257 .repeat_highlight_symbol(true);
258
259 state.select(Some(app.append_list.index));
260 frame.render_widget(Clear, area); //this clears out the background
261 frame.render_stateful_widget(list, area, &mut state);
262}
263
264fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect {
265 let popup_layout = Layout::vertical([
266 Constraint::Percentage((100 - percent_y) / 2),
267 Constraint::Percentage(percent_y),
268 Constraint::Percentage((100 - percent_y) / 2),
269 ])
270 .split(r);
271
272 Layout::horizontal([
273 Constraint::Percentage((100 - percent_x) / 2),
274 Constraint::Percentage(percent_x),
275 Constraint::Percentage((100 - percent_x) / 2),
276 ])
277 .split(popup_layout[1])[1]
278}