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(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()
|
||||
|
|
|
@ -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_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) {
|
||||
|
|
|
@ -32,9 +32,14 @@ void MySlider::update(bool force) {
|
|||
classes.push_back("hexpand");
|
||||
}
|
||||
set_css_classes(classes);
|
||||
update_log_data();
|
||||
if (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());
|
||||
suffix_label.set_text(suffix);
|
||||
|
@ -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]() {
|
||||
if (get_logarithmic()) {
|
||||
value = unscale_log(slider.get_value());
|
||||
} else {
|
||||
value = slider.get_value();
|
||||
}
|
||||
value_changed.emit(value);
|
||||
update();
|
||||
});
|
||||
|
|
|
@ -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<void(double)> 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();
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,8 +226,11 @@ void RendererBackend::UpdateScale() {
|
|||
#else
|
||||
96.0;
|
||||
#endif
|
||||
if (scaleOverride.has_value()) {
|
||||
scale = scaleOverride.value();
|
||||
} else {
|
||||
#ifdef __EMSCRIPTEN__
|
||||
scale = get_dpi() / defaultDPI;
|
||||
scale = get_dpi();
|
||||
#else
|
||||
float dpi = defaultDPI;
|
||||
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);
|
||||
#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);
|
||||
theme = new Theme(false);
|
||||
|
||||
UpdateScale();
|
||||
|
||||
|
||||
theme = new Theme(false);
|
||||
#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();
|
||||
|
|
|
@ -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<bool> touchScreenModeOverride;
|
||||
std::optional<double> 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);
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include <cctype>
|
||||
#include <stdio.h>
|
||||
#include <log.hpp>
|
||||
#include <web_functions.hpp>
|
||||
#ifdef __ANDROID__
|
||||
#include <jni.h>
|
||||
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();
|
||||
|
|
|
@ -77,10 +77,20 @@ void MainLoop::Init() {
|
|||
{
|
||||
std::string themeName = get_option<std::string>("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<std::string>("ui.imgui.lang");
|
||||
} 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.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 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<double>("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<bool>("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<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))) {
|
||||
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<std::string>("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<std::string>("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<std::string>("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<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();
|
||||
}
|
||||
|
@ -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<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.s", accent_color.y);
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
#include "IconsForkAwesome.h"
|
||||
#include "imgui_stdlib.h"
|
||||
#include <log.hpp>
|
||||
#include <util.hpp>
|
||||
#include <web_functions.hpp>
|
||||
|
||||
using namespace std::filesystem;
|
||||
const char* Theme::prefPath = NULL;
|
||||
|
@ -351,26 +353,35 @@ 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++)
|
||||
{
|
||||
if (AccentColorizers.contains(i)) {
|
||||
AccentColorizers[i].Colorize(accent, actual_style.Colors[i]);
|
||||
}
|
||||
}
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
|
||||
{
|
||||
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,6 +508,7 @@ 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);
|
||||
|
@ -470,6 +516,7 @@ void Theme::updateAvailableThemes() {
|
|||
}
|
||||
}
|
||||
}
|
||||
#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;
|
||||
|
|
|
@ -56,7 +56,7 @@ class Theme {
|
|||
std::map<int, AccentColorizer> 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();
|
||||
|
|
|
@ -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:
|
||||
|
|
7
log.cpp
7
log.cpp
|
@ -6,7 +6,12 @@
|
|||
#endif
|
||||
namespace Looper::Log {
|
||||
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*> used_outputs;
|
||||
for (auto my_output : outputs) {
|
||||
|
|
1
log.hpp
1
log.hpp
|
@ -3,6 +3,7 @@
|
|||
#include <streambuf>
|
||||
#include <ostream>
|
||||
#include <set>
|
||||
#include <config.h>
|
||||
#include <vector>
|
||||
#include <variant>
|
||||
#ifdef __ANDROID__
|
||||
|
|
30
options.cpp
30
options.cpp
|
@ -1,22 +1,42 @@
|
|||
#include "thirdparty/toml.hpp"
|
||||
#include "util.hpp"
|
||||
#include <filesystem>
|
||||
#include <sstream>
|
||||
#ifdef __ANDROID__
|
||||
#include <SDL.h>
|
||||
#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
|
||||
}
|
||||
}
|
|
@ -77,6 +77,7 @@ namespace Looper::Options {
|
|||
path = parent_path;
|
||||
}
|
||||
}
|
||||
save_options();
|
||||
}
|
||||
template<class T>
|
||||
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<class T>
|
||||
inline void init_option(std::string name, T value) {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include "translation.hpp"
|
||||
#include <libintl.h>
|
||||
#include <locale.h>
|
||||
#include "config.h"
|
||||
static const char *orig_language;
|
||||
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);
|
||||
}
|
||||
#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");
|
||||
},
|
||||
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();
|
||||
}
|
||||
})
|
|
@ -67,7 +67,7 @@
|
|||
</canvas>
|
||||
<input type="file" value="" hidden id="file-picker">
|
||||
<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>
|
||||
var Module = {
|
||||
print: (function() {
|
||||
|
|
139
web/shell.js
139
web/shell.js
|
@ -59,7 +59,7 @@ class FilePicker {
|
|||
this.closed = false;
|
||||
this.cancelled = false;
|
||||
this.value = "";
|
||||
if (this.puterEnabled) {
|
||||
if (window.puterEnabled) {
|
||||
puter.ui.showOpenFilePicker({"accept": this.el.getAttribute("accept"), "multiple": false}).then(this.openPuterFile)
|
||||
} else {
|
||||
this.el.click();
|
||||
|
@ -134,7 +134,7 @@ class FilePicker {
|
|||
}
|
||||
constructor() {
|
||||
if (puter.auth.isSignedIn()) {
|
||||
this.puterEnabled = true;
|
||||
window.puterEnabled = true;
|
||||
}
|
||||
this.el = document.getElementById("file-picker")
|
||||
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