diff --git a/CMakeLists.txt b/CMakeLists.txt index cc86d85..03b39af 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,7 +59,7 @@ if (DEFINED EMSCRIPTEN) set(DEBUG_INFO ${CMAKE_BUILD_TYPE} STREQUAL Debug OR ${CMAKE_BUILD_TYPE} STREQUAL RelWithDebInfo) set(RELASE_OPTS ${CMAKE_BUILD_TYPE} STREQUAL Release OR ${CMAKE_BUILD_TYPE} STREQUAL RelWithDebInfo) set(PROFILE_ENABLED ${CMAKE_BUILD_TYPE} STREQUAL RelWithDebInfo) - set(EXTRA_LINKER_FLAGS "-sALLOW_MEMORY_GROWTH=1 -sEXPORTED_RUNTIME_METHODS=UTF8ToString,stringToUTF8,lengthBytesUTF8 -sEXPORTED_FUNCTIONS=_malloc,_main -sASYNCIFY_IMPORTS=read_file") + set(EXTRA_LINKER_FLAGS "-sALLOW_MEMORY_GROWTH=1 -sEXPORTED_RUNTIME_METHODS=UTF8ToString,stringToUTF8,lengthBytesUTF8 -sEXPORTED_FUNCTIONS=_malloc,_main -sASYNCIFY_IMPORTS=read_file,read_storage,write_storage,remove_storage,find_keys") set(OPENMP OFF CACHE BOOL "" FORCE) set(SOUNDSTRETCH OFF CACHE BOOL "" FORCE) set(SOUNDTOUCH_DLL OFF CACHE BOOL "" FORCE) @@ -257,8 +257,6 @@ endif() target_include_directories(liblooper PUBLIC ${LIBINTL_INCDIRS}) target_link_libraries(liblooper PUBLIC ${LIBINTL_LIBRARY}) if(DEFINED EMSCRIPTEN) - add_subdirectory(subprojects/jsoncpp) - add_subdirectory(subprojects/soundtouch) target_link_libraries(liblooper PUBLIC ${SDL_MIXER_X_TARGET} ${SOUNDTOUCH_TARGET} libvgmstream ${JSONCPP_TARGET}) target_compile_options(liblooper PUBLIC "-sUSE_SDL=2") else() @@ -328,8 +326,9 @@ ui_backend_subdir(NAME "IMGUI" READABLE_NAME "Dear ImGui" SUBDIR backends/ui/img if (NOT (DEFINED EMSCRIPTEN OR DEFINED ANDROID_NDK)) ui_backend_subdir(NAME "GTK" READABLE_NAME "GTK4" SUBDIR backends/ui/gtk) endif() -execute_process(COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/gen_ui_backend_inc.py ${ENABLED_UIS}) -prefix_all(SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/ backend_glue.cpp main.cpp daemon_backend.cpp proxy_backend.cpp) +execute_process(COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/gen_ui_backend_inc.py ${CMAKE_CURRENT_BINARY_DIR} ${ENABLED_UIS}) +prefix_all(SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/ main.cpp daemon_backend.cpp proxy_backend.cpp) +list(APPEND SOURCES ${CMAKE_CURRENT_BINARY_DIR}/backend_glue.cpp) if(DEFINED EMSCRIPTEN) set(CMAKE_EXECUTABLE_SUFFIX ".html") endif() diff --git a/backend_glue.cpp b/backend_glue.cpp deleted file mode 100644 index d46c01b..0000000 --- a/backend_glue.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include "backend.hpp" -#include "backends/ui/imgui/ui_backend.hpp" -#include "backends/ui/gtk/main.h" - -void init_backends() { - UIBackend::register_backend(); - UIBackend::register_backend(); -} diff --git a/backends/ui/gtk/main_window.cpp b/backends/ui/gtk/main_window.cpp index 4952c34..2764ef5 100644 --- a/backends/ui/gtk/main_window.cpp +++ b/backends/ui/gtk/main_window.cpp @@ -315,6 +315,7 @@ MainWindow::MainWindow(Playback *playback, Glib::RefPtr app) speed_slider.set_suffix("x"); speed_slider.set_min_value(0.25); speed_slider.set_max_value(4); + speed_slider.set_logarithmic(true); speed_slider.set_value(playback->GetSpeed()); speed_slider.set_name("speed-slider"); speed_slider.value_changed.connect([this](double value) { diff --git a/backends/ui/gtk/my_slider.cpp b/backends/ui/gtk/my_slider.cpp index 598c98c..cfb641e 100644 --- a/backends/ui/gtk/my_slider.cpp +++ b/backends/ui/gtk/my_slider.cpp @@ -32,8 +32,13 @@ void MySlider::update(bool force) { classes.push_back("hexpand"); } set_css_classes(classes); + update_log_data(); if (min_value <= max_value) { - slider.set_range(min_value, max_value); + if (logarithmic) { + slider.set_range(log_data.x0, log_data.x1); + } else { + slider.set_range(min_value, max_value); + } } prefix_label.set_text(prefix); value_label.set_text(value_str.c_str()); @@ -49,6 +54,20 @@ void MySlider::update(bool force) { } update_occurring = false; } +double MySlider::scale_log(double input) { + return log_data.a + (log_data.b * log(value)); +} +void MySlider::update_log_data() { + log_data.xmin = min_value; + log_data.xmax = max_value; + log_data.x0 = log(log_data.xmin); + log_data.x1 = log(log_data.xmax); + log_data.b = (log_data.x1 - log_data.x0) / log(log_data.xmax - log_data.xmin); + log_data.a = log_data.x0 - (log_data.b * log_data.x0); +} +double MySlider::unscale_log(double input) { + return (exp(value) / log_data.b) - log_data.a; +} void MySlider::set_value(double value, bool force) { this->value = value; update(force); @@ -78,19 +97,23 @@ void MySlider::update_text_entry(std::string text) { text_entry.insert_text(text.c_str(), text.size(), pos); } void MySlider::update_slider(double value) { - slider.set_value(value); + if (logarithmic) { + slider.set_value(scale_log(value)); + } } double MySlider::get_min_value() { return min_value; } void MySlider::set_min_value(double value) { min_value = value; + update_log_data(); } double MySlider::get_max_value() { return max_value; } void MySlider::set_max_value(double value) { max_value = value; + update_log_data(); } MySlider::MySlider() { min_value = 0; @@ -138,7 +161,11 @@ MySlider::MySlider() { update(); }); slider.signal_value_changed().connect([this]() { - value = slider.get_value(); + if (get_logarithmic()) { + value = unscale_log(slider.get_value()); + } else { + value = slider.get_value(); + } value_changed.emit(value); update(); }); diff --git a/backends/ui/gtk/my_slider.hpp b/backends/ui/gtk/my_slider.hpp index 0add55e..c4286b4 100644 --- a/backends/ui/gtk/my_slider.hpp +++ b/backends/ui/gtk/my_slider.hpp @@ -6,6 +6,15 @@ class MySlider : public Gtk::Box { double value; double max_value; double min_value; + struct { + double xmin; + double xmax; + double x0; + double x1; + double a; + double b; + } log_data; + bool logarithmic = false; bool text_valid = true; bool text_editing; Gtk::Button edit_btn; @@ -21,10 +30,15 @@ class MySlider : public Gtk::Box { Glib::ustring prefix; Glib::ustring suffix; unsigned digits_after_decimal; + void update_log_data(); + double scale_log(double input); + double unscale_log(double input); public: double get_value(); void set_value(double value, bool force = false); sigc::signal value_changed; + bool get_logarithmic(); + void set_logarithmic(bool value = true); double get_max_value(); void set_max_value(double value); double get_min_value(); diff --git a/backends/ui/imgui/CMakeLists.txt b/backends/ui/imgui/CMakeLists.txt index 03178db..4162457 100644 --- a/backends/ui/imgui/CMakeLists.txt +++ b/backends/ui/imgui/CMakeLists.txt @@ -53,7 +53,7 @@ endif() if(DEFINED EMSCRIPTEN) target_compile_options(imgui_ui PRIVATE "-sUSE_SDL_IMAGE=2") target_link_options(imgui_ui PUBLIC "-sMAX_WEBGL_VERSION=2" "-sMIN_WEBGL_VERSION=2" "-sFULL_ES3") - target_link_libraries(imgui PRIVATE ${LIBINTL_LIBRARY}) + target_link_libraries(imgui_ui PRIVATE ${LIBINTL_LIBRARY}) else() find_package(SDL2 REQUIRED) find_package(SDL2_image REQUIRED) diff --git a/backends/ui/imgui/RendererBackend.cpp b/backends/ui/imgui/RendererBackend.cpp index 381b1cd..3f95c7b 100644 --- a/backends/ui/imgui/RendererBackend.cpp +++ b/backends/ui/imgui/RendererBackend.cpp @@ -38,6 +38,9 @@ void RendererBackend::resize_static() { renderer_backend->resize_needed = true; } void main_loop() { + if (!renderer_backend->started) { + return; + } renderer_backend->LoopFunction(); #ifdef __EMSCRIPTEN__ if (renderer_backend->done) { @@ -91,8 +94,17 @@ void RendererBackend::BackendDeinit() { bool RendererBackend::isTouchScreenMode() { return touchScreenModeOverride.value_or(SDL_GetNumTouchDevices() > 0); } +void RendererBackend::QueueUpdateScale() { + update_scale = true; +} void RendererBackend::LoopFunction() { + if (update_scale) { + UpdateScale(); + update_scale = false; + } + #ifndef __EMSCRIPTEN__ SDL_RenderSetVSync(rend, vsync ? 1 : 0); + #endif ImGuiIO& io = ImGui::GetIO(); (void)io; if (resize_needed) { on_resize(); @@ -214,21 +226,29 @@ void RendererBackend::UpdateScale() { #else 96.0; #endif -#ifdef __EMSCRIPTEN__ - scale = get_dpi() / defaultDPI; -#else - float dpi = defaultDPI; - if (SDL_GetDisplayDPI(SDL_GetWindowDisplayIndex(window), NULL, &dpi, NULL) == 0){ - scale = dpi / defaultDPI; + if (scaleOverride.has_value()) { + scale = scaleOverride.value(); } else { - WARNING.writeln("DPI couldn't be determined!"); - scale = 1.0; - } +#ifdef __EMSCRIPTEN__ + scale = get_dpi(); +#else + float dpi = defaultDPI; + if (SDL_GetDisplayDPI(SDL_GetWindowDisplayIndex(window), NULL, &dpi, NULL) == 0){ + scale = dpi / defaultDPI; + } else { + WARNING.writeln("DPI couldn't be determined!"); + scale = 1.0; + } #ifndef __ANDROID__ - SDL_SetWindowSize(window, window_width * scale, window_height * scale); + SDL_SetWindowSize(window, window_width * scale, window_height * scale); #endif #endif + } AddFonts(); + OnScale((float)scale); +} +void RendererBackend::OnScale(float scale) { + theme->Apply(accent_color, scale); } void RendererBackend::SetWindowSize(int w, int h) { #if !(defined(__ANDROID__) || defined(__EMSCRIPTEN__)) @@ -269,9 +289,12 @@ void RendererBackend::AddFonts() { #ifdef __EMSCRIPTEN__ static EM_BOOL resize_callback(int event_type, const EmscriptenUiEvent *event, void *userdata) { RendererBackend::resize_static(); + return EM_FALSE; } #endif int RendererBackend::Run() { + started = false; + renderer_backend = this; setup_locale("neko_player"); DEBUG.writefln("Loaded locale '%s' from '%s'...", CURRENT_LANGUAGE, LOCALE_DIR); DEBUG.writefln("Locale name: %s", _TR_CTX("Language name", "English (United States)")); @@ -299,7 +322,10 @@ int RendererBackend::Run() { #ifdef SDL_HINT_IME_SHOW_UI SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1"); #endif - +#ifdef __EMSCRIPTEN__ + emscripten_set_main_loop(&main_loop, 0, 0); + emscripten_pause_main_loop(); +#endif SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_HIDDEN); SDL_CreateWindowAndRenderer(window_width, window_height, window_flags, &window, &rend); @@ -352,10 +378,10 @@ int RendererBackend::Run() { //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); - UpdateScale(); - - theme = new Theme(false); + + UpdateScale(); + #ifdef __ANDROID__ userdir = SDL_AndroidGetExternalStoragePath(); #else @@ -367,18 +393,23 @@ int RendererBackend::Run() { #endif ); #endif + #ifndef __EMSCRIPTEN__ SDL_RenderSetVSync(rend, vsync ? 1 : 0); - theme->Apply(accent_color); + #endif + theme->Apply(accent_color, (float)scale); Init(); SDL_ShowWindow(window); - renderer_backend = this; #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_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, 0, resize_callback); - emscripten_set_main_loop(&main_loop, 0, 1); + emscripten_resume_main_loop(); + emscripten_set_main_loop_timing(1, 0); + started = true; + emscripten_exit_with_live_runtime(); #else + started = true; while (!done) { LoopFunction(); diff --git a/backends/ui/imgui/RendererBackend.h b/backends/ui/imgui/RendererBackend.h index 4ef0e88..d3d4cb2 100644 --- a/backends/ui/imgui/RendererBackend.h +++ b/backends/ui/imgui/RendererBackend.h @@ -19,11 +19,14 @@ static const char* NAME = "Looper"; class RendererBackend { void BackendDeinit(); void LoopFunction(); + bool started = false; //SDL_GLContext gl_context; bool resize_needed = true; void on_resize(); + bool update_scale = false; public: std::optional touchScreenModeOverride; + std::optional scaleOverride; bool isTouchScreenMode(); static void resize_static(); double scale = 1.0; @@ -47,7 +50,9 @@ class RendererBackend { virtual void GuiFunction(); virtual void Deinit(); virtual void Drop(std::string file); + virtual void OnScale(float scale); void UpdateScale(); + void QueueUpdateScale(); void AddFonts(); void SetWindowSize(int w, int h); void GetWindowsize(int *w, int *h); diff --git a/backends/ui/imgui/file_browser.cpp b/backends/ui/imgui/file_browser.cpp index e736416..334b7da 100644 --- a/backends/ui/imgui/file_browser.cpp +++ b/backends/ui/imgui/file_browser.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #ifdef __ANDROID__ #include jclass MainActivity; @@ -39,19 +40,6 @@ void ClearSelected() { env->CallStaticVoidMethod(MainActivity, ClearSelected_Method, 0); } #endif -#ifdef __EMSCRIPTEN__ -extern "C" { - extern void open_filepicker(); - extern void set_filter(const char *filter); - extern const char *get_first_file(); - extern bool file_picker_cancelled(); - extern bool file_picker_confirmed(); - extern bool file_picker_closed(); - extern bool file_picker_visible(); - extern bool file_picker_loading(); - extern void clear_file_selection(); -} -#endif FileBrowser::FileBrowser(bool save, ImGuiFileBrowserFlags extra_fallback_flags) { #ifdef PORTALS main_context = g_main_context_default(); diff --git a/backends/ui/imgui/main.cpp b/backends/ui/imgui/main.cpp index 8690b83..cac4fcb 100644 --- a/backends/ui/imgui/main.cpp +++ b/backends/ui/imgui/main.cpp @@ -77,10 +77,20 @@ void MainLoop::Init() { { std::string themeName = get_option("ui.imgui.theme", "light"); path themePath = Theme::themeDir / path(themeName + ".toml"); + #ifdef __EMSCRIPTEN__ + try { + auto newTheme = new Theme(themePath); + delete theme; + theme = newTheme; + } catch (std::exception _) { + + } + #else if (exists(themePath)) { delete theme; theme = new Theme(themePath); } + #endif if (option_set("ui.imgui.lang")) { lang = get_option("ui.imgui.lang"); } else { @@ -95,7 +105,8 @@ void MainLoop::Init() { accent_color.z = (float)get_option("ui.imgui.accent_color.v", accent_color.z); accent_color.w = (float)get_option("ui.imgui.accent_color.a", accent_color.w); } - if (is_empty(Theme::themeDir)) { + Theme::updateAvailableThemes(); + if (Theme::availableThemes.empty()) { path lightPath = Theme::themeDir / "light.toml"; path darkPath = Theme::themeDir / "dark.toml"; string builtinDescription = _TRS_CTX("Built-in themes | Theme default strings | name", "(built-in)"); @@ -119,7 +130,7 @@ void MainLoop::Init() { theme = new Theme(darkPath); } } - theme->Apply(accent_color); + theme->Apply(accent_color, (float)scale); FileLoaded(); } void MainLoop::Drop(std::string file) { @@ -173,6 +184,7 @@ void MainLoop::GuiFunction() { 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)) { show_demo_window = !show_demo_window; + set_option("ui.imgui.demo_window", show_demo_window); } ImGui::EndMenu(); } @@ -302,10 +314,14 @@ void MainLoop::GuiFunction() { } ImGui::EndCombo(); } - ImGui::Checkbox(_TR_CTX("Preference | VSync checkbox", "Enable VSync"), &vsync); + if (ImGui::Checkbox(_TR_CTX("Preference | VSync checkbox", "Enable VSync"), &vsync)) { + set_option("ui.imgui.vsync", vsync); + } ImGui::SameLine(); ImGui::SetNextItemWidth(ImGui::GetWindowWidth() - ImGui::GetCursorPosX() - ImGui::GetStyle().WindowPadding.x); - ImGui::SliderInt("##Framerate", &framerate, 10, 480, _TR_CTX("Preferences | Framerate slider", "Max framerate without VSync: %d")); + if (ImGui::SliderInt("##Framerate", &framerate, 10, 480, _TR_CTX("Preferences | Framerate slider", "Max framerate without VSync: %d"))) { + set_option("ui.imgui.framerate", framerate); + } 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))) { theme_editor = true; } @@ -321,6 +337,11 @@ void MainLoop::GuiFunction() { lang = DEFAULT_LANG; SET_LANG(lang.c_str()); } + if (lang == DEFAULT_LANG) { + delete_option("ui.imgui.lang"); + } else { + set_option("ui.imgui.lang", lang); + } } if (override_lang) { ImGui::SameLine(); @@ -329,6 +350,11 @@ void MainLoop::GuiFunction() { ImGui::SameLine(); if (ImGui::Button(ICON_FK_CHECK)) { SET_LANG(lang.c_str()); + if (lang == DEFAULT_LANG) { + delete_option("ui.imgui.lang"); + } else { + set_option("ui.imgui.lang", lang); + } } } bool overrideTouchscreenMode = touchScreenModeOverride.has_value(); @@ -341,10 +367,26 @@ void MainLoop::GuiFunction() { } if (overrideTouchscreenMode) { bool touchScreenMode = isTouchScreenMode(); - if (ImGui::Checkbox(_TRI_CTX(ICON_FK_HAND_O_UP, "Preference | Checkbox (Only shown when override enabled)", "Enable touch screen mode."), &touchScreenMode)) { + if (ImGui::Checkbox(_TRI_CTX(ICON_FK_HAND_O_UP, "Preference | Checkbox (Only shown when override enabled)", "Enable touch screen mode"), &touchScreenMode)) { touchScreenModeOverride = touchScreenMode; } } + bool overrideScale = scaleOverride.has_value(); + if (ImGui::Checkbox(_TR_CTX("Preference | override enable checkbox", "Override DPI scaling"), &overrideScale)) { + if (overrideScale) { + scaleOverride = scale; + } else { + scaleOverride = {}; + } + QueueUpdateScale(); + } + if (overrideScale) { + float newScale = scale; + if (ImGui::SliderFloat(_TRI_CTX(ICON_FK_HAND_O_UP, "Preference | Slider (Only shown when override enabled)", "DPI Scaling amount"), &newScale, 0.25, 4, "%.2f", ImGuiSliderFlags_Logarithmic)) { + scaleOverride = newScale; + QueueUpdateScale(); + } + } static string filter = ""; ImGui::TextUnformatted(_TR_CTX("Preference | Theme selector | Filter label", "Filter:")); ImGui::SameLine(); @@ -367,7 +409,11 @@ void MainLoop::GuiFunction() { if (ImGui::Selectable((theme->themeStrings[themePath].name + string(" (") + string(themeStem) + string(")")).c_str(), is_selected, 0)) { delete theme; theme = new Theme(themePath); - theme->Apply(accent_color); + theme->Apply(accent_color, (float)scale); + path themeFName = themePath.stem(); + if (!themeFName.empty()) { + set_option("ui.imgui.theme", themeFName.string()); + } break; } if (is_selected) { @@ -387,8 +433,13 @@ void MainLoop::GuiFunction() { } ImGui::EndChildFrame(); ImGui::SetNextItemWidth(ImGui::GetWindowWidth() - (ImGui::GetStyle().WindowPadding.x * 2)); - ImGui::ColorEdit4("##AccentColor", &accent_color.x, ImGuiColorEditFlags_InputHSV|ImGuiColorEditFlags_DisplayHSV|ImGuiColorEditFlags_Float); - theme->Apply(accent_color); + if (ImGui::ColorEdit4("##AccentColor", &accent_color.x, ImGuiColorEditFlags_InputHSV|ImGuiColorEditFlags_DisplayHSV|ImGuiColorEditFlags_Float)) { + set_option("ui.imgui.accent_color.h", accent_color.x); + set_option("ui.imgui.accent_color.s", accent_color.y); + set_option("ui.imgui.accent_color.v", accent_color.z); + set_option("ui.imgui.accent_color.a", accent_color.w); + } + theme->Apply(accent_color, (float)scale); } ImGui::End(); } @@ -440,7 +491,7 @@ void MainLoop::GuiFunction() { if (theme_editor) { Theme::ShowEditor(&theme_editor, theme, dockid, window_width, window_height); // Immediately apply any changes made in the theme editor. - theme->Apply(accent_color); + theme->Apply(accent_color, (float)scale); } if (fileDialog.IsOpened()) { // Make the fallback file dialog fill the window. @@ -467,9 +518,9 @@ void MainLoop::Deinit() { { path themePath(theme->file_path); - themePath = themePath.filename(); + themePath = themePath.stem(); if (!themePath.empty()) { - set_option("ui.imgui.theme", themePath.filename().string()); + set_option("ui.imgui.theme", themePath.string()); } set_option("ui.imgui.accent_color.h", accent_color.x); set_option("ui.imgui.accent_color.s", accent_color.y); diff --git a/backends/ui/imgui/theme.cpp b/backends/ui/imgui/theme.cpp index 51e74b7..267fbe7 100644 --- a/backends/ui/imgui/theme.cpp +++ b/backends/ui/imgui/theme.cpp @@ -12,6 +12,8 @@ #include "IconsForkAwesome.h" #include "imgui_stdlib.h" #include +#include +#include using namespace std::filesystem; const char* Theme::prefPath = NULL; @@ -351,12 +353,14 @@ void AccentColorizer::Colorize(ImVec4 accent, ImVec4 &color) { if (Alpha) color.w *= accent.w; } -void Theme::Apply(ImVec4 accent) { +void Theme::Apply(ImVec4 accent, float scale) { ImGuiStyle& actual_style = ImGui::GetStyle(); actual_style = style; for (int i = 0; i < ImGuiCol_COUNT; i++) { - AccentColorizers[i].Colorize(accent, actual_style.Colors[i]); + if (AccentColorizers.contains(i)) { + AccentColorizers[i].Colorize(accent, actual_style.Colors[i]); + } } ImGuiIO& io = ImGui::GetIO(); if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) @@ -364,13 +368,20 @@ void Theme::Apply(ImVec4 accent) { actual_style.WindowRounding = 0.0f; actual_style.Colors[ImGuiCol_WindowBg].w = 1.0f; } + actual_style.ScaleAllSizes(scale); + actual_style.WindowMinSize.x = MAX(actual_style.WindowMinSize.x, 1.0); + actual_style.WindowMinSize.y = MAX(actual_style.WindowMinSize.y, 1.0); } void Theme::Save(string path) { INFO.writefln("Saving theme to %s...", path.c_str()); { toml::table config; + #ifdef __EMSCRIPTEN__ + std::ostringstream stream; + #else std::ofstream stream; stream.open(path); + #endif { toml::table metadata; metadata.insert("SchemaVersion", 3); @@ -442,7 +453,13 @@ void Theme::Save(string path) { config.insert("colors", colors); } stream << config; + #ifdef __EMSCRIPTEN__ + path = path; + std::string str = stream.str(); + write_storage(path.c_str(), str.c_str(), str.length() + 1); + #else stream.close(); + #endif } updateAvailableThemes(); } @@ -450,10 +467,38 @@ void Theme::Save(path path) { Save((string)path.string()); } void Theme::updateAvailableThemes() { + #ifdef __EMSCRIPTEN__ + themeDir = "theme"; + #else themeDir = path(prefPath) / path("themes"); create_directories(themeDir); + #endif availableThemes.clear(); themeStrings.clear(); +#ifdef __EMSCRIPTEN__ + const char **theme_files = nullptr; + find_keys("theme/", &theme_files); + if (theme_files[0] != nullptr) { + for (size_t i = 0; theme_files[i] != nullptr; i++) { + const char *dir_entry = theme_files[i]; + if (dir_entry == nullptr) { + break; + } + string curpath = dir_entry; + const char *theme_contents = nullptr; + read_storage(dir_entry, &theme_contents, nullptr); + if (theme_contents == nullptr) { + continue; + } + availableThemes.insert(curpath); + toml::table config = toml::parse(theme_contents); + free((void*)theme_contents); + themeStrings[curpath] = ThemeStrings(config); + free((void*)dir_entry); + } + } + free((void*)theme_files); +#else for (auto const& dir_entry : directory_iterator(themeDir)) { if (dir_entry.is_regular_file()) { if (dir_entry.path().extension().string() == ".json") { @@ -463,13 +508,15 @@ void Theme::updateAvailableThemes() { toml::table config = toml::parse_file(curpath); themeStrings[curpath] = ThemeStrings(config); } else if (dir_entry.path().extension().string() == ".toml") { + string curpath = dir_entry.path().string(); availableThemes.insert(curpath); toml::table config = toml::parse_file(curpath); themeStrings[curpath] = ThemeStrings(config); } - } + } } +#endif } Theme::Theme() { if (prefPath == NULL) { @@ -645,7 +692,17 @@ Theme::Theme(string path) : Theme() { path = Migrate(path); INFO.writefln("Loading theme '%s'...", path.c_str()); toml::table config; +#ifdef __EMSCRIPTEN__ + const char *contents = nullptr; + read_storage(path.c_str(), &contents, nullptr); + if (contents == nullptr) { + throw std::exception(); + } + config = toml::parse(contents); + free((void*)contents); +#else config = toml::parse_file(path); +#endif if (config.contains("meta")) { toml::table metadata = *config["meta"].as_table(); //metadata["SchemaVersion"] = 1; diff --git a/backends/ui/imgui/theme.h b/backends/ui/imgui/theme.h index 9b94e61..a17ab6c 100644 --- a/backends/ui/imgui/theme.h +++ b/backends/ui/imgui/theme.h @@ -56,7 +56,7 @@ class Theme { std::map AccentColorizers; ThemeStrings GetStrings(); static bool ShowEditor(bool *open, Theme* &theme, ImGuiID dockid, int window_width, int window_height); - void Apply(ImVec4 accent); + void Apply(ImVec4 accent, float scale); void Save(string path); void Save(path path); Theme(); diff --git a/gen_ui_backend_inc.py b/gen_ui_backend_inc.py index 44f70d4..7fdd31d 100755 --- a/gen_ui_backend_inc.py +++ b/gen_ui_backend_inc.py @@ -7,10 +7,10 @@ import json olddir = os.curdir scriptdir = path.realpath(path.dirname(__file__)) os.chdir(scriptdir) -outpath = path.join(scriptdir, "backend_glue.cpp") +outpath = path.join(sys.argv[1], "backend_glue.cpp") ui_backend_dir = path.join(scriptdir, "backends", "ui") ui_backend_metafiles = [] -for backend in sys.argv[1:]: +for backend in sys.argv[2:]: ui_backend_metafiles += [ui_backend_dir + "/" + backend + "/ui.json"] ui_backends = [] for metafile_path in ui_backend_metafiles: diff --git a/log.cpp b/log.cpp index fddac4e..7267dd4 100644 --- a/log.cpp +++ b/log.cpp @@ -6,7 +6,12 @@ #endif namespace Looper::Log { std::set LogStream::global_outputs; - int LogStream::log_level = 0; + int LogStream::log_level = + #ifdef DEBUG_MODE + -2; + #else + 0; + #endif std::set LogStream::get_used_outputs() { std::set used_outputs; for (auto my_output : outputs) { diff --git a/log.hpp b/log.hpp index 954255a..71f979e 100644 --- a/log.hpp +++ b/log.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #ifdef __ANDROID__ diff --git a/options.cpp b/options.cpp index 0f23d47..c7d0494 100644 --- a/options.cpp +++ b/options.cpp @@ -1,22 +1,42 @@ #include "thirdparty/toml.hpp" #include "util.hpp" #include +#include #ifdef __ANDROID__ #include #endif +#include "web_functions.hpp" using namespace std::filesystem; namespace Looper::Options { toml::table *options; toml::table opts_value; std::string get_options_path() { + #ifdef __EMSCRIPTEN__ + return "config_toml"; + #else path prefs_path(get_prefs_path()); prefs_path /= path("looper"); create_directories(prefs_path); path config_file = prefs_path / path("config.toml"); return config_file.string(); + #endif } void load_options() { std::string config_path = get_options_path(); + #ifdef __EMSCRIPTEN__ + const char *value = nullptr; + read_storage(config_path.c_str(), &value, nullptr); + if (value != nullptr) { + auto res = toml::parse(value); + opts_value = res; + options = &opts_value; + if (options == nullptr) { + options = new toml::table(); + } + } else { + options = new toml::table(); + } + #else if (exists(config_path)) { auto res = toml::parse_file(config_path); opts_value = res; @@ -27,10 +47,20 @@ namespace Looper::Options { } else { options = new toml::table(); } + #endif } void save_options() { + #ifdef __EMSCRIPTEN__ + std::ostringstream output; + #else std::ofstream output(get_options_path()); + #endif output << *options; + #ifdef __EMSCRIPTEN__ + std::string str = output.str(); + write_storage(get_options_path().c_str(), str.c_str(), str.length() + 1); + #else output.close(); + #endif } } \ No newline at end of file diff --git a/options.hpp b/options.hpp index 4236e29..0f33418 100644 --- a/options.hpp +++ b/options.hpp @@ -77,6 +77,7 @@ namespace Looper::Options { path = parent_path; } } + save_options(); } template void set_option(std::string name, T value) { @@ -108,6 +109,7 @@ namespace Looper::Options { } DEBUG.writefln(".%s%s", last_component.c_str(), tmp->contains(last_component) ? "[set]" : "[new]"); tmp->insert_or_assign(last_component, value); + save_options(); } template inline void init_option(std::string name, T value) { diff --git a/translation.cpp b/translation.cpp index ad65181..48279fb 100644 --- a/translation.cpp +++ b/translation.cpp @@ -1,5 +1,6 @@ #include "translation.hpp" #include +#include #include "config.h" static const char *orig_language; static const char *cur_locale_dir; diff --git a/util.hpp b/util.hpp index 2a28216..7cc14d1 100644 --- a/util.hpp +++ b/util.hpp @@ -54,4 +54,6 @@ inline size_t combine_hashes(std::initializer_list hashes) { values += ";"; } return std::hash()(values); -} \ No newline at end of file +} +#define MIN(x, y) ((x < y) ? x : y) +#define MAX(x, y) ((x > y) ? x : y) \ No newline at end of file diff --git a/web/api.js b/web/api.js index 189879f..e94a56b 100644 --- a/web/api.js +++ b/web/api.js @@ -44,12 +44,132 @@ addToLibrary({ setValue(y, canvas.offsetHeight, "i32"); }, is_puter_enabled: function() { - return window.filePicker.puterEnabled; + return window.puterEnabled; }, enable_puter: function(enable) { - window.filePicker.puterEnabled = enable; + window.puterEnabled = enable; + }, + write_storage: async function(keyPtr, valuePtr, valueLen) { + let key = Module.UTF8ToString(keyPtr) + /** + * @type {Uint8Array} + */ + let valueData = HEAPU8.subarray(valuePtr, valuePtr + valueLen) + let valueBase64 = window.bytesToBase64(valueData); + if (valueBase64 === null) { + console.error("Couldn't encode key '", key, "' to base64."); + return; + } + if (window.puterEnabled) { + value = await puter.kv.set(key, valueBase64); + } + localStorage.setItem(key, valueBase64); + }, + remove_storage: async function(keyPtr) { + let key = Module.UTF8ToString(keyPtr) + if (window.puterEnabled) { + await puter.kv.del(key) + } + localStorage.removeItem(Module.UTF8ToString(key)); + }, + get_dpi: function() { + return window.devicePixelRatio; + }, + read_storage: async function(keyPtr, valuePtr, lenPtr) { + let key = Module.UTF8ToString(keyPtr); + let valueBase64 = localStorage.getItem(key); + if (window.puterEnabled) { + let puterValue = await puter.kv.get(key) + if (puterValue !== null) { + valueBase64 = puterValue; + } + } + if (valueBase64 === null) { + console.error("Could not find key '", key, "'.") + } + /** + * @type {Uint8Array|null} + */ + let value = null; + try { + value = window.base64ToBytes(valueBase64) + } catch (e) { + console.error(e) + value = null; + } + if (value == null) { + console.error("Could not decode key '", key, "'.") + } else { + let len = value.length; + let outptr = Module._malloc(len + 1); + for (let i = 0; i < len; i++) { + setValue(outptr + i, value[i], 'i8'); + } + if (lenPtr !== 0) { + setValue(lenPtr, value.length, "i32") + } + setValue(outptr + len, 0, 'i8') + setValue(valuePtr, outptr, "i32"); + } + }, + find_keys: async function(prefixPtr, pppOut) { + let prefix = Module.UTF8ToString(prefixPtr); + let storageLen = localStorage.length; + let puterList = []; + if (window.puterEnabled) { + puterList = await puter.kv.list(false); + } + let arr = []; + let dict = {}; + for (let i = 0; i < storageLen; i++) { + let key = localStorage.key(i); + if (key.startsWith(prefix)) { + let base64Test = localStorage.getItem(key); + try { + window.base64ToBytes(base64Test) + } catch (e) { + continue; + } + arr = [...arr, key]; + dict[key] = true; + } + } + if (window.puterEnabled) { + for (let i = 0; i < puterList.length; i++) { + let key = puterList[i]; + if (key.startsWith(prefix)) { + if (dict[key] !== true) { + let base64Test = await puter.kv.get(key); + try { + window.base64ToBytes(base64Test) + } catch (e) { + continue; + } + arr = [...arr, key]; + } + } + } + } + let arrLen = arr.length; + let ptrLen = arrLen + 1; + let arrOutPtr = Module._malloc(ptrLen * 4); + + for (let i = 0; i < ptrLen; i++) { + setValue(arrOutPtr + (i * 4), 0, 'i32'); + } + + for (let i = 0; i < arrLen; i++) { + let len = Module.lengthBytesUTF8(arr[i]) + 1; + let outptr = Module._malloc(len); + for (let j = 0; j < len; j++) { + setValue(outptr + j, 0, 'i8'); + } + Module.stringToUTF8(arr[i], outptr, len); + setValue(arrOutPtr + (i * 4), outptr, "i32"); + } + setValue(pppOut, arrOutPtr, "i32") }, quit: function() { - puter.ui.exit(); + puter.exit(); } }) \ No newline at end of file diff --git a/web/shell.html b/web/shell.html index a65f241..4a4c7b8 100644 --- a/web/shell.html +++ b/web/shell.html @@ -67,7 +67,7 @@ - +