Various improvements.

This commit is contained in:
Zachary Hall 2024-05-01 09:07:08 -07:00
parent 6159f52e8a
commit a1fd9e1427
23 changed files with 550 additions and 73 deletions

View file

@ -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()

View file

@ -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>();
}

View file

@ -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) {

View file

@ -32,8 +32,13 @@ void MySlider::update(bool force) {
classes.push_back("hexpand");
}
set_css_classes(classes);
update_log_data();
if (min_value <= max_value) {
slider.set_range(min_value, max_value);
if (logarithmic) {
slider.set_range(log_data.x0, log_data.x1);
} else {
slider.set_range(min_value, max_value);
}
}
prefix_label.set_text(prefix);
value_label.set_text(value_str.c_str());
@ -49,6 +54,20 @@ void MySlider::update(bool force) {
}
update_occurring = false;
}
double MySlider::scale_log(double input) {
return log_data.a + (log_data.b * log(value));
}
void MySlider::update_log_data() {
log_data.xmin = min_value;
log_data.xmax = max_value;
log_data.x0 = log(log_data.xmin);
log_data.x1 = log(log_data.xmax);
log_data.b = (log_data.x1 - log_data.x0) / log(log_data.xmax - log_data.xmin);
log_data.a = log_data.x0 - (log_data.b * log_data.x0);
}
double MySlider::unscale_log(double input) {
return (exp(value) / log_data.b) - log_data.a;
}
void MySlider::set_value(double value, bool force) {
this->value = value;
update(force);
@ -78,19 +97,23 @@ void MySlider::update_text_entry(std::string text) {
text_entry.insert_text(text.c_str(), text.size(), pos);
}
void MySlider::update_slider(double value) {
slider.set_value(value);
if (logarithmic) {
slider.set_value(scale_log(value));
}
}
double MySlider::get_min_value() {
return min_value;
}
void MySlider::set_min_value(double value) {
min_value = value;
update_log_data();
}
double MySlider::get_max_value() {
return max_value;
}
void MySlider::set_max_value(double value) {
max_value = value;
update_log_data();
}
MySlider::MySlider() {
min_value = 0;
@ -138,7 +161,11 @@ MySlider::MySlider() {
update();
});
slider.signal_value_changed().connect([this]() {
value = slider.get_value();
if (get_logarithmic()) {
value = unscale_log(slider.get_value());
} else {
value = slider.get_value();
}
value_changed.emit(value);
update();
});

View file

@ -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();

View file

@ -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)

View file

