Krolyxon
Commit 8269766

rmptuisrcui.rs

240 lines · 7.6 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::BOLD)
82 .add_modifier(Modifier::REVERSED),
83 )
84 .highlight_symbol(">>")
85 .repeat_highlight_symbol(true)
86 .scroll_padding(20);
87
88 song_state.select(Some(app.browser.selected));
89 frame.render_stateful_widget(list, size, &mut song_state);
90}
91
92/// draws playing queue
93fn draw_queue(frame: &mut Frame, app: &mut App, size: Rect) {
94 let mut queue_state = ListState::default();
95 let title = Block::default()
96 .title(Title::from("Play Queue".green().bold()))
97 .title(
98 Title::from(format!("Volume: {}%", app.conn.volume).bold().green())
99 .alignment(Alignment::Right),
100 );
101 let list = List::new(app.queue_list.list.clone())
102 .block(title.borders(Borders::ALL))
103 .highlight_style(
104 Style::new()
105 .fg(Color::Cyan)
106 .bg(Color::Black)
107 .add_modifier(Modifier::BOLD)
108 .add_modifier(Modifier::REVERSED),
109 )
110 .highlight_symbol(">>");
111
112 queue_state.select(Some(app.queue_list.index));
113 frame.render_stateful_widget(list, size, &mut queue_state);
114}
115
116/// draws all playlists
117fn draw_playlists(frame: &mut Frame, app: &mut App, size: Rect) {
118 let mut state = ListState::default();
119
120 let title = Block::default()
121 .title(Title::from("Playlist".green().bold()))
122 .title(
123 Title::from(format!("Volume: {}%", app.conn.volume).bold().green())
124 .alignment(Alignment::Right),
125 );
126
127 let list = List::new(app.pl_list.list.clone())
128 .block(title.borders(Borders::ALL))
129 .highlight_style(
130 Style::new()
131 .fg(Color::Cyan)
132 .bg(Color::Black)
133 .add_modifier(Modifier::BOLD)
134 .add_modifier(Modifier::REVERSED),
135 )
136 .highlight_symbol(">>")
137 .repeat_highlight_symbol(true);
138
139 state.select(Some(app.pl_list.index));
140 frame.render_stateful_widget(list, size, &mut state);
141}
142
143// Draw search bar
144fn draw_search_bar(frame: &mut Frame, app: &mut App, size: Rect) {
145 match app.inputmode {
146 InputMode::Normal =>
147 // Hide the cursor. `Frame` does this by default, so we don't need to do anything here
148 {}
149
150 InputMode::Editing => {
151 // Make the cursor visible and ask ratatui to put it at the specified coordinates after
152 // rendering
153 #[allow(clippy::cast_possible_truncation)]
154 frame.set_cursor(
155 // Draw the cursor at the current position in the input field.
156 // This position is can be controlled via the left and right arrow key
157 size.x + app.cursor_position as u16 + 2,
158 // Move one line down, from the border to the input line
159 size.y + 1,
160 );
161 }
162 }
163
164 let input = Paragraph::new("/".to_string() + &app.search_input)
165 .style(Style::default())
166 .block(
167 Block::default()
168 .borders(Borders::ALL)
169 .title("Search Forward: ".bold().green()),
170 );
171 frame.render_widget(input, size);
172}
173
174/// Draws Progress Bar
175fn draw_progress_bar(frame: &mut Frame, app: &mut App, size: Rect) {
176 // Get the current playing song
177 let song = app
178 .conn
179 .now_playing()
180 .unwrap()
181 .unwrap_or_else(|| "No Title Found".to_string());
182
183 // Get the current playing state
184 let mut state: String = String::new();
185 if !app.queue_list.list.is_empty() {
186 state = app.conn.state.clone();
187 state.push(':');
188 }
189
190 // Get the current modes
191 let mut modes_bottom: String = String::new();
192 // we do this to check if at least one mode is enabled so we can push "[]"
193 if app.conn.repeat | app.conn.random {
194 modes_bottom.push('r');
195 }
196
197 if !modes_bottom.is_empty() {
198 modes_bottom.clear();
199 modes_bottom.push('[');
200 if app.conn.repeat {
201 modes_bottom.push('r');
202 }
203 if app.conn.random {
204 modes_bottom.push('z');
205 }
206 modes_bottom.push(']');
207 };
208
209 // get the duration
210 let duration = if app.conn.total_duration.as_secs() != 0 {
211 format!(
212 "[{}/{}]",
213 humantime::format_duration(app.conn.elapsed),
214 humantime::format_duration(app.conn.total_duration)
215 )
216 } else {
217 "".to_string()
218 };
219
220 // Define the title
221 let title = Block::default()
222 .title(Title::from(format!("{}", state).red().bold()))
223 .title(Title::from(song.green().bold()))
224 .title(Title::from(duration.cyan().bold()).alignment(Alignment::Right))
225 .title(Title::from(format!("{}", modes_bottom)).position(block::Position::Bottom))
226 .borders(Borders::ALL);
227
228 let progress_bar = LineGauge::default()
229 .block(title.borders(Borders::ALL))
230 .gauge_style(
231 Style::default()
232 .fg(Color::Blue)
233 .bg(Color::Black)
234 .add_modifier(Modifier::BOLD),
235 )
236 .line_set(symbols::line::THICK)
237 .ratio(app.conn.get_progress_ratio());
238
239 frame.render_widget(progress_bar, size);
240}