Add cat support
Some checks failed
Build / build-gentoo (push) Failing after 1m11s
Build / download-system-deps (push) Successful in 3m44s
Build / get-source-code (push) Successful in 13m23s
Build / build-deb (push) Successful in 11m40s
Build / build-appimage (push) Successful in 4m53s
Build / build-android (push) Failing after 3m19s
Build / build-windows (push) Failing after 8m9s

This commit is contained in:
Zachary Hall 2024-12-21 14:23:00 -08:00
parent d6f440c8d4
commit 9899794b81
18 changed files with 499 additions and 22 deletions

View file

@ -44,7 +44,7 @@ def add_font(input: str, output: str|None = None):
def add_graphic(input: str):
GRAPHIC=path.basename(input).removesuffix(".png").lower().replace('-', '_')
print("Adding graphic '%s' from file '%s'" % (GRAPHIC, input))
add_base85(input, GRAPHIC)
add_basic(input, GRAPHIC)
def add_license(input: str, output: str):
LICENSE = output + "_license"
print("Adding license '%s' (C identifier: '%s') from file '%s'" % (output, LICENSE, input))
@ -72,6 +72,7 @@ for i in glob("Noto_Sans_JP/*.ttf"):
add_font(i)
add_font("ForkAwesome/fonts/forkawesome-webfont.ttf", "forkawesome")
add_graphic("icon.png")
add_graphic("catoc.png")
add_license("Noto_Sans/OFL.txt", "notosans")
add_license("Noto_Sans_JP/OFL.txt", "notosansjp")
add_license("../LICENSE.MIT", "looper_mit")

View file