@ -38,6 +38,9 @@ void RendererBackend::resize_static() {
renderer_backend->resize_needed = true;
}
void main_loop() {
if (!renderer_backend->started) {
return;
}
renderer_backend->LoopFunction();
#ifdef __EMSCRIPTEN__
if (renderer_backend->done) {
@ -91,8 +94,17 @@ void RendererBackend::BackendDeinit() {
bool RendererBackend::isTouchScreenMode() {
return touchScreenModeOverride.value_or(SDL_GetNumTouchDevices() > 0);
}
void RendererBackend::QueueUpdateScale() {
update_scale = true;
}
void RendererBackend::LoopFunction() {
if (update_scale) {
UpdateScale();
update_scale = false;
}
#ifndef __EMSCRIPTEN__
SDL_RenderSetVSync(rend, vsync ? 1 : 0);
#endif
ImGuiIO& io = ImGui::GetIO(); (void)io;
if (resize_needed) {
on_resize();
@ -214,21 +226,29 @@ void RendererBackend::UpdateScale() {
#else
96.0;
#endif
#ifdef __EMSCRIPTEN__
scale = get_dpi() / defaultDPI;
#else
float dpi = defaultDPI;
if (SDL_GetDisplayDPI(SDL_GetWindowDisplayIndex(window), NULL, &dpi, NULL) == 0){
scale = dpi / defaultDPI;
if (scaleOverride.has_value()) {
scale = scaleOverride.value();
} else {
WARNING.writeln("DPI couldn't be determined!");
scale = 1.0;
}
#ifdef __EMSCRIPTEN__
scale = get_dpi();
#else
float dpi = defaultDPI;
if (SDL_GetDisplayDPI(SDL_GetWindowDisplayIndex(window), NULL, &dpi, NULL) == 0){
scale = dpi / defaultDPI;
} else {
WARNING.writeln("DPI couldn't be determined!");
scale = 1.0;
}
#ifndef __ANDROID__
SDL_SetWindowSize(window, window_width * scale, window_height * scale);
SDL_SetWindowSize(window, window_width * scale, window_height * scale);
#endif
#endif
}
AddFonts();
OnScale((float)scale);
}
void RendererBackend::OnScale(float scale) {
theme->Apply(accent_color, scale);
}
void RendererBackend::SetWindowSize(int w, int h) {
#if !(defined(__ANDROID__) || defined(__EMSCRIPTEN__))
@ -269,9 +289,12 @@ void RendererBackend::AddFonts() {
#ifdef __EMSCRIPTEN__
static EM_BOOL resize_callback(int event_type, const EmscriptenUiEvent *event, void *userdata) {
RendererBackend::resize_static();
return EM_FALSE;
}
#endif
int RendererBackend::Run() {
started = false;
renderer_backend = this;
setup_locale("neko_player");
DEBUG.writefln("Loaded locale '%s' from '%s'...", CURRENT_LANGUAGE, LOCALE_DIR);
DEBUG.writefln("Locale name: %s", _TR_CTX("Language name", "English (United States)"));
@ -299,7 +322,10 @@ int RendererBackend::Run() {
#ifdef SDL_HINT_IME_SHOW_UI
SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1");
#endif
#ifdef __EMSCRIPTEN__
emscripten_set_main_loop(&main_loop, 0, 0);
emscripten_pause_main_loop();
#endif
SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_HIDDEN);
SDL_CreateWindowAndRenderer(window_width, window_height, window_flags, &window, &rend);
@ -352,10 +378,10 @@ int RendererBackend::Run() {
//io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f);
//ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, nullptr, io.Fonts->GetGlyphRangesJapanese());
//IM_ASSERT(font != nullptr);
UpdateScale();
theme = new Theme(false);
UpdateScale();
#ifdef __ANDROID__
userdir = SDL_AndroidGetExternalStoragePath();
#else
@ -367,18 +393,23 @@ int RendererBackend::Run() {
#endif
);
#endif
#ifndef __EMSCRIPTEN__
SDL_RenderSetVSync(rend, vsync ? 1 : 0);
theme->Apply(accent_color);
#endif
theme->Apply(accent_color, (float)scale);
Init();
SDL_ShowWindow(window);
renderer_backend = this;
#ifdef __EMSCRIPTEN__
// For an Emscripten build we are disabling file-system access, so let's not attempt to do a fopen() of the imgui.ini file.
// You may manually call LoadIniSettingsFromMemory() to load settings from your own storage.
io.IniFilename = nullptr;
emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, 0, resize_callback);
emscripten_set_main_loop(&main_loop, 0, 1);
emscripten_resume_main_loop();
emscripten_set_main_loop_timing(1, 0);
started = true;
emscripten_exit_with_live_runtime();
#else
started = true;
while (!done)
{
LoopFunction();

View file

@ -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);

View file

@ -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();

View file

@ -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);

View file

@ -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,12 +353,14 @@ void AccentColorizer::Colorize(ImVec4 accent, ImVec4 &color) {
if (Alpha)
color.w *= accent.w;
}
void Theme::Apply(ImVec4 accent) {
void Theme::Apply(ImVec4 accent, float scale) {
ImGuiStyle& actual_style = ImGui::GetStyle();
actual_style = style;
for (int i = 0; i < ImGuiCol_COUNT; i++)
{
AccentColorizers[i].Colorize(accent, actual_style.Colors[i]);
if (AccentColorizers.contains(i)) {
AccentColorizers[i].Colorize(accent, actual_style.Colors[i]);
}
}
ImGuiIO& io = ImGui::GetIO();
if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
@ -364,13 +368,20 @@ void Theme::Apply(ImVec4 accent) {
actual_style.WindowRounding = 0.0f;
actual_style.Colors[ImGuiCol_WindowBg].w = 1.0f;
}
actual_style.ScaleAllSizes(scale);
actual_style.WindowMinSize.x = MAX(actual_style.WindowMinSize.x, 1.0);
actual_style.WindowMinSize.y = MAX(actual_style.WindowMinSize.y, 1.0);
}
void Theme::Save(string path) {
INFO.writefln("Saving theme to %s...", path.c_str());
{
toml::table config;
#ifdef __EMSCRIPTEN__
std::ostringstream stream;
#else
std::ofstream stream;
stream.open(path);
#endif
{
toml::table metadata;
metadata.insert("SchemaVersion", 3);
@ -442,7 +453,13 @@ void Theme::Save(string path) {
config.insert("colors", colors);
}
stream << config;
#ifdef __EMSCRIPTEN__
path = path;
std::string str = stream.str();
write_storage(path.c_str(), str.c_str(), str.length() + 1);
#else
stream.close();
#endif
}
updateAvailableThemes();
}
@ -450,10 +467,38 @@ void Theme::Save(path path) {
Save((string)path.string());
}
void Theme::updateAvailableThemes() {
#ifdef __EMSCRIPTEN__
themeDir = "theme";
#else
themeDir = path(prefPath) / path("themes");
create_directories(themeDir);
#endif
availableThemes.clear();
themeStrings.clear();
#ifdef __EMSCRIPTEN__
const char **theme_files = nullptr;
find_keys("theme/", &theme_files);
if (theme_files[0] != nullptr) {
for (size_t i = 0; theme_files[i] != nullptr; i++) {
const char *dir_entry = theme_files[i];
if (dir_entry == nullptr) {
break;
}
string curpath = dir_entry;
const char *theme_contents = nullptr;
read_storage(dir_entry, &theme_contents, nullptr);
if (theme_contents == nullptr) {
continue;
}
availableThemes.insert(curpath);
toml::table config = toml::parse(theme_contents);
free((void*)theme_contents);
themeStrings[curpath] = ThemeStrings(config);
free((void*)dir_entry);
}
}
free((void*)theme_files);
#else
for (auto const& dir_entry : directory_iterator(themeDir)) {
if (dir_entry.is_regular_file()) {
if (dir_entry.path().extension().string() == ".json") {
@ -463,13 +508,15 @@ void Theme::updateAvailableThemes() {
toml::table config = toml::parse_file(curpath);
themeStrings[curpath] = ThemeStrings(config);
} else if (dir_entry.path().extension().string() == ".toml") {
string curpath = dir_entry.path().string();
availableThemes.insert(curpath);
toml::table config = toml::parse_file(curpath);
themeStrings[curpath] = ThemeStrings(config);
}
}
}
}
#endif
}
Theme::Theme() {
if (prefPath == NULL) {
@ -645,7 +692,17 @@ Theme::Theme(string path) : Theme() {
path = Migrate(path);
INFO.writefln("Loading theme '%s'...", path.c_str());
toml::table config;
#ifdef __EMSCRIPTEN__
const char *contents = nullptr;
read_storage(path.c_str(), &contents, nullptr);
if (contents == nullptr) {
throw std::exception();
}
config = toml::parse(contents);
free((void*)contents);
#else
config = toml::parse_file(path);
#endif
if (config.contains("meta")) {
toml::table metadata = *config["meta"].as_table();
//metadata["SchemaVersion"] = 1;

View file

@ -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();

View file

@ -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:

View file

@ -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) {

View file

@ -3,6 +3,7 @@
#include <streambuf>
#include <ostream>
#include <set>
#include <config.h>
#include <vector>
#include <variant>
#ifdef __ANDROID__

View file

@ -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
}
}

View file

@ -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) {

View file

@ -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;

View file

@ -54,4 +54,6 @@ inline size_t combine_hashes(std::initializer_list<size_t> hashes) {
values += ";";
}
return std::hash<std::string>()(values);
}
}
#define MIN(x, y) ((x < y) ? x : y)
#define MAX(x, y) ((x > y) ? x : y)

View file

@ -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();
}
})

View file

@ -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() {

View file

@ -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
View 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