2023-07-20 14:45:35 -07:00
# include "config.h"
2023-04-24 13:45:06 -07:00
# include "imgui.h"
# include "imgui_impl_sdl2.h"
# include "imgui_impl_opengl3.h"
2023-07-17 12:21:38 -07:00
# include "file_browser.h"
2023-04-24 13:45:06 -07:00
# include "playback.h"
2023-07-09 18:56:12 -07:00
# include "theme.h"
2023-07-20 14:45:35 -07:00
# include "assets.h"
2023-04-24 13:45:06 -07:00
# include "IconsForkAwesome.h"
2023-07-24 23:13:47 -07:00
# include <libintl.h>
2023-04-24 13:45:06 -07:00
# include <iostream>
# include <fstream>
# include <json/json.h>
# include <stdio.h>
# include <numbers>
# include <cmath>
# include <cstdlib>
2023-07-09 10:57:48 -07:00
# include <string>
2023-04-24 13:45:06 -07:00
# include <SDL.h>
# include <SDL_image.h>
2023-07-10 19:27:55 -07:00
# include <filesystem>
2023-07-17 17:35:21 -07:00
# include <SDL_video.h>
2023-04-24 13:45:06 -07:00
# if defined(IMGUI_IMPL_OPENGL_ES2)
# include <SDL_opengles2.h>
# else
# include <SDL_opengl.h>
# endif
2023-07-20 14:45:35 -07:00
# include "license.h"
2023-07-24 23:13:47 -07:00
# include "base85.h"
2023-04-24 13:45:06 -07:00
static const char * NAME = " Neko Player " ;
# ifdef __EMSCRIPTEN__
# include "../libs/emscripten/emscripten_mainloop_stub.h"
# endif
2023-07-24 23:13:47 -07:00
# include <translation.h>
2023-04-24 13:45:06 -07:00
using namespace std : : filesystem ;
using namespace std : : numbers ;
2023-07-09 10:57:48 -07:00
using std : : string ;
2023-04-24 13:45:06 -07:00
static float accent_color = 280.0 ;
2023-07-09 10:57:48 -07:00
string PadZeros ( string input , size_t required_length ) {
return std : : string ( required_length - std : : min ( required_length , input . length ( ) ) , ' 0 ' ) + input ;
}
uint8_t TimeToComponentCount ( double time_code ) {
int seconds = ( int ) time_code ;
int minutes = seconds / 60 ;
seconds - = minutes * 60 ;
int hours = minutes / 60 ;
minutes - = hours * 60 ;
if ( hours > 0 ) {
return 3 ;
} else if ( minutes > 0 ) {
return 2 ;
} else {
return 1 ;
}
}
string TimeToString ( double time_code , uint8_t min_components = 1 ) {
uint8_t components = std : : max ( TimeToComponentCount ( time_code ) , min_components ) ;
int seconds = ( int ) time_code ;
int minutes = seconds / 60 ;
seconds - = minutes * 60 ;
int hours = minutes / 60 ;
minutes - = hours * 60 ;
string output = PadZeros ( std : : to_string ( seconds ) , components < 2 ? 1 : 2 ) ;
if ( components > = 2 ) {
output = PadZeros ( std : : to_string ( minutes ) , components = = 2 ? 1 : 2 ) + " : " + output ;
}
if ( components > = 3 ) {
output = PadZeros ( std : : to_string ( hours ) , components = = 3 ? 1 : 2 ) + " : " + output ;
}
return output ;
}
2023-07-24 23:13:47 -07:00
struct FontData {
const char * data ;
const ImWchar * ranges ;
} ;
ImFont * add_font ( vector < FontData > data_vec , int size = 13 ) {
ImFont * font = nullptr ;
2023-07-20 14:45:35 -07:00
ImGuiIO & io = ImGui : : GetIO ( ) ;
2023-07-24 23:13:47 -07:00
for ( auto data : data_vec ) {
2023-07-20 14:45:35 -07:00
ImFontConfig font_cfg = ImFontConfig ( ) ;
font_cfg . SizePixels = size ;
font_cfg . OversampleH = font_cfg . OversampleV = 1 ;
font_cfg . PixelSnapH = true ;
if ( font_cfg . SizePixels < = 0.0f )
font_cfg . SizePixels = 13.0f * 1.0f ;
2023-07-24 23:13:47 -07:00
if ( font ! = nullptr ) {
font_cfg . DstFont = font ;
font_cfg . MergeMode = true ;
}
2023-07-20 14:45:35 -07:00
//font_cfg.EllipsisChar = (ImWchar)0x0085;
//font_cfg.GlyphOffset.y = 1.0f * IM_FLOOR(font_cfg.SizePixels / 13.0f); // Add +1 offset per 13 units
2023-07-24 23:13:47 -07:00
const char * ttf_compressed_base85 = data . data ;
const ImWchar * glyph_ranges = data . ranges ;
auto new_font = io . Fonts - > AddFontFromMemoryCompressedBase85TTF ( ttf_compressed_base85 , font_cfg . SizePixels , & font_cfg , glyph_ranges ) ;
if ( font = = nullptr ) font = new_font ;
}
{
ImFontConfig config ;
config . MergeMode = true ;
config . GlyphMinAdvanceX = size ;
config . SizePixels = size ;
config . DstFont = font ;
static const ImWchar icon_ranges [ ] = { ICON_MIN_FK , ICON_MAX_FK , 0 } ;
io . Fonts - > AddFontFromMemoryCompressedBase85TTF ( forkawesome_compressed_data_base85 , float ( size ) , & config , icon_ranges ) ;
2023-07-20 14:45:35 -07:00
}
return font ;
}
2023-04-24 13:45:06 -07:00
// Main code
int main ( int , char * * )
{
2023-07-24 23:13:47 -07:00
bindtextdomain ( " neko_player " , LOCALE_DIR ) ;
2023-07-17 15:45:43 -07:00
# ifdef PORTALS
g_set_application_name ( " Neko Player " ) ;
# endif
2023-04-24 13:45:06 -07:00
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 ) ;
2023-07-09 18:56:12 -07:00
Theme : : prefPath = prefPath ;
2023-04-24 13:45:06 -07:00
// 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 ) ;
2023-07-16 14:11:50 -07:00
int window_width = 475 ;
int window_height = 354 ;
2023-04-24 13:45:06 -07:00
SDL_WindowFlags window_flags = ( SDL_WindowFlags ) ( SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI ) ;
2023-07-16 14:11:50 -07:00
SDL_Window * window = SDL_CreateWindow ( NAME , SDL_WINDOWPOS_CENTERED , SDL_WINDOWPOS_CENTERED , window_width , window_height , window_flags ) ;
SDL_SetWindowMinimumSize ( window , window_width , window_height ) ;
2023-04-24 13:45:06 -07:00
if ( enable_kms ) {
SDL_SetWindowFullscreen ( window , SDL_WINDOW_FULLSCREEN_DESKTOP ) ;
}
2023-07-24 23:13:47 -07:00
const vector < unsigned char > icon_data = DecodeBase85 ( icon_compressed_data_base85 ) ;
SDL_Surface * icon = IMG_Load_RW ( SDL_RWFromConstMem ( icon_data . data ( ) , icon_data . size ( ) ) , 1 ) ;
2023-04-24 13:45:06 -07:00
SDL_SetWindowIcon ( window , icon ) ;
SDL_GLContext gl_context = SDL_GL_CreateContext ( window ) ;
SDL_GL_MakeCurrent ( window , gl_context ) ;
2023-07-16 13:00:04 -07:00
2023-04-24 13:45:06 -07:00
// 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 ;
2023-07-16 13:00:04 -07:00
//io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;
2023-04-24 13:45:06 -07:00
io . IniFilename = strdup ( ( std : : string ( prefPath ) + " imgui.ini " ) . c_str ( ) ) ;
if ( enable_kms ) {
io . MouseDrawCursor = true ;
}
//io.ConfigViewportsNoAutoMerge = true;
//io.ConfigViewportsNoTaskBarIcon = true;
//ImGui::StyleColorsLight();
// 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);
2023-07-24 23:13:47 -07:00
add_font ( { FontData { notosans_regular_compressed_data_base85 , io . Fonts - > GetGlyphRangesDefault ( ) } , FontData { notosansjp_regular_compressed_data_base85 , io . Fonts - > GetGlyphRangesJapanese ( ) } } ) ;
ImFont * title = add_font ( { FontData { notosans_thin_compressed_data_base85 , io . Fonts - > GetGlyphRangesDefault ( ) } , FontData { notosansjp_thin_compressed_data_base85 , io . Fonts - > GetGlyphRangesJapanese ( ) } } , 48 ) ;
2023-04-24 13:45:06 -07:00
// Our state
bool show_demo_window = false ;
ImVec4 clear_color = ImVec4 ( 0.45f , 0.55f , 0.60f , 1.00f ) ;
2023-07-17 14:56:02 -07:00
FileBrowser fileDialog ( false , ImGuiFileBrowserFlags_NoTitleBar | ImGuiFileBrowserFlags_NoMove | ImGuiFileBrowserFlags_NoResize ) ;
2023-07-24 23:13:47 -07:00
fileDialog . SetTitle ( _TR_CTX ( " File dialog title " , " Open... " ) ) ;
fileDialog . SetTypeFilters ( _TR_CTX ( " File dialog filter name " , " Audio files " ) , { " .wav " , " .ogg " , " .mp3 " , " .qoa " , " .flac " , " .xm " , " .mod " } ) ;
2023-04-24 13:45:06 -07:00
std : : string userdir = std : : getenv (
# ifdef _WIN32
" UserProfile "
# else
" HOME "
# endif
) ;
fileDialog . SetPwd ( path ( userdir ) / path ( " Music " ) ) ;
2023-07-16 13:00:04 -07:00
fileDialog . SetWindowSize ( window_width , window_height ) ;
//fileDialog.SetWindowPos(0, 0);
2023-04-24 13:45:06 -07:00
Playback * playback = new Playback ( ) ;
float position = 0.0 ;
// Main loop
bool done = false ;
2023-07-09 18:56:12 -07:00
Theme * theme = new Theme ( false ) ;
2023-04-24 13:45:06 -07:00
bool prefs_window = false ;
2023-07-09 18:56:12 -07:00
bool theme_editor = false ;
2023-04-24 13:45:06 -07:00
bool stopped = true ;
2023-07-16 12:06:03 -07:00
bool vsync = false ;
2023-07-20 14:45:35 -07:00
bool about_window = false ;
2023-07-24 23:13:47 -07:00
currentLanguage = " osdefault " ;
2023-07-17 17:35:21 -07:00
int framerate = 60 ;
2023-04-24 13:45:06 -07:00
{
Json : : Value config ;
std : : ifstream stream ;
stream . open ( path ( prefPath ) / " config.json " ) ;
if ( stream . is_open ( ) ) {
stream > > config ;
2023-07-09 18:56:12 -07:00
if ( config . isMember ( " theme_name " ) ) {
path themePath = theme - > themeDir / config [ " theme_name " ] . asString ( ) ;
if ( exists ( themePath ) ) {
delete theme ;
theme = new Theme ( themePath ) ;
}
2023-04-24 13:45:06 -07:00
}
if ( config . isMember ( " accent_color " ) ) {
accent_color = config [ " accent_color " ] . asFloat ( ) ;
}
if ( config . isMember ( " demo_window " ) ) {
show_demo_window = config [ " demo_window " ] . asBool ( ) ;
}
2023-07-16 12:06:03 -07:00
if ( config . isMember ( " vsync " ) ) {
vsync = config [ " vsync " ] . asBool ( ) ;
}
2023-07-17 17:35:21 -07:00
if ( config . isMember ( " framerate " ) ) {
framerate = config [ " framerate " ] . asUInt ( ) ;
}
2023-07-24 23:13:47 -07:00
if ( config . isMember ( " lang " ) ) {
currentLanguage = config [ " lang " ] . asString ( ) ;
}
2023-04-24 13:45:06 -07:00
stream . close ( ) ;
2023-07-10 19:27:55 -07:00
}
if ( is_empty ( Theme : : themeDir ) ) {
path lightPath = Theme : : themeDir / " light.json " ;
path darkPath = Theme : : themeDir / " dark.json " ;
if ( ! exists ( lightPath ) ) {
Theme light ( false ) ;
light . Save ( lightPath ) ;
}
if ( ! exists ( darkPath ) ) {
Theme dark ( true ) ;
dark . Save ( darkPath ) ;
}
2023-07-17 13:23:02 -07:00
delete theme ;
theme = new Theme ( darkPath ) ;
2023-04-24 13:45:06 -07:00
}
}
2023-07-16 12:37:34 -07:00
SDL_GL_SetSwapInterval ( vsync ? 1 : 0 ) ;
2023-07-16 12:06:03 -07:00
theme - > Apply ( accent_color ) ;
2023-04-24 13:45:06 -07:00
# 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
2023-07-16 13:00:04 -07:00
{ /*
2023-07-15 14:52:49 -07:00
{
int min_x ;
int min_y ;
SDL_GetWindowMinimumSize ( window , & min_x , & min_y ) ;
int height = ImGui : : GetFrameHeightWithSpacing ( ) + ImGui : : GetFrameHeight ( ) + ( ImGui : : GetStyle ( ) . WindowPadding . y * 2 ) + ( ( ImGui : : GetStyle ( ) . FramePadding . y * 2 ) + ImGui : : GetFontSize ( ) ) ;
if ( height ! = min_y ) {
min_y = height ;
SDL_SetWindowMinimumSize ( window , 475 , min_y ) ;
}
2023-07-16 13:00:04 -07:00
} */
2023-07-17 17:35:21 -07:00
auto next_frame = std : : chrono : : steady_clock : : now ( ) + std : : chrono : : milliseconds ( 1000 / framerate ) ;
2023-04-24 13:45:06 -07:00
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 ;
2023-07-17 12:21:38 -07:00
if ( event . type = = SDL_WINDOWEVENT ) {
if ( event . window . event = = SDL_WINDOWEVENT_RESIZED ) {
window_width = event . window . data1 ;
window_height = event . window . data2 ;
//SDL_GetWindowSize(window, &window_width, &window_height);
}
if ( event . window . event = = SDL_WINDOWEVENT_CLOSE & & event . window . windowID = = SDL_GetWindowID ( window ) ) {
done = true ;
}
}
2023-04-24 13:45:06 -07:00
}
// Start the Dear ImGui frame
ImGui_ImplOpenGL3_NewFrame ( ) ;
ImGui_ImplSDL2_NewFrame ( ) ;
ImGui : : NewFrame ( ) ;
2023-07-20 14:45:35 -07:00
auto dockid = ImGui : : DockSpaceOverViewport ( nullptr , ImGuiDockNodeFlags_PassthruCentralNode | ImGuiDockNodeFlags_AutoHideTabBar ) ;
2023-04-24 13:45:06 -07:00
// 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 ) ;
2023-07-21 09:56:13 -07:00
if ( ImGui : : BeginMainMenuBar ( ) ) {
2023-07-24 23:13:47 -07:00
if ( ImGui : : BeginMenu ( _TRI_CTX ( ICON_FK_FILE , " Main menu " , " File " ) ) ) {
if ( ImGui : : MenuItem ( _TRI_CTX ( ICON_FK_FOLDER_OPEN , " Main menu > File " , " Open " ) ) ) {
2023-07-21 09:56:13 -07:00
fileDialog . Open ( ) ;
2023-04-24 13:45:06 -07:00
}
2023-07-24 23:13:47 -07:00
if ( ImGui : : MenuItem ( _TRI_CTX ( ICON_FK_WINDOW_CLOSE , " Main menu > File " , " Quit " ) ) ) {
2023-07-21 09:56:13 -07:00
done = true ;
}
ImGui : : EndMenu ( ) ;
}
2023-07-24 23:13:47 -07:00
if ( ImGui : : BeginMenu ( _TRI_CTX ( ICON_FK_SCISSORS , " Main menu " , " Edit " ) ) ) {
if ( ImGui : : MenuItem ( _TRI_CTX ( ICON_FK_COG , " Main menu > Edit " , " Preferences... " ) ) ) {
2023-07-21 09:56:13 -07:00
prefs_window = true ;
2023-04-24 13:45:06 -07:00
}
2023-07-21 09:56:13 -07:00
ImGui : : EndMenu ( ) ;
}
# ifdef DEBUG
2023-07-24 23:13:47 -07:00
if ( ImGui : : BeginMenu ( _TRI_CTX ( ICON_FK_COG , " Main menu (in debug builds) " , " Debug " ) ) ) {
if ( ImGui : : MenuItem ( _TR_CTX ( " Main menu > Debug " , " Show ImGui Demo Window " ) , nullptr , show_demo_window ) ) {
2023-07-21 09:56:13 -07:00
show_demo_window = ! show_demo_window ;
2023-04-24 13:45:06 -07:00
}
2023-07-21 09:56:13 -07:00
ImGui : : EndMenu ( ) ;
}
# endif
2023-07-24 23:13:47 -07:00
if ( ImGui : : BeginMenu ( _TRI_CTX ( ICON_FK_INFO_CIRCLE , " Main menu " , " Help " ) ) ) {
if ( ImGui : : MenuItem ( _TRI_CTX ( ICON_FK_INFO , " Main menu > Help " , " About " ) , nullptr , about_window ) ) {
2023-07-21 09:56:13 -07:00
about_window = ! about_window ;
2023-07-20 14:45:35 -07:00
}
2023-07-21 09:56:13 -07:00
ImGui : : EndMenu ( ) ;
2023-04-24 13:45:06 -07:00
}
2023-07-21 09:56:13 -07:00
ImGui : : EndMainMenuBar ( ) ;
}
2023-07-20 14:45:35 -07:00
ImGui : : SetNextWindowDockID ( dockid ) ;
2023-07-24 23:13:47 -07:00
ImGui : : Begin ( _TRI_CTX ( ICON_FK_PLAY , " Main window title " , " Player " ) , nullptr , 0 ) ;
2023-07-20 14:45:35 -07:00
{
2023-07-15 14:52:49 -07:00
ImGui : : SetCursorPosY ( ImGui : : GetWindowHeight ( ) - ImGui : : GetFrameHeightWithSpacing ( ) - ImGui : : GetFrameHeight ( ) - ImGui : : GetStyle ( ) . WindowPadding . y ) ;
2023-04-24 13:45:06 -07:00
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 ( ) ;
2023-07-15 14:52:49 -07:00
const int NEXT_SLIDER_COUNT = 1 ;
2023-07-18 18:44:19 -07:00
ImGui : : SetNextItemWidth ( - ( ImGui : : GetFontSize ( ) * ( 1 + ( 8 * NEXT_SLIDER_COUNT ) ) ) - ( ( ImGui : : GetStyle ( ) . ItemSpacing . x + ImGui : : GetStyle ( ) . FramePadding . x ) * ( NEXT_SLIDER_COUNT + 1 ) ) ) ;
2023-07-09 10:57:48 -07:00
uint8_t components = TimeToComponentCount ( playback - > GetLength ( ) ) ;
string time_str = TimeToString ( position , components ) ;
if ( ImGui : : SliderFloat ( " ##Seek " , & position , 0.0f , playback - > GetLength ( ) , time_str . c_str ( ) , ImGuiSliderFlags_NoRoundToFormat ) )
2023-04-24 13:45:06 -07:00
playback - > Seek ( position ) ;
ImGui : : SameLine ( ) ;
if ( ImGui : : Button ( ICON_FK_STOP " ##Stop " ) ) {
playback - > Stop ( ) ;
}
ImGui : : SameLine ( ) ;
ImGui : : SetNextItemWidth ( ImGui : : GetFontSize ( ) * 8 ) ;
if ( ImGui : : SliderFloat ( " ##Volume " , & playback - > volume , 0.0 , 100.0 , ICON_FK_VOLUME_UP " : %.0f%% " ) ) {
playback - > Update ( ) ;
2023-07-15 14:52:49 -07:00
}
2023-07-18 18:44:19 -07:00
const float items = 3.0f ;
const float between_items = items - 1.0f ;
ImGui : : PushItemWidth ( ( ImGui : : GetWindowWidth ( ) / items ) - ( ImGui : : GetStyle ( ) . ItemSpacing . x / ( items / between_items ) ) - ( ( ImGui : : GetStyle ( ) . WindowPadding . x / items ) * 2.0f ) ) ;
2023-07-24 23:13:47 -07:00
if ( ImGui : : SliderFloat ( " ##Speed " , & playback - > speed , 0.25 , 4.0 , _TR_CTX ( " Playback controls > slider " , " Speed: %.2fx " ) , ImGuiSliderFlags_Logarithmic ) ) {
2023-04-24 13:45:06 -07:00
playback - > Update ( ) ;
2023-07-15 14:52:49 -07:00
}
ImGui : : SameLine ( ) ;
2023-07-24 23:13:47 -07:00
if ( ImGui : : SliderFloat ( " ##Tempo " , & playback - > tempo , 0.25 , 4.0 , _TR_CTX ( " Playback controls > slider " , " Tempo: %.2fx " ) , ImGuiSliderFlags_Logarithmic ) ) {
2023-07-15 14:52:49 -07:00
playback - > Update ( ) ;
}
ImGui : : SameLine ( ) ;
2023-07-24 23:13:47 -07:00
if ( ImGui : : SliderFloat ( " ##Pitch " , & playback - > pitch , 0.25 , 4.0 , _TR_CTX ( " Playback controls > slider " , " Pitch: %.2fx " ) , ImGuiSliderFlags_Logarithmic ) ) {
2023-07-15 14:52:49 -07:00
playback - > Update ( ) ;
}
ImGui : : PopItemWidth ( ) ;
2023-04-24 13:45:06 -07:00
}
ImGui : : End ( ) ;
2023-07-20 14:45:35 -07:00
if ( prefs_window ) {
2023-07-17 15:45:43 -07:00
ImGui : : SetNextWindowDockID ( dockid ) ;
2023-07-24 23:13:47 -07:00
ImGui : : Begin ( _TRI_CTX ( ICON_FK_COG , " Window title, window opened by menu item " , " Preferences... " ) , & prefs_window ) ;
2023-04-24 13:45:06 -07:00
{
2023-07-24 23:13:47 -07:00
if ( ImGui : : Checkbox ( _TR_CTX ( " Preference > VSync checkbox " , " Enable VSync " ) , & vsync ) ) {
2023-07-16 12:06:03 -07:00
SDL_GL_SetSwapInterval ( vsync ? 1 : 0 ) ;
}
2023-07-17 17:35:21 -07:00
ImGui : : SameLine ( ) ;
ImGui : : SetNextItemWidth ( ImGui : : GetWindowWidth ( ) - ImGui : : GetCursorPosX ( ) - ImGui : : GetStyle ( ) . WindowPadding . x ) ;
2023-07-24 23:13:47 -07:00
ImGui : : SliderInt ( " ##Framerate " , & framerate , 10 , 480 , _TR_CTX ( " Preferences > Framerate slider " , " Max framerate without VSync: %d " ) ) ;
if ( ImGui : : Button ( _TRI_CTX ( ICON_FK_MAGIC , " Preference > Related non-preference button " , " Theme Editor " ) , ImVec2 ( ImGui : : GetWindowWidth ( ) - ( ImGui : : GetStyle ( ) . WindowPadding . x * 2.0f ) , 0 ) ) ) {
2023-07-09 18:56:12 -07:00
theme_editor = true ;
2023-04-24 13:45:06 -07:00
}
2023-07-10 19:27:55 -07:00
static char filter [ 1024 ] = { 0 } ;
2023-07-24 23:13:47 -07:00
ImGui : : Text ( _TR_CTX ( " Preference > Theme selector > Filter label " , " Filter: " ) ) ; ImGui : : SameLine ( ) ;
2023-07-10 19:27:55 -07:00
ImGui : : SetNextItemWidth ( ImGui : : GetWindowWidth ( ) - ImGui : : GetCursorPosX ( ) - ImGui : : GetStyle ( ) . WindowPadding . x ) ;
ImGui : : InputText ( " ##FilterInput " , filter , 1024 ) ;
2023-07-24 23:13:47 -07:00
ImGui : : Text ( _TR_CTX ( " Preferences > Theme selector > Selector label " , " Select a theme... " ) ) ;
2023-07-18 18:44:19 -07:00
ImVec2 ChildSize = ImVec2 ( ImGui : : GetWindowWidth ( ) - ( ImGui : : GetStyle ( ) . WindowPadding . x * 2.0f ) , - ImGui : : GetFrameHeightWithSpacing ( ) ) ;
2023-07-17 14:56:02 -07:00
if ( ImGui : : BeginChildFrame ( ImGui : : GetID ( " ##ThemesContainer " ) , ChildSize ) ) {
2023-07-18 18:44:19 -07:00
ImVec2 TableSize = ImVec2 ( 0 , 0 ) ;
if ( ImGui : : BeginTable ( " ##Themes " , 2 , ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoSavedSettings | ImGuiTableFlags_ScrollY , TableSize ) ) {
2023-07-24 23:13:47 -07:00
// Text in TableSetupColumn calls not translated because they're not visible to the user.
2023-07-17 14:56:02 -07:00
ImGui : : TableSetupColumn ( " Name " , ImGuiTableColumnFlags_WidthStretch ) ;
2023-07-18 18:44:19 -07:00
ImGui : : TableSetupColumn ( " Remove " , 0 ) ;
2023-07-17 14:56:02 -07:00
for ( auto themePath : Theme : : availableThemes ) {
if ( themePath . stem ( ) . string ( ) . starts_with ( filter ) ) {
ImGui : : TableNextRow ( ) ;
ImGui : : TableSetColumnIndex ( 0 ) ;
const bool is_selected = themePath = = theme - > file_path ;
if ( ImGui : : Selectable ( themePath . stem ( ) . generic_string ( ) . c_str ( ) , is_selected , 0 ) ) {
delete theme ;
theme = new Theme ( themePath ) ;
theme - > Apply ( accent_color ) ;
2023-07-17 13:23:02 -07:00
break ;
}
2023-07-17 14:56:02 -07:00
if ( is_selected ) {
ImGui : : SetItemDefaultFocus ( ) ;
} else {
ImGui : : TableSetColumnIndex ( 1 ) ;
if ( ImGui : : SmallButton ( ( string ( ICON_FK_WINDOW_CLOSE " ## " ) + themePath . stem ( ) . generic_string ( ) ) . c_str ( ) ) ) {
std : : filesystem : : remove ( themePath ) ;
Theme : : updateAvailableThemes ( ) ;
break ;
}
}
2023-07-10 19:27:55 -07:00
}
}
2023-07-17 14:56:02 -07:00
ImGui : : EndTable ( ) ;
2023-07-10 19:27:55 -07:00
}
}
2023-07-17 15:45:43 -07:00
ImGui : : EndChildFrame ( ) ;
2023-07-18 18:44:19 -07:00
ImGui : : SetNextItemWidth ( ImGui : : GetWindowWidth ( ) - ( ImGui : : GetStyle ( ) . WindowPadding . x * 2 ) ) ;
2023-07-24 23:13:47 -07:00
if ( ImGui : : SliderFloat ( " ##AccentColor " , & accent_color , 0.0 , 360.0 , _TR_CTX ( " Preference > Accent hue slider, range 0-360 from HSV algorithm hue component " , " Accent color hue: %.0f° " ) , ImGuiSliderFlags_NoRoundToFormat ) ) {
2023-07-16 17:24:59 -07:00
theme - > Apply ( accent_color ) ;
2023-07-17 15:45:43 -07:00
}
2023-04-24 13:45:06 -07:00
}
ImGui : : End ( ) ;
}
2023-07-20 14:45:35 -07:00
if ( about_window ) {
ImGui : : SetNextWindowDockID ( dockid ) ;
2023-07-24 23:13:47 -07:00
if ( ImGui : : Begin ( _TRI_CTX ( ICON_FK_INFO , " Window title, window opened by menu item " , " About and Licenses " ) , & about_window ) ) {
2023-07-20 14:45:35 -07:00
ImGui : : PushFont ( title ) ;
2023-07-24 23:13:47 -07:00
static const string APP_NAME_STR = _TR_CTX ( " Application name - Japanese word should remain Japanese and stay the same when translating for stylistic purposes. " , " ねこ Player " ) ;
2023-07-20 14:45:35 -07:00
ImGui : : SetCursorPosX ( ( ImGui : : GetWindowWidth ( ) - ImGui : : GetFont ( ) - > CalcTextSizeA ( ImGui : : GetFontSize ( ) , FLT_MAX , 0.0f , APP_NAME_STR . c_str ( ) ) . x ) / 2.0f ) ;
ImGui : : Text ( APP_NAME_STR . c_str ( ) ) ;
ImGui : : PopFont ( ) ;
2023-07-24 23:13:47 -07:00
static const string APP_NAME_ROMANIZED = _TR_CTX ( " Application name - Japanese word should be converted to the translated language's characters. Use an empty string to disable for Japanese. " , " (Neko Player) " ) ;
if ( APP_NAME_ROMANIZED ! = " " ) {
ImGui : : SetCursorPosX ( ( ImGui : : GetWindowWidth ( ) - ImGui : : GetFont ( ) - > CalcTextSizeA ( ImGui : : GetFontSize ( ) , FLT_MAX , 0.0f , APP_NAME_ROMANIZED . c_str ( ) ) . x ) / 2.0f ) ;
ImGui : : Text ( APP_NAME_ROMANIZED . c_str ( ) ) ;
}
static const string VER_STRING = _TR_CTX ( " Version string format specifier " , " Version " ) + string ( TAG ) + _TRS_CTX ( " Suffix to the version string in the about window, if needed " , " " ) ;
2023-07-20 14:45:35 -07:00
ImGui : : SetCursorPosX ( ( ImGui : : GetWindowWidth ( ) - ImGui : : GetFont ( ) - > CalcTextSizeA ( ImGui : : GetFontSize ( ) , FLT_MAX , 0.0f , VER_STRING . c_str ( ) ) . x ) / 2.0f ) ;
ImGui : : Text ( VER_STRING . c_str ( ) ) ;
ImGui : : NewLine ( ) ;
static vector < LicenseData > projects = {
2023-07-24 23:13:47 -07:00
LicenseData ( APP_NAME_STR , " MIT " ) ,
LicenseData ( _TR_CTX ( " Library name " , " SDL Mixer X " ) , " Zlib " ) ,
LicenseData ( _TR_CTX ( " Library name " , " JsonCpp " ) , " MIT " ) ,
LicenseData ( _TR_CTX ( " Library name " , " SoundTouch " ) , " LGPL-2.1-only " ) ,
LicenseData ( _TR_CTX ( " Library name " , " libintl " ) , " LGPL-2.1-only " ) ,
LicenseData ( _TR_CTX ( " Library name " , " Dear ImGui " ) , " MIT " ) ,
LicenseData ( _TR_CTX ( " Library name " , " imgui-filebrowser " ) , " MIT " ) ,
2023-07-20 14:45:35 -07:00
# ifdef PORTALS
2023-07-24 23:13:47 -07:00
LicenseData ( _TR_CTX ( " Library name " , " libportal " ) , " LGPL-3.0-only " ) , // Only include the license if it applies.
2023-07-20 14:45:35 -07:00
# endif
2023-07-24 23:13:47 -07:00
LicenseData ( _TR_CTX ( " Library name " , " Noto Sans " ) , " OFL-1.1-RFN " ) ,
LicenseData ( _TR_CTX ( " Library name " , " Fork Awesome " ) , " OFL-1.1-RFN " ) ,
LicenseData ( _TR_CTX ( " Library name " , " IconFontCppHeaders " ) , " Zlib " )
2023-07-20 14:45:35 -07:00
} ;
2023-07-24 23:13:47 -07:00
// Do this in an inner scope so that 'i' isn't accidentally used outside it,
// and so that 'i' can refer to another variable such as in a for loop.
{
int i = 0 ;
// Use a variable instead of hardcoding so that a #ifdef can change the indices later on.
LOAD_LICENSE ( projects [ i ] , nekoplayer ) ; i + + ;
LOAD_LICENSE ( projects [ i ] , sdl_mixer_x ) ; i + + ;
LOAD_LICENSE ( projects [ i ] , jsoncpp ) ; i + + ;
LOAD_LICENSE ( projects [ i ] , soundtouch ) ; i + + ;
LOAD_LICENSE ( projects [ i ] , libintl ) ; i + + ;
LOAD_LICENSE ( projects [ i ] , imgui ) ; i + + ;
LOAD_LICENSE ( projects [ i ] , imgui_filebrowser ) ; i + + ;
# ifdef PORTALS
LOAD_LICENSE ( projects [ i ] , libportal ) ; i + + ;
# endif
LOAD_LICENSE ( projects [ i ] , notosans ) ; i + + ;
LOAD_LICENSE ( projects [ i ] , forkawesome ) ; i + + ;
LOAD_LICENSE ( projects [ i ] , icnfntcpphdrs ) ; i + + ;
}
2023-07-20 14:45:35 -07:00
// Left
static LicenseData selected = projects [ 0 ] ;
{
ImGui : : BeginGroup ( ) ;
2023-07-24 23:13:47 -07:00
ImGui : : TextUnformatted ( _TR_CTX ( " Project selector label. " , " Project " ) ) ;
// Next string is internal.
2023-07-20 14:45:35 -07:00
ImGui : : BeginChild ( " project selector " , ImVec2 ( 150 , 0 ) , true ) ;
for ( auto project : projects )
{
if ( ImGui : : Selectable ( project . Project . c_str ( ) , selected . Project = = project . Project ) )
selected = project ;
}
ImGui : : EndChild ( ) ;
ImGui : : EndGroup ( ) ;
}
ImGui : : SameLine ( ) ;
// Right
{
ImGui : : BeginGroup ( ) ;
2023-07-24 23:13:47 -07:00
ImGui : : TextUnformatted ( _TR_CTX ( " License viewer label " , " License " ) ) ;
// Next string is internal.
ImGui : : BeginChild ( " license view " , ImVec2 ( 0 , 0 ) , true ) ; // *don't* leave room for the nonexistant line below us!
ImGui : : Text ( _TR_CTX ( " License viewer > information above license - string 1: selected project, string 2: SPDX license identifier " , " %s: %s " ) , selected . Project . c_str ( ) , selected . Spdx . c_str ( ) ) ;
2023-07-20 14:45:35 -07:00
ImGui : : Separator ( ) ;
ImGui : : TextWrapped ( " %s " , selected . LicenseContents . c_str ( ) ) ;
ImGui : : EndChild ( ) ;
ImGui : : EndGroup ( ) ;
}
}
ImGui : : End ( ) ;
}
2023-07-09 18:56:12 -07:00
if ( theme_editor ) {
2023-07-17 15:45:43 -07:00
Theme : : ShowEditor ( & theme_editor , theme , dockid , window_width , window_height ) ;
2023-07-16 12:06:03 -07:00
theme - > Apply ( accent_color ) ;
2023-07-09 18:56:12 -07:00
}
2023-07-17 12:21:38 -07:00
if ( fileDialog . IsOpened ( ) ) {
fileDialog . SetWindowSize ( window_width , window_height ) ;
fileDialog . SetWindowPos ( 0 , 0 ) ;
}
2023-04-24 13:45:06 -07:00
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 ) ;
2023-07-17 17:35:21 -07:00
if ( ! vsync ) {
std : : this_thread : : sleep_until ( next_frame ) ;
}
2023-04-24 13:45:06 -07:00
}
// 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 " ) ;
2023-07-09 18:56:12 -07:00
path themePath ( theme - > file_path ) ;
themePath = themePath . filename ( ) ;
if ( ! themePath . empty ( ) ) {
config [ " theme_name " ] = themePath . filename ( ) . string ( ) ;
}
2023-04-24 13:45:06 -07:00
config [ " accent_color " ] = accent_color ;
config [ " demo_window " ] = show_demo_window ;
2023-07-16 12:06:03 -07:00
config [ " vsync " ] = vsync ;
2023-07-17 17:35:21 -07:00
config [ " framerate " ] = framerate ;
2023-07-24 23:13:47 -07:00
config [ " lang " ] = currentLanguage ;
2023-04-24 13:45:06 -07:00
stream < < config ;
stream . close ( ) ;
}
free ( ( void * ) io . IniFilename ) ;
return 0 ;
}