@ -18,7 +18,9 @@
#include <fmt/core.h>
#include <fmt/format.h>
#include <LayoutItem.h>
#include <TranslationUtils.h>
#include <SDL.h>
#include <cats.hpp>
#include "icons.h"
#include "utils.h"
#include "aboutwindow.h"
@ -61,6 +63,22 @@ void HaikuLooperWindow::UpdateViewFlags(BLayout *layout) {
}
UpdateViewFlags(owner);
}
void HaikuLooperWindow::AddCat(std::string name, BBitmap *bitmap) {
if (bitmap == NULL) return;
if (cats.contains(name)) {
delete cats[name];
}
cats[name] = bitmap;
}
void HaikuLooperWindow::LoadCat(const char *path) {
return BTranslationUtils::LoadBitmap(path);
}
void HaikuLooperWindow::LoadCat(const void *ptr, size_t len, const char *name) {
BMemoryIO *io = new BMemoryIO(ptr, len);
BBitmap *output = BTranslationUtils::LoadBitmap(io);
delete io;
return output;
}
HaikuLooperWindow::HaikuLooperWindow(Playback *playback) : BWindow(BRect(100, 100, 500, 100), "Looper", B_TITLED_WINDOW, 0) {
pause_bitmap = load_icon(ICON_PAUSE);
resume_bitmap = load_icon(ICON_PLAY);
@ -94,7 +112,7 @@ HaikuLooperWindow::HaikuLooperWindow(Playback *playback) : BWindow(BRect(100, 10
menu_bar->AddItem(file_menu);
menu_bar->AddItem(help_menu);
layout->AddView(menu_bar);
BView *spacer = new BView("spacer", B_SUPPORTS_LAYOUT|B_FRAME_EVENTS);
spacer = new BView("spacer", B_SUPPORTS_LAYOUT|B_FRAME_EVENTS);
spacer->SetExplicitPreferredSize(BSize(0, 0));
spacer->SetExplicitMinSize(BSize(0, 0));
layout->AddView(spacer);
@ -161,6 +179,30 @@ HaikuLooperWindow::HaikuLooperWindow(Playback *playback) : BWindow(BRect(100, 10
MessageReceived(msg);
delete msg;
}
void HaikuLooperWindow::UpdateCat(BBitmap *cat) {
if (cat != NULL) {
auto bounds = cat->Bounds();
auto w = bounds.Width(), h = bounds.Height();
auto sb = spacer->Bounds();
auto sw = sb.Width(), sh = sb.Height();
BRect src(0, 0, w, h);
if (w > sw || h > sh) {
float aspect = w / h;
if (w > sw) {
w = sw;
h = sw / aspect;
}
if (h > sh) {
h = sh;
w = sh * aspect;
}
}
BRect dst(sw - w, sh - h, w, h);
spacer->SetOverlayBitmap(cat, src, dst, NULL, B_FOLLOW_RIGHT|B_FOLLOW_BOTTOM);
} else {
spacer->ClearOverlayBitmap();
}
}
HaikuLooperWindow::~HaikuLooperWindow() {
delete ref_handler;
delete pause_bitmap;
@ -245,6 +287,13 @@ void HaikuLooperWindow::MessageReceived(BMessage *msg) {
case CMD_PREFS: {
prefs_subwindow->Show();
} break;
case CMD_SET_CAT: {
BBitmap *cat = NULL;
if (msg->FindBitmap("cat", &cat) != B_OK) {
cat = NULL;
}
UpdateCat(cat);
}
case CMD_UPDATE_LABEL_SETTING: {
volume_slider->MessageReceived(msg);
speed_slider->MessageReceived(msg);

View file

@ -59,6 +59,8 @@ class HaikuLooperWindow : public BWindow {
BButton *restart_btn;
BButton *stop_btn;
BButton *pause_resume_btn;
BView *spacer;
void UpdateCat(BBitmap *cat);
bool volume_clicked = false;
bool speed_clicked = false;
bool pitch_clicked = false;

View file

@ -13,10 +13,25 @@ using namespace Looper::Options;
#define CMD_SET_SETTING 0x1002
#define CMD_REVERT 0x1003
#define CMD_APPLY 0x1004
#define CMD_SET_SETTING_CHECKBOX = 0x1005
bool show_icons, show_labels;
HaikuPrefsWindow::HaikuPrefsWindow(BLooper *next_handler) : BWindow(BRect(100, 100, 0, 0), "Preferences", B_TITLED_WINDOW, B_AUTO_UPDATE_SIZE_LIMITS) {
this->next_handler = next_handler;
for (auto &cat : get_cat_data()) {
switch (cat.get_type()) {
case CatDataType::Memory: {
MemoryCat mem_cat = cat.get_memory_cat();
AddCat(cat.get_name(), LoadCat(mem_cat.get_ptr(), mem_cat.get_len(), cat.get_path().c_str()));
} break;
case CatDataType::File: {
fs::path cat_path = cat.get_path();
AddCat(cat.get_name(), LoadCat(cat_path.c_str()));
} break;
}
}
auto *root_layout = new BGroupLayout(B_VERTICAL);
SetLayout(root_layout);
root_layout->SetSpacing(0.0);
@ -56,7 +71,35 @@ HaikuPrefsWindow::HaikuPrefsWindow(BLooper *next_handler) : BWindow(BRect(100, 1
label_settings_layout->AddView(labels_only);
label_settings_layout->AddView(icons_only);
label_settings_layout->AddView(both_labels_icons);
box->AddChild(label_settings_group);
BMessage *cat_enable_msg = new BMessage(CMD_SET_SETTING_CHECKBOX);
cat_enable_msg->AddString("pref_path", "ui.cat_enable");
cat_enable = new BCheckBox("Enable Cat", cat_enable_msg);
BGroupView *cat_group = new BGroupView(B_VERTICAL);
BGroupLayout *cat_layout = cat_group->GroupLayout();
for (auto &kv : cats) {
BMessage *msg = new BMessage(CMD_SET_SETTING);
msg->AddString("pref_path", "ui.cat");
msg->AddString("pref_value", kv.first.c_str());
BRadioButton *cat_radio = new BRadioButton(fmt::format("prefs:cat:{}", kv.first).c_str(), msg);
cat_btns[kv.first] = cat_radio;
BBitmap *cat_bmp = kv.second;
BRect src = cat_bmp->bounds();
float w = src.Width(), h = src.Height();
float rw = cat_radio->Bounds().Width(), rh = cat_radio->Bounds().Height();
if (w > rw || h > rh) {
float aspect = w / h;
if (h > rh) {
h = rh;
w = h * aspect;
}
if (w > rw) {
w = rw;
h = h / aspect;
}
}
BRect dst(rw - w, (rh - h) / 2, rw, rh);
cat_radio->SetOverlayBitmap(cat_bmp, src, dst, B_FOLLOW_RIGHT|B_FOLLOW_TOP_BOTTOM);
}
root_layout->AddView(box, 3.0);
BGroupView *btn_view = new BGroupView(B_HORIZONTAL);
BGroupLayout *btn_box = btn_view->GroupLayout();
@ -102,7 +145,12 @@ void HaikuPrefsWindow::MessageReceived(BMessage *msg) {
else restart_warning->Hide();
set_option<std::string>("ui.haiku.label_setting", new_label_setting);
update_label_setting();
set_option<std::string>("ui.cat", cat_id);
set_option<bool>("ui.enable_cat", enable_cat);
next_handler->PostMessage(CMD_UPDATE_LABEL_SETTING);
BMessage *cat_msg = new BMessage(CMD_SET_CAT);
if (enable_cat) cat_msg->AddBitmap("cat", cats[cat_id])
next_handler->PostMessage(cat_msg);
} break;
case CMD_REVERT: {
set_options_changed(false);
@ -117,8 +165,16 @@ void HaikuPrefsWindow::MessageReceived(BMessage *msg) {
break;
}
}
cat_btns[cat_id].SetValue(B_CONTROL_OFF);
cat_id = get_option<std::string>("ui.cat", cats.empty() ? "" : cats.begin()->first);
if (cat_btns.contains(cat_id)) cat_btns[cat_id].SetValue(B_CONTROL_ON);
enable_cat = get_option<bool>("ui.enable_cat", false);
cat_enable->SetValue(enable_cat ? B_CONTROL_ON : B_CONTROL_OFF);
update_label_setting();
next_handler->PostMessage(CMD_UPDATE_LABEL_SETTING);
BMessage *cat_msg = new BMessage(CMD_SET_CAT);
if (enable_cat) cat_msg->AddBitmap("cat", cats[cat_id])
next_handler->PostMessage(cat_msg);
} break;
case CMD_FRONTEND: {
auto option = msg->GetInt32("be:value", cur_option);
@ -136,6 +192,21 @@ void HaikuPrefsWindow::MessageReceived(BMessage *msg) {
if (std::string(setting) == "ui.haiku.label_setting") {
new_label_setting = setting_value;
set_options_changed(true);
} else if (std::string(setting) == "ui.cat") {
cat_id = setting_value;
set_options_changed(true);
}
}
}
} break;
case CMD_SET_SETTING_CHECKBOX: {
const char *setting;
if (msg->FindString("pref_path", &setting) == B_OK) {
int32 setting_value_int32;
if (msg->FindInt32("be:value", &setting_value_int32) == B_OK) {
if (std::string(setting) == "ui.enable_cat") {
enable_cat = setting_value_int32 == B_CONTROL_ON;
set_options_changed(true);
}
}
}

View file

@ -13,8 +13,17 @@
#include <vector>
#include <string>
#include <map>
#define CMD_SET_CAT 0x1010
class HaikuPrefsWindow : public BWindow {
std::vector<std::string> backend_ids;
std::string cat_id;
bool enable_cat;
std::map<std::string, BBitmap*> cats;
std::map<std::string, BRadioButton*> cat_btns;
BCheckBox *cat_enable;
void AddCat(std:string name, BBitmap* img);
BBitmap *LoadCat(const char *path);
BBitmap *LoadCat(const void *ptr, size_t len, const char *name);
int32 cur_option = 0;
BLooper *next_handler;
BStringView *restart_warning;

View file

@ -329,8 +329,7 @@ void RendererBackend::BackendInit() {
}
#endif
SDL_EventState(SDL_DROPFILE, SDL_ENABLE);
const vector<unsigned char> icon_data = DecodeBase85(icon_compressed_data_base85);
SDL_Surface* icon = IMG_Load_RW(SDL_RWFromConstMem(icon_data.data(), icon_data.size()), 1);
SDL_Surface* icon = IMG_Load_RW(SDL_RWFromConstMem(icon_data, icon_size), 1);
SDL_SetWindowIcon(window, icon);
// Setup Dear ImGui context
@ -403,7 +402,8 @@ int RendererBackend::Run() {
#else
try {
BackendInit();
} catch (std::exception) {
} catch (std::exception &e) {
ERROR.writefln("Error occurred during initialization: %s", e.what());
return -1;
}
started = true;

View file

@ -9,6 +9,7 @@
#include "thirdparty/CLI11.hpp"
#include "imgui/misc/cpp/imgui_stdlib.h"
#include <web_functions.hpp>
#include <cats.hpp>
using namespace Looper::Options;
void MainLoop::Init() {
#ifdef PORTALS
@ -28,6 +29,25 @@ void MainLoop::Init() {
theme_editor = false;
stopped = true;
about_window = false;
for (auto &cat : get_cat_data()) {
switch (cat.get_type()) {
case CatDataType::Memory: {
auto mem_cat = cat.get_memory_cat();
AddCat(cat.get_name(), LoadCatFromMemory(mem_cat.get_ptr(), mem_cat.get_len(), cat.get_path().c_str()));
} break;
case CatDataType::File: {
fs::path cat_path = cat.get_path();
try {
AddCat(cat.get_name(), LoadCat(cat_path.string()));
} catch (std::exception &e) {
WARNING.writefln("Failed to load cat %s at path %s: %s", cat_path.c_str(), cat.get_name().c_str(), e.what());
}
} break;
default: {
WARNING.writefln("Invalid cat type with numerical value: %d", cat.get_type());
} break;
}
}
string lang;
{
Json::Value config;
@ -103,6 +123,11 @@ void MainLoop::Init() {
accent_color.w = (float)get_option<double>("ui.imgui.accent_color.a", accent_color.w);
debug_mode = get_option<bool>("ui.imgui.debug_mode", false);
}
{
enable_cat = get_option<bool>("ui.enable_cat", false);
cat_setting = get_option<std::string>("ui.cat", cats.empty() ? "" : cats.begin()->first);
if (cats.contains(cat_setting)) cat = cats[cat_setting];
}
Theme::updateAvailableThemes();
if (Theme::availableThemes.empty()) {
path lightPath = Theme::themeDir / "light.toml";
@ -134,6 +159,44 @@ void MainLoop::Init() {
void MainLoop::Drop(std::string file) {
LoadFile(file);
}
SDL_Texture *MainLoop::LoadCat(File *file) {
size_t pos = file->get_pos();
file->seek(0, SeekType::SET);
std::string fname = file->name;
SDL_RWops *rwops = get_sdl_file(file);
const char *ext = std::filesystem::path(fname).extension().c_str();
DEBUG.writefln("Extension: %s\n", ext);
if (ext[0] == '.') ext = ext + 1;
SDL_Texture *tex = IMG_LoadTextureTyped_RW(rend, rwops, 0, ext);
delete rwops;
if (!tex) throw CustomException(fmt::format("Failed to load cat {}: {}", fname, IMG_GetError()));
file->seek(pos, SeekType::SET);
return tex;
}
SDL_Texture *MainLoop::LoadCat(std::string path) {
std::string fname = path;
SDL_RWops *rwops = SDL_RWFromFile(path.c_str(), "rb");
SDL_Texture *tex = IMG_LoadTexture_RW(rend, rwops, 1);
if (!tex) throw CustomException(fmt::format("Failed to load cat {}: {}", fname, IMG_GetError()));
return tex;
}
SDL_Texture *MainLoop::LoadCatFromMemory(const void *ptr, size_t len, const char *name) {
std::string fname = name;
SDL_RWops *rwops = SDL_RWFromConstMem(ptr, len);
SDL_Texture *tex = IMG_LoadTexture_RW(rend, rwops, 1);
if (!tex) throw CustomException(fmt::format("Failed to load cat {}: {}", fname, IMG_GetError()));
return tex;
}
void MainLoop::AddCat(std::string name, SDL_Texture *tex) {
if (cats.contains(name)) {
SDL_DestroyTexture(cats[name]);
}
cats[name] = tex;
}
void MainLoop::FileLoaded() {
auto file_maybe = playback->get_current_title();
if (file_maybe.has_value()) {
@ -216,6 +279,17 @@ void MainLoop::GuiFunction() {
ImGui::SetNextWindowDockID(dockid);
ImGui::Begin(_TRI_CTX(ICON_FK_PLAY, "Main window title", "Player"), nullptr, 0);
{
ImGui::SetCursorPosY(0);
float y_pos = ImGui::GetWindowHeight() - ImGui::GetFrameHeightWithSpacing() - ImGui::GetFrameHeight() - ImGui::GetStyle().WindowPadding.y;
if (enable_cat && cat) {
int cw, ch;
SDL_QueryTexture(cat, NULL, NULL, &cw, &ch);
float aspect = ((float)cw) / ((float)ch);
float x_size = y_pos * aspect;
float x_pos = ImGui::GetWindowWidth() - ImGui::GetStyle().WindowPadding.x - x_size;
ImGui::SetCursorPosX(x_pos);
ImGui::Image((ImTextureID)cat, ImVec2(x_size, y_pos));
}
float centerSpace = ImGui::GetWindowHeight() - ImGui::GetFrameHeightWithSpacing() - ImGui::GetFrameHeight() - ImGui::GetStyle().WindowPadding.y;
if (streams.size() > 0) {
static string filter = "";
@ -255,7 +329,7 @@ void MainLoop::GuiFunction() {
}
ImGui::EndChildFrame();
}
ImGui::SetCursorPosY(ImGui::GetWindowHeight() - ImGui::GetFrameHeightWithSpacing() - ImGui::GetFrameHeight() - ImGui::GetStyle().WindowPadding.y);
ImGui::SetCursorPosY(y_pos);
if (ImGui::Button(playback->IsPaused() ? ICON_FK_PLAY "##Pause" : ICON_FK_PAUSE "##Pause")) {
playback->Pause();
}
@ -517,6 +591,54 @@ void MainLoop::GuiFunction() {
set_option<std::string>("ui.imgui.lang", lang);
}
}
static SDL_Texture *disp_cat = nullptr;
if (ImGui::Checkbox(_TR_CTX("Preference | cat enable checkbox", "Enable cat"), &enable_cat)) {
disp_cat = nullptr;
set_option<bool>("ui.enable_cat", enable_cat);
}
if (enable_cat) {
ImVec2 TableSize = ImVec2(0, 0);
if (ImGui::BeginTable("##Cats", 2, ImGuiTableFlags_SizingFixedFit|ImGuiTableFlags_NoSavedSettings|ImGuiTableFlags_ScrollY, TableSize)) {
ImGui::TableSetupColumn("Cat Name", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableSetupColumn("Preview", 0);
for (auto &kv : cats) {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
if (ImGui::Selectable(kv.first.c_str(), kv.first == cat_setting, 0)) {
cat_setting = kv.first;
cat = cats[cat_setting];
set_option<std::string>("ui.cat", cat_setting);
}
ImGui::TableSetColumnIndex(1);
int cw, ch;
SDL_QueryTexture(kv.second, NULL, NULL, &cw, &ch);
float aspect = ((float)cw) / ((float)ch);
bool portrait = ch > cw;
ImVec2 size = ImVec2(16.0f * aspect, 16.0f);
if (ImGui::ImageButton(fmt::format("disp_cat_{}", kv.first).c_str(), (ImTextureID)kv.second, size)) {
if (disp_cat == kv.second) {
disp_cat = nullptr;
} else {
disp_cat = kv.second;
}
}
}
ImGui::EndTable();
}
bool show_cat_preview = disp_cat != nullptr;
if (show_cat_preview && ImGui::Begin("Cat Preview", &show_cat_preview, ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_AlwaysAutoResize)) {
int cw, ch;
SDL_QueryTexture(disp_cat, NULL, NULL, &cw, &ch);
ImGui::Image((ImTextureID)disp_cat, ImVec2(cw, ch));
ImGui::End();
}
// Handle window close.
if (!show_cat_preview) {
disp_cat = nullptr;
}
}
if (override_lang) {
ImGui::SameLine();
ImGui::SetNextItemWidth(ImGui::GetWindowWidth() - ImGui::GetCursorPosX() - (ImGui::GetFontSize()) - ((ImGui::GetStyle().ItemSpacing.x + (ImGui::GetStyle().FramePadding.x * 2.0f))) - (ImGui::GetStyle().WindowPadding.x));
@ -689,13 +811,18 @@ void MainLoop::LoadFile(std::string file) {
playback->Start(file);
}
void MainLoop::Deinit() {
for (auto kv : cats) {
SDL_DestroyTexture(kv.second);
}
cats.clear();
{
path themePath(theme->file_path);
themePath = themePath.stem();
if (!themePath.empty()) {
set_option<std::string>("ui.imgui.theme", themePath.string());
}
set_option<bool>("ui.enable_cat", enable_cat);
set_option<std::string>("ui.cat", cat_setting);
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);
@ -731,7 +858,7 @@ void ImGuiUIBackend::QuitHandler() {
// Main code
int ImGuiUIBackend::run(std::vector<std::string> realArgs, int argc, char** argv)
{
SDL_setenv("SDL_VIDEO_X11_WMCLASS", "looper");
SDL_setenv("SDL_VIDEO_X11_WMCLASS", "looper", 1);
int possible_error = UIBackend::run(realArgs, argc, argv);
if (possible_error != 0) {
return possible_error;

View file

@ -36,6 +36,7 @@
using namespace std::filesystem;
using std::string;
#define IMGUI_FRONTEND
class MainLoop : public RendererBackend {
bool show_demo_window = false;
FileBrowser fileDialog = FileBrowser(false);
@ -49,6 +50,8 @@ class MainLoop : public RendererBackend {
bool property_editor = false;
bool restart_needed = false;
bool stopped = true;
bool enable_cat = false;
std::string cat_setting = "__default__";
std::vector<UIBackend*> backends;
UIBackend *cur_backend;
friend class ImGuiUIBackend;
@ -59,6 +62,12 @@ class MainLoop : public RendererBackend {
std::map<std::string, std::string> string_properties;
std::vector<Property> properties;
std::vector<PlaybackStream> streams;
std::map<std::string, SDL_Texture*> cats;
SDL_Texture *cat = nullptr;
SDL_Texture *LoadCatFromMemory(const void *ptr, size_t len, const char *name);
SDL_Texture *LoadCat(File *file);
SDL_Texture *LoadCat(std::string path);
void AddCat(std::string name, SDL_Texture *tex);
public:
Playback *playback;
vector<std::string> args;

View file

@ -80,9 +80,11 @@ LooperWindow::LooperWindow(Playback *playback) : QMainWindow() {
help_menu->addAction(about_item);
bar->addMenu(file_menu);
bar->addMenu(help_menu);
root_layout->addWidget(bar);
QSpacerItem *spacer = new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding);
root_layout->addSpacerItem(spacer);
cat_disp = new QLabel();
cat_disp->setAlignment(Qt::Alignment::enum_type::AlignRight);
root_layout->addWidget(cat_disp);
QBoxLayout *top_row = new QBoxLayout(QBoxLayout::LeftToRight, this);
pause_resume_btn = new QPushButton("Pause", this);
QObject::connect(pause_resume_btn, &QPushButton::pressed, [=,this]() {
@ -146,6 +148,8 @@ LooperWindow::LooperWindow(Playback *playback) : QMainWindow() {
timer->setInterval(1);
timer->start();
QObject::connect(prefs_window, &PrefsWindow::settings_changed, this, &LooperWindow::update_label_setting);
QObject::connect(prefs_window, &PrefsWindow::cat_set, this, &LooperWindow::update_cat);
QObject::connect(prefs_window, &PrefsWindow::cat_unset, this, &LooperWindow::clear_cat);
//setLayout(layout);
}
void LooperWindow::update_label_setting(bool labels_visible, bool icons_visible) {
@ -173,3 +177,11 @@ void LooperWindow::update_label_setting(bool labels_visible, bool icons_visible)
}
}
}
void LooperWindow::clear_cat() {
cat_disp->hide();
}
void LooperWindow::update_cat(QPixmap &img) {
cat_disp->setPixmap(img);
cat_disp->show();
}

View file

@ -41,7 +41,10 @@ class LooperWindow : public QMainWindow {
QAction *about_item;
QFileDialog *file_dialog;
QBoxLayout *root_layout;
QLabel *cat_disp;
void update_label_setting(bool labels_visible, bool icons_visible);
void update_cat(QPixmap &img);
void clear_cat();
public:
AboutWindow *about_window;
PrefsWindow *prefs_window;

View file

@ -1,10 +1,35 @@
#include "preferences.h"
#include <QMenu>
#include <QBoxLayout>
#include <QBuffer>
#include <QImage>
#include <QDataStream>
#include <backend.hpp>
#include <options.hpp>
#include <cats.hpp>
#include <log.hpp>
using namespace Looper::Options;
PrefsWindow::PrefsWindow() {
for (auto &cat : get_cat_data()) {
switch (cat.get_type()) {
case CatDataType::Memory: {
QImage image;
QBuffer buffer;
QDataStream in(&buffer);
auto mem_cat = cat.get_memory_cat();
buffer.setData(const_cast<char*>((const char*)mem_cat.get_ptr()), mem_cat.get_len());
in >> image;
cats[cat.get_name()] = QPixmap::fromImage(image);
} break;
case CatDataType::File: {
try {
cats[cat.get_name()] = QPixmap(cat.get_path().c_str());
} catch (std::exception e) {
WARNING.writefln("Failed to load cat %s at path %s: %s", cat.get_name().c_str(), cat.get_path().c_str(), e.what());
}
} break;
}
}
auto *root_layout = new QBoxLayout(QBoxLayout::TopToBottom, this);
this->setLayout(root_layout);
restart_warning = new QLabel("A restart is needed to apply some changes.", this);
@ -49,6 +74,31 @@ PrefsWindow::PrefsWindow() {
label_settings_group->addWidget(icons_only);
label_settings_group->addWidget(both_labels_icons);
root_layout->addWidget(frame);
cat_enable = new QCheckBox("Enable Cat", this);
QObject::connect(cat_enable, &QCheckBox::pressed, [=,this]() {
this->enable_cat = cat_enable->isChecked();
this->set_options_changed(true);
});
root_layout->addWidget(cat_enable);
QFrame *catFrame = new QFrame(this);
catFrame->setWindowTitle("Cat Selection");
auto *cat_btns_layout = new QBoxLayout(QBoxLayout::TopToBottom, this);
catFrame->setLayout(cat_btns_layout);
for (auto &kv : cats) {
auto id = kv.first;
auto pixmap = kv.second;
QRadioButton *cat_radio = new QRadioButton(id.c_str(), catFrame);
QLabel *cat_img = new QLabel(cat_radio);
cat_img->setPixmap(pixmap);
cat_img->setAlignment(Qt::Alignment::enum_type::AlignRight);
QObject::connect(cat_radio, &QRadioButton::pressed, [=,this]() {
this->cat_setting = id;
this->set_options_changed(true);
});
cat_btns_layout->addWidget(cat_radio);
cat_btns[id] = cat_radio;
}
root_layout->addWidget(catFrame);
QWidget *btn_view = new QWidget(this);
QBoxLayout *btn_box = new QBoxLayout(QBoxLayout::LeftToRight, this);
revert_btn = new QPushButton("Revert", btn_view);
@ -83,6 +133,21 @@ void PrefsWindow::revert() {
if (new_frontend != "qt") restart_warning->show();
else restart_warning->hide();
frontend_btn->setText(new_frontend.c_str());
enable_cat = get_option<bool>("ui.enable_cat");
cat_enable->setCheckState(enable_cat ? Qt::CheckState::Checked : Qt::CheckState::Unchecked);
cat_setting = get_option<std::string>("ui.cat", cats.empty() ? "" : cats.begin()->first);
std::map<QRadioButton*, bool> radio_btn_values = {{labels_only, false}, {icons_only, false}, {both_labels_icons, false}};
if (new_label_setting == "labels") {
radio_btn_values[labels_only] = true;
} else if (new_label_setting == "icons") {
radio_btn_values[icons_only] = true;
} else if (new_label_setting == "both") {
radio_btn_values[both_labels_icons] = true;
}
for (auto &kv : radio_btn_values) {
kv.first->setDown(kv.second);
}
if (cat_btns.contains(cat_setting)) cat_btns[cat_setting]->setDown(true);
update_label_setting();
}
void PrefsWindow::apply() {
@ -93,4 +158,8 @@ void PrefsWindow::apply() {
else restart_warning->hide();
frontend_btn->setText(new_frontend.c_str());
update_label_setting();
set_option<bool>("ui.enable_cat", enable_cat);
set_option<std::string>("ui.cat", cat_setting);
if (enable_cat && cats.contains(cat_setting)) emit(cat_set(cats[cat_setting]));
else emit(cat_unset());
}

View file

@ -18,12 +18,17 @@ class PrefsWindow : public QWidget {
QPushButton *frontend_btn;
QMenu *frontend_menu;
std::vector<QAction*> frontend_options;
std::map<std::string, QPixmap> cats;
std::map<std::string, QRadioButton*> cat_btns;
QCheckBox *menu_icons;
QRadioButton *labels_only;
QRadioButton *icons_only;
QRadioButton *both_labels_icons;
QPushButton *revert_btn;
QPushButton *apply_btn;
QCheckBox *cat_enable;
bool enable_cat;
std::string cat_setting;
void update_label_setting();
void set_options_changed(bool changed);
void revert();
@ -31,5 +36,7 @@ class PrefsWindow : public QWidget {
public:
PrefsWindow();
Q_SIGNALS:
void cat_set(QPixmap &img);
void cat_unset();
void settings_changed(bool use_labels, bool use_icons);
};

View file

@ -4,10 +4,11 @@ using std::vector;
static unsigned int DecodeBase85Byte(char c) {
return c >= '\\' ? c-36 : c-35;
}
vector<unsigned char> DecodeBase85(const char *src) {
vector<unsigned char> dst_vec;
dst_vec.resize(((strlen(src) + 4) / 5) * 4);
unsigned char *dst = dst_vec.data();
vector<unsigned char> *DecodeBase85(const char *src) {
vector<unsigned char> *dst_vec = new vector<unsigned char>();
dst_vec->resize(((strlen(src) + 4) / 5) * 4);
unsigned char *dst = dst_vec->data();
memset(dst, 0, dst_vec->size());
size_t dst_size = 0;
while (*src)
{
@ -17,6 +18,6 @@ vector<unsigned char> DecodeBase85(const char *src) {
dst += 4;
dst_size += 4;
}
dst_vec.resize(dst_size);
dst_vec->resize(dst_size);
return dst_vec;
}

View file

@ -3,8 +3,4 @@
#include <cstring>
#include <string>
// Modified from Dear ImGui
std::vector<unsigned char> DecodeBase85(const char *src);
// May not be needed now, but could be useful in the future.
static inline std::vector<unsigned char> DecodeBase85(const std::string src) {
return DecodeBase85(src.c_str());
}
std::vector<unsigned char> *DecodeBase85(const char *src);

88
cats.hpp Normal file
View file

@ -0,0 +1,88 @@
#pragma once
#include <stddef.h>
#include <string>
#include <filesystem>
#include <vector>
namespace fs = std::filesystem;
class MemoryCat {
const void *ptr;
size_t len;
bool owned;
public:
inline size_t get_len() {
return len;
}
inline const void *get_ptr() {
return (const void*)ptr;
}
inline ~MemoryCat() {
if (owned) free((void*)ptr);
}
/// \brief Constructor that doesn't take ownership of the pointer
inline MemoryCat(const void *ptr, size_t len) {
this->owned = false;
this->ptr = ptr;
this->len = len;
}
/// \brief Constructor that takes ownership of the pointer
inline MemoryCat(void *ptr, size_t len) {
this->owned = true;
this->ptr = ptr;
this->len = len;
}
};
enum class CatDataType {
Memory,
File
};
class CatData {
std::optional<MemoryCat> mem;
fs::path path;
std::string name;
CatDataType type;
public:
inline std::string get_name() {
return name;
}
inline CatDataType get_type() {
return type;
}
inline fs::path get_path() {
return path;
}
inline MemoryCat get_memory_cat() {
if (type == CatDataType::Memory) {
return mem.value();
} else {
throw std::exception();
}
}
inline MemoryCat as_memory_cat() {
if (mem.has_value()) return mem.value();
if (type == CatDataType::File) {
FILE *file = fopen(path.c_str(), "rb");
fseek(file, 0, SEEK_END);
size_t len = ftell(file);
fseek(file, 0, SEEK_SET);
void *ptr = malloc(len);
len = fread(ptr, 1, len, file);
ptr = realloc(ptr, len);
MemoryCat output(ptr, len);
this->mem = output;
return output;
}
throw std::exception();
}
inline CatData(const void *ptr, size_t len, std::string name, fs::path virtual_path) {
this->type = CatDataType::Memory;
this->name = name;
this->path = virtual_path;
this->mem = MemoryCat(ptr, len);
}
inline CatData(fs::path path) {
this->name = path.stem();
this->type = CatDataType::File;
this->path = path;
}
};
extern std::vector<CatData> &get_cat_data();

View file

@ -1,6 +1,7 @@
#include "file_backend.hpp"
#include <filesystem>
#include <string.h>
#include <cassert>
File::File() {
}
@ -56,9 +57,18 @@ bool CFile::is_open() {
return file != NULL;
}
MemFile::MemFile() {
memory_owned = false;
ptr = NULL;
len = 0;
}
void MemFile::open_memory(void *ptr, size_t len, const char *name) {
assert(name != NULL);
this->name = strdup(name);
this->ptr = ptr;
this->len = len;
this->pos = 0;
memory_owned = false;
}
void MemFile::open(const char *path) {
CFile file;
file.open(path);
@ -67,9 +77,10 @@ void MemFile::open(const char *path) {
this->len = file.read(this->ptr, 1, file.get_len());
file.close();
this->pos = 0;
memory_owned = true;
}
void MemFile::close() {
free(this->ptr);
if (memory_owned) free(this->ptr);
free((void*)this->name);
this->ptr = NULL;
this->name = NULL;

View file

@ -93,8 +93,10 @@ class MemFile : public File {
void *ptr;
size_t len;
int64_t pos;
bool memory_owned = true;
public:
MemFile();
void open_memory(void *ptr, size_t len, const char *name = "<mem_file>");
void open(const char *path) override;
void close() override;
size_t read(void *ptr, size_t size, size_t len) override;

View file

@ -16,6 +16,7 @@
#include <image.h>
#endif
#include "web_functions.hpp"
#include "cats.hpp"
using namespace Looper;
using namespace Looper::Options;
using namespace Looper::Log;
@ -24,6 +25,10 @@ extern "C" {
void quit();
}
#endif
std::vector<CatData> &get_cat_data() {
static std::vector<CatData> data;
return data;
}
std::unordered_set<LicenseData> license_data;
std::unordered_set<LicenseData> &get_license_data() {
return license_data;
@ -175,6 +180,21 @@ extern "C" int looper_run_as_executable(std::vector<std::string> args) {
}
DEBUG.writeln("Loading options file...");
load_options();
{
auto &cat_data = get_cat_data();
cat_data.push_back(CatData(catoc_data, catoc_size, "Cat OC (Built-In)", "catoc.png"));
#ifndef __EMSCRIPTEN__
fs::path baseDir = get_prefs_path() / fs::path("looper") / fs::path("themes");
if (!fs::exists(baseDir)) {
fs::create_directories(baseDir);
}
for (auto const&dir_entry : std::filesystem::directory_iterator(baseDir)) {
if (dir_entry.is_regular_file()) {
cat_data.push_back(CatData(dir_entry.path()));
}
}
#endif
}
std::string backend_id = get_option<std::string>("ui.frontend", "imgui");
UIBackend *backend = UIBackend::get_backend(ui_backend_option).value_or(UIBackend::get_backend(backend_id).value_or(UIBackend::get_first_backend()));
int output = 0;