inLimbo
TUI Music Player that keeps you in Limbo.
 
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
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