Various improvements.
This commit is contained in:
parent
6159f52e8a
commit
a1fd9e1427
23 changed files with 550 additions and 73 deletions
|
@ -59,7 +59,7 @@ if (DEFINED EMSCRIPTEN)
|
||||||
set(DEBUG_INFO ${CMAKE_BUILD_TYPE} STREQUAL Debug OR ${CMAKE_BUILD_TYPE} STREQUAL RelWithDebInfo)
|
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(RELASE_OPTS ${CMAKE_BUILD_TYPE} STREQUAL Release OR ${CMAKE_BUILD_TYPE} STREQUAL RelWithDebInfo)
|
||||||
set(PROFILE_ENABLED ${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(OPENMP OFF CACHE BOOL "" FORCE)
|
||||||
set(SOUNDSTRETCH OFF CACHE BOOL "" FORCE)
|
set(SOUNDSTRETCH OFF CACHE BOOL "" FORCE)
|
||||||
set(SOUNDTOUCH_DLL OFF CACHE BOOL "" FORCE)
|
set(SOUNDTOUCH_DLL OFF CACHE BOOL "" FORCE)
|
||||||
|
@ -257,8 +257,6 @@ endif()
|
||||||
target_include_directories(liblooper PUBLIC ${LIBINTL_INCDIRS})
|
target_include_directories(liblooper PUBLIC ${LIBINTL_INCDIRS})
|
||||||
target_link_libraries(liblooper PUBLIC ${LIBINTL_LIBRARY})
|
target_link_libraries(liblooper PUBLIC ${LIBINTL_LIBRARY})
|
||||||
if(DEFINED EMSCRIPTEN)
|
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_link_libraries(liblooper PUBLIC ${SDL_MIXER_X_TARGET} ${SOUNDTOUCH_TARGET} libvgmstream ${JSONCPP_TARGET})
|
||||||
target_compile_options(liblooper PUBLIC "-sUSE_SDL=2")
|
target_compile_options(liblooper PUBLIC "-sUSE_SDL=2")
|
||||||
else()
|
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))
|
if (NOT (DEFINED EMSCRIPTEN OR DEFINED ANDROID_NDK))
|
||||||
ui_backend_subdir(NAME "GTK" READABLE_NAME "GTK4" SUBDIR backends/ui/gtk)
|
ui_backend_subdir(NAME "GTK" READABLE_NAME "GTK4" SUBDIR backends/ui/gtk)
|
||||||
endif()
|
endif()
|
||||||
execute_process(COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/gen_ui_backend_inc.py ${ENABLED_UIS})
|
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}/ backend_glue.cpp main.cpp daemon_backend.cpp proxy_backend.cpp)
|
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)
|
if(DEFINED EMSCRIPTEN)
|
||||||
set(CMAKE_EXECUTABLE_SUFFIX ".html")
|
set(CMAKE_EXECUTABLE_SUFFIX ".html")
|
||||||
endif()
|
endif()
|
||||||
|
|
|
@ -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<ImGuiUIBackend>();
|
|
||||||
UIBackend::register_backend<GtkBackend>();
|
|
||||||
}
|
|
|
@ -315,6 +315,7 @@ MainWindow::MainWindow(Playback *playback, Glib::RefPtr<Gtk::Application> app)
|
||||||
speed_slider.set_suffix("x");
|
speed_slider.set_suffix("x");
|
||||||
speed_slider.set_min_value(0.25);
|
speed_slider.set_min_value(0.25);
|
||||||
speed_slider.set_max_value(4);
|
speed_slider.set_max_value(4);
|
||||||
|
speed_slider.set_logarithmic(true);
|
||||||
speed_slider.set_value(playback->GetSpeed());
|
speed_slider.set_value(playback->GetSpeed());
|
||||||
speed_slider.set_name("speed-slider");
|
speed_slider.set_name("speed-slider");
|
||||||
speed_slider.value_changed.connect([this](double value) {
|
speed_slider.value_changed.connect([this](double value) {
|
||||||
|
|
|
@ -32,9 +32,14 @@ void MySlider::update(bool force) {
|
||||||
classes.push_back("hexpand");
|
classes.push_back("hexpand");
|
||||||
}
|
}
|
||||||
set_css_classes(classes);
|
set_css_classes(classes);
|
||||||
|
update_log_data();
|
||||||
if (min_value <= max_value) {
|
if (min_value <= max_value) {
|
||||||
|
if (logarithmic) {
|
||||||
|
slider.set_range(log_data.x0, log_data.x1);
|
||||||
|
} else {
|
||||||
slider.set_range(min_value, max_value);
|
slider.set_range(min_value, max_value);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
prefix_label.set_text(prefix);
|
prefix_label.set_text(prefix);
|
||||||
value_label.set_text(value_str.c_str());
|
value_label.set_text(value_str.c_str());
|
||||||
suffix_label.set_text(suffix);
|
suffix_label.set_text(suffix);
|
||||||
|
@ -49,6 +54,20 @@ void MySlider::update(bool force) {
|
||||||
}
|
}
|
||||||
update_occurring = false;
|
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) {
|
void MySlider::set_value(double value, bool force) {
|
||||||
this->value = value;
|
this->value = value;
|
||||||
update(force);
|
update(force);
|
||||||
|
@ -78,19 +97,23 @@ void MySlider::update_text_entry(std::string text) {
|
||||||
text_entry.insert_text(text.c_str(), text.size(), pos);
|
text_entry.insert_text(text.c_str(), text.size(), pos);
|
||||||
}
|
}
|
||||||
void MySlider::update_slider(double value) {
|
void MySlider::update_slider(double value) {
|
||||||
slider.set_value(value);
|
if (logarithmic) {
|
||||||
|
slider.set_value(scale_log(value));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
double MySlider::get_min_value() {
|
double MySlider::get_min_value() {
|
||||||
return min_value;
|
return min_value;
|
||||||
}
|
}
|
||||||
void MySlider::set_min_value(double value) {
|
void MySlider::set_min_value(double value) {
|
||||||
min_value = value;
|
min_value = value;
|
||||||
|
update_log_data();
|
||||||
}
|
}
|
||||||
double MySlider::get_max_value() {
|
double MySlider::get_max_value() {
|
||||||
return max_value;
|
return max_value;
|
||||||
}
|
}
|
||||||
void MySlider::set_max_value(double value) {
|
void MySlider::set_max_value(double value) {
|
||||||
max_value = value;
|
max_value = value;
|
||||||
|
update_log_data();
|
||||||
}
|
}
|
||||||
MySlider::MySlider() {
|
MySlider::MySlider() {
|
||||||
min_value = 0;
|
min_value = 0;
|
||||||
|
@ -138,7 +161,11 @@ MySlider::MySlider() {
|
||||||
update();
|
update();
|
||||||
});
|
});
|
||||||
slider.signal_value_changed().connect([this]() {
|
slider.signal_value_changed().connect([this]() {
|
||||||
|
if (get_logarithmic()) {
|
||||||
|
value = unscale_log(slider.get_value());
|
||||||
|
} else {
|
||||||
value = slider.get_value();
|
value = slider.get_value();
|
||||||
|
}
|
||||||
value_changed.emit(value);
|
value_changed.emit(value);
|
||||||
update();
|
update();
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,6 +6,15 @@ class MySlider : public Gtk::Box {
|
||||||
double value;
|
double value;
|
||||||
double max_value;
|
double max_value;
|
||||||
double min_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_valid = true;
|
||||||
bool text_editing;
|
bool text_editing;
|
||||||
Gtk::Button edit_btn;
|
Gtk::Button edit_btn;
|
||||||
|
@ -21,10 +30,15 @@ class MySlider : public Gtk::Box {
|
||||||
Glib::ustring prefix;
|
Glib::ustring prefix;
|
||||||
Glib::ustring suffix;
|
Glib::ustring suffix;
|
||||||
unsigned digits_after_decimal;
|
unsigned digits_after_decimal;
|
||||||
|
void update_log_data();
|
||||||
|
double scale_log(double input);
|
||||||
|
double unscale_log(double input);
|
||||||
public:
|
public:
|
||||||
double get_value();
|
double get_value();
|
||||||
void set_value(double value, bool force = false);
|
void set_value(double value, bool force = false);
|
||||||
sigc::signal<void(double)> value_changed;
|
sigc::signal<void(double)> value_changed;
|
||||||
|
bool get_logarithmic();
|
||||||
|
void set_logarithmic(bool value = true);
|
||||||
double get_max_value();
|
double get_max_value();
|
||||||
void set_max_value(double value);
|
void set_max_value(double value);
|
||||||
double get_min_value();
|
double get_min_value();
|
||||||
|
|
|
@ -53,7 +53,7 @@ endif()
|
||||||
if(DEFINED EMSCRIPTEN)
|
if(DEFINED EMSCRIPTEN)
|
||||||
target_compile_options(imgui_ui PRIVATE "-sUSE_SDL_IMAGE=2")
|
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_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()
|
else()
|
||||||
find_package(SDL2 REQUIRED)
|
find_package(SDL2 REQUIRED)
|
||||||
find_package(SDL2_image REQUIRED)
|
find_package(SDL2_image REQUIRED)
|
||||||
|
|
|
@ -38,6 +38,9 @@ void RendererBackend::resize_static() {
|
||||||
renderer_backend->resize_needed = true;
|
renderer_backend->resize_needed = true;
|
||||||
}
|
}
|
||||||
void main_loop() {
|
void main_loop() {
|
||||||
|
if (!renderer_backend->started) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
renderer_backend->LoopFunction();
|
renderer_backend->LoopFunction();
|
||||||
#ifdef __EMSCRIPTEN__
|
#ifdef __EMSCRIPTEN__
|
||||||
if (renderer_backend->done) {
|
if (renderer_backend->done) {
|
||||||
|
@ -91,8 +94,17 @@ void RendererBackend::BackendDeinit() {
|
||||||
bool RendererBackend::isTouchScreenMode() {
|
bool RendererBackend::isTouchScreenMode() {
|
||||||
return touchScreenModeOverride.value_or(SDL_GetNumTouchDevices() > 0);
|
return touchScreenModeOverride.value_or(SDL_GetNumTouchDevices() > 0);
|
||||||
}
|
}
|
||||||
|
void RendererBackend::QueueUpdateScale() {
|
||||||
|
update_scale = true;
|
||||||
|
}
|
||||||
void RendererBackend::LoopFunction() {
|
void RendererBackend::LoopFunction() {
|
||||||
|
if (update_scale) {
|
||||||
|
UpdateScale();
|
||||||
|
update_scale = false;
|
||||||
|
}
|
||||||
|
#ifndef __EMSCRIPTEN__
|
||||||
SDL_RenderSetVSync(rend, vsync ? 1 : 0);
|
SDL_RenderSetVSync(rend, vsync ? 1 : 0);
|
||||||
|
#endif
|
||||||
ImGuiIO& io = ImGui::GetIO(); (void)io;
|
ImGuiIO& io = ImGui::GetIO(); (void)io;
|
||||||
if (resize_needed) {
|
if (resize_needed) {
|
||||||
on_resize();
|
on_resize();
|
||||||
|
@ -214,8 +226,11 @@ void RendererBackend::UpdateScale() {
|
||||||
#else
|
#else
|
||||||
96.0;
|
96.0;
|
||||||
#endif
|
#endif
|
||||||
|
if (scaleOverride.has_value()) {
|
||||||
|
scale = scaleOverride.value();
|
||||||
|
} else {
|
||||||
#ifdef __EMSCRIPTEN__
|
#ifdef __EMSCRIPTEN__
|
||||||
scale = get_dpi() / defaultDPI;
|
scale = get_dpi();
|
||||||
#else
|
#else
|
||||||
float dpi = defaultDPI;
|
float dpi = defaultDPI;
|
||||||
if (SDL_GetDisplayDPI(SDL_GetWindowDisplayIndex(window), NULL, &dpi, NULL) == 0){
|
if (SDL_GetDisplayDPI(SDL_GetWindowDisplayIndex(window), NULL, &dpi, NULL) == 0){
|
||||||
|
@ -228,7 +243,12 @@ void RendererBackend::UpdateScale() {
|
||||||
SDL_SetWindowSize(window, window_width * scale, window_height * scale);
|
SDL_SetWindowSize(window, window_width * scale, window_height * scale);
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
}
|
||||||
AddFonts();
|
AddFonts();
|
||||||
|
OnScale((float)scale);
|
||||||
|
}
|
||||||
|
void RendererBackend::OnScale(float scale) {
|
||||||
|
theme->Apply(accent_color, scale);
|
||||||
}
|
}
|
||||||
void RendererBackend::SetWindowSize(int w, int h) {
|
void RendererBackend::SetWindowSize(int w, int h) {
|
||||||
#if !(defined(__ANDROID__) || defined(__EMSCRIPTEN__))
|
#if !(defined(__ANDROID__) || defined(__EMSCRIPTEN__))
|
||||||
|
@ -269,9 +289,12 @@ void RendererBackend::AddFonts() {
|
||||||
#ifdef __EMSCRIPTEN__
|
#ifdef __EMSCRIPTEN__
|
||||||
static EM_BOOL resize_callback(int event_type, const EmscriptenUiEvent *event, void *userdata) {
|
static EM_BOOL resize_callback(int event_type, const EmscriptenUiEvent *event, void *userdata) {
|
||||||
RendererBackend::resize_static();
|
RendererBackend::resize_static();
|
||||||
|
return EM_FALSE;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
int RendererBackend::Run() {
|
int RendererBackend::Run() {
|
||||||
|
started = false;
|
||||||
|
renderer_backend = this;
|
||||||
setup_locale("neko_player");
|
setup_locale("neko_player");
|
||||||
DEBUG.writefln("Loaded locale '%s' from '%s'...", CURRENT_LANGUAGE, LOCALE_DIR);
|
DEBUG.writefln("Loaded locale '%s' from '%s'...", CURRENT_LANGUAGE, LOCALE_DIR);
|
||||||
DEBUG.writefln("Locale name: %s", _TR_CTX("Language name", "English (United States)"));
|
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
|
#ifdef SDL_HINT_IME_SHOW_UI
|
||||||
SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1");
|
SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1");
|
||||||
#endif
|
#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_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);
|
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);
|
//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());
|
//ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, nullptr, io.Fonts->GetGlyphRangesJapanese());
|
||||||
//IM_ASSERT(font != nullptr);
|
//IM_ASSERT(font != nullptr);
|
||||||
|
theme = new Theme(false);
|
||||||
|
|
||||||
UpdateScale();
|
UpdateScale();
|
||||||
|
|
||||||
|
|
||||||
theme = new Theme(false);
|
|
||||||
#ifdef __ANDROID__
|
#ifdef __ANDROID__
|
||||||
userdir = SDL_AndroidGetExternalStoragePath();
|
userdir = SDL_AndroidGetExternalStoragePath();
|
||||||
#else
|
#else
|
||||||
|
@ -367,18 +393,23 @@ int RendererBackend::Run() {
|
||||||
#endif
|
#endif
|
||||||
);
|
);
|
||||||
#endif
|
#endif
|
||||||
|
#ifndef __EMSCRIPTEN__
|
||||||
SDL_RenderSetVSync(rend, vsync ? 1 : 0);
|
SDL_RenderSetVSync(rend, vsync ? 1 : 0);
|
||||||
theme->Apply(accent_color);
|
#endif
|
||||||
|
theme->Apply(accent_color, (float)scale);
|
||||||
Init();
|
Init();
|
||||||
SDL_ShowWindow(window);
|
SDL_ShowWindow(window);
|
||||||
renderer_backend = this;
|
|
||||||
#ifdef __EMSCRIPTEN__
|
#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.
|
// 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.
|
// You may manually call LoadIniSettingsFromMemory() to load settings from your own storage.
|
||||||
io.IniFilename = nullptr;
|
io.IniFilename = nullptr;
|
||||||
emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, 0, resize_callback);
|
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
|
#else
|
||||||
|
started = true;
|
||||||
while (!done)
|
while (!done)
|
||||||
{
|
{
|
||||||
LoopFunction();
|
LoopFunction();
|
||||||
|
|
|
@ -19,11 +19,14 @@ static const char* NAME = "Looper";
|
||||||
class RendererBackend {
|
class RendererBackend {
|
||||||
void BackendDeinit();
|
void BackendDeinit();
|
||||||
void LoopFunction();
|
void LoopFunction();
|
||||||
|
bool started = false;
|
||||||
//SDL_GLContext gl_context;
|
//SDL_GLContext gl_context;
|
||||||
bool resize_needed = true;
|
bool resize_needed = true;
|
||||||
void on_resize();
|
void on_resize();
|
||||||
|
bool update_scale = false;
|
||||||
public:
|
public:
|
||||||
std::optional<bool> touchScreenModeOverride;
|
std::optional<bool> touchScreenModeOverride;
|
||||||
|
std::optional<double> scaleOverride;
|
||||||
bool isTouchScreenMode();
|
bool isTouchScreenMode();
|
||||||
static void resize_static();
|
static void resize_static();
|
||||||
double scale = 1.0;
|
double scale = 1.0;
|
||||||
|
@ -47,7 +50,9 @@ class RendererBackend {
|
||||||
virtual void GuiFunction();
|
virtual void GuiFunction();
|
||||||
virtual void Deinit();
|
virtual void Deinit();
|
||||||
virtual void Drop(std::string file);
|
virtual void Drop(std::string file);
|
||||||
|
virtual void OnScale(float scale);
|
||||||
void UpdateScale();
|
void UpdateScale();
|
||||||
|
void QueueUpdateScale();
|
||||||
void AddFonts();
|
void AddFonts();
|
||||||
void SetWindowSize(int w, int h);
|
void SetWindowSize(int w, int h);
|
||||||
void GetWindowsize(int *w, int *h);
|
void GetWindowsize(int *w, int *h);
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <log.hpp>
|
#include <log.hpp>
|
||||||
|
#include <web_functions.hpp>
|
||||||
#ifdef __ANDROID__
|
#ifdef __ANDROID__
|
||||||
#include <jni.h>
|
#include <jni.h>
|
||||||
jclass MainActivity;
|
jclass MainActivity;
|
||||||
|
@ -39,19 +40,6 @@ void ClearSelected() {
|
||||||
env->CallStaticVoidMethod(MainActivity, ClearSelected_Method, 0);
|
env->CallStaticVoidMethod(MainActivity, ClearSelected_Method, 0);
|
||||||
}
|
}
|
||||||
#endif
|
#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) {
|
FileBrowser::FileBrowser(bool save, ImGuiFileBrowserFlags extra_fallback_flags) {
|
||||||
#ifdef PORTALS
|
#ifdef PORTALS
|
||||||
main_context = g_main_context_default();
|
main_context = g_main_context_default();
|
||||||
|
|
|
@ -77,10 +77,20 @@ void MainLoop::Init() {
|
||||||
{
|
{
|
||||||
std::string themeName = get_option<std::string>("ui.imgui.theme", "light");
|
std::string themeName = get_option<std::string>("ui.imgui.theme", "light");
|
||||||
path themePath = Theme::themeDir / path(themeName + ".toml");
|
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)) {
|
if (exists(themePath)) {
|
||||||
delete theme;
|
delete theme;
|
||||||
theme = new Theme(themePath);
|
theme = new Theme(themePath);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
if (option_set("ui.imgui.lang")) {
|
if (option_set("ui.imgui.lang")) {
|
||||||
lang = get_option<std::string>("ui.imgui.lang");
|
lang = get_option<std::string>("ui.imgui.lang");
|
||||||
} else {
|
} else {
|
||||||
|
@ -95,7 +105,8 @@ void MainLoop::Init() {
|
||||||
accent_color.z = (float)get_option<double>("ui.imgui.accent_color.v", accent_color.z);
|
accent_color.z = (float)get_option<double>("ui.imgui.accent_color.v", accent_color.z);
|
||||||
accent_color.w = (float)get_option<double>("ui.imgui.accent_color.a", accent_color.w);
|
accent_color.w = (float)get_option<double>("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 lightPath = Theme::themeDir / "light.toml";
|
||||||
path darkPath = Theme::themeDir / "dark.toml";
|
path darkPath = Theme::themeDir / "dark.toml";
|
||||||
string builtinDescription = _TRS_CTX("Built-in themes | Theme default strings | name", "(built-in)");
|
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 = new Theme(darkPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
theme->Apply(accent_color);
|
theme->Apply(accent_color, (float)scale);
|
||||||
FileLoaded();
|
FileLoaded();
|
||||||
}
|
}
|
||||||
void MainLoop::Drop(std::string file) {
|
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::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)) {
|
if (ImGui::MenuItem(_TR_CTX("Main menu | Debug", "Show ImGui Demo Window"), nullptr, show_demo_window)) {
|
||||||
show_demo_window = !show_demo_window;
|
show_demo_window = !show_demo_window;
|
||||||
|
set_option<double>("ui.imgui.demo_window", show_demo_window);
|
||||||
}
|
}
|
||||||
ImGui::EndMenu();
|
ImGui::EndMenu();
|
||||||
}
|
}
|
||||||
|
@ -302,10 +314,14 @@ void MainLoop::GuiFunction() {
|
||||||
}
|
}
|
||||||
ImGui::EndCombo();
|
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<bool>("ui.imgui.vsync", vsync);
|
||||||
|
}
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::SetNextItemWidth(ImGui::GetWindowWidth() - ImGui::GetCursorPosX() - ImGui::GetStyle().WindowPadding.x);
|
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<int64_t>("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))) {
|
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;
|
theme_editor = true;
|
||||||
}
|
}
|
||||||
|
@ -321,6 +337,11 @@ void MainLoop::GuiFunction() {
|
||||||
lang = DEFAULT_LANG;
|
lang = DEFAULT_LANG;
|
||||||
SET_LANG(lang.c_str());
|
SET_LANG(lang.c_str());
|
||||||
}
|
}
|
||||||
|
if (lang == DEFAULT_LANG) {
|
||||||
|
delete_option("ui.imgui.lang");
|
||||||
|
} else {
|
||||||
|
set_option<std::string>("ui.imgui.lang", lang);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (override_lang) {
|
if (override_lang) {
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
|
@ -329,6 +350,11 @@ void MainLoop::GuiFunction() {
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
if (ImGui::Button(ICON_FK_CHECK)) {
|
if (ImGui::Button(ICON_FK_CHECK)) {
|
||||||
SET_LANG(lang.c_str());
|
SET_LANG(lang.c_str());
|
||||||
|
if (lang == DEFAULT_LANG) {
|
||||||
|
delete_option("ui.imgui.lang");
|
||||||
|
} else {
|
||||||
|
set_option<std::string>("ui.imgui.lang", lang);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bool overrideTouchscreenMode = touchScreenModeOverride.has_value();
|
bool overrideTouchscreenMode = touchScreenModeOverride.has_value();
|
||||||
|
@ -341,10 +367,26 @@ void MainLoop::GuiFunction() {
|
||||||
}
|
}
|
||||||
if (overrideTouchscreenMode) {
|
if (overrideTouchscreenMode) {
|
||||||
bool touchScreenMode = isTouchScreenMode();
|
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;
|
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 = "";
|
static string filter = "";
|
||||||
ImGui::TextUnformatted(_TR_CTX("Preference | Theme selector | Filter label", "Filter:")); ImGui::SameLine();
|
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)) {
|
if (ImGui::Selectable((theme->themeStrings[themePath].name + string(" (") + string(themeStem) + string(")")).c_str(), is_selected, 0)) {
|
||||||
delete theme;
|
delete theme;
|
||||||
theme = new Theme(themePath);
|
theme = new Theme(themePath);
|
||||||
theme->Apply(accent_color);
|
theme->Apply(accent_color, (float)scale);
|
||||||
|
path themeFName = themePath.stem();
|
||||||
|
if (!themeFName.empty()) {
|
||||||
|
set_option<std::string>("ui.imgui.theme", themeFName.string());
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (is_selected) {
|
if (is_selected) {
|
||||||
|
@ -387,8 +433,13 @@ void MainLoop::GuiFunction() {
|
||||||
}
|
}
|
||||||
ImGui::EndChildFrame();
|
ImGui::EndChildFrame();
|
||||||
ImGui::SetNextItemWidth(ImGui::GetWindowWidth() - (ImGui::GetStyle().WindowPadding.x * 2));
|
ImGui::SetNextItemWidth(ImGui::GetWindowWidth() - (ImGui::GetStyle().WindowPadding.x * 2));
|
||||||
ImGui::ColorEdit4("##AccentColor", &accent_color.x, ImGuiColorEditFlags_InputHSV|ImGuiColorEditFlags_DisplayHSV|ImGuiColorEditFlags_Float);
|
if (ImGui::ColorEdit4("##AccentColor", &accent_color.x, ImGuiColorEditFlags_InputHSV|ImGuiColorEditFlags_DisplayHSV|ImGuiColorEditFlags_Float)) {
|
||||||
theme->Apply(accent_color);
|
set_option<double>("ui.imgui.accent_color.h", accent_color.x);
|
||||||
|
set_option<double>("ui.imgui.accent_color.s", accent_color.y);
|
||||||
|
set_option<double>("ui.imgui.accent_color.v", accent_color.z);
|
||||||
|
set_option<double>("ui.imgui.accent_color.a", accent_color.w);
|
||||||
|
}
|
||||||
|
theme->Apply(accent_color, (float)scale);
|
||||||
}
|
}
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
}
|
}
|
||||||
|
@ -440,7 +491,7 @@ void MainLoop::GuiFunction() {
|
||||||
if (theme_editor) {
|
if (theme_editor) {
|
||||||
Theme::ShowEditor(&theme_editor, theme, dockid, window_width, window_height);
|
Theme::ShowEditor(&theme_editor, theme, dockid, window_width, window_height);
|
||||||
// Immediately apply any changes made in the theme editor.
|
// Immediately apply any changes made in the theme editor.
|
||||||
theme->Apply(accent_color);
|
theme->Apply(accent_color, (float)scale);
|
||||||
}
|
}
|
||||||
if (fileDialog.IsOpened()) {
|
if (fileDialog.IsOpened()) {
|
||||||
// Make the fallback file dialog fill the window.
|
// Make the fallback file dialog fill the window.
|
||||||
|
@ -467,9 +518,9 @@ void MainLoop::Deinit() {
|
||||||
|
|
||||||
{
|
{
|
||||||
path themePath(theme->file_path);
|
path themePath(theme->file_path);
|
||||||
themePath = themePath.filename();
|
themePath = themePath.stem();
|
||||||
if (!themePath.empty()) {
|
if (!themePath.empty()) {
|
||||||
set_option<std::string>("ui.imgui.theme", themePath.filename().string());
|
set_option<std::string>("ui.imgui.theme", themePath.string());
|
||||||
}
|
}
|
||||||
set_option<double>("ui.imgui.accent_color.h", accent_color.x);
|
set_option<double>("ui.imgui.accent_color.h", accent_color.x);
|
||||||
set_option<double>("ui.imgui.accent_color.s", accent_color.y);
|
set_option<double>("ui.imgui.accent_color.s", accent_color.y);
|
||||||
|
|
|
@ -12,6 +12,8 @@
|
||||||
#include "IconsForkAwesome.h"
|
#include "IconsForkAwesome.h"
|
||||||
#include "imgui_stdlib.h"
|
#include "imgui_stdlib.h"
|
||||||
#include <log.hpp>
|
#include <log.hpp>
|
||||||
|
#include <util.hpp>
|
||||||
|
#include <web_functions.hpp>
|
||||||
|
|
||||||
using namespace std::filesystem;
|
using namespace std::filesystem;
|
||||||
const char* Theme::prefPath = NULL;
|
const char* Theme::prefPath = NULL;
|
||||||
|
@ -351,26 +353,35 @@ void AccentColorizer::Colorize(ImVec4 accent, ImVec4 &color) {
|
||||||
if (Alpha)
|
if (Alpha)
|
||||||
color.w *= accent.w;
|
color.w *= accent.w;
|
||||||
}
|
}
|
||||||
void Theme::Apply(ImVec4 accent) {
|
void Theme::Apply(ImVec4 accent, float scale) {
|
||||||
ImGuiStyle& actual_style = ImGui::GetStyle();
|
ImGuiStyle& actual_style = ImGui::GetStyle();
|
||||||
actual_style = style;
|
actual_style = style;
|
||||||
for (int i = 0; i < ImGuiCol_COUNT; i++)
|
for (int i = 0; i < ImGuiCol_COUNT; i++)
|
||||||
{
|
{
|
||||||
|
if (AccentColorizers.contains(i)) {
|
||||||
AccentColorizers[i].Colorize(accent, actual_style.Colors[i]);
|
AccentColorizers[i].Colorize(accent, actual_style.Colors[i]);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
ImGuiIO& io = ImGui::GetIO();
|
ImGuiIO& io = ImGui::GetIO();
|
||||||
if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
|
if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
|
||||||
{
|
{
|
||||||
actual_style.WindowRounding = 0.0f;
|
actual_style.WindowRounding = 0.0f;
|
||||||
actual_style.Colors[ImGuiCol_WindowBg].w = 1.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) {
|
void Theme::Save(string path) {
|
||||||
INFO.writefln("Saving theme to %s...", path.c_str());
|
INFO.writefln("Saving theme to %s...", path.c_str());
|
||||||
{
|
{
|
||||||
toml::table config;
|
toml::table config;
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
std::ostringstream stream;
|
||||||
|
#else
|
||||||
std::ofstream stream;
|
std::ofstream stream;
|
||||||
stream.open(path);
|
stream.open(path);
|
||||||
|
#endif
|
||||||
{
|
{
|
||||||
toml::table metadata;
|
toml::table metadata;
|
||||||
metadata.insert("SchemaVersion", 3);
|
metadata.insert("SchemaVersion", 3);
|
||||||
|
@ -442,7 +453,13 @@ void Theme::Save(string path) {
|
||||||
config.insert("colors", colors);
|
config.insert("colors", colors);
|
||||||
}
|
}
|
||||||
stream << config;
|
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();
|
stream.close();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
updateAvailableThemes();
|
updateAvailableThemes();
|
||||||
}
|
}
|
||||||
|
@ -450,10 +467,38 @@ void Theme::Save(path path) {
|
||||||
Save((string)path.string());
|
Save((string)path.string());
|
||||||
}
|
}
|
||||||
void Theme::updateAvailableThemes() {
|
void Theme::updateAvailableThemes() {
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
themeDir = "theme";
|
||||||
|
#else
|
||||||
themeDir = path(prefPath) / path("themes");
|
themeDir = path(prefPath) / path("themes");
|
||||||
create_directories(themeDir);
|
create_directories(themeDir);
|
||||||
|
#endif
|
||||||
availableThemes.clear();
|
availableThemes.clear();
|
||||||
themeStrings.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)) {
|
for (auto const& dir_entry : directory_iterator(themeDir)) {
|
||||||
if (dir_entry.is_regular_file()) {
|
if (dir_entry.is_regular_file()) {
|
||||||
if (dir_entry.path().extension().string() == ".json") {
|
if (dir_entry.path().extension().string() == ".json") {
|
||||||
|
@ -463,6 +508,7 @@ void Theme::updateAvailableThemes() {
|
||||||
toml::table config = toml::parse_file(curpath);
|
toml::table config = toml::parse_file(curpath);
|
||||||
themeStrings[curpath] = ThemeStrings(config);
|
themeStrings[curpath] = ThemeStrings(config);
|
||||||
} else if (dir_entry.path().extension().string() == ".toml") {
|
} else if (dir_entry.path().extension().string() == ".toml") {
|
||||||
|
|
||||||
string curpath = dir_entry.path().string();
|
string curpath = dir_entry.path().string();
|
||||||
availableThemes.insert(curpath);
|
availableThemes.insert(curpath);
|
||||||
toml::table config = toml::parse_file(curpath);
|
toml::table config = toml::parse_file(curpath);
|
||||||
|
@ -470,6 +516,7 @@ void Theme::updateAvailableThemes() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
Theme::Theme() {
|
Theme::Theme() {
|
||||||
if (prefPath == NULL) {
|
if (prefPath == NULL) {
|
||||||
|
@ -645,7 +692,17 @@ Theme::Theme(string path) : Theme() {
|
||||||
path = Migrate(path);
|
path = Migrate(path);
|
||||||
INFO.writefln("Loading theme '%s'...", path.c_str());
|
INFO.writefln("Loading theme '%s'...", path.c_str());
|
||||||
toml::table config;
|
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);
|
config = toml::parse_file(path);
|
||||||
|
#endif
|
||||||
if (config.contains("meta")) {
|
if (config.contains("meta")) {
|
||||||
toml::table metadata = *config["meta"].as_table();
|
toml::table metadata = *config["meta"].as_table();
|
||||||
//metadata["SchemaVersion"] = 1;
|
//metadata["SchemaVersion"] = 1;
|
||||||
|
|
|
@ -56,7 +56,7 @@ class Theme {
|
||||||
std::map<int, AccentColorizer> AccentColorizers;
|
std::map<int, AccentColorizer> AccentColorizers;
|
||||||
ThemeStrings GetStrings();
|
ThemeStrings GetStrings();
|
||||||
static bool ShowEditor(bool *open, Theme* &theme, ImGuiID dockid, int window_width, int window_height);
|
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(string path);
|
||||||
void Save(path path);
|
void Save(path path);
|
||||||
Theme();
|
Theme();
|
||||||
|
|
|
@ -7,10 +7,10 @@ import json
|
||||||
olddir = os.curdir
|
olddir = os.curdir
|
||||||
scriptdir = path.realpath(path.dirname(__file__))
|
scriptdir = path.realpath(path.dirname(__file__))
|
||||||
os.chdir(scriptdir)
|
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_dir = path.join(scriptdir, "backends", "ui")
|
||||||
ui_backend_metafiles = []
|
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_backend_metafiles += [ui_backend_dir + "/" + backend + "/ui.json"]
|
||||||
ui_backends = []
|
ui_backends = []
|
||||||
for metafile_path in ui_backend_metafiles:
|
for metafile_path in ui_backend_metafiles:
|
||||||
|
|
7
log.cpp
7
log.cpp
|
@ -6,7 +6,12 @@
|
||||||
#endif
|
#endif
|
||||||
namespace Looper::Log {
|
namespace Looper::Log {
|
||||||
std::set<FILE*> LogStream::global_outputs;
|
std::set<FILE*> LogStream::global_outputs;
|
||||||
int LogStream::log_level = 0;
|
int LogStream::log_level =
|
||||||
|
#ifdef DEBUG_MODE
|
||||||
|
-2;
|
||||||
|
#else
|
||||||
|
0;
|
||||||
|
#endif
|
||||||
std::set<FILE*> LogStream::get_used_outputs() {
|
std::set<FILE*> LogStream::get_used_outputs() {
|
||||||
std::set<FILE*> used_outputs;
|
std::set<FILE*> used_outputs;
|
||||||
for (auto my_output : outputs) {
|
for (auto my_output : outputs) {
|
||||||
|
|
1
log.hpp
1
log.hpp
|
@ -3,6 +3,7 @@
|
||||||
#include <streambuf>
|
#include <streambuf>
|
||||||
#include <ostream>
|
#include <ostream>
|
||||||
#include <set>
|
#include <set>
|
||||||
|
#include <config.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <variant>
|
#include <variant>
|
||||||
#ifdef __ANDROID__
|
#ifdef __ANDROID__
|
||||||
|
|
30
options.cpp
30
options.cpp
|
@ -1,22 +1,42 @@
|
||||||
#include "thirdparty/toml.hpp"
|
#include "thirdparty/toml.hpp"
|
||||||
#include "util.hpp"
|
#include "util.hpp"
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
#include <sstream>
|
||||||
#ifdef __ANDROID__
|
#ifdef __ANDROID__
|
||||||
#include <SDL.h>
|
#include <SDL.h>
|
||||||
#endif
|
#endif
|
||||||
|
#include "web_functions.hpp"
|
||||||
using namespace std::filesystem;
|
using namespace std::filesystem;
|
||||||
namespace Looper::Options {
|
namespace Looper::Options {
|
||||||
toml::table *options;
|
toml::table *options;
|
||||||
toml::table opts_value;
|
toml::table opts_value;
|
||||||
std::string get_options_path() {
|
std::string get_options_path() {
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
return "config_toml";
|
||||||
|
#else
|
||||||
path prefs_path(get_prefs_path());
|
path prefs_path(get_prefs_path());
|
||||||
prefs_path /= path("looper");
|
prefs_path /= path("looper");
|
||||||
create_directories(prefs_path);
|
create_directories(prefs_path);
|
||||||
path config_file = prefs_path / path("config.toml");
|
path config_file = prefs_path / path("config.toml");
|
||||||
return config_file.string();
|
return config_file.string();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
void load_options() {
|
void load_options() {
|
||||||
std::string config_path = get_options_path();
|
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)) {
|
if (exists(config_path)) {
|
||||||
auto res = toml::parse_file(config_path);
|
auto res = toml::parse_file(config_path);
|
||||||
opts_value = res;
|
opts_value = res;
|
||||||
|
@ -27,10 +47,20 @@ namespace Looper::Options {
|
||||||
} else {
|
} else {
|
||||||
options = new toml::table();
|
options = new toml::table();
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
void save_options() {
|
void save_options() {
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
std::ostringstream output;
|
||||||
|
#else
|
||||||
std::ofstream output(get_options_path());
|
std::ofstream output(get_options_path());
|
||||||
|
#endif
|
||||||
output << *options;
|
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();
|
output.close();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -77,6 +77,7 @@ namespace Looper::Options {
|
||||||
path = parent_path;
|
path = parent_path;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
save_options();
|
||||||
}
|
}
|
||||||
template<class T>
|
template<class T>
|
||||||
void set_option(std::string name, T value) {
|
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]");
|
DEBUG.writefln(".%s%s", last_component.c_str(), tmp->contains(last_component) ? "[set]" : "[new]");
|
||||||
tmp->insert_or_assign(last_component, value);
|
tmp->insert_or_assign(last_component, value);
|
||||||
|
save_options();
|
||||||
}
|
}
|
||||||
template<class T>
|
template<class T>
|
||||||
inline void init_option(std::string name, T value) {
|
inline void init_option(std::string name, T value) {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#include "translation.hpp"
|
#include "translation.hpp"
|
||||||
#include <libintl.h>
|
#include <libintl.h>
|
||||||
|
#include <locale.h>
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
static const char *orig_language;
|
static const char *orig_language;
|
||||||
static const char *cur_locale_dir;
|
static const char *cur_locale_dir;
|
||||||
|
|
2
util.hpp
2
util.hpp
|
@ -55,3 +55,5 @@ inline size_t combine_hashes(std::initializer_list<size_t> hashes) {
|
||||||
}
|
}
|
||||||
return std::hash<std::string>()(values);
|
return std::hash<std::string>()(values);
|
||||||
}
|
}
|
||||||
|
#define MIN(x, y) ((x < y) ? x : y)
|
||||||
|
#define MAX(x, y) ((x > y) ? x : y)
|
126
web/api.js
126
web/api.js
|
@ -44,12 +44,132 @@ addToLibrary({
|
||||||
setValue(y, canvas.offsetHeight, "i32");
|
setValue(y, canvas.offsetHeight, "i32");
|
||||||
},
|
},
|
||||||
is_puter_enabled: function() {
|
is_puter_enabled: function() {
|
||||||
return window.filePicker.puterEnabled;
|
return window.puterEnabled;
|
||||||
},
|
},
|
||||||
enable_puter: function(enable) {
|
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() {
|
quit: function() {
|
||||||
puter.ui.exit();
|
puter.exit();
|
||||||
}
|
}
|
||||||
})
|
})
|
|
@ -67,7 +67,7 @@
|
||||||
</canvas>
|
</canvas>
|
||||||
<input type="file" value="" hidden id="file-picker">
|
<input type="file" value="" hidden id="file-picker">
|
||||||
<script type="text/javascript" src="https://js.puter.com/v2/"></script>
|
<script type="text/javascript" src="https://js.puter.com/v2/"></script>
|
||||||
<script type='text/javascript' src="/shell.js"></script>
|
<script type='text/javascript' src="shell.js"></script>
|
||||||
<script>
|
<script>
|
||||||
var Module = {
|
var Module = {
|
||||||
print: (function() {
|
print: (function() {
|
||||||
|
|
139
web/shell.js
139
web/shell.js
|
@ -59,7 +59,7 @@ class FilePicker {
|
||||||
this.closed = false;
|
this.closed = false;
|
||||||
this.cancelled = false;
|
this.cancelled = false;
|
||||||
this.value = "";
|
this.value = "";
|
||||||
if (this.puterEnabled) {
|
if (window.puterEnabled) {
|
||||||
puter.ui.showOpenFilePicker({"accept": this.el.getAttribute("accept"), "multiple": false}).then(this.openPuterFile)
|
puter.ui.showOpenFilePicker({"accept": this.el.getAttribute("accept"), "multiple": false}).then(this.openPuterFile)
|
||||||
} else {
|
} else {
|
||||||
this.el.click();
|
this.el.click();
|
||||||
|
@ -134,7 +134,7 @@ class FilePicker {
|
||||||
}
|
}
|
||||||
constructor() {
|
constructor() {
|
||||||
if (puter.auth.isSignedIn()) {
|
if (puter.auth.isSignedIn()) {
|
||||||
this.puterEnabled = true;
|
window.puterEnabled = true;
|
||||||
}
|
}
|
||||||
this.el = document.getElementById("file-picker")
|
this.el = document.getElementById("file-picker")
|
||||||
this.el.addEventListener("cancel", () => {
|
this.el.addEventListener("cancel", () => {
|
||||||
|
@ -203,4 +203,137 @@ class FilePicker {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
window.filePicker = new FilePicker()
|
window.puterEnabled = false;
|
||||||
|
window.filePicker = new FilePicker()/*
|
||||||
|
MIT License
|
||||||
|
Copyright (c) 2020 Egor Nepomnyaschih
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
// This constant can also be computed with the following algorithm:
|
||||||
|
const base64abc = [],
|
||||||
|
A = "A".charCodeAt(0),
|
||||||
|
a = "a".charCodeAt(0),
|
||||||
|
n = "0".charCodeAt(0);
|
||||||
|
for (let i = 0; i < 26; ++i) {
|
||||||
|
base64abc.push(String.fromCharCode(A + i));
|
||||||
|
}
|
||||||
|
for (let i = 0; i < 26; ++i) {
|
||||||
|
base64abc.push(String.fromCharCode(a + i));
|
||||||
|
}
|
||||||
|
for (let i = 0; i < 10; ++i) {
|
||||||
|
base64abc.push(String.fromCharCode(n + i));
|
||||||
|
}
|
||||||
|
base64abc.push("+");
|
||||||
|
base64abc.push("/");
|
||||||
|
*/
|
||||||
|
const base64abc = [
|
||||||
|
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
|
||||||
|
"N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
|
||||||
|
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
|
||||||
|
"n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
|
||||||
|
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "+", "/"
|
||||||
|
];
|
||||||
|
|
||||||
|
/*
|
||||||
|
// This constant can also be computed with the following algorithm:
|
||||||
|
const l = 256, base64codes = new Uint8Array(l);
|
||||||
|
for (let i = 0; i < l; ++i) {
|
||||||
|
base64codes[i] = 255; // invalid character
|
||||||
|
}
|
||||||
|
base64abc.forEach((char, index) => {
|
||||||
|
base64codes[char.charCodeAt(0)] = index;
|
||||||
|
});
|
||||||
|
base64codes["=".charCodeAt(0)] = 0; // ignored anyway, so we just need to prevent an error
|
||||||
|
*/
|
||||||
|
const base64codes = [
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 62, 255, 255, 255, 63,
|
||||||
|
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255, 255, 0, 255, 255,
|
||||||
|
255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
||||||
|
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 255, 255, 255, 255, 255,
|
||||||
|
255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
|
||||||
|
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
|
||||||
|
];
|
||||||
|
|
||||||
|
function getBase64Code(charCode) {
|
||||||
|
if (charCode >= base64codes.length) {
|
||||||
|
throw new Error("Unable to parse base64 string.");
|
||||||
|
}
|
||||||
|
const code = base64codes[charCode];
|
||||||
|
if (code === 255) {
|
||||||
|
throw new Error("Unable to parse base64 string.");
|
||||||
|
}
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.bytesToBase64 = function(bytes) {
|
||||||
|
let result = '', i, l = bytes.length;
|
||||||
|
for (i = 2; i < l; i += 3) {
|
||||||
|
result += base64abc[bytes[i - 2] >> 2];
|
||||||
|
result += base64abc[((bytes[i - 2] & 0x03) << 4) | (bytes[i - 1] >> 4)];
|
||||||
|
result += base64abc[((bytes[i - 1] & 0x0F) << 2) | (bytes[i] >> 6)];
|
||||||
|
result += base64abc[bytes[i] & 0x3F];
|
||||||
|
}
|
||||||
|
if (i === l + 1) { // 1 octet yet to write
|
||||||
|
result += base64abc[bytes[i - 2] >> 2];
|
||||||
|
result += base64abc[(bytes[i - 2] & 0x03) << 4];
|
||||||
|
result += "==";
|
||||||
|
}
|
||||||
|
if (i === l) { // 2 octets yet to write
|
||||||
|
result += base64abc[bytes[i - 2] >> 2];
|
||||||
|
result += base64abc[((bytes[i - 2] & 0x03) << 4) | (bytes[i - 1] >> 4)];
|
||||||
|
result += base64abc[(bytes[i - 1] & 0x0F) << 2];
|
||||||
|
result += "=";
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.base64ToBytes = function(str) {
|
||||||
|
if (str.length % 4 !== 0) {
|
||||||
|
throw new Error("Unable to parse base64 string.");
|
||||||
|
}
|
||||||
|
const index = str.indexOf("=");
|
||||||
|
if (index !== -1 && index < str.length - 2) {
|
||||||
|
throw new Error("Unable to parse base64 string.");
|
||||||
|
}
|
||||||
|
let missingOctets = str.endsWith("==") ? 2 : str.endsWith("=") ? 1 : 0,
|
||||||
|
n = str.length,
|
||||||
|
result = new Uint8Array(3 * (n / 4)),
|
||||||
|
buffer;
|
||||||
|
for (let i = 0, j = 0; i < n; i += 4, j += 3) {
|
||||||
|
buffer =
|
||||||
|
getBase64Code(str.charCodeAt(i)) << 18 |
|
||||||
|
getBase64Code(str.charCodeAt(i + 1)) << 12 |
|
||||||
|
getBase64Code(str.charCodeAt(i + 2)) << 6 |
|
||||||
|
getBase64Code(str.charCodeAt(i + 3));
|
||||||
|
result[j] = buffer >> 16;
|
||||||
|
result[j + 1] = (buffer >> 8) & 0xFF;
|
||||||
|
result[j + 2] = buffer & 0xFF;
|
||||||
|
}
|
||||||
|
return result.subarray(0, result.length - missingOctets);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.base64encode = function(str, encoder = new TextEncoder()) {
|
||||||
|
return bytesToBase64(encoder.encode(str));
|
||||||
|
}
|
||||||
|
|
||||||
|
window.base64decode = function(str, decoder = new TextDecoder()) {
|
||||||
|
return decoder.decode(base64ToBytes(str));
|
||||||
|
}
|
18
web_functions.hpp
Normal file
18
web_functions.hpp
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
#pragma once
|
||||||
|
#ifdef __EMSCRIPTEN__
|
||||||
|
extern "C" {
|
||||||
|
extern void write_storage(const char *key, const char *value, int len);
|
||||||
|
extern void remove_storage(const char *key);
|
||||||
|
extern void read_storage(const char *key, const char **value, int *len);
|
||||||
|
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();
|
||||||
|
extern void find_keys(const char *prefix, const char ***output);
|
||||||
|
}
|
||||||
|
#endif
|
Loading…
Reference in a new issue