inLimbo
TUI Music Player that keeps you in Limbo.
 
Loading...
Searching...
No Matches
misc.hpp
Go to the documentation of this file.
1#pragma once
2
3#include "../arg-handler.hpp"
5#include "./colors.hpp"
6#include "./keymaps.hpp"
8#include <algorithm>
9#include <cctype>
10#include <ftxui/component/captured_mouse.hpp>
11#include <ftxui/component/component.hpp>
12#include <ftxui/component/component_base.hpp>
13#include <ftxui/component/event.hpp>
14#include <ftxui/component/screen_interactive.hpp>
15#include <ftxui/dom/elements.hpp>
16#include <string>
17#include <vector>
18
20#define MAX_LENGTH_SONG_NAME 50
21#define MAX_LENGTH_ARTIST_NAME 30
22
23#define SONG_TITLE_DELIM " • "
24#define LYRICS_AVAIL "L*"
25#define ADDN_PROPS_AVAIL "&*"
26#define STATUS_BAR_DELIM " | "
27
28auto handleToggleMute(int* volume, int* lastVolume, bool* muted) -> int
29{
30 *muted = !*muted;
31 if (*muted)
32 {
33 *lastVolume = *volume;
34 *volume = 0;
35 }
36 else
37 {
38 *volume = *lastVolume;
39 }
40 return *volume;
41}
42
43auto formatDiscTrackInfo(const int& disc_number, const int& track_number)
44{
45 std::string formatted_info =
46 " " + std::to_string(disc_number) + "-" + std::to_string(track_number) + " ";
47
48 return formatted_info;
49}
50
51auto formatLyrics(const std::string& lyrics)
52{
53 std::vector<std::string> lines;
54 std::string currentLine;
55 bool insideSquareBrackets = false;
56 bool insideCurlBrackets = false;
57 bool lastWasUppercase = false;
58 bool lastWasSpecialChar = false; // Tracks special characters within words
59 char previousChar = '\0';
60
61 for (char c : lyrics)
62 {
63 if (c == '[' || c == '(')
64 {
65 if (!currentLine.empty())
66 {
67 lines.push_back(currentLine);
68 currentLine.clear();
69 }
70 if (c == '[')
71 insideSquareBrackets = true;
72 else
73 insideCurlBrackets = true;
74 currentLine += c;
75 continue;
76 }
77
78 if (insideSquareBrackets || insideCurlBrackets)
79 {
80 currentLine += c;
81 if (c == ']' && insideSquareBrackets)
82 {
83 lines.push_back(currentLine);
84 currentLine.clear();
85 insideSquareBrackets = false;
86 }
87
88 else if (c == ')' && insideCurlBrackets)
89 {
90 lines.push_back(currentLine);
91 currentLine.clear();
92 insideCurlBrackets = false;
93 }
94 continue;
95 }
96
97 if (c == '\'' || c == '-')
98 {
99 currentLine += c;
100 lastWasSpecialChar = true;
101 continue;
102 }
103
104 if (std::isupper(c) && !lastWasUppercase && !lastWasSpecialChar && !currentLine.empty() &&
105 previousChar != '\n' && previousChar != ' ')
106 {
107 lines.push_back(currentLine);
108 currentLine.clear();
109 }
110
111 currentLine += c;
112
113 if (c == '\n')
114 {
115 if (!currentLine.empty())
116 {
117 lines.push_back(currentLine);
118 currentLine.clear();
119 }
120 }
121
122 lastWasUppercase = std::isupper(c);
123 lastWasSpecialChar = false;
124 previousChar = c;
125 }
126
127 if (!currentLine.empty())
128 {
129 lines.push_back(currentLine);
130 }
131
132 // Trim empty lines (optional)
133 lines.erase(std::remove_if(lines.begin(), lines.end(),
134 [](const std::string& line) { return line.empty(); }),
135 lines.end());
136
137 return lines;
138}
139
140auto formatAdditionalInfo(const std::string& genre, const bool& has_comment, const bool& has_lyrics,
141 const bool& show_bitrate, const int& bitrate) -> std::string
142{
143 std::string additional_info = "";
144 if (genre != "Unknown Genre")
145 {
146 additional_info += "Genre: " + genre + STATUS_BAR_DELIM;
147 }
148 if (has_comment)
149 {
150 additional_info += ADDN_PROPS_AVAIL;
151 }
152 if (has_lyrics)
153 {
154 additional_info += STATUS_BAR_DELIM;
155 additional_info += LYRICS_AVAIL;
156 }
157 if (show_bitrate)
158 {
159 additional_info += STATUS_BAR_DELIM;
160 additional_info += std::to_string(bitrate) + " kbps";
161 }
162 additional_info += STATUS_BAR_DELIM;
163
164 return additional_info;
165}
166
167auto charToStr(char ch) -> std::string
168{
169 switch (ch)
170 {
171 case '\t':
172 return "Tab";
173 case ' ':
174 return "Space";
175 case '\n':
176 return "Enter";
177 case 27:
178 return "Escape"; // ASCII value for the Escape key
179 default:
180 return std::string(1, static_cast<char>(ch));
181 }
182}
183
184auto FormatTime(int seconds)
185{
186 int minutes = seconds / 60;
187 seconds = seconds % 60;
188 std::stringstream ss;
189 ss << std::setfill('0') << std::setw(2) << minutes << ":" << std::setfill('0') << std::setw(2)
190 << seconds << " ";
191 return ss.str();
192}
193
194auto getTrueColor(TrueColors::Color color) { return ftxui::color(TrueColors::GetColor(color)); }
195
196auto getTrueBGColor(TrueColors::Color color) { return ftxui::bgcolor(TrueColors::GetColor(color)); }
197
198auto renderAlbumName(const std::string& album_name, const int& year, ftxui::Color sel_color,
199 ftxui::Color sel_color_fg)
200{
201 auto albumText = vbox(text(album_name) | color(sel_color_fg) | bold);
202 return hbox({text(" "), albumText, filler(),
203 text(std::to_string(year)) | color(sel_color_fg) | bold | align_right, text(" ")}) |
204 bgcolor(sel_color);
205}
206
207auto renderSongName(const std::string& disc_track_info, const std::string& song_name,
208 const int& duration)
209{
210 return hbox({text(disc_track_info), text(song_name) | bold | flex_grow,
211 filler(), // Spacer for dynamic layout
212 text(FormatTime(duration)) | align_right});
213}
214
215auto RenderArtistNames(const std::vector<std::string>& artist_list)
216{
217 std::vector<Element> artist_names_elements;
218
219 for (const auto& a : artist_list)
220 {
221 auto artistElement = hbox({text(" "), text(a) | bold});
222 artist_names_elements.push_back(artistElement);
223 }
224
225 return artist_names_elements;
226}
227
228auto CreateMenu(const std::vector<std::string>* vecLines, int* currLine)
229{
230 MenuOption menu_options;
231 menu_options.on_change = [&]() {};
232 menu_options.focused_entry = currLine;
233
234 return Menu(vecLines, currLine, menu_options);
235}
236
237auto RenderSongMenu(const std::vector<Element>& items)
238{
239 Elements rendered_items;
240 for (const auto& item : items)
241 {
242 rendered_items.push_back(item | frame);
243 }
244
245 return vbox(std::move(rendered_items));
246}
247
248auto RenderArtistMenu(const std::vector<std::string>& artist_list)
249{
250 auto items = RenderArtistNames(artist_list);
251 Elements rendered_items;
252 for (const auto& item : items)
253 {
254 rendered_items.push_back(item | frame);
255 }
256
257 return vbox(std::move(rendered_items));
258}
259
260auto getMimeTypeFromExtension(const std::string& filePath) -> std::string
261{
262 // Map file extensions to MIME types
263 static std::map<std::string, std::string> mimeTypes = {
264 {".mp3", "audio/mpeg"}, {".flac", "audio/flac"}, {".wav", "audio/wav"},
265 {".ogg", "audio/ogg"}, {".aac", "audio/aac"}, {".m4a", "audio/mp4"},
266 {".opus", "audio/opus"}, {".wma", "audio/x-ms-wma"}, {".alac", "audio/alac"}};
267
268 // Extract the file extension
269 std::string::size_type idx = filePath.rfind('.');
270 if (idx != std::string::npos)
271 {
272 std::string extension = filePath.substr(idx);
273 auto it = mimeTypes.find(extension);
274 if (it != mimeTypes.end())
275 {
276 return it->second;
277 }
278 }
279
280 // Default MIME type if the extension is unknown
281 return "application/octet-stream";
282}
283
284auto RenderThumbnail(const std::string& songFilePath, const std::string& cacheDirPath,
285 const std::string& songTitle, const std::string& artistName,
286 const std::string& albumName, const std::string& genre, unsigned int year,
287 unsigned int trackNumber, unsigned int discNumber,
288 float progress) // progress: a value between 0.0 (0%) and 1.0 (100%)
289{
290 auto thumbnailFilePath = cacheDirPath + "thumbnail.png";
291
292 if (extractThumbnail(songFilePath, thumbnailFilePath))
293 {
294 auto thumbnail = Renderer(
295 [&]
296 {
297 return vbox({
298 image_view(thumbnailFilePath) | size(WIDTH, LESS_THAN, 50) |
299 size(HEIGHT, LESS_THAN, 50),
300 }) |
301 center;
302 });
303
304 auto metadataView =
305 vbox({
306 hbox({text(songTitle) | center | bold | size(WIDTH, EQUAL, MAX_LENGTH_SONG_NAME)}) | center,
307 hbox({text(artistName) | dim}) | center,
308 hbox({text(albumName) | underlined, text(SONG_TITLE_DELIM),
309 text(std::to_string(year)) | dim}) |
310 center,
311 hbox({text("Track ") | dim, text(std::to_string(trackNumber)) | bold, text(" on Disc "),
312 text(std::to_string(discNumber)) | bold}) |
313 center,
314 separator(),
315 hbox({text("Genre: "), text(genre) | bold}) | center,
316 hbox({text("Format: "), text(getMimeTypeFromExtension(songFilePath)) | bold}) | center,
317 }) |
318 borderRounded;
319
320 auto progressBar = vbox({
321 separator(),
322 hbox({text("Playback Progress") | bold}) | center,
323 hbox({gauge(progress) | flex,
324 text(" " + std::to_string(static_cast<int>(progress * 100)) + "%")}),
325 });
326
327 auto modernUI = vbox({
328 thumbnail->Render() | flex_shrink,
329 separator(),
330 metadataView,
331 separator(),
332 progressBar,
333 }) |
334 borderRounded;
335
336 return modernUI;
337 }
338
339 // Fallback UI when thumbnail extraction fails
340 auto errorView = vbox({
341 text("⚠ Thumbnail Unavailable") | bold | center | color(Color::Red),
342 separator(),
343 text("Ensure the file has embedded artwork.") | dim | center,
344 }) |
345 border;
346
347 return errorView;
348}
349
350auto RenderSearchBar(std::string& user_input) -> Element
351{
352 return hbox({
353 text("/") | color(Color::GrayLight),
354 text(user_input) | color(Color::LightSteelBlue) | bold | inverted,
355 }) |
356 color(Color::GrayDark) | bgcolor(Color::Black) | size(HEIGHT, EQUAL, 1);
357}
358
359auto RenderDialog(const std::string& dialog_message) -> Element
360{
361 return window(text(" inLimbo Information ") | bold | center |
363 vbox({
364 text(dialog_message) | getTrueColor(TrueColors::Color::Coral),
365 separator() | color(Color::GrayLight),
366 text("Press 'x' to close") | dim | center | getTrueColor(TrueColors::Color::Pink),
367 }) |
368 center) |
369 size(WIDTH, LESS_THAN, 60) | size(HEIGHT, LESS_THAN, 8) |
371}
372
373auto RenderHelpScreen(Keybinds& global_keybinds) -> Element
374{
375 auto title = text("inLimbo Controls") | bold | getTrueColor(TrueColors::Color::Teal);
376
377 // Helper function to create a single keybind row
378 auto createRow =
379 [](const std::string& key, const std::string& description, TrueColors::Color color)
380 {
381 return hbox({
382 text(key) | getTrueColor(color),
384 text(description) | getTrueColor(TrueColors::Color::White),
385 });
386 };
387
388 // List of keybinds for easier modification
389 std::vector<std::tuple<std::string, std::string, TrueColors::Color>> keybinds = {
390 {charToStr(global_keybinds.scroll_up), "Scroll up in the current view",
392 {charToStr(global_keybinds.scroll_down), "Scroll down in the current view",
394 {charToStr(global_keybinds.toggle_focus), "Switch focus between panes",
396 {charToStr(global_keybinds.show_help), "Toggle this help window", TrueColors::Color::Cyan},
397 {charToStr(global_keybinds.toggle_play), "Play/Pause playback", TrueColors::Color::Teal},
398 {charToStr(global_keybinds.play_song), "Play the selected song", TrueColors::Color::LightGreen},
399 {charToStr(global_keybinds.play_song_next), "Skip to next song", TrueColors::Color::LightCyan},
400 {charToStr(global_keybinds.play_song_prev), "Go back to previous song",
402 {charToStr(global_keybinds.vol_up), "Increase volume", TrueColors::Color::LightGreen},
403 {charToStr(global_keybinds.vol_down), "Decrease volume", TrueColors::Color::LightGreen},
404 {charToStr(global_keybinds.toggle_mute), "Mute/Unmute audio", TrueColors::Color::LightRed},
405 {charToStr(global_keybinds.quit_app), "Quit inLimbo", TrueColors::Color::LightRed},
406 {charToStr(global_keybinds.seek_ahead_5), "Seek forward by 5 seconds",
408 {charToStr(global_keybinds.seek_behind_5), "Seek backward by 5 seconds",
410 {charToStr(global_keybinds.view_lyrics), "View lyrics for the current song",
412 {charToStr(global_keybinds.goto_main_screen), "Return to main UI", TrueColors::Color::Teal},
413 {charToStr(global_keybinds.replay_song), "Replay the current song", TrueColors::Color::Orange},
414 {charToStr(global_keybinds.add_song_to_queue), "Add selected song to queue",
416 {charToStr(global_keybinds.add_artists_songs_to_queue), "Queue all songs by the artist",
418 {charToStr(global_keybinds.remove_song_from_queue), "Remove selected song from queue",
420 {charToStr(global_keybinds.play_this_song_next), "Play selected song next",
422 {charToStr(global_keybinds.view_song_queue), "View currently queued songs",
424 {charToStr(global_keybinds.view_current_song_info), "View info of the currently playing song",
426 {charToStr(global_keybinds.toggle_audio_devices), "Switch between available audio devices",
428 {charToStr(global_keybinds.search_menu), "Open the search menu", TrueColors::Color::LightGreen},
429 {"gg", "Go to the top of the active menu", TrueColors::Color::LightBlue},
430 {"G", "Go to the bottom of the active menu", TrueColors::Color::LightBlue},
431 {"x", "Close the dialog box", TrueColors::Color::LightRed},
432 };
433
434 // Generate controls list dynamically
435 std::vector<Element> control_elements;
436 for (const auto& [key, description, color] : keybinds)
437 {
438 control_elements.push_back(createRow(key, description, color));
439 }
440
441 auto controls_list =
442 vbox(std::move(control_elements)) | getTrueColor(TrueColors::Color::LightGreen);
443
444 // Symbols Legend
445 auto symbols_explanation = vbox({
446 hbox({text(LYRICS_AVAIL), text(" -> "), text("The current song has lyrics metadata.")}) |
448 hbox({text(ADDN_PROPS_AVAIL), text(" -> "),
449 text("The current song has additional properties metadata.")}) |
451 });
452
453 // Application details
454 auto app_name = text("inLimbo - Music player that keeps you in Limbo...") | bold |
456 auto contributor =
457 text("Developed by: Siddharth Karanam (nots1dd)") | getTrueColor(TrueColors::Color::LightGreen);
458 auto github_link = hbox({
459 text("GitHub: "),
460 text("Click me!") | underlined | bold | getTrueColor(TrueColors::Color::LightBlue) |
461 hyperlink(REPOSITORY_URL),
462 });
463
464 std::string footer_text =
465 "Press '" + charToStr(global_keybinds.show_help) + "' to return to inLimbo.";
466 auto footer = vbox({
467 app_name,
468 contributor,
469 github_link,
470 text(footer_text) | getTrueColor(TrueColors::Color::LightYellow) | center,
471 }) |
472 flex | border;
473
474 return vbox({
475 title,
476 controls_list | border | flex,
477 text("Symbols Legend") | bold | getTrueColor(TrueColors::Color::LightBlue),
478 symbols_explanation | border | flex,
479 footer,
480 }) |
481 flex;
482}
483
484void searchModeIndices(const std::vector<std::string>& words, const std::string& prefix,
485 std::vector<int>& search_indices)
486{
487 auto start = lower_bound(words.begin(), words.end(), prefix);
488
489 for (auto it = start; it != words.end(); ++it)
490 {
491 if (it->substr(0, prefix.size()) != prefix)
492 break;
493 search_indices.push_back(std::distance(words.begin(), it));
494 }
495}
496
497void UpdateSelectedIndex(int& index, int max_size, bool move_down)
498{
499 if (max_size == 0)
500 return;
501 index = move_down ? (index + 1) % max_size : (index == 0 ? max_size - 1 : index - 1);
502}
503
504auto RenderStatusBar(const std::string& status, const std::string& current_song_info,
505 const std::string& additional_info, const std::string& year_info,
506 InLimboColors& global_colors, const std::string& current_artist) -> Element
507{
508 return hbox({
509 text(status) | getTrueColor(TrueColors::Color::Black) | bold,
510 text(current_artist) | color(global_colors.status_bar_artist_col) | bold |
511 size(WIDTH, LESS_THAN, MAX_LENGTH_ARTIST_NAME),
512 text(current_song_info) | bold | color(global_colors.status_bar_song_col) |
513 size(WIDTH, LESS_THAN, MAX_LENGTH_SONG_NAME),
514 filler(), // Push the right-aligned content to the end
515 hbox({
516 text(additional_info) | bold | color(global_colors.status_bar_addn_info_col) | flex,
517 text(year_info) | color(global_colors.status_bar_addn_info_col) |
518 size(WIDTH, LESS_THAN, 15),
519 text(" "),
520 }) |
521 align_right,
522 }) |
523 size(HEIGHT, EQUAL, 1) | bgcolor(global_colors.status_bar_bg);
524}
525
526auto RenderVolumeBar(int volume, ftxui::Color volume_bar_col) -> Element
527{
528 return hbox({
529 text(" Vol: ") | dim,
530 gauge(volume / 100.0) | size(WIDTH, EQUAL, 10) | color(volume_bar_col),
531 text(std::to_string(volume) + "%") | dim,
532 });
533}
constexpr const char * REPOSITORY_URL
Definition arg-handler.hpp:22
auto RenderHelpScreen(Keybinds &global_keybinds) -> Element
Definition misc.hpp:373
auto getTrueBGColor(TrueColors::Color color)
Definition misc.hpp:196
auto getTrueColor(TrueColors::Color color)
Definition misc.hpp:194
auto getMimeTypeFromExtension(const std::string &filePath) -> std::string
Definition misc.hpp:260
auto handleToggleMute(int *volume, int *lastVolume, bool *muted) -> int
Definition misc.hpp:28
auto charToStr(char ch) -> std::string
Definition misc.hpp:167
#define SONG_TITLE_DELIM
Definition misc.hpp:23
auto RenderArtistNames(const std::vector< std::string > &artist_list)
Definition misc.hpp:215
auto RenderThumbnail(const std::string &songFilePath, const std::string &cacheDirPath, const std::string &songTitle, const std::string &artistName, const std::string &albumName, const std::string &genre, unsigned int year, unsigned int trackNumber, unsigned int discNumber, float progress)
Definition misc.hpp:284
auto RenderDialog(const std::string &dialog_message) -> Element
Definition misc.hpp:359
auto RenderArtistMenu(const std::vector< std::string > &artist_list)
Definition misc.hpp:248
auto renderAlbumName(const std::string &album_name, const int &year, ftxui::Color sel_color, ftxui::Color sel_color_fg)
Definition misc.hpp:198
#define MAX_LENGTH_ARTIST_NAME
Definition misc.hpp:21
auto renderSongName(const std::string &disc_track_info, const std::string &song_name, const int &duration)
Definition misc.hpp:207
auto formatAdditionalInfo(const std::string &genre, const bool &has_comment, const bool &has_lyrics, const bool &show_bitrate, const int &bitrate) -> std::string
Definition misc.hpp:140
void UpdateSelectedIndex(int &index, int max_size, bool move_down)
Definition misc.hpp:497
auto RenderStatusBar(const std::string &status, const std::string &current_song_info, const std::string &additional_info, const std::string &year_info, InLimboColors &global_colors, const std::string &current_artist) -> Element
Definition misc.hpp:504
auto RenderSongMenu(const std::vector< Element > &items)
Definition misc.hpp:237
void searchModeIndices(const std::vector< std::string > &words, const std::string &prefix, std::vector< int > &search_indices)
Definition misc.hpp:484
auto formatDiscTrackInfo(const int &disc_number, const int &track_number)
Definition misc.hpp:43
auto RenderVolumeBar(int volume, ftxui::Color volume_bar_col) -> Element
Definition misc.hpp:526
#define LYRICS_AVAIL
Definition misc.hpp:24
auto CreateMenu(const std::vector< std::string > *vecLines, int *currLine)
Definition misc.hpp:228
#define MAX_LENGTH_SONG_NAME
Definition misc.hpp:20
auto FormatTime(int seconds)
Definition misc.hpp:184
auto formatLyrics(const std::string &lyrics)
Definition misc.hpp:51
#define ADDN_PROPS_AVAIL
Definition misc.hpp:25
auto RenderSearchBar(std::string &user_input) -> Element
Definition misc.hpp:350
#define STATUS_BAR_DELIM
Definition misc.hpp:26
auto GetColor(Color color) -> ftxui::Color
Maps a predefined color enum to its corresponding ftxui::Color::RGB value.
Definition colors.hpp:76
Color
Enumeration for predefined true colors.
Definition colors.hpp:24
@ Cyan
Definition colors.hpp:35
@ White
Definition colors.hpp:26
@ LightBlue
Definition colors.hpp:32
@ Coral
Definition colors.hpp:51
@ LightGreen
Definition colors.hpp:30
@ Pink
Definition colors.hpp:46
@ Orange
Definition colors.hpp:42
@ Gray
Definition colors.hpp:39
@ LightPink
Definition colors.hpp:47
@ LightCyan
Definition colors.hpp:36
@ Teal
Definition colors.hpp:48
@ Black
Definition colors.hpp:25
@ LightMagenta
Definition colors.hpp:38
@ LightRed
Definition colors.hpp:28
@ LightYellow
Definition colors.hpp:34
Element image_view(std::string_view url)
Definition image_view.cpp:85
Represents a collection of colors used in the application.
Definition colors.hpp:182
Struct to hold keybinding mappings.
Definition keymaps.hpp:15
A header file for the TagLibParser class and Metadata structure, used to parse metadata from audio fi...
auto extractThumbnail(const std::string &audioFilePath, const std::string &outputImagePath) -> bool
Extracts the thumbnail (album art) from an audio file and saves it to an image file....
Definition taglib_parser.h:325