2023-04-24 13:45:06 -07:00
# include "imgui.h"
# include "imgui_impl_sdl2.h"
# include "imgui_impl_opengl3.h"
# include "imfilebrowser.h"
# include "playback.h"
# include "icon.h"
# include "IconsForkAwesome.h"
# include <iostream>
# include <fstream>
# include <json/json.h>
# include <stdio.h>
# include "forkawesome.h"
# include <numbers>
# include <cmath>
# include <cstdlib>
# include <SDL.h>
# include <SDL_image.h>
# if defined(IMGUI_IMPL_OPENGL_ES2)
# include <SDL_opengles2.h>
# else
# include <SDL_opengl.h>
# endif
static const char * NAME = " Neko Player " ;
# ifdef __EMSCRIPTEN__
# include "../libs/emscripten/emscripten_mainloop_stub.h"
# endif
using namespace std : : filesystem ;
using namespace std : : numbers ;
static float accent_color = 280.0 ;
float GetHue ( ImVec4 rgba ) {
float r = rgba . x , g = rgba . y , b = rgba . z ;
2023-06-04 03:39:28 -07:00
if ( r = = g & & g = = b ) {
return - 1.0 ;
}
2023-04-24 13:45:06 -07:00
float hue ;
if ( ( r > = g ) & & ( g > = b ) ) {
hue = 60.0 * ( g - b ) / ( r - b ) ;
} else if ( ( g > r ) & & ( r > = b ) ) {
hue = 60.0 * ( 2.0 - ( ( r - b ) / ( g - b ) ) ) ;
} else if ( ( g > = b ) & & ( b > r ) ) {
hue = 60.0 * ( 2.0 + ( ( b - r ) / ( g - r ) ) ) ;
} else if ( ( b > g ) & & ( g > r ) ) {
hue = 60.0 * ( 4.0 - ( ( g - r ) / ( b - r ) ) ) ;
} else if ( ( b > r ) & & ( r > = g ) ) {
hue = 60.0 * ( 4.0 - ( ( r - g ) / ( b - g ) ) ) ;
} else if ( ( r > = b ) & & ( b > g ) ) {
hue = 60.0 * ( 6.0 - ( ( b - g ) / ( r - g ) ) ) ;
} else {
hue = - 1.0 ;
}
return hue ;
}
2023-06-04 03:39:28 -07:00
void change_accent_color ( ImVec4 & color , float hue ) {
//ImGuiStyle& style = ImGui::GetStyle();
//ImVec4 in = style.Colors[color];
ImVec4 in = color ;
2023-04-24 13:45:06 -07:00
float Target = hue ;
float Current = GetHue ( in ) ;
2023-06-04 03:39:28 -07:00
if ( Current < 0.0f ) {
return ;
}
2023-04-24 13:45:06 -07:00
float H = 360 - Target + Current ; // TODO: Figure out why these magic numbers are necessary.
//printf("Cur: %f, Target: %f, Diff: %f", Current, Target, H);
float U = cos ( H * pi / 180.0 ) ;
float W = sin ( H * pi / 180.0 ) ;
ImVec4 out = in ;
out . x = ( .299 + .701 * U + .168 * W ) * in . x
+ ( .587 - .587 * U + .330 * W ) * in . y
+ ( .114 - .114 * U - .497 * W ) * in . z ;
out . y = ( .299 - .299 * U - .328 * W ) * in . x
+ ( .587 + .413 * U + .035 * W ) * in . y
+ ( .114 - .114 * U + .292 * W ) * in . z ;
out . z = ( .299 - .3 * U + 1.25 * W ) * in . x
+ ( .587 - .588 * U - 1.05 * W ) * in . y
+ ( .114 + .886 * U - .203 * W ) * in . z ;
//printf(", Actual: %f\n", GetHue(out));
2023-06-04 03:39:28 -07:00
color = out ;
2023-04-24 13:45:06 -07:00
}
void UpdateStyle ( bool dark ) {
if ( dark ) {
ImGui : : StyleColorsDark ( ) ;
} else {
ImGui : : StyleColorsLight ( ) ;
2023-06-04 03:39:28 -07:00
} /*
2023-04-24 13:45:06 -07:00
ImGuiCol colors [ ] = {
ImGuiCol_FrameBgHovered ,
ImGuiCol_FrameBgActive ,
ImGuiCol_CheckMark ,
ImGuiCol_SliderGrab ,
ImGuiCol_SliderGrabActive ,
ImGuiCol_Button ,
ImGuiCol_ButtonHovered ,
ImGuiCol_ButtonActive ,
ImGuiCol_Header ,
ImGuiCol_HeaderHovered ,
ImGuiCol_HeaderActive ,
ImGuiCol_ResizeGripActive ,
ImGuiCol_ResizeGripHovered ,
ImGuiCol_Tab ,
ImGuiCol_TabHovered ,
ImGuiCol_TabActive ,
ImGuiCol_TabUnfocusedActive ,
ImGuiCol_DockingPreview ,
ImGuiCol_TableHeaderBg ,
ImGuiCol_TextSelectedBg ,
ImGuiCol_DragDropTarget ,
ImGuiCol_SeparatorHovered ,
ImGuiCol_SeparatorActive ,
ImGuiCol_NavHighlight ,
} ;
ImGuiCol colors_dark [ ] = {
ImGuiCol_FrameBg ,
ImGuiCol_TitleBgActive ,
ImGuiCol_ResizeGrip ,
2023-06-04 03:39:28 -07:00
} ; */
for ( auto & color : ImGui : : GetStyle ( ) . Colors ) {
2023-04-24 13:45:06 -07:00
change_accent_color ( color , accent_color ) ;
2023-06-04 03:39:28 -07:00
} /*
2023-04-24 13:45:06 -07:00
if ( dark ) {
for ( auto color : colors_dark ) {
change_accent_color ( color , accent_color ) ;
}
2023-06-04 03:39:28 -07:00
} */
2023-04-24 13:45:06 -07:00
}
// Main code
int main ( int , char * * )
{
bool enable_kms = std : : getenv ( " LAP_KMS " ) ! = nullptr ;
SDL_SetHint ( SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR , " false " ) ;
SDL_SetHint ( SDL_HINT_APP_NAME , NAME ) ;
// Setup SDL
if ( SDL_Init ( SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_GAMECONTROLLER ) ! = 0 )
{
printf ( " Error: %s \n " , SDL_GetError ( ) ) ;
return - 1 ;
}
if ( std : : string ( SDL_GetCurrentVideoDriver ( ) ) = = " KMSDRM " ) {
enable_kms = true ;
}
IMG_Init ( IMG_INIT_PNG | IMG_INIT_WEBP ) ;
const char * prefPath = SDL_GetPrefPath ( " Catmeow72 " , NAME ) ;
// Decide GL+GLSL versions
# if defined(IMGUI_IMPL_OPENGL_ES2)
// GL ES 2.0 + GLSL 100
const char * glsl_version = " #version 100 " ;
SDL_GL_SetAttribute ( SDL_GL_CONTEXT_FLAGS , 0 ) ;
SDL_GL_SetAttribute ( SDL_GL_CONTEXT_PROFILE_MASK , SDL_GL_CONTEXT_PROFILE_ES ) ;
SDL_GL_SetAttribute ( SDL_GL_CONTEXT_MAJOR_VERSION , 2 ) ;
SDL_GL_SetAttribute ( SDL_GL_CONTEXT_MINOR_VERSION , 0 ) ;
# elif defined(__APPLE__)
// GL 3.2 Core + GLSL 150
const char * glsl_version = " #version 150 " ;
SDL_GL_SetAttribute ( SDL_GL_CONTEXT_FLAGS , SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG ) ; // Always required on Mac
SDL_GL_SetAttribute ( SDL_GL_CONTEXT_PROFILE_MASK , SDL_GL_CONTEXT_PROFILE_CORE ) ;
SDL_GL_SetAttribute ( SDL_GL_CONTEXT_MAJOR_VERSION , 3 ) ;
SDL_GL_SetAttribute ( SDL_GL_CONTEXT_MINOR_VERSION , 2 ) ;
# else
// GL 3.0 + GLSL 130
const char * glsl_version = " #version 130 " ;
SDL_GL_SetAttribute ( SDL_GL_CONTEXT_FLAGS , 0 ) ;
SDL_GL_SetAttribute ( SDL_GL_CONTEXT_PROFILE_MASK , SDL_GL_CONTEXT_PROFILE_CORE ) ;
SDL_GL_SetAttribute ( SDL_GL_CONTEXT_MAJOR_VERSION , 3 ) ;
SDL_GL_SetAttribute ( SDL_GL_CONTEXT_MINOR_VERSION , 0 ) ;
# endif
// From 2.0.18: Enable native IME.
# ifdef SDL_HINT_IME_SHOW_UI
SDL_SetHint ( SDL_HINT_IME_SHOW_UI , " 1 " ) ;
# endif
// Create window with graphics context
SDL_GL_SetAttribute ( SDL_GL_DOUBLEBUFFER , 1 ) ;
SDL_GL_SetAttribute ( SDL_GL_DEPTH_SIZE , 24 ) ;
SDL_GL_SetAttribute ( SDL_GL_STENCIL_SIZE , 8 ) ;
SDL_WindowFlags window_flags = ( SDL_WindowFlags ) ( SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI ) ;
SDL_Window * window = SDL_CreateWindow ( NAME , SDL_WINDOWPOS_CENTERED , SDL_WINDOWPOS_CENTERED , 475 , 54 , window_flags ) ;
if ( enable_kms ) {
SDL_SetWindowFullscreen ( window , SDL_WINDOW_FULLSCREEN_DESKTOP ) ;
}
SDL_SetWindowMinimumSize ( window , 475 , 54 ) ;
SDL_Surface * icon = IMG_Load_RW ( SDL_RWFromConstMem ( icon_data , icon_size ) , 1 ) ;
SDL_SetWindowIcon ( window , icon ) ;
SDL_GLContext gl_context = SDL_GL_CreateContext ( window ) ;
SDL_GL_MakeCurrent ( window , gl_context ) ;
SDL_GL_SetSwapInterval ( 1 ) ; // Enable vsync
// Setup Dear ImGui context
IMGUI_CHECKVERSION ( ) ;
ImGui : : CreateContext ( ) ;
ImGuiIO & io = ImGui : : GetIO ( ) ; ( void ) io ;
io . ConfigFlags | = ImGuiConfigFlags_NavEnableKeyboard ; // Enable Keyboard Controls
io . ConfigFlags | = ImGuiConfigFlags_NavEnableGamepad ; // Enable Gamepad Controls
io . ConfigFlags | = ImGuiConfigFlags_DockingEnable ;
io . ConfigFlags | = ImGuiConfigFlags_ViewportsEnable ;
io . IniFilename = strdup ( ( std : : string ( prefPath ) + " imgui.ini " ) . c_str ( ) ) ;
if ( enable_kms ) {
io . MouseDrawCursor = true ;
}
//io.ConfigViewportsNoAutoMerge = true;
//io.ConfigViewportsNoTaskBarIcon = true;
// Setup Dear ImGui style
UpdateStyle ( true ) ;
//ImGui::StyleColorsLight();
// When viewports are enabled we tweak WindowRounding/WindowBg so platform windows can look identical to regular ones.
ImGuiStyle & style = ImGui : : GetStyle ( ) ;
if ( io . ConfigFlags & ImGuiConfigFlags_ViewportsEnable )
{
style . WindowRounding = 0.0f ;
style . Colors [ ImGuiCol_WindowBg ] . w = 1.0f ;
}
style . FrameRounding = 12 ;
style . WindowRounding = 12 ;
// Setup Platform/Renderer backends
ImGui_ImplSDL2_InitForOpenGL ( window , gl_context ) ;
ImGui_ImplOpenGL3_Init ( glsl_version ) ;
// Load Fonts
// - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them.
// - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple.
// - If the file cannot be loaded, the function will return a nullptr. Please handle those errors in your application (e.g. use an assertion, or display an error and quit).
// - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call.
// - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering.
// - Read 'docs/FONTS.md' for more instructions and details.
// - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ !
//io.Fonts->AddFontDefault();
//io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf", 18.0f);
//io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f);
//io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f);
//io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f);
//ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, nullptr, io.Fonts->GetGlyphRangesJapanese());
//IM_ASSERT(font != nullptr);
io . Fonts - > AddFontDefault ( ) ;
ImFontConfig config ;
config . MergeMode = true ;
config . GlyphMinAdvanceX = 13.0f ;
static const ImWchar icon_ranges [ ] = { ICON_MIN_FK , ICON_MAX_FK , 0 } ;
io . Fonts - > AddFontFromMemoryCompressedBase85TTF ( forkawesome_compressed_data_base85 , 13.0f , & config , icon_ranges ) ;
// Our state
bool show_demo_window = false ;
ImVec4 clear_color = ImVec4 ( 0.45f , 0.55f , 0.60f , 1.00f ) ;
ImGui : : FileBrowser fileDialog ;
fileDialog . SetTitle ( " Open... " ) ;
fileDialog . SetTypeFilters ( { " .wav " , " .ogg " , " .mp3 " , " .qoa " , " .flac " , " .xm " , " .mod " } ) ;
std : : string userdir = std : : getenv (
# ifdef _WIN32
" UserProfile "
# else
" HOME "
# endif
) ;
fileDialog . SetPwd ( path ( userdir ) / path ( " Music " ) ) ;
Playback * playback = new Playback ( ) ;
float position = 0.0 ;
// Main loop
bool done = false ;
bool dark_mode = true ;
bool prefs_window = false ;
bool stopped = true ;
{
Json : : Value config ;
std : : ifstream stream ;
stream . open ( path ( prefPath ) / " config.json " ) ;
if ( stream . is_open ( ) ) {
stream > > config ;
if ( config . isMember ( " dark_mode " ) ) {
dark_mode = config [ " dark_mode " ] . asBool ( ) ; \
}
if ( config . isMember ( " accent_color " ) ) {
accent_color = config [ " accent_color " ] . asFloat ( ) ;
}
if ( config . isMember ( " demo_window " ) ) {
show_demo_window = config [ " demo_window " ] . asBool ( ) ;
}
stream . close ( ) ;
UpdateStyle ( dark_mode ) ;
}
}
# ifdef __EMSCRIPTEN__
// For an Emscripten build we are disabling file-system access, so let's not attempt to do a fopen() of the imgui.ini file.
// You may manually call LoadIniSettingsFromMemory() to load settings from your own storage.
io . IniFilename = nullptr ;
EMSCRIPTEN_MAINLOOP_BEGIN
# else
while ( ! done )
# endif
{
position = playback - > GetPosition ( ) ;
// Poll and handle events (inputs, window resize, etc.)
// You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs.
// - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data.
// - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data.
// Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags.
SDL_Event event ;
while ( SDL_PollEvent ( & event ) )
{
ImGui_ImplSDL2_ProcessEvent ( & event ) ;
if ( event . type = = SDL_QUIT )
done = true ;
if ( event . type = = SDL_WINDOWEVENT & & event . window . event = = SDL_WINDOWEVENT_CLOSE & & event . window . windowID = = SDL_GetWindowID ( window ) )
done = true ;
}
// Start the Dear ImGui frame
ImGui_ImplOpenGL3_NewFrame ( ) ;
ImGui_ImplSDL2_NewFrame ( ) ;
ImGui : : NewFrame ( ) ;
auto dockid = ImGui : : DockSpaceOverViewport ( nullptr , ImGuiDockNodeFlags_AutoHideTabBar ) ;
// 1. Show the big demo window (Most of the sample code is in ImGui::ShowDemoWindow()! You can browse its code to learn more about Dear ImGui!).
if ( show_demo_window )
ImGui : : ShowDemoWindow ( & show_demo_window ) ;
ImGui : : SetNextWindowDockID ( dockid ) ;
ImGui : : Begin ( " Player " , nullptr , ImGuiWindowFlags_MenuBar ) ;
{
if ( ImGui : : BeginMenuBar ( ) ) {
if ( ImGui : : BeginMenu ( ICON_FK_FILE " File " ) ) {
if ( ImGui : : MenuItem ( ICON_FK_FOLDER_OPEN " Open " ) ) {
fileDialog . Open ( ) ;
}
if ( ImGui : : MenuItem ( ICON_FK_WINDOW_CLOSE " Quit " ) ) {
done = true ;
}
ImGui : : EndMenu ( ) ;
}
if ( ImGui : : BeginMenu ( ICON_FK_SCISSORS " Edit " ) ) {
if ( ImGui : : MenuItem ( ICON_FK_COG " Preferences... " ) ) {
prefs_window = true ;
}
ImGui : : EndMenu ( ) ;
}
# ifdef DEBUG
if ( ImGui : : BeginMenu ( ICON_FK_COG " Debug " ) ) {
if ( ImGui : : MenuItem ( " Show ImGui Demo Window " , nullptr , show_demo_window ) ) {
show_demo_window = ! show_demo_window ;
}
ImGui : : EndMenu ( ) ;
}
# endif
ImGui : : EndMenuBar ( ) ;
}
ImGui : : SetCursorPosY ( ImGui : : GetWindowHeight ( ) - 27 ) ;
if ( ImGui : : Button ( playback - > IsPaused ( ) ? ICON_FK_PLAY " ##Pause " : ICON_FK_PAUSE " ##Pause " ) ) {
playback - > Pause ( ) ;
}
ImGui : : SameLine ( ) ;
if ( ImGui : : Button ( ICON_FK_REFRESH " ##Restart " ) ) {
playback - > Seek ( 0.0 ) ;
}
ImGui : : SameLine ( ) ;
ImGui : : SetNextItemWidth ( ImGui : : GetWindowWidth ( ) - ( ImGui : : GetFontSize ( ) * 22 ) - ( ImGui : : GetStyle ( ) . FramePadding . x * 10 ) ) ;
if ( ImGui : : SliderFloat ( " ##Seek " , & position , 0.0f , playback - > GetLength ( ) , " %.0fs " , ImGuiSliderFlags_NoRoundToFormat ) )
playback - > Seek ( position ) ;
ImGui : : SameLine ( ) ;
if ( ImGui : : Button ( ICON_FK_STOP " ##Stop " ) ) {
playback - > Stop ( ) ;
}
ImGui : : SameLine ( ) ;
ImGui : : SetNextItemWidth ( ImGui : : GetFontSize ( ) * 8 ) ;
ImGui : : SameLine ( ) ;
if ( ImGui : : SliderFloat ( " ##Volume " , & playback - > volume , 0.0 , 100.0 , ICON_FK_VOLUME_UP " : %.0f%% " ) ) {
playback - > Update ( ) ;
}
ImGui : : SameLine ( ) ;
ImGui : : SameLine ( ) ;
ImGui : : SetNextItemWidth ( ImGui : : GetFontSize ( ) * 8 ) ;
if ( ImGui : : SliderFloat ( " ##Speed " , & playback - > speed , 0.25 , 4.0 , " Speed: %.2fx " , ImGuiSliderFlags_Logarithmic ) ) {
playback - > Update ( ) ;
}
}
ImGui : : End ( ) ;
if ( prefs_window ) {
ImVec2 min_size ;
min_size . x = ImGui : : GetFontSize ( ) * 18 ;
min_size . y = ( ImGui : : GetStyle ( ) . FramePadding . y * 5 ) + ( ImGui : : GetFontSize ( ) * 5 ) ;
ImVec2 max_size ;
max_size . x = 99999999999 ;
max_size . y = min_size . y ;
ImGui : : SetNextWindowSizeConstraints ( min_size , max_size ) ;
ImGui : : Begin ( " Preferences... " , & prefs_window ) ;
{
if ( ImGui : : Checkbox ( ICON_FK_MOON " Dark Mode " , & dark_mode ) ) {
UpdateStyle ( dark_mode ) ;
}
ImGui : : SetNextItemWidth ( ImGui : : GetWindowWidth ( ) - ( ImGui : : GetStyle ( ) . FramePadding . x * 4 ) ) ;
if ( ImGui : : SliderFloat ( " ##AccentColor " , & accent_color , 0.0 , 360.0 , " UI hue: %.0f° " , ImGuiSliderFlags_NoRoundToFormat ) ) {
UpdateStyle ( dark_mode ) ;
}
}
ImGui : : End ( ) ;
}
fileDialog . Display ( ) ;
if ( fileDialog . HasSelected ( ) ) {
playback - > Start ( fileDialog . GetSelected ( ) . string ( ) ) ;
SDL_SetWindowTitle ( window , ( fileDialog . GetSelected ( ) . filename ( ) . replace_extension ( " " ) . string ( ) + std : : string ( " - " ) + std : : string ( NAME ) ) . c_str ( ) ) ;
fileDialog . ClearSelected ( ) ;
}
if ( playback - > IsStopped ( ) & & ! stopped ) {
SDL_SetWindowTitle ( window , NAME ) ;
}
// Rendering
ImGui : : Render ( ) ;
glViewport ( 0 , 0 , ( int ) io . DisplaySize . x , ( int ) io . DisplaySize . y ) ;
glClearColor ( clear_color . x * clear_color . w , clear_color . y * clear_color . w , clear_color . z * clear_color . w , clear_color . w ) ;
glClear ( GL_COLOR_BUFFER_BIT ) ;
ImGui_ImplOpenGL3_RenderDrawData ( ImGui : : GetDrawData ( ) ) ;
// Update and Render additional Platform Windows
// (Platform functions may change the current OpenGL context, so we save/restore it to make it easier to paste this code elsewhere.
// For this specific demo app we could also call SDL_GL_MakeCurrent(window, gl_context) directly)
if ( io . ConfigFlags & ImGuiConfigFlags_ViewportsEnable )
{
SDL_Window * backup_current_window = SDL_GL_GetCurrentWindow ( ) ;
SDL_GLContext backup_current_context = SDL_GL_GetCurrentContext ( ) ;
ImGui : : UpdatePlatformWindows ( ) ;
ImGui : : RenderPlatformWindowsDefault ( ) ;
SDL_GL_MakeCurrent ( backup_current_window , backup_current_context ) ;
}
SDL_GL_SwapWindow ( window ) ;
}
// Cleanup
# ifdef __EMSCRIPTEN__
EMSCRIPTEN_MAINLOOP_END ;
# endif
delete playback ;
// Cleanup
ImGui_ImplOpenGL3_Shutdown ( ) ;
ImGui_ImplSDL2_Shutdown ( ) ;
ImGui : : DestroyContext ( ) ;
SDL_GL_DeleteContext ( gl_context ) ;
SDL_DestroyWindow ( window ) ;
IMG_Quit ( ) ;
SDL_Quit ( ) ;
{
Json : : Value config ;
std : : ofstream stream ;
stream . open ( path ( prefPath ) / " config.json " ) ;
config [ " dark_mode " ] = dark_mode ;
config [ " accent_color " ] = accent_color ;
config [ " demo_window " ] = show_demo_window ;
stream < < config ;
stream . close ( ) ;
}
free ( ( void * ) io . IniFilename ) ;
return 0 ;
}