Krolyxon
Commit e500c52

rmptuisrcui.rs

238 lines · 7.5 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 // This is where you add new widgets.
25 // See the following resources:
26 // - https://docs.rs/ratatui/latest/ratatui/widgets/index.html
27 // - https://github.com/ratatui-org/ratatui/tree/master/examples
28
29 // Layout
30 let layout = Layout::default()
31 .direction(Direction::Vertical)
32 .constraints(vec![Constraint::Percentage(93), Constraint::Min(3)])
33 .split(frame.size());
34
35 match app.selected_tab {
36 SelectedTab::Queue => draw_queue(frame, app, layout[0]),
37 SelectedTab::Playlists => draw_playlists(frame, app, layout[0]),
38 SelectedTab::DirectoryBrowser => draw_directory_browser(frame, app, layout[0]),
39 }
40
41 match app.inputmode {
42 InputMode::Normal => {
43 draw_progress_bar(frame, app, layout[1]);
44 }
45 InputMode::Editing => {
46 draw_search_bar(frame, app, layout[1]);
47 }
48 }
49}
50
51/// Draws the file tree browser
52fn draw_directory_browser(frame: &mut Frame, app: &mut App, size: Rect) {
53 let mut song_state = ListState::default();
54 let total_songs = app.conn.conn.stats().unwrap().songs.to_string();
55 let mut list: Vec<String> = vec![];
56 for (t, s) in app.browser.filetree.iter() {
57 if t == "file" {
58 list.push(s.to_string());
59 } else {
60 list.push(format!("[{}]", *s));
61 }
62 }
63 let list = List::new(list)
64 .block(
65 Block::default()
66 .title(format!("File Browser: {}", app.browser.path.clone()).bold())
67 .title(
68 Title::from(format!("Total Songs: {}", total_songs).bold().green())
69 .alignment(Alignment::Center),
70 )
71 .title(
72 Title::from(format!("Volume: {}%", app.conn.volume).bold().green())
73 .alignment(Alignment::Right),
74 )
75 .borders(Borders::ALL),
76 )
77 .highlight_style(
78 Style::new()
79 .fg(Color::Cyan)
80 .bg(Color::Black)
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::REVERSED),
107 )
108 .highlight_symbol(">>")
109 .repeat_highlight_symbol(true);
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::REVERSED),
133 )
134 .highlight_symbol(">>")
135 .repeat_highlight_symbol(true);
136
137 state.select(Some(app.pl_list.index));
138 frame.render_stateful_widget(list, size, &mut state);
139}
140
141// Draw search bar
142fn draw_search_bar(frame: &mut Frame, app: &mut App, size: Rect) {
143 match app.inputmode {
144 InputMode::Normal =>
145 // Hide the cursor. `Frame` does this by default, so we don't need to do anything here
146 {}
147
148 InputMode::Editing => {
149 // Make the cursor visible and ask ratatui to put it at the specified coordinates after
150 // rendering
151 #[allow(clippy::cast_possible_truncation)]
152 frame.set_cursor(
153 // Draw the cursor at the current position in the input field.
154 // This position is can be controlled via the left and right arrow key
155 size.x + app.cursor_position as u16 + 2,
156 // Move one line down, from the border to the input line
157 size.y + 1,
158 );
159 }
160 }
161
162 let input = Paragraph::new("/".to_string() + &app.search_input)
163 .style(Style::default())
164 .block(
165 Block::default()
166 .borders(Borders::ALL)
167 .title("Search Forward: ".bold().green()),
168 );
169 frame.render_widget(input, size);
170}
171
172/// Draws Progress Bar
173fn draw_progress_bar(frame: &mut Frame, app: &mut App, size: Rect) {
174 // Get the current playing song
175 let song = app
176 .conn
177 .now_playing()
178 .unwrap()
179 .unwrap_or_else(|| "No Title Found".to_string());
180
181 // Get the current playing state
182 let mut state: String = String::new();
183 if !app.queue_list.list.is_empty() {
184 state = app.conn.state.clone();
185 state.push(':');
186 }
187
188 // Get the current modes
189 let mut modes_bottom: String = String::new();
190 // we do this to check if at least one mode is enabled so we can push "[]"
191 if app.conn.repeat | app.conn.random {
192 modes_bottom.push('r');
193 }
194
195 if !modes_bottom.is_empty() {
196 modes_bottom.clear();
197 modes_bottom.push('[');
198 if app.conn.repeat {
199 modes_bottom.push('r');
200 }
201 if app.conn.random {
202 modes_bottom.push('z');
203 }
204 modes_bottom.push(']');
205 };
206
207 // get the duration
208 let duration = if app.conn.total_duration.as_secs() != 0 {
209 format!(
210 "[{}/{}]",
211 humantime::format_duration(app.conn.elapsed),
212 humantime::format_duration(app.conn.total_duration)
213 )
214 } else {
215 "".to_string()
216 };
217
218 // Define the title
219 let title = Block::default()
220 .title(Title::from(format!("{}", state).red().bold()))
221 .title(Title::from(song.green().bold()))
222 .title(Title::from(duration.cyan().bold()).alignment(Alignment::Right))
223 .title(Title::from(format!("{}", modes_bottom)).position(block::Position::Bottom))
224 .borders(Borders::ALL);
225
226 let progress_bar = LineGauge::default()
227 .block(title.borders(Borders::ALL))
228 .gauge_style(
229 Style::default()
230 .fg(Color::Blue)
231 .bg(Color::Black)
232 .add_modifier(Modifier::BOLD),
233 )
234 .line_set(symbols::line::THICK)
235 .ratio(app.conn.get_progress_ratio());
236
237 frame.render_widget(progress_bar, size);
238}