diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 7cc0c04..f4af679 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -51,37 +51,36 @@ jobs:
with:
submodules: recursive
- - name: Install development tools
+ - name: Install dependencies
run: |
sudo apt-get update
- sudo apt-get install -y \
- build-essential \
- cmake \
- libgl1-mesa-dev \
- libglfw3-dev \
- libx11-dev \
- libxrandr-dev \
- libxinerama-dev \
- libxcursor-dev \
- libxi-dev \
- libwayland-dev \
- libxkbcommon-dev
-
- - name: Build on Linux
+ sudo apt-get install -y build-essential cmake python3-pip libgl1-mesa-dev
+ pip install aqtinstall
+
+ - name: Install Qt 6.10.0
+ run: python3 -m aqt install-qt linux desktop 6.10.0 linux_gcc_64 --outputdir external
+
+ - name: Build and Install
run: |
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --config Release
+ cmake --install build --prefix "${{ github.workspace }}/dist" --config Release
- name: Extract branch name
shell: bash
id: extract_branch
run: echo "branch=$(echo ${GITHUB_REF#refs/heads/} | tr '/' '_')" >> $GITHUB_OUTPUT
+ - name: Prepare artifact structure
+ run: |
+ mkdir -p release/Sprite-Tools
+ cp -r dist/* release/Sprite-Tools/
+
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: Sprite-Tools-${{ steps.extract_branch.outputs.branch }}-linux
- path: build/SpriteTools
+ path: release
windows:
name: Windows
@@ -91,27 +90,34 @@ jobs:
uses: actions/checkout@v5
with:
submodules: recursive
+
+ - name: Install aqtinstall
+ run: pip install aqtinstall
- - name: Set developer command prompt
- uses: ilammy/msvc-dev-cmd@v1
- with:
- arch: x64
+ - name: Install Qt 6.10.0
+ run: python -m aqt install-qt windows desktop 6.10.0 win64_msvc2022_64 --outputdir external
- - name: Build on Windows
+ - name: Build and Install
run: |
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --config Release
+ cmake --install build --prefix "${{ github.workspace }}/dist" --config Release
- name: Extract branch name
shell: bash
id: extract_branch
run: echo "branch=$(echo ${GITHUB_REF#refs/heads/} | tr '/' '_')" >> $GITHUB_OUTPUT
+ - name: Prepare artifact structure
+ run: |
+ mkdir -p release/Sprite-Tools
+ cp -r dist/* release/Sprite-Tools/
+
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: Sprite-Tools-${{ steps.extract_branch.outputs.branch }}-windows
- path: build/Release/SpriteTools.exe
+ path: release
release:
name: Upload release
@@ -129,7 +135,6 @@ jobs:
uses: actions/download-artifact@v4
with:
path: artifacts
- merge-multiple: true
- name: Set tag name
run: |
@@ -141,6 +146,16 @@ jobs:
RELEASE_NAME="Sprite-Tools $TAG_NAME Build"
echo "TAG_NAME=$TAG_NAME" >> $GITHUB_ENV
echo "RELEASE_NAME=$RELEASE_NAME" >> $GITHUB_ENV
+
+ - name: Pack Archives
+ run: |
+ mkdir final_assets
+ cd artifacts
+ for dir in */; do
+ zip_name="${dir%/}.zip"
+ echo "Creating $zip_name..."
+ cd "$dir" && zip -r "../../final_assets/$zip_name" . && cd ..
+ done
- name: Remove old release if exists
run: |
@@ -150,17 +165,12 @@ jobs:
fi
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-
- - name: List downloaded artifacts
- run: |
- echo "Listing all artifacts in artifacts/:"
- ls -R artifacts/
- name: Upload new release
run: |
gh release create "$TAG_NAME" \
--title "$RELEASE_NAME" \
- --prerelease artifacts/* \
+ --prerelease final_assets/* \
--repo ${{ github.repository }} \
--target ${{ github.sha }}
env:
diff --git a/.gitignore b/.gitignore
index 8cdb5c7..e7a749e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -24,3 +24,6 @@ CMakeSettings.json
CMakeFiles
CMakeCache.txt
Makefile
+external/6.10.0
+dist/
+aqtinstall.log
\ No newline at end of file
diff --git a/.gitmodules b/.gitmodules
index dd84f8f..ee3bc5e 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,12 +1,3 @@
[submodule "external/stb"]
path = external/stb
url = https://github.com/nothings/stb
-[submodule "external/portable-file-dialogs"]
- path = external/portable-file-dialogs
- url = https://github.com/samhocevar/portable-file-dialogs
-[submodule "external/imgui"]
- path = external/imgui
- url = https://github.com/ocornut/imgui
-[submodule "external/glfw"]
- path = external/glfw
- url = https://github.com/glfw/glfw.git
diff --git a/CMakeLists.txt b/CMakeLists.txt
index fa9e209..270f30e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,9 +1,16 @@
cmake_minimum_required(VERSION 3.18)
+
+if(POLICY CMP0177)
+ cmake_policy(SET CMP0177 NEW)
+endif()
+
project(SpriteTools)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
+set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
+
if(ANDROID)
set(PLATFORM_ANDROID TRUE)
message(STATUS "Platform: Android")
@@ -16,7 +23,6 @@ elseif(UNIX)
endif()
if(PLATFORM_ANDROID)
-
add_library(${PROJECT_NAME} SHARED
src/core/sprite_converter_capi.cpp
src/core/sprite_converter.cpp
@@ -25,54 +31,104 @@ if(PLATFORM_ANDROID)
src/core/sprite_jni.cpp
src/core/stb_impl.cpp
)
-
- target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/core external/stb)
-
+ target_include_directories(${PROJECT_NAME} PRIVATE src/core external/stb)
target_link_libraries(${PROJECT_NAME} android log)
else()
+ set(CMAKE_AUTOMOC ON)
+ set(CMAKE_AUTORCC ON)
+ set(CMAKE_AUTOUIC ON)
+
+ if(PLATFORM_WIN32)
+ set(QT_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/external/6.10.0/msvc2022_64")
+ else()
+ set(QT_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/external/6.10.0/gcc_64")
+ endif()
- add_subdirectory(external/glfw)
+ set(CMAKE_PREFIX_PATH ${QT_ROOT})
+
+ find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets NO_DEFAULT_PATH PATHS ${QT_ROOT})
+
+ qt_standard_project_setup()
set(SOURCES
src/main.cpp
- src/ui.cpp
- src/ui_utils.cpp
+ src/mainwindow.cpp
+ src/sprite_viewport.cpp
+ src/properties_panel.cpp
+ src/export_dialog.cpp
+ src/import_dialog.cpp
+ src/about_dialog.cpp
+ src/frame_slider.cpp
src/core/stb_impl.cpp
src/core/sprite_loader.cpp
src/core/sprite_converter.cpp
- src/renderer.cpp
-
- external/imgui/imgui.cpp
- external/imgui/imgui_demo.cpp
- external/imgui/imgui_draw.cpp
- external/imgui/imgui_tables.cpp
- external/imgui/imgui_widgets.cpp
- external/imgui/backends/imgui_impl_glfw.cpp
- external/imgui/backends/imgui_impl_opengl3.cpp
+ resources/resources.qrc
)
- add_executable(${PROJECT_NAME} ${SOURCES})
-
- target_include_directories(${PROJECT_NAME} PRIVATE
- src
- src/core
- src/icons
- external/imgui
- external/imgui/backends
- external/stb
- external/portable-file-dialogs
+ set(HEADERS
+ src/mainwindow.h
+ src/sprite_viewport.h
+ src/properties_panel.h
+ src/export_dialog.h
+ src/import_dialog.h
+ src/about_dialog.h
+ src/frame_slider.h
+ src/app_state.h
+ src/theme.h
)
+ add_executable(${PROJECT_NAME} ${SOURCES} ${HEADERS})
+
+ target_include_directories(${PROJECT_NAME} PRIVATE src src/core external/stb)
+ target_link_libraries(${PROJECT_NAME} PRIVATE Qt6::Core Qt6::Gui Qt6::Widgets)
+
if(PLATFORM_WIN32)
target_compile_definitions(${PROJECT_NAME} PRIVATE PLATFORM_WIN32)
- target_link_libraries(${PROJECT_NAME} PRIVATE glfw opengl32)
+ set(LIB_DEST ".")
endif()
if(PLATFORM_LINUX)
- find_package(OpenGL REQUIRED)
target_compile_definitions(${PROJECT_NAME} PRIVATE PLATFORM_LINUX)
- target_link_libraries(${PROJECT_NAME} PRIVATE glfw OpenGL::GL)
+ set(LIB_DEST "lib")
+
+ set_target_properties(${PROJECT_NAME} PROPERTIES
+ BUILD_RPATH "${QT_ROOT}/lib"
+ INSTALL_RPATH "$ORIGIN/lib"
+ BUILD_WITH_INSTALL_RPATH FALSE
+ )
endif()
-endif()
\ No newline at end of file
+ install(TARGETS ${PROJECT_NAME}
+ RUNTIME DESTINATION .
+ LIBRARY DESTINATION ${LIB_DEST}
+ )
+
+ set(QT_DEPLOY_BIN_DIR ".")
+ set(QT_DEPLOY_LIB_DIR "${LIB_DEST}")
+ set(QT_DEPLOY_PLUGINS_DIR "plugins")
+
+ if(IS_ABSOLUTE "${CMAKE_INSTALL_PREFIX}")
+ set(QT_DEPLOY_QT_CONF_PATH "${CMAKE_INSTALL_PREFIX}/qt.conf")
+ else()
+ get_filename_component(ABS_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}" ABSOLUTE)
+ set(QT_DEPLOY_QT_CONF_PATH "${ABS_INSTALL_PREFIX}/qt.conf")
+ endif()
+
+ qt_generate_deploy_app_script(
+ TARGET ${PROJECT_NAME}
+ OUTPUT_SCRIPT deploy_script
+ NO_TRANSLATIONS
+ POST_EXCLUDE_REGEXES ""
+ )
+
+ install(SCRIPT ${deploy_script})
+
+ if(PLATFORM_LINUX)
+ file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/qt.conf"
+ "[Paths]\nPrefix = .\nLibraries = lib\nPlugins = plugins\n"
+ )
+ install(FILES "${CMAKE_CURRENT_BINARY_DIR}/qt.conf" DESTINATION .)
+ endif()
+
+endif()
diff --git a/external/glfw b/external/glfw
deleted file mode 160000
index fdd14e6..0000000
--- a/external/glfw
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit fdd14e65b1c29e4e6df875fb5669ec00d6793531
diff --git a/external/imgui b/external/imgui
deleted file mode 160000
index 41765fb..0000000
--- a/external/imgui
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 41765fbda723d23e04e98afec40447d149d02ec8
diff --git a/external/portable-file-dialogs b/external/portable-file-dialogs
deleted file mode 160000
index c12ea8c..0000000
--- a/external/portable-file-dialogs
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit c12ea8c9a727f5320a2b4570aee863bbede2a204
diff --git a/external/stb b/external/stb
index 2fb8c5a..e6cd956 160000
--- a/external/stb
+++ b/external/stb
@@ -1 +1 @@
-Subproject commit 2fb8c5a3deb2110c89669f8d6f36e5833b556b44
+Subproject commit e6cd9561ea6dae43d41633797745789d142b691e
diff --git a/resources/add_photo.png b/resources/add_photo.png
new file mode 100644
index 0000000..1db8328
Binary files /dev/null and b/resources/add_photo.png differ
diff --git a/resources/center_focus.png b/resources/center_focus.png
new file mode 100644
index 0000000..967a73e
Binary files /dev/null and b/resources/center_focus.png differ
diff --git a/resources/close.png b/resources/close.png
new file mode 100644
index 0000000..02d9621
Binary files /dev/null and b/resources/close.png differ
diff --git a/resources/folder.png b/resources/folder.png
new file mode 100644
index 0000000..adde88f
Binary files /dev/null and b/resources/folder.png differ
diff --git a/resources/info.png b/resources/info.png
new file mode 100644
index 0000000..0659467
Binary files /dev/null and b/resources/info.png differ
diff --git a/resources/navigate_before.png b/resources/navigate_before.png
new file mode 100644
index 0000000..dabef91
Binary files /dev/null and b/resources/navigate_before.png differ
diff --git a/resources/navigate_next.png b/resources/navigate_next.png
new file mode 100644
index 0000000..2b22f59
Binary files /dev/null and b/resources/navigate_next.png differ
diff --git a/resources/pause.png b/resources/pause.png
new file mode 100644
index 0000000..7a5b0db
Binary files /dev/null and b/resources/pause.png differ
diff --git a/resources/play.png b/resources/play.png
new file mode 100644
index 0000000..e322140
Binary files /dev/null and b/resources/play.png differ
diff --git a/resources/resources.qrc b/resources/resources.qrc
new file mode 100644
index 0000000..21f745e
--- /dev/null
+++ b/resources/resources.qrc
@@ -0,0 +1,18 @@
+
+
+ center_focus.png
+ folder.png
+ navigate_before.png
+ navigate_next.png
+ pause.png
+ play.png
+ skip_next.png
+ skip_previous.png
+ zoom_in.png
+ zoom_out.png
+ close.png
+ save_alt.png
+ info.png
+ add_photo.png
+
+
diff --git a/resources/save_alt.png b/resources/save_alt.png
new file mode 100644
index 0000000..f0e39d8
Binary files /dev/null and b/resources/save_alt.png differ
diff --git a/resources/skip_next.png b/resources/skip_next.png
new file mode 100644
index 0000000..de4e01d
Binary files /dev/null and b/resources/skip_next.png differ
diff --git a/resources/skip_previous.png b/resources/skip_previous.png
new file mode 100644
index 0000000..a3b6b12
Binary files /dev/null and b/resources/skip_previous.png differ
diff --git a/resources/zoom_in.png b/resources/zoom_in.png
new file mode 100644
index 0000000..c09cf5b
Binary files /dev/null and b/resources/zoom_in.png differ
diff --git a/resources/zoom_out.png b/resources/zoom_out.png
new file mode 100644
index 0000000..3e7bd88
Binary files /dev/null and b/resources/zoom_out.png differ
diff --git a/src/about_dialog.cpp b/src/about_dialog.cpp
new file mode 100644
index 0000000..32d6a2a
--- /dev/null
+++ b/src/about_dialog.cpp
@@ -0,0 +1,41 @@
+#include "about_dialog.h"
+#include "theme.h"
+
+#include
+#include
+
+AboutDialog::AboutDialog(QWidget* parent) : QDialog(parent)
+{
+ setWindowTitle("About");
+ setFixedSize(380, 150);
+
+ auto* lay = new QVBoxLayout(this);
+ lay->setContentsMargins(18, 18, 18, 18);
+ lay->setSpacing(0);
+
+ auto* nameLbl = new QLabel("Sprite-Tools");
+ nameLbl->setStyleSheet(QString("font-size: 17px; font-weight: bold; color: %1;").arg(SpriteColors::Accent.name()));
+ lay->addWidget(nameLbl);
+ lay->addSpacing(6);
+
+ auto* descLbl = new QLabel("Sprite viewer and creator for Quake / Half-Life sprites");
+ descLbl->setStyleSheet(QString("font-size: 12px; color: %1;").arg(SpriteColors::TextPrimary.name()));
+ descLbl->setWordWrap(true);
+ lay->addWidget(descLbl);
+ lay->addSpacing(16);
+
+
+ auto* linkLbl = new QLabel;
+ linkLbl->setTextFormat(Qt::RichText);
+ linkLbl->setOpenExternalLinks(true);
+ linkLbl->setText(
+ QString("GitHub: "
+ ""
+ "https://github.com/Elinsrc/Sprite-Tools")
+ .arg(SpriteColors::TextPrimary.name(), SpriteColors::Accent.name()));
+ linkLbl->setCursor(Qt::PointingHandCursor);
+ lay->addWidget(linkLbl);
+
+ lay->addStretch();
+}
\ No newline at end of file
diff --git a/src/about_dialog.h b/src/about_dialog.h
new file mode 100644
index 0000000..6ad719e
--- /dev/null
+++ b/src/about_dialog.h
@@ -0,0 +1,10 @@
+#pragma once
+
+#include
+
+class AboutDialog : public QDialog
+{
+ Q_OBJECT
+public:
+ explicit AboutDialog(QWidget* parent = nullptr);
+};
\ No newline at end of file
diff --git a/src/app_state.h b/src/app_state.h
new file mode 100644
index 0000000..fb48678
--- /dev/null
+++ b/src/app_state.h
@@ -0,0 +1,40 @@
+#pragma once
+
+#include
+#include
+#include
+
+struct AppState
+{
+ bool sprite_loaded = false;
+ std::string filepath;
+ std::string fileName;
+ int current_frame = 0;
+ int total_frames = 0;
+
+ float zoom = 1.0f;
+ float scroll_x = 0.0f;
+ float scroll_y = 0.0f;
+
+ bool animating = false;
+ float anim_speed = 1.0f;
+ double anim_time = 0.0;
+
+ bool show_checker = true;
+ bool show_properties = true;
+ bool show_toolbar = true;
+ bool show_about = false;
+ bool show_export = false;
+ bool show_import = false;
+
+ bool show_progress = false;
+ bool progress_done = false;
+ bool progress_success = false;
+ float progress_value = 0.0f;
+ std::string progress_title;
+ std::string progress_status;
+ std::string progress_result;
+
+ std::string last_dir;
+ std::string status_msg;
+};
\ No newline at end of file
diff --git a/src/export_dialog.cpp b/src/export_dialog.cpp
new file mode 100644
index 0000000..df450e5
--- /dev/null
+++ b/src/export_dialog.cpp
@@ -0,0 +1,151 @@
+#include "export_dialog.h"
+#include "sprite_converter.h"
+#include "theme.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+ExportDialog::ExportDialog(QWidget* parent, AppState& state, SpriteLoader& loader) : QDialog(parent), m_state(state), m_loader(loader)
+{
+ setWindowTitle("Export Frames");
+ setFixedSize(360, 280);
+
+ auto* lay = new QVBoxLayout(this);
+ lay->setContentsMargins(24, 24, 24, 24);
+ lay->setSpacing(0);
+
+ auto* fmtLbl = new QLabel("Output Format");
+ fmtLbl->setStyleSheet(QString("font-size: 14px; font-weight: 500; color: %1;").arg(SpriteColors::TextSection.name()));
+ lay->addWidget(fmtLbl);
+ lay->addSpacing(8);
+
+ m_fmtGroup = new QButtonGroup(this);
+ auto* fmtPng = new QRadioButton("PNG (with alpha)");
+ auto* fmtBmp = new QRadioButton("BMP (no alpha)");
+ fmtPng->setChecked(true);
+ m_fmtGroup->addButton(fmtPng, 0);
+ m_fmtGroup->addButton(fmtBmp, 1);
+
+ lay->addWidget(fmtPng);
+ lay->addSpacing(4);
+ lay->addWidget(fmtBmp);
+ lay->addSpacing(20);
+
+ auto* selLbl = new QLabel("Frame Selection");
+ selLbl->setStyleSheet(QString("font-size: 14px; font-weight: 500; color: %1;").arg(SpriteColors::TextSection.name()));
+ lay->addWidget(selLbl);
+ lay->addSpacing(8);
+
+ m_allFrames = new QRadioButton(QString("All frames (%1)").arg(state.total_frames));
+ m_currentOnly = new QRadioButton(QString("Current frame (%1)").arg(state.current_frame + 1));
+ m_allFrames->setChecked(true);
+
+ lay->addWidget(m_allFrames);
+ lay->addSpacing(4);
+ lay->addWidget(m_currentOnly);
+
+ auto* btnRow = new QHBoxLayout;
+ btnRow->setSpacing(8);
+ btnRow->addStretch();
+
+ auto* cancelBtn = new QPushButton("Cancel");
+ cancelBtn->setCursor(Qt::PointingHandCursor);
+ cancelBtn->setStyleSheet(QString(
+ "QPushButton { background: transparent; color: %1; border: none; font-weight: 500; padding: 0 12px; height: 36px; }"
+ "QPushButton:hover { background: rgba(255, 255, 255, 0.05); border-radius: 4px; }"
+ ).arg(SpriteColors::Accent.name()));
+
+ auto* exportBtn = new QPushButton("Export");
+ exportBtn->setCursor(Qt::PointingHandCursor);
+ exportBtn->setStyleSheet(QString(
+ "QPushButton { background: %1; color: #FFFFFF; border: none; border-radius: 4px; font-weight: 500; padding: 0 16px; height: 36px; }"
+ "QPushButton:hover { background: %2; }"
+ ).arg(SpriteColors::Accent.name(), SpriteColors::AccentLit.name()));
+
+ btnRow->addWidget(cancelBtn);
+ btnRow->addWidget(exportBtn);
+ lay->addLayout(btnRow);
+
+ connect(exportBtn, &QPushButton::clicked, this, &ExportDialog::onExport);
+ connect(cancelBtn, &QPushButton::clicked, this, &QDialog::reject);
+}
+
+void ExportDialog::onExport()
+{
+ QSettings settings("Sprite-Tools");
+ QString lastDir = settings.value("lastExportDir", QString::fromStdString(m_state.last_dir)).toString();
+
+ QString dir = QFileDialog::getExistingDirectory(this, "Output Directory", lastDir);
+
+ if (dir.isEmpty())
+ return;
+
+ settings.setValue("lastExportDir", dir);
+ m_state.last_dir = dir.toStdString();
+
+ int format = m_fmtGroup->checkedId();
+ int frameIdx = m_currentOnly->isChecked() ? m_state.current_frame : -1;
+ accept();
+
+ QProgressDialog prog("Exporting...", "Cancel", 0, 100, parentWidget());
+ prog.setWindowTitle("Exporting Frames");
+ prog.setWindowModality(Qt::WindowModal);
+ prog.setMinimumDuration(0);
+
+ SpriteLoader loader;
+ if (!loader.Load(m_state.filepath))
+ {
+ QMessageBox::critical(parentWidget(), "Error", "Failed to load sprite");
+ return;
+ }
+
+ QFileInfo fi(QString::fromStdString(m_state.filepath));
+ QString prefix = fi.completeBaseName();
+ int t = loader.GetTotalFrameCount();
+ int start = 0, end = t;
+ if (frameIdx >= 0 && frameIdx < t) { start = frameIdx; end = frameIdx + 1; }
+
+ int exported = 0;
+ std::string ext = SpriteConverter::GetFormatExtension(static_cast(format));
+
+ for (int i = start; i < end; i++)
+ {
+ if (prog.wasCanceled()) break;
+ prog.setLabelText(QString("Exporting frame %1 / %2...").arg(i + 1).arg(end));
+
+ SpriteFrame* frame = loader.GetFrame(i);
+ if (!frame || frame->rgba.empty())
+ {
+ QMessageBox::critical(parentWidget(), "Error", QString("Failed to get frame %1").arg(i));
+ return;
+ }
+
+ QString outPath = (t == 1 && frameIdx < 0)
+ ? QString("%1/%2%3").arg(dir, prefix, QString::fromStdString(ext))
+ : QString("%1/%2_%3%4").arg(dir, prefix).arg(i, 3, 10, QChar('0')).arg(QString::fromStdString(ext));
+
+ if (!SpriteConverter::SaveImageRGBA(outPath.toStdString().c_str(),
+ frame->rgba.data(), frame->width, frame->height,
+ static_cast(format)))
+ {
+ QMessageBox::critical(parentWidget(), "Error", "Failed to save: " + outPath);
+ return;
+ }
+ exported++;
+ prog.setValue((int)((float)(i - start + 1) / (end - start) * 100));
+ QApplication::processEvents();
+ }
+
+ if (!prog.wasCanceled())
+ QMessageBox::information(parentWidget(), "Export Complete",
+ QString("Exported %1 file(s) to:\n%2").arg(exported).arg(dir));
+}
\ No newline at end of file
diff --git a/src/export_dialog.h b/src/export_dialog.h
new file mode 100644
index 0000000..7a30f6b
--- /dev/null
+++ b/src/export_dialog.h
@@ -0,0 +1,26 @@
+#pragma once
+
+#include
+#include
+#include
+
+#include "app_state.h"
+#include "sprite_loader.h"
+
+class ExportDialog : public QDialog
+{
+ Q_OBJECT
+public:
+ ExportDialog(QWidget* parent, AppState& state, SpriteLoader& loader);
+
+private slots:
+ void onExport();
+
+private:
+ AppState& m_state;
+ SpriteLoader& m_loader;
+
+ QButtonGroup* m_fmtGroup;
+ QRadioButton* m_allFrames;
+ QRadioButton* m_currentOnly;
+};
\ No newline at end of file
diff --git a/src/frame_slider.cpp b/src/frame_slider.cpp
new file mode 100644
index 0000000..4996c47
--- /dev/null
+++ b/src/frame_slider.cpp
@@ -0,0 +1,145 @@
+#include "frame_slider.h"
+#include "theme.h"
+
+#include
+#include
+#include
+#include
+
+FrameSlider::FrameSlider(QWidget* parent) : QWidget(parent)
+{
+ setMinimumHeight(28);
+ setMaximumHeight(28);
+ setMinimumWidth(80);
+ setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
+ setMouseTracking(true);
+}
+
+void FrameSlider::setRange(int min, int max)
+{
+ m_min = min;
+ m_max = std::max(min, max);
+ m_value = std::clamp(m_value, m_min, m_max);
+ update();
+}
+
+void FrameSlider::setValue(int value)
+{
+ int clamped = std::clamp(value, m_min, m_max);
+ if (clamped != m_value)
+ {
+ m_value = clamped;
+ update();
+ emit valueChanged(m_value);
+ }
+}
+
+void FrameSlider::setEnabled(bool enabled)
+{
+ m_enabled = enabled;
+ QWidget::setEnabled(enabled);
+ update();
+}
+
+int FrameSlider::posToValue(int x) const
+{
+ if (m_max <= m_min) return m_min;
+ const int margin = 10;
+ int trackW = width() - margin * 2;
+ if (trackW <= 0) return m_min;
+ float ratio = std::clamp((float)(x - margin) / (float)trackW, 0.0f, 1.0f);
+ return m_min + (int)std::round(ratio * (m_max - m_min));
+}
+
+int FrameSlider::valueToPos(int val) const
+{
+ if (m_max <= m_min) return width() / 2;
+ const int margin = 10;
+ int trackW = width() - margin * 2;
+ float ratio = (float)(val - m_min) / (float)(m_max - m_min);
+ return margin + (int)(ratio * trackW);
+}
+
+void FrameSlider::paintEvent(QPaintEvent*)
+{
+ QPainter p(this);
+ p.setRenderHint(QPainter::Antialiasing);
+
+ int cy = height() / 2;
+ const int margin = 10;
+ int trackW = width() - margin * 2;
+ int steps = m_max - m_min;
+
+ QColor trackBg = m_enabled ? SpriteColors::BgLight : SpriteColors::BgDark;
+ p.setPen(Qt::NoPen);
+ p.setBrush(trackBg);
+ p.drawRoundedRect(margin, cy - 2, trackW, 4, 2, 2);
+
+ if (!m_enabled || steps <= 0)
+ return;
+
+ int thumbX = valueToPos(m_value);
+ int activeW = thumbX - margin;
+ if (activeW > 0)
+ {
+ p.setBrush(SpriteColors::AccentDim);
+ p.drawRoundedRect(margin, cy - 2, activeW, 4, 2, 2);
+ }
+
+ if (steps > 0 && steps <= 60)
+ {
+ float dotR = (steps <= 20) ? 3.0f : (steps <= 40) ? 2.0f : 1.5f;
+
+ for (int i = 0; i <= steps; i++)
+ {
+ int dx = valueToPos(m_min + i);
+ bool active = (m_min + i) <= m_value;
+
+ QColor dc = active ? SpriteColors::Accent : SpriteColors::TextDim;
+ dc.setAlpha(active ? 200 : 100);
+
+ p.setBrush(dc);
+ p.drawEllipse(QPointF(dx, cy), dotR, dotR);
+ }
+ }
+
+ p.setBrush(QColor(0, 0, 0, 40));
+ p.drawEllipse(QPointF(thumbX, cy + 1), 8, 8);
+
+ p.setBrush(SpriteColors::Accent);
+ p.drawEllipse(QPointF(thumbX, cy), 7, 7);
+}
+
+void FrameSlider::mousePressEvent(QMouseEvent* event)
+{
+ if (!m_enabled || m_max <= m_min) return;
+ if (event->button() == Qt::LeftButton)
+ {
+ m_dragging = true;
+ int v = posToValue(event->pos().x());
+ if (v != m_value)
+ {
+ m_value = v;
+ update();
+ emit valueChanged(m_value);
+ }
+ }
+}
+
+void FrameSlider::mouseMoveEvent(QMouseEvent* event)
+{
+ if (!m_dragging || !m_enabled) return;
+ int v = posToValue(event->pos().x());
+ if (v != m_value)
+ {
+ m_value = v;
+ update();
+ emit valueChanged(m_value);
+ }
+}
+
+void FrameSlider::mouseReleaseEvent(QMouseEvent* event)
+{
+ if (event->button() == Qt::LeftButton)
+ m_dragging = false;
+}
\ No newline at end of file
diff --git a/src/frame_slider.h b/src/frame_slider.h
new file mode 100644
index 0000000..0a68257
--- /dev/null
+++ b/src/frame_slider.h
@@ -0,0 +1,35 @@
+#pragma once
+
+#include
+
+class FrameSlider : public QWidget
+{
+ Q_OBJECT
+
+public:
+ explicit FrameSlider(QWidget* parent = nullptr);
+
+ void setRange(int min, int max);
+ void setValue(int value);
+ int value() const { return m_value; }
+ void setEnabled(bool enabled);
+
+signals:
+ void valueChanged(int value);
+
+protected:
+ void paintEvent(QPaintEvent* event) override;
+ void mousePressEvent(QMouseEvent* event) override;
+ void mouseMoveEvent(QMouseEvent* event) override;
+ void mouseReleaseEvent(QMouseEvent* event) override;
+
+private:
+ int posToValue(int x) const;
+ int valueToPos(int val) const;
+
+ int m_min = 0;
+ int m_max = 0;
+ int m_value = 0;
+ bool m_dragging = false;
+ bool m_enabled = true;
+};
\ No newline at end of file
diff --git a/src/icons/center_focus_strong.h b/src/icons/center_focus_strong.h
deleted file mode 100644
index 063a926..0000000
--- a/src/icons/center_focus_strong.h
+++ /dev/null
@@ -1,85 +0,0 @@
-unsigned char center_focus_strong_png[] = {
- 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
- 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40,
- 0x08, 0x06, 0x00, 0x00, 0x00, 0xaa, 0x69, 0x71, 0xde, 0x00, 0x00, 0x03,
- 0x8d, 0x49, 0x44, 0x41, 0x54, 0x78, 0x01, 0xec, 0x5a, 0xbd, 0x72, 0xda,
- 0x40, 0x10, 0xb6, 0x80, 0x61, 0x26, 0xcf, 0xc0, 0xdf, 0xd0, 0xe1, 0xce,
- 0x74, 0x76, 0x67, 0x57, 0x8e, 0xbb, 0xa4, 0x8a, 0xd3, 0x25, 0x5d, 0x1e,
- 0x21, 0x79, 0x82, 0xe4, 0x15, 0x52, 0x25, 0x9d, 0x27, 0x95, 0x53, 0x92,
- 0xca, 0xee, 0xec, 0xce, 0xee, 0x4c, 0xc7, 0x0c, 0x3f, 0xef, 0x90, 0x61,
- 0xf8, 0xf1, 0xb7, 0x1a, 0x34, 0x03, 0x02, 0xb4, 0x7b, 0xa7, 0x5b, 0x21,
- 0x90, 0x3c, 0xb7, 0x96, 0x74, 0xb7, 0xbf, 0xdf, 0xee, 0xd9, 0x7b, 0x82,
- 0xc2, 0x51, 0xc6, 0x7f, 0x72, 0x00, 0x32, 0x5e, 0x00, 0x47, 0x79, 0x05,
- 0xe4, 0x15, 0x90, 0x71, 0x04, 0xf2, 0x2d, 0x90, 0xf1, 0x02, 0x30, 0xfb,
- 0x23, 0x38, 0x18, 0x0c, 0x9e, 0x40, 0x73, 0x13, 0x8a, 0x0b, 0xb0, 0x89,
- 0xad, 0x05, 0xef, 0x8b, 0x89, 0x4d, 0xd1, 0x16, 0xe8, 0xf5, 0x7a, 0xa7,
- 0xa4, 0x1c, 0x8a, 0x4f, 0x40, 0x69, 0x1f, 0x2d, 0xf2, 0x75, 0x38, 0x1c,
- 0x9e, 0x4a, 0x1c, 0x15, 0x01, 0x50, 0x2a, 0x95, 0x1e, 0x24, 0xca, 0xd2,
- 0xc4, 0x33, 0x9f, 0xcf, 0x45, 0x3e, 0xb3, 0x00, 0x00, 0x4d, 0x91, 0xa2,
- 0x34, 0x05, 0x1f, 0xf8, 0x02, 0xdf, 0x9f, 0x82, 0xfb, 0x6d, 0x57, 0x16,
- 0x00, 0x08, 0x8a, 0x4a, 0x09, 0x7c, 0x69, 0x1c, 0xec, 0x96, 0x95, 0x00,
- 0xb0, 0x16, 0x18, 0xca, 0xeb, 0xe3, 0x64, 0x32, 0x69, 0x4b, 0x68, 0x4d,
- 0xd8, 0x70, 0x42, 0x62, 0x83, 0x78, 0xc8, 0x27, 0x43, 0xd5, 0x3e, 0xbb,
- 0x15, 0x00, 0xd3, 0xe9, 0xb4, 0xdb, 0x6c, 0x36, 0x9f, 0x25, 0xe4, 0x5b,
- 0x89, 0xf1, 0x4b, 0x62, 0x83, 0x78, 0xc8, 0x27, 0x1b, 0x33, 0x56, 0x00,
- 0xd8, 0x18, 0x4a, 0x8b, 0x4c, 0xd8, 0x8f, 0x1c, 0x80, 0x30, 0x22, 0x59,
- 0x7b, 0xce, 0x2b, 0x20, 0x6b, 0x19, 0x0f, 0xc7, 0x9b, 0x57, 0x40, 0x18,
- 0x91, 0x24, 0x9e, 0x71, 0xb6, 0x68, 0x8d, 0x46, 0xa3, 0xef, 0xe8, 0xd4,
- 0xee, 0xfa, 0xfd, 0x7e, 0x87, 0xee, 0x31, 0xc7, 0x36, 0x2d, 0x1a, 0xbe,
- 0x25, 0x5a, 0x01, 0x38, 0xa0, 0x3c, 0x20, 0xe8, 0x39, 0xce, 0x16, 0x2f,
- 0xb3, 0xd9, 0xec, 0x2b, 0x02, 0x3a, 0xf7, 0x3c, 0xef, 0x92, 0xee, 0x31,
- 0xe7, 0x9f, 0x34, 0xc1, 0xc3, 0xb6, 0xaf, 0x90, 0x73, 0x36, 0x12, 0x01,
- 0x00, 0x59, 0xbe, 0xa6, 0xc0, 0xd1, 0xad, 0xb1, 0x6d, 0x35, 0x78, 0x4e,
- 0x88, 0x17, 0x32, 0xef, 0x9c, 0x45, 0x19, 0xa1, 0x88, 0x05, 0xa0, 0x56,
- 0xab, 0x79, 0x61, 0xa2, 0xce, 0x2b, 0x42, 0xe7, 0xca, 0x12, 0x32, 0xfa,
- 0x0b, 0x59, 0xbe, 0x59, 0x99, 0x14, 0x3c, 0x40, 0xe6, 0x96, 0x64, 0x05,
- 0xac, 0x3e, 0x0b, 0xf9, 0x14, 0xf6, 0x93, 0x9e, 0xfd, 0xc5, 0x88, 0x5f,
- 0x2c, 0x00, 0x11, 0xb2, 0xec, 0x12, 0xb2, 0x78, 0x8d, 0x8c, 0x7e, 0x62,
- 0x19, 0xb7, 0x30, 0x90, 0x2c, 0x74, 0xa8, 0x56, 0x82, 0x2a, 0x00, 0xc8,
- 0xa2, 0x71, 0xe6, 0xc3, 0x58, 0x40, 0xc7, 0x6d, 0x78, 0xce, 0xe5, 0xb3,
- 0x1a, 0x00, 0xd8, 0xc7, 0x77, 0xae, 0x1c, 0xc5, 0x56, 0x50, 0x7b, 0x27,
- 0xa1, 0x06, 0x00, 0x82, 0x3f, 0x07, 0x39, 0x19, 0xd8, 0x0a, 0xec, 0x1f,
- 0x4f, 0x5b, 0x43, 0x2a, 0x00, 0x20, 0xfb, 0xce, 0x82, 0x0f, 0x02, 0xd3,
- 0xea, 0x13, 0x54, 0x00, 0x28, 0x14, 0x0a, 0x97, 0x81, 0xe3, 0xae, 0xae,
- 0xe5, 0x72, 0xf9, 0x83, 0x2b, 0x5d, 0xcb, 0x7a, 0x54, 0x00, 0xc0, 0xcb,
- 0x89, 0xf6, 0xb2, 0x11, 0x17, 0xf7, 0xb6, 0x3a, 0x39, 0xdb, 0x2a, 0x00,
- 0x70, 0x46, 0xd3, 0xb4, 0xae, 0x02, 0x40, 0xb1, 0x58, 0x74, 0xde, 0xce,
- 0x6a, 0xe8, 0xa4, 0x44, 0xa8, 0x00, 0x30, 0x1e, 0x8f, 0xff, 0x90, 0x72,
- 0x97, 0xa4, 0xa1, 0x93, 0xfc, 0x53, 0x01, 0x80, 0xda, 0x52, 0x52, 0xee,
- 0x92, 0x34, 0x74, 0x92, 0x7f, 0x2a, 0x00, 0x90, 0x62, 0xd0, 0x3d, 0xc8,
- 0xd5, 0x70, 0xa9, 0x6b, 0xc5, 0x27, 0x35, 0x00, 0x70, 0x10, 0xb9, 0x58,
- 0xb1, 0x14, 0xe3, 0xc1, 0xa5, 0xae, 0xb0, 0x1b, 0x6a, 0x00, 0x2c, 0x0c,
- 0x5d, 0x2d, 0xae, 0xd6, 0x17, 0x74, 0x81, 0xef, 0xad, 0x85, 0x05, 0x82,
- 0xaa, 0x00, 0x20, 0x73, 0x1d, 0xbc, 0xec, 0xf8, 0x29, 0xf0, 0x63, 0x23,
- 0x0b, 0x0e, 0x42, 0xbf, 0xeb, 0xf5, 0xfa, 0xdf, 0x8d, 0x8b, 0x8e, 0x26,
- 0x59, 0x00, 0xd0, 0xd6, 0xae, 0x7d, 0x1f, 0xc0, 0xa4, 0x2d, 0x6d, 0x34,
- 0x1a, 0x5f, 0xe0, 0xab, 0x4d, 0x25, 0x5c, 0x55, 0xab, 0xd5, 0xcf, 0x90,
- 0x15, 0x0d, 0xf2, 0x69, 0x93, 0xaf, 0x9c, 0x30, 0x0b, 0x00, 0xa7, 0x40,
- 0xb2, 0x4e, 0x95, 0x00, 0xf2, 0xc0, 0xfb, 0x08, 0x8a, 0x1c, 0xc8, 0xfa,
- 0x23, 0xf1, 0x82, 0x3a, 0x91, 0x8c, 0x8e, 0x16, 0x13, 0x01, 0x20, 0xf0,
- 0x15, 0x41, 0x9d, 0x81, 0x3c, 0x7c, 0x98, 0x79, 0x8c, 0xf3, 0xc2, 0x0f,
- 0xec, 0xef, 0x7f, 0x58, 0xbb, 0xa7, 0x7b, 0xcc, 0xb5, 0x69, 0x0d, 0x59,
- 0x3f, 0xc3, 0x5c, 0x62, 0x23, 0x51, 0x00, 0x82, 0xa8, 0xf0, 0x3f, 0xbd,
- 0x5b, 0xa9, 0x54, 0xbe, 0x61, 0x7f, 0xbf, 0x45, 0xd0, 0x17, 0x74, 0x8f,
- 0xb9, 0xe7, 0x60, 0x3d, 0xc9, 0xeb, 0x4e, 0x00, 0x48, 0x32, 0x40, 0xce,
- 0x56, 0x0e, 0x00, 0x87, 0xd0, 0xa1, 0xaf, 0x1f, 0x5c, 0x05, 0x98, 0x26,
- 0x2c, 0x07, 0xc0, 0x14, 0xb1, 0x43, 0xe3, 0xb7, 0xaa, 0x00, 0xbc, 0x9c,
- 0x68, 0x51, 0xe7, 0x25, 0xa1, 0xb8, 0x80, 0x49, 0x6c, 0x10, 0x0f, 0xf9,
- 0x64, 0x63, 0xcb, 0x0a, 0x00, 0x74, 0x6b, 0x37, 0xf4, 0x61, 0xa6, 0x84,
- 0x6c, 0x9c, 0x5a, 0x96, 0x91, 0xd8, 0x20, 0x1e, 0xf2, 0x69, 0x59, 0x4e,
- 0x7a, 0x2f, 0x01, 0x60, 0x27, 0x0d, 0x8a, 0x34, 0x00, 0x86, 0x8f, 0x7d,
- 0x8f, 0xc0, 0x02, 0x80, 0x16, 0x35, 0xd1, 0xd6, 0x94, 0x09, 0xc8, 0x68,
- 0x99, 0xba, 0x4c, 0x4e, 0x80, 0x05, 0x00, 0x2d, 0xea, 0x7f, 0x28, 0xb1,
- 0x39, 0xcd, 0x41, 0x6c, 0x77, 0x03, 0x5b, 0x42, 0x94, 0x38, 0x16, 0x00,
- 0x0a, 0x01, 0x48, 0x76, 0x50, 0x09, 0x6f, 0x70, 0xcf, 0x9e, 0xe6, 0xc0,
- 0xb3, 0xeb, 0xd1, 0x85, 0xbf, 0x1e, 0x0e, 0x55, 0x22, 0x5f, 0x45, 0x00,
- 0x50, 0x44, 0x54, 0x09, 0x50, 0xec, 0x9f, 0xe6, 0x70, 0x5d, 0xfb, 0xce,
- 0xc0, 0xb6, 0x39, 0x92, 0x8d, 0x43, 0xdb, 0xf4, 0x46, 0xcc, 0x1f, 0x9b,
- 0xd8, 0x13, 0x03, 0x60, 0xa2, 0x74, 0x9f, 0x78, 0x73, 0x00, 0xf6, 0x29,
- 0x5b, 0x1a, 0xbe, 0xe6, 0x15, 0xa0, 0x81, 0xea, 0x3e, 0xe9, 0xcc, 0x2b,
- 0x60, 0x9f, 0xb2, 0xb5, 0xc9, 0xd7, 0xb8, 0x73, 0xaf, 0x00, 0x00, 0x00,
- 0xff, 0xff, 0xab, 0x0d, 0x5f, 0x8b, 0x00, 0x00, 0x00, 0x06, 0x49, 0x44,
- 0x41, 0x54, 0x03, 0x00, 0x5c, 0x2f, 0x0d, 0x9f, 0xd1, 0xb6, 0x2a, 0x31,
- 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
-};
-unsigned int center_focus_strong_png_len = 984;
diff --git a/src/icons/folder.h b/src/icons/folder.h
deleted file mode 100644
index 02ca6b3..0000000
--- a/src/icons/folder.h
+++ /dev/null
@@ -1,50 +0,0 @@
-unsigned char folder_png[] = {
- 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
- 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40,
- 0x08, 0x06, 0x00, 0x00, 0x00, 0xaa, 0x69, 0x71, 0xde, 0x00, 0x00, 0x01,
- 0xe1, 0x49, 0x44, 0x41, 0x54, 0x78, 0x01, 0xec, 0x95, 0xb1, 0x6e, 0x83,
- 0x30, 0x10, 0x86, 0xc1, 0x02, 0xa9, 0x5b, 0x3b, 0x66, 0x00, 0x24, 0x6f,
- 0xed, 0x96, 0xbc, 0x45, 0x1f, 0xa5, 0x5b, 0xc7, 0x8e, 0xed, 0x23, 0x74,
- 0x6c, 0x9f, 0xa2, 0x7d, 0x93, 0x64, 0xec, 0x46, 0x10, 0x43, 0xd4, 0x27,
- 0xe8, 0x82, 0xa0, 0x77, 0x99, 0xa2, 0x60, 0x5f, 0x6c, 0x41, 0x12, 0xcc,
- 0x5d, 0xc4, 0x2f, 0x12, 0x7c, 0x36, 0xf7, 0x7f, 0xfe, 0x21, 0x2a, 0x62,
- 0xfe, 0x11, 0x00, 0xcc, 0x03, 0x10, 0x49, 0x02, 0x24, 0x01, 0xcc, 0x09,
- 0xc8, 0x23, 0xc0, 0x3c, 0x00, 0xf2, 0x12, 0x94, 0x47, 0xc0, 0xf5, 0x11,
- 0x28, 0xcb, 0x72, 0x01, 0x5a, 0x0e, 0x91, 0xeb, 0xbd, 0x2e, 0x59, 0x47,
- 0x26, 0x00, 0xcc, 0x2e, 0xea, 0xba, 0xee, 0x50, 0x49, 0x92, 0xec, 0x40,
- 0xeb, 0x21, 0xaa, 0xaa, 0xea, 0xed, 0x92, 0xe6, 0x5c, 0xee, 0x65, 0x05,
- 0x00, 0xe6, 0x97, 0x60, 0x76, 0xe7, 0xb2, 0x88, 0x6b, 0x8d, 0x52, 0xea,
- 0x75, 0x6a, 0x10, 0xac, 0x00, 0xc0, 0xfc, 0xda, 0xd5, 0x98, 0x4f, 0xdd,
- 0xd4, 0x20, 0x18, 0x01, 0x40, 0xe4, 0x1f, 0x7d, 0x4c, 0xf9, 0xd6, 0x4e,
- 0x09, 0x82, 0x11, 0x40, 0xd7, 0x75, 0x4f, 0xbe, 0xa6, 0x7c, 0xeb, 0xaf,
- 0x05, 0xe1, 0xb8, 0x4f, 0x23, 0x80, 0x38, 0x8e, 0x6f, 0x8f, 0x0b, 0xcf,
- 0xf1, 0x1b, 0x21, 0x40, 0xda, 0xf6, 0x2f, 0xd9, 0x33, 0x9d, 0xdf, 0x4f,
- 0xf5, 0x6d, 0x03, 0x70, 0x77, 0x6a, 0x62, 0x20, 0xe3, 0xcf, 0x08, 0x96,
- 0xea, 0xd5, 0x08, 0x80, 0x9a, 0x10, 0xe2, 0x18, 0x40, 0xf8, 0xb3, 0xf5,
- 0xcd, 0x02, 0x00, 0x98, 0xbf, 0xc1, 0xbf, 0x75, 0x38, 0xf7, 0x0e, 0x2e,
- 0x00, 0xa2, 0x34, 0x4d, 0x5f, 0x7a, 0xee, 0xe1, 0x02, 0x1b, 0x00, 0xe0,
- 0xf5, 0x1e, 0xd4, 0x3b, 0x38, 0x01, 0xe8, 0x99, 0xc7, 0x0b, 0x02, 0x00,
- 0x29, 0x70, 0x96, 0x24, 0x80, 0xf3, 0xee, 0xa3, 0x77, 0x49, 0x00, 0x52,
- 0xe0, 0x2c, 0x49, 0x00, 0xe7, 0xdd, 0x47, 0xef, 0xb3, 0x4f, 0x00, 0x9a,
- 0xa4, 0x24, 0x00, 0x28, 0x3a, 0x1c, 0xc6, 0x24, 0x01, 0x1c, 0x76, 0x99,
- 0xf2, 0x28, 0x09, 0xa0, 0xe8, 0x70, 0x18, 0x93, 0x04, 0x70, 0xd8, 0x65,
- 0xca, 0xa3, 0x24, 0x80, 0xa2, 0xc3, 0x61, 0x4c, 0x12, 0xc0, 0x61, 0x97,
- 0x29, 0x8f, 0x92, 0x00, 0x8a, 0x0e, 0x87, 0x31, 0x49, 0xc0, 0xdc, 0x76,
- 0xd9, 0xd7, 0x8f, 0x31, 0x01, 0x6d, 0xdb, 0xfe, 0xfa, 0x2e, 0x34, 0xf5,
- 0x7a, 0x9b, 0x27, 0x23, 0x00, 0x30, 0xf3, 0x01, 0x9a, 0xd5, 0xa1, 0x94,
- 0xfa, 0x32, 0x19, 0x32, 0x02, 0x28, 0x8a, 0xe2, 0xdb, 0x54, 0x1c, 0xf2,
- 0xb5, 0x2c, 0xcb, 0x3e, 0x4d, 0xfd, 0x1b, 0x01, 0x60, 0x61, 0xd3, 0x34,
- 0x2b, 0x3c, 0xcf, 0x41, 0x94, 0x17, 0x2b, 0x00, 0xad, 0xf5, 0x06, 0x26,
- 0xea, 0xd0, 0x01, 0xa0, 0x07, 0xf4, 0x62, 0xf3, 0x61, 0x05, 0x80, 0x13,
- 0x60, 0xe2, 0x36, 0xcf, 0xf3, 0x18, 0x05, 0x0b, 0x3d, 0x80, 0x56, 0x81,
- 0x48, 0x63, 0xcf, 0x28, 0xf4, 0x80, 0x5e, 0x6c, 0x22, 0x01, 0x1c, 0x4e,
- 0x82, 0x85, 0x7e, 0x40, 0x9b, 0x40, 0xb4, 0x3d, 0xec, 0x9d, 0xfa, 0xee,
- 0x0c, 0x80, 0x5a, 0x24, 0xe4, 0x31, 0x01, 0x10, 0xf2, 0xee, 0x8d, 0xd1,
- 0xbb, 0x24, 0x60, 0x0c, 0x8a, 0x21, 0xaf, 0x21, 0x09, 0x08, 0x79, 0xf7,
- 0xc6, 0xe8, 0x5d, 0x12, 0x30, 0x06, 0xc5, 0x6b, 0xae, 0x31, 0xf4, 0xde,
- 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xe0, 0xd2, 0x2d, 0x47, 0x00, 0x00,
- 0x00, 0x06, 0x49, 0x44, 0x41, 0x54, 0x03, 0x00, 0x55, 0xba, 0xa5, 0x90,
- 0xa1, 0x07, 0x34, 0xd0, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44,
- 0xae, 0x42, 0x60, 0x82
-};
-unsigned int folder_png_len = 556;
diff --git a/src/icons/icons.h b/src/icons/icons.h
deleted file mode 100644
index edc42a7..0000000
--- a/src/icons/icons.h
+++ /dev/null
@@ -1,10 +0,0 @@
-#include "center_focus_strong.h"
-#include "folder.h"
-#include "navigate_before.h"
-#include "navigate_next.h"
-#include "pause.h"
-#include "play.h"
-#include "skip_next.h"
-#include "skip_previous.h"
-#include "zoom_in.h"
-#include "zoom_out.h"
\ No newline at end of file
diff --git a/src/icons/navigate_before.h b/src/icons/navigate_before.h
deleted file mode 100644
index 3826694..0000000
--- a/src/icons/navigate_before.h
+++ /dev/null
@@ -1,45 +0,0 @@
-unsigned char navigate_before_png[] = {
- 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
- 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40,
- 0x08, 0x06, 0x00, 0x00, 0x00, 0xaa, 0x69, 0x71, 0xde, 0x00, 0x00, 0x01,
- 0xa8, 0x49, 0x44, 0x41, 0x54, 0x78, 0x01, 0xec, 0xd7, 0xcd, 0x4d, 0xc3,
- 0x40, 0x10, 0x05, 0x60, 0x1b, 0xf9, 0x00, 0x37, 0x28, 0xc0, 0x96, 0x5c,
- 0x06, 0x47, 0x6e, 0xb4, 0x40, 0x85, 0xd0, 0x01, 0x2d, 0xd0, 0x85, 0x0f,
- 0x2e, 0x80, 0xdc, 0xc8, 0xc1, 0x92, 0xd9, 0xb1, 0x14, 0x29, 0x8a, 0x12,
- 0xdb, 0x90, 0xbc, 0x37, 0xcf, 0xda, 0x89, 0xb2, 0xf9, 0xd1, 0x46, 0xde,
- 0x79, 0x9f, 0x67, 0x13, 0xe7, 0xae, 0xc8, 0xfc, 0x16, 0x00, 0x99, 0x37,
- 0x40, 0x11, 0x1d, 0x10, 0x1d, 0x90, 0xb9, 0x40, 0x6c, 0x81, 0xcc, 0x1b,
- 0x20, 0xbe, 0x04, 0x63, 0x0b, 0xc4, 0x16, 0xc8, 0x5c, 0x20, 0xb6, 0x40,
- 0xe6, 0x0d, 0x10, 0xbf, 0x02, 0xd9, 0x6d, 0x81, 0xd3, 0x8e, 0x97, 0x03,
- 0xe8, 0xba, 0xee, 0xb1, 0xef, 0xfb, 0x9f, 0xd3, 0x42, 0x51, 0xef, 0xa5,
- 0x00, 0x2c, 0x7c, 0x55, 0x55, 0xdf, 0x29, 0xec, 0x3d, 0x0b, 0x41, 0x06,
- 0xe0, 0x28, 0x7c, 0xca, 0x3f, 0xdd, 0x29, 0x08, 0x12, 0x00, 0x67, 0xc2,
- 0x4f, 0x02, 0xe9, 0x01, 0x8e, 0xe0, 0x0e, 0x30, 0x13, 0x3e, 0xe5, 0x2f,
- 0x8a, 0x61, 0x18, 0x5e, 0xa6, 0x17, 0xa0, 0x07, 0x57, 0x80, 0x15, 0xe1,
- 0x9f, 0xdb, 0xb6, 0xfd, 0x02, 0x65, 0x9f, 0x0e, 0xeb, 0x06, 0xa0, 0x10,
- 0xde, 0x04, 0x5c, 0x00, 0x54, 0xc2, 0xbb, 0x00, 0x28, 0x85, 0xa7, 0x03,
- 0xa8, 0x85, 0xa7, 0x02, 0x28, 0x86, 0xa7, 0x01, 0xa8, 0x86, 0xa7, 0x00,
- 0x28, 0x87, 0x87, 0x03, 0xa8, 0x87, 0x87, 0x02, 0x2c, 0x85, 0x1f, 0xc7,
- 0xf1, 0x0d, 0x7d, 0x91, 0x63, 0x01, 0x97, 0x06, 0xe4, 0x3a, 0x60, 0x4d,
- 0xf8, 0xa6, 0x69, 0x3e, 0x96, 0x8a, 0xbb, 0xc5, 0xfc, 0xd2, 0x31, 0x20,
- 0x00, 0x69, 0xd1, 0x7d, 0x1a, 0x17, 0xef, 0x65, 0x59, 0xee, 0x2e, 0x4e,
- 0x92, 0x27, 0x20, 0x00, 0xa9, 0xb5, 0xf7, 0xe9, 0x4f, 0xcc, 0xc3, 0x4c,
- 0x96, 0xcf, 0xf4, 0x7f, 0xff, 0x75, 0x66, 0x9e, 0x36, 0x05, 0x01, 0xb0,
- 0xea, 0xb7, 0x82, 0x00, 0x03, 0xd8, 0x0a, 0x02, 0x14, 0x60, 0x0b, 0x08,
- 0x70, 0x00, 0x75, 0x04, 0x0a, 0x80, 0x32, 0x02, 0x0d, 0x40, 0x15, 0x81,
- 0x0a, 0xa0, 0x88, 0x40, 0x07, 0x50, 0x43, 0x70, 0x01, 0x50, 0x42, 0x70,
- 0x03, 0x50, 0x41, 0x70, 0x05, 0x58, 0x89, 0xf0, 0x6e, 0x9f, 0x43, 0x0d,
- 0x77, 0x00, 0x0b, 0x36, 0x73, 0xd9, 0xbc, 0xab, 0xeb, 0xfa, 0xc9, 0x3e,
- 0x83, 0x1a, 0x12, 0x00, 0x16, 0xee, 0x0c, 0x02, 0x3c, 0xbc, 0xad, 0x2b,
- 0x03, 0x60, 0xc5, 0x1c, 0x21, 0xfc, 0x3b, 0xbc, 0x1d, 0xe7, 0x2f, 0x43,
- 0x0a, 0xc0, 0x0a, 0x37, 0x04, 0x74, 0xdb, 0xdb, 0x3a, 0x87, 0x21, 0x07,
- 0x70, 0x28, 0x8c, 0xf5, 0x1c, 0x00, 0x2c, 0x69, 0xd5, 0x75, 0xa2, 0x03,
- 0x54, 0xcf, 0x0c, 0xab, 0xae, 0xe8, 0x00, 0x96, 0xb4, 0xea, 0x3a, 0xd1,
- 0x01, 0xaa, 0x67, 0x86, 0x55, 0x57, 0x74, 0x00, 0x4b, 0x5a, 0x75, 0x9d,
- 0xe8, 0x00, 0xd5, 0x33, 0xc3, 0xaa, 0x2b, 0x3a, 0x80, 0x25, 0x8d, 0x5a,
- 0xe7, 0xda, 0xe3, 0xfe, 0x02, 0x00, 0x00, 0xff, 0xff, 0x54, 0xb9, 0x82,
- 0x91, 0x00, 0x00, 0x00, 0x06, 0x49, 0x44, 0x41, 0x54, 0x03, 0x00, 0x07,
- 0xde, 0xed, 0x81, 0xdd, 0xcd, 0x75, 0xfa, 0x00, 0x00, 0x00, 0x00, 0x49,
- 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
-};
-unsigned int navigate_before_png_len = 499;
diff --git a/src/icons/navigate_next.h b/src/icons/navigate_next.h
deleted file mode 100644
index c9dc50e..0000000
--- a/src/icons/navigate_next.h
+++ /dev/null
@@ -1,44 +0,0 @@
-unsigned char navigate_next_png[] = {
- 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
- 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40,
- 0x08, 0x06, 0x00, 0x00, 0x00, 0xaa, 0x69, 0x71, 0xde, 0x00, 0x00, 0x01,
- 0x9e, 0x49, 0x44, 0x41, 0x54, 0x78, 0x01, 0xec, 0xd8, 0x3d, 0x6e, 0xc2,
- 0x40, 0x10, 0x05, 0x60, 0x1c, 0xb9, 0x48, 0x09, 0x07, 0x70, 0xe1, 0x9b,
- 0xa4, 0x4d, 0x97, 0x36, 0xa7, 0xa4, 0xa5, 0xe3, 0x28, 0x96, 0xec, 0x03,
- 0xc0, 0x01, 0x2c, 0x99, 0x79, 0x48, 0x6e, 0x8c, 0x41, 0xfc, 0xe8, 0xcd,
- 0x3c, 0x69, 0x27, 0xf2, 0x06, 0x01, 0x62, 0x3d, 0xef, 0xf3, 0x2c, 0x2c,
- 0x7c, 0x6d, 0x0a, 0xff, 0x4b, 0x80, 0xc2, 0x1b, 0x60, 0x93, 0x1d, 0x90,
- 0x1d, 0x50, 0xb8, 0x40, 0x2e, 0x81, 0xc2, 0x1b, 0x20, 0xdf, 0x04, 0x73,
- 0x09, 0xe4, 0x12, 0x28, 0x5c, 0x20, 0x97, 0x40, 0xe1, 0x0d, 0x90, 0x9f,
- 0x02, 0xc5, 0x2d, 0x81, 0x65, 0xc7, 0xbb, 0x01, 0x0c, 0xc3, 0x70, 0xea,
- 0xba, 0x6e, 0xbb, 0x2c, 0x20, 0xfa, 0xbe, 0x0b, 0x00, 0xc2, 0x5b, 0xd0,
- 0x6d, 0x5d, 0xd7, 0x72, 0x08, 0x74, 0x80, 0x39, 0xbc, 0x01, 0x5c, 0x0f,
- 0x35, 0x04, 0x2a, 0xc0, 0x32, 0xfc, 0x55, 0xc0, 0xfe, 0x29, 0x21, 0x50,
- 0x01, 0x2c, 0xeb, 0xbf, 0x8d, 0xd5, 0x43, 0x05, 0x81, 0x0a, 0xd0, 0x34,
- 0xcd, 0xc1, 0xd2, 0xff, 0xda, 0x58, 0x3d, 0x14, 0x10, 0xa8, 0x00, 0x48,
- 0xad, 0x8e, 0x40, 0x07, 0x50, 0x47, 0x70, 0x01, 0x50, 0x46, 0x70, 0x03,
- 0x50, 0x45, 0x70, 0x05, 0x50, 0x44, 0x70, 0x07, 0x50, 0x43, 0x08, 0x01,
- 0x50, 0x42, 0x08, 0x03, 0x50, 0x41, 0x08, 0x05, 0x50, 0x40, 0x08, 0x07,
- 0x60, 0x23, 0x60, 0xfe, 0x47, 0x43, 0x02, 0x00, 0x05, 0x4e, 0xd3, 0xf4,
- 0x8d, 0x5b, 0xef, 0x21, 0x01, 0xd0, 0xf7, 0xfd, 0x5f, 0x55, 0x55, 0xfb,
- 0x7b, 0xe1, 0xc7, 0x71, 0xdc, 0xb5, 0x6d, 0x7b, 0xbe, 0xf7, 0xfc, 0x27,
- 0x8f, 0x87, 0x03, 0xd8, 0xaf, 0x44, 0x3f, 0x55, 0x50, 0x78, 0xc0, 0x85,
- 0x02, 0x20, 0xbc, 0x7d, 0x23, 0x3c, 0xa2, 0x90, 0xb5, 0xc1, 0xbc, 0xf2,
- 0xf3, 0xf9, 0xc2, 0x00, 0x14, 0xc2, 0x03, 0x21, 0x04, 0x40, 0x25, 0x7c,
- 0x08, 0x80, 0x52, 0x78, 0x77, 0x00, 0xb5, 0xf0, 0xae, 0x00, 0x8a, 0xe1,
- 0xdd, 0x00, 0x54, 0xc3, 0xbb, 0x00, 0x28, 0x87, 0xa7, 0x03, 0xa8, 0x87,
- 0xa7, 0x03, 0xd8, 0x26, 0x27, 0x64, 0x7b, 0x8b, 0x60, 0xcf, 0x0e, 0xea,
- 0x3e, 0xc0, 0x7e, 0x12, 0xdf, 0x59, 0x21, 0x37, 0x7b, 0x78, 0x8f, 0x1d,
- 0x9e, 0x9d, 0xf7, 0xa9, 0x83, 0x0a, 0x80, 0x0a, 0x96, 0x08, 0x4a, 0xe1,
- 0x51, 0x1f, 0x1d, 0x00, 0x27, 0x99, 0x11, 0x3c, 0xc2, 0xe3, 0x7c, 0xaf,
- 0x0c, 0x17, 0x00, 0x14, 0x04, 0x04, 0xd6, 0x57, 0x5a, 0xcc, 0xff, 0xee,
- 0x70, 0x03, 0x78, 0xb7, 0x40, 0xf6, 0xeb, 0x12, 0x80, 0x2d, 0xac, 0x3e,
- 0x7f, 0x76, 0x80, 0xfa, 0x15, 0x62, 0xd7, 0x97, 0x1d, 0xc0, 0x16, 0x56,
- 0x9f, 0x3f, 0x3b, 0x40, 0xfd, 0x0a, 0xb1, 0xeb, 0xcb, 0x0e, 0x60, 0x0b,
- 0xab, 0xcf, 0x9f, 0x1d, 0xa0, 0x7e, 0x85, 0xd8, 0xf5, 0x65, 0x07, 0xb0,
- 0x85, 0xd9, 0xf3, 0x7f, 0x3a, 0xff, 0x05, 0x00, 0x00, 0xff, 0xff, 0x22,
- 0x66, 0x2e, 0xf3, 0x00, 0x00, 0x00, 0x06, 0x49, 0x44, 0x41, 0x54, 0x03,
- 0x00, 0x4d, 0xeb, 0xca, 0x81, 0xd1, 0x01, 0x58, 0xbd, 0x00, 0x00, 0x00,
- 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
-};
-unsigned int navigate_next_png_len = 489;
diff --git a/src/icons/pause.h b/src/icons/pause.h
deleted file mode 100644
index 11c6ddf..0000000
--- a/src/icons/pause.h
+++ /dev/null
@@ -1,35 +0,0 @@
-unsigned char pause_png[] = {
- 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
- 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40,
- 0x08, 0x06, 0x00, 0x00, 0x00, 0xaa, 0x69, 0x71, 0xde, 0x00, 0x00, 0x01,
- 0x30, 0x49, 0x44, 0x41, 0x54, 0x78, 0x01, 0xec, 0x94, 0xc1, 0x0d, 0x83,
- 0x30, 0x0c, 0x45, 0xa1, 0x73, 0x30, 0x00, 0xc7, 0x76, 0x1c, 0x26, 0x64,
- 0x9c, 0xf6, 0xc8, 0x00, 0xec, 0x41, 0xcd, 0xd5, 0x52, 0x64, 0x47, 0x91,
- 0xa2, 0x60, 0xbf, 0x0a, 0x1f, 0x42, 0x5c, 0xc7, 0xfe, 0xff, 0x85, 0xd7,
- 0x94, 0xfc, 0x87, 0x00, 0xc9, 0x01, 0x98, 0x20, 0x00, 0x02, 0x92, 0x2b,
- 0xc0, 0x15, 0x48, 0x0e, 0x00, 0x1f, 0x41, 0xae, 0x00, 0x57, 0x20, 0xb9,
- 0x02, 0x5c, 0x81, 0x56, 0x00, 0xce, 0xf3, 0xfc, 0x3a, 0x63, 0xf7, 0x9e,
- 0x25, 0xf5, 0x76, 0x09, 0x57, 0x5d, 0x6f, 0xcd, 0x52, 0x5e, 0x33, 0x01,
- 0xd7, 0x75, 0xbd, 0x3d, 0x21, 0x0d, 0xac, 0x12, 0xde, 0x67, 0xf5, 0xd4,
- 0xbc, 0x73, 0xbc, 0x05, 0x4b, 0x79, 0xcd, 0x02, 0x94, 0x0a, 0x8f, 0xfa,
- 0x5e, 0xf7, 0x85, 0x00, 0x5a, 0x91, 0x6c, 0x6b, 0x08, 0xc8, 0xe6, 0xb8,
- 0x9e, 0x17, 0x02, 0xb4, 0x22, 0xd9, 0xd6, 0x10, 0x90, 0xcd, 0x71, 0x3d,
- 0x2f, 0x04, 0x68, 0x45, 0xb2, 0xad, 0x21, 0x20, 0x9b, 0xe3, 0x7a, 0x5e,
- 0x08, 0xd0, 0x8a, 0x64, 0x5b, 0x43, 0x40, 0x74, 0xc7, 0xad, 0xf9, 0x20,
- 0xc0, 0x52, 0x28, 0xfa, 0x3e, 0x04, 0x44, 0x77, 0xd8, 0x9a, 0x0f, 0x02,
- 0x2c, 0x85, 0xa2, 0xef, 0x43, 0x40, 0x74, 0x87, 0xad, 0xf9, 0x20, 0xc0,
- 0x52, 0x28, 0xfa, 0x3e, 0x04, 0x44, 0x77, 0xd8, 0x9a, 0x0f, 0x02, 0x2c,
- 0x85, 0xa2, 0xef, 0x43, 0x40, 0x34, 0x87, 0x6b, 0xe7, 0x81, 0x80, 0x5a,
- 0xc5, 0xa2, 0xe5, 0x37, 0x13, 0x30, 0xcf, 0xf3, 0xcf, 0x13, 0x22, 0xdc,
- 0x21, 0xe1, 0x7d, 0x0e, 0x4f, 0xcd, 0x3b, 0xc7, 0x5b, 0xb0, 0x94, 0xd7,
- 0x2c, 0xc0, 0xb2, 0x2c, 0x1f, 0x67, 0x6c, 0xa5, 0x26, 0xf4, 0x7b, 0xa9,
- 0xb7, 0x49, 0xb8, 0xea, 0xea, 0xff, 0xd6, 0xae, 0x9b, 0x05, 0xa8, 0x3d,
- 0x70, 0xb4, 0x7c, 0x04, 0x18, 0xcd, 0x91, 0xde, 0xfd, 0x40, 0x40, 0x6f,
- 0xc5, 0x47, 0x3b, 0x0f, 0x02, 0x46, 0x73, 0xa4, 0x77, 0x3f, 0x10, 0xd0,
- 0x5b, 0xf1, 0xd1, 0xce, 0x83, 0x80, 0xd1, 0x1c, 0xe9, 0xdd, 0xcf, 0xe3,
- 0x09, 0x68, 0x15, 0xec, 0x0f, 0x00, 0x00, 0xff, 0xff, 0x82, 0x3f, 0xf1,
- 0x58, 0x00, 0x00, 0x00, 0x06, 0x49, 0x44, 0x41, 0x54, 0x03, 0x00, 0xc8,
- 0x93, 0xaa, 0x81, 0x34, 0x99, 0x4c, 0xfb, 0x00, 0x00, 0x00, 0x00, 0x49,
- 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
-};
-unsigned int pause_png_len = 379;
diff --git a/src/icons/play.h b/src/icons/play.h
deleted file mode 100644
index 1f9ad63..0000000
--- a/src/icons/play.h
+++ /dev/null
@@ -1,58 +0,0 @@
-unsigned char play_png[] = {
- 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
- 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40,
- 0x08, 0x06, 0x00, 0x00, 0x00, 0xaa, 0x69, 0x71, 0xde, 0x00, 0x00, 0x02,
- 0x40, 0x49, 0x44, 0x41, 0x54, 0x78, 0x01, 0xec, 0x98, 0x3d, 0x52, 0xc3,
- 0x30, 0x10, 0x85, 0x6d, 0x92, 0x82, 0x94, 0x74, 0x14, 0xf9, 0xc1, 0x1d,
- 0x57, 0xe0, 0x06, 0x70, 0x0a, 0x4a, 0x28, 0xe1, 0x26, 0x74, 0x40, 0xc9,
- 0x29, 0xe0, 0x08, 0xdc, 0x80, 0xd2, 0x24, 0x29, 0x68, 0xe9, 0x92, 0x22,
- 0x89, 0x79, 0xab, 0xb1, 0x67, 0x3c, 0x8c, 0x94, 0x22, 0xd6, 0x4a, 0xab,
- 0x78, 0x33, 0x91, 0x65, 0xed, 0xc4, 0xb2, 0xde, 0xa7, 0x95, 0x9e, 0x9d,
- 0x93, 0xac, 0xe7, 0x1f, 0x05, 0xd0, 0xf3, 0x04, 0xc8, 0x34, 0x03, 0x34,
- 0x03, 0x7a, 0x4e, 0x40, 0x97, 0x40, 0xcf, 0x13, 0x40, 0x37, 0x41, 0x5d,
- 0x02, 0xba, 0x04, 0x7a, 0x4e, 0x40, 0x97, 0x00, 0x47, 0x02, 0xcc, 0xe7,
- 0xf3, 0x07, 0x8e, 0x7e, 0x39, 0xfa, 0x64, 0xc9, 0x80, 0xc1, 0x60, 0x70,
- 0xbb, 0x5c, 0x2e, 0xab, 0xb2, 0x2c, 0x2f, 0x38, 0x06, 0xed, 0xb3, 0x4f,
- 0x16, 0x00, 0xcd, 0x00, 0x87, 0xc3, 0x61, 0x09, 0x10, 0x5f, 0x4d, 0x5b,
- 0x42, 0xfd, 0x7f, 0x0c, 0xac, 0x00, 0xea, 0x9b, 0x5d, 0x02, 0x42, 0x85,
- 0x72, 0x5d, 0xb7, 0x45, 0x55, 0x21, 0x00, 0x34, 0x82, 0xdf, 0x01, 0xe1,
- 0xa7, 0x69, 0x48, 0xa9, 0x43, 0x02, 0x20, 0xcd, 0xe7, 0x80, 0x50, 0x2d,
- 0x16, 0x8b, 0x7b, 0x6a, 0x48, 0x28, 0xa1, 0x01, 0x18, 0xcd, 0x79, 0x9e,
- 0x3f, 0x03, 0xc4, 0x0a, 0x9b, 0xe4, 0xa9, 0x09, 0x44, 0x3c, 0x44, 0x01,
- 0x50, 0xeb, 0x3d, 0xc5, 0x26, 0xb9, 0x42, 0x36, 0xbc, 0xd4, 0xed, 0x28,
- 0x55, 0x4c, 0x00, 0x46, 0x30, 0xb2, 0xe1, 0x0e, 0xd9, 0x10, 0xcd, 0x32,
- 0xa3, 0x03, 0x30, 0x14, 0x70, 0x40, 0x36, 0x44, 0xb1, 0x4c, 0x31, 0x00,
- 0xc0, 0x80, 0xbe, 0xc1, 0x2d, 0x53, 0x1a, 0x00, 0x82, 0x40, 0x25, 0x98,
- 0x65, 0x4a, 0x05, 0x40, 0x10, 0x82, 0x58, 0xa6, 0x64, 0x00, 0x04, 0x21,
- 0xc3, 0x26, 0xc9, 0x6a, 0x99, 0xe2, 0x01, 0x18, 0x0a, 0x59, 0xc6, 0x66,
- 0x99, 0xa9, 0x00, 0x30, 0x1c, 0x90, 0x0d, 0xde, 0x2d, 0x33, 0x29, 0x00,
- 0x86, 0x02, 0x0e, 0x3e, 0x2d, 0x33, 0x49, 0x00, 0x60, 0x40, 0x5f, 0x2f,
- 0x96, 0x99, 0x32, 0x00, 0x82, 0x40, 0x65, 0xaf, 0x65, 0xd2, 0x0f, 0xf6,
- 0x95, 0x63, 0x00, 0x40, 0xfa, 0x0e, 0x7e, 0xa9, 0x3a, 0x06, 0x00, 0x37,
- 0x93, 0xc9, 0xe4, 0x8c, 0x28, 0x1c, 0x52, 0x92, 0x05, 0x00, 0x47, 0xf8,
- 0x84, 0xf0, 0x1c, 0xe5, 0xe3, 0x10, 0xe1, 0xcd, 0x35, 0x49, 0x02, 0xd8,
- 0x6c, 0x36, 0xc5, 0x78, 0x3c, 0xbe, 0x6a, 0x44, 0x74, 0xa9, 0x93, 0x02,
- 0x50, 0x55, 0xd5, 0x2b, 0x66, 0x3c, 0x2f, 0x8a, 0xe2, 0xbb, 0x8b, 0xe8,
- 0xf6, 0xb5, 0xa9, 0x00, 0x58, 0x63, 0xd6, 0x47, 0xd3, 0xe9, 0xd4, 0xfb,
- 0x5f, 0x69, 0xe2, 0x01, 0xec, 0x76, 0xbb, 0x47, 0xcc, 0xfa, 0x08, 0xb3,
- 0xbe, 0x6e, 0xcf, 0x9c, 0xaf, 0x73, 0xc9, 0x00, 0x7e, 0x21, 0x3c, 0x9f,
- 0xcd, 0x66, 0x4f, 0xbe, 0xc4, 0xda, 0xfa, 0x91, 0x0a, 0xa0, 0x93, 0xb5,
- 0xd9, 0x84, 0xba, 0x62, 0xa2, 0x00, 0xf8, 0xb2, 0x36, 0x97, 0x58, 0x5b,
- 0x5c, 0x0c, 0x00, 0x6c, 0x72, 0xde, 0xac, 0xcd, 0x26, 0xd4, 0x15, 0x8b,
- 0x0e, 0x80, 0xc3, 0xda, 0x5c, 0x62, 0x6d, 0xf1, 0x98, 0x00, 0xd8, 0xac,
- 0xcd, 0x26, 0xd4, 0x15, 0x8b, 0x02, 0x80, 0xdb, 0xda, 0x5c, 0x62, 0x6d,
- 0xf1, 0xd0, 0x00, 0x82, 0x58, 0x9b, 0x4d, 0xa8, 0x2b, 0x16, 0x12, 0x40,
- 0x10, 0x6b, 0x73, 0x09, 0x75, 0xc5, 0xd9, 0x01, 0xc4, 0xb0, 0x36, 0x97,
- 0x58, 0x5b, 0x9c, 0x15, 0x40, 0x2c, 0x6b, 0xb3, 0x09, 0x75, 0xc5, 0x58,
- 0x00, 0x6c, 0xb7, 0xdb, 0x37, 0x7a, 0x8c, 0xc5, 0xf3, 0xbb, 0xb7, 0xb7,
- 0x36, 0x97, 0x80, 0xae, 0x71, 0x16, 0x00, 0xdc, 0xcf, 0xef, 0x5d, 0x45,
- 0xb7, 0xaf, 0x67, 0x01, 0xd0, 0xbe, 0x81, 0xf4, 0x73, 0x05, 0x20, 0x7d,
- 0x86, 0xb8, 0xc7, 0xa7, 0x19, 0xc0, 0x4d, 0x58, 0x7a, 0xff, 0x9a, 0x01,
- 0xd2, 0x67, 0x88, 0x7b, 0x7c, 0x9a, 0x01, 0xdc, 0x84, 0xa5, 0xf7, 0xaf,
- 0x19, 0x20, 0x7d, 0x86, 0xb8, 0xc7, 0x97, 0x7c, 0x06, 0x74, 0x05, 0xf4,
- 0x07, 0x00, 0x00, 0xff, 0xff, 0xad, 0x38, 0x57, 0x55, 0x00, 0x00, 0x00,
- 0x06, 0x49, 0x44, 0x41, 0x54, 0x03, 0x00, 0x16, 0x98, 0xa6, 0x81, 0x52,
- 0xf2, 0x22, 0xbc, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae,
- 0x42, 0x60, 0x82
-};
-unsigned int play_png_len = 651;
diff --git a/src/icons/skip_next.h b/src/icons/skip_next.h
deleted file mode 100644
index 662b23f..0000000
--- a/src/icons/skip_next.h
+++ /dev/null
@@ -1,58 +0,0 @@
-unsigned char skip_next_png[] = {
- 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
- 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40,
- 0x08, 0x06, 0x00, 0x00, 0x00, 0xaa, 0x69, 0x71, 0xde, 0x00, 0x00, 0x02,
- 0x40, 0x49, 0x44, 0x41, 0x54, 0x78, 0x01, 0xec, 0x97, 0xa1, 0x73, 0xc2,
- 0x30, 0x14, 0xc6, 0xe9, 0x0e, 0x01, 0x6e, 0x78, 0x38, 0x56, 0x37, 0x39,
- 0x8b, 0xc3, 0xce, 0xcd, 0xce, 0xcd, 0x6d, 0x72, 0xfb, 0x4f, 0x36, 0xb5,
- 0xb9, 0xd9, 0xb9, 0xd9, 0x39, 0x2c, 0x72, 0x6e, 0xb2, 0x1c, 0xfc, 0x01,
- 0xc3, 0x81, 0xe0, 0x28, 0x5f, 0x72, 0x51, 0xb9, 0x52, 0xe8, 0x35, 0x79,
- 0x79, 0xb9, 0x3c, 0x8e, 0xb4, 0xbc, 0xb6, 0x97, 0xbc, 0xef, 0xf7, 0xbe,
- 0x84, 0xe6, 0xa2, 0x93, 0xf8, 0x47, 0x00, 0x24, 0x6e, 0x80, 0x8e, 0x38,
- 0x40, 0x1c, 0x90, 0x38, 0x01, 0x99, 0x02, 0x89, 0x1b, 0x40, 0x16, 0x41,
- 0x99, 0x02, 0x32, 0x05, 0x12, 0x27, 0x20, 0x53, 0x20, 0x71, 0x03, 0xc8,
- 0xbf, 0x40, 0x72, 0x53, 0xc0, 0x76, 0x7c, 0x6b, 0x00, 0xab, 0xd5, 0x6a,
- 0x66, 0x77, 0xea, 0x23, 0x2e, 0x8a, 0xe2, 0x06, 0x63, 0x95, 0x76, 0x6b,
- 0x3b, 0x56, 0x6b, 0x00, 0x48, 0x60, 0xaa, 0x92, 0x42, 0x82, 0x57, 0xf8,
- 0x1d, 0xdd, 0xd7, 0x05, 0x00, 0x2d, 0xba, 0xdb, 0xed, 0x16, 0xcb, 0xe5,
- 0xf2, 0x43, 0x07, 0x11, 0x1d, 0x9c, 0x01, 0x50, 0x9a, 0xb3, 0x2c, 0x7b,
- 0x84, 0x1b, 0x36, 0xea, 0x77, 0x2c, 0xcd, 0x29, 0x00, 0x23, 0xba, 0x07,
- 0x08, 0x25, 0xdc, 0xf0, 0x60, 0x62, 0xd6, 0x27, 0x1f, 0x00, 0xb4, 0x60,
- 0xb8, 0xe1, 0x13, 0x20, 0xfe, 0x74, 0xc0, 0xf8, 0xe0, 0x0d, 0x80, 0xd1,
- 0x7c, 0x0d, 0x08, 0x25, 0xe7, 0x05, 0xd2, 0x37, 0x00, 0xcd, 0x41, 0x2d,
- 0x90, 0x00, 0xf1, 0xad, 0x03, 0x66, 0x07, 0x12, 0x00, 0x46, 0xf3, 0x1d,
- 0x20, 0x6c, 0xe0, 0x86, 0x9e, 0x89, 0x59, 0x9c, 0x28, 0x01, 0x28, 0xc1,
- 0x3d, 0xb8, 0x61, 0x83, 0x05, 0xf2, 0x49, 0x05, 0x1c, 0x1a, 0x35, 0x00,
- 0xad, 0x19, 0x0b, 0xe4, 0x3b, 0xdc, 0xf0, 0xaf, 0x83, 0xc0, 0x87, 0x20,
- 0x00, 0x8c, 0xe6, 0x4b, 0x40, 0x50, 0x0b, 0xe4, 0xd4, 0xc4, 0x41, 0x4e,
- 0x21, 0x01, 0x68, 0xc1, 0x98, 0x12, 0x33, 0x80, 0x20, 0xd9, 0x4f, 0xe8,
- 0x01, 0xad, 0x43, 0x70, 0x00, 0x26, 0x9f, 0x60, 0xfb, 0x09, 0x2e, 0x00,
- 0x34, 0x07, 0xb8, 0xa1, 0xc0, 0x02, 0x49, 0xba, 0x9f, 0x60, 0x05, 0x40,
- 0x51, 0xc0, 0x02, 0xe9, 0x74, 0x3f, 0xa1, 0xfa, 0xac, 0x6b, 0xec, 0x00,
- 0xd4, 0x25, 0xeb, 0xe3, 0x1e, 0x47, 0x00, 0x6f, 0xa3, 0xd1, 0xa8, 0xef,
- 0x43, 0x6c, 0x55, 0x9f, 0xac, 0x00, 0xec, 0x76, 0xbb, 0x01, 0xc4, 0x3f,
- 0x57, 0x25, 0xea, 0xeb, 0x1a, 0x17, 0x00, 0x73, 0x08, 0xcf, 0xf2, 0x3c,
- 0x5f, 0xfb, 0x12, 0x7a, 0xac, 0xdf, 0xe0, 0x00, 0x50, 0xf5, 0x09, 0xc4,
- 0x4f, 0x8e, 0x25, 0xe8, 0xfb, 0x7a, 0x48, 0x00, 0x6b, 0x08, 0x57, 0x55,
- 0x9f, 0xfb, 0x16, 0x59, 0xd7, 0x7f, 0x10, 0x00, 0xfb, 0xfd, 0xfe, 0x05,
- 0xe2, 0x07, 0x75, 0x89, 0x51, 0xdd, 0x23, 0x07, 0x00, 0xcb, 0xf7, 0xc7,
- 0xe3, 0xf1, 0x2b, 0x95, 0xc0, 0x53, 0xe3, 0x90, 0x01, 0xc0, 0x0b, 0xce,
- 0x17, 0xaa, 0xae, 0x2c, 0xbf, 0x3d, 0x95, 0x14, 0xe5, 0x7d, 0x12, 0x00,
- 0xa8, 0x7a, 0x3e, 0x1c, 0x0e, 0xef, 0x29, 0x85, 0x9d, 0x3b, 0x96, 0x57,
- 0x00, 0xa8, 0xfa, 0xc2, 0x54, 0x7d, 0x71, 0x6e, 0x42, 0xd4, 0xcf, 0xf9,
- 0x04, 0x70, 0x8b, 0xaa, 0xe7, 0xd4, 0x82, 0x9a, 0x8e, 0xe7, 0x03, 0xc0,
- 0x56, 0x55, 0x1d, 0xed, 0xa7, 0x69, 0x32, 0x21, 0x9e, 0x77, 0x0d, 0x80,
- 0xf4, 0x3d, 0xde, 0x05, 0x30, 0x67, 0x00, 0xb0, 0xd0, 0x91, 0xbf, 0xc7,
- 0x73, 0x01, 0x10, 0xec, 0x3d, 0xbe, 0x0a, 0x40, 0xd3, 0x6b, 0xad, 0x1d,
- 0x80, 0xb9, 0x4e, 0xf2, 0x1e, 0x8f, 0x8d, 0xd2, 0x2f, 0xc6, 0xca, 0xec,
- 0xd6, 0x54, 0xb0, 0xfd, 0x7c, 0x6b, 0x00, 0x76, 0x87, 0xb1, 0xc5, 0x02,
- 0x20, 0xb6, 0x8a, 0xb9, 0xce, 0x57, 0x1c, 0xe0, 0x9a, 0x68, 0x6c, 0xfd,
- 0x89, 0x03, 0x62, 0xab, 0x98, 0xeb, 0x7c, 0xc5, 0x01, 0xae, 0x89, 0xc6,
- 0xd6, 0x9f, 0x38, 0x20, 0xb6, 0x8a, 0xb9, 0xce, 0x57, 0x1c, 0xe0, 0x9a,
- 0x68, 0x6c, 0xfd, 0x89, 0x03, 0x62, 0xab, 0x98, 0x9d, 0x6f, 0xdb, 0xf8,
- 0x00, 0x00, 0x00, 0xff, 0xff, 0xd9, 0x1d, 0xa8, 0x02, 0x00, 0x00, 0x00,
- 0x06, 0x49, 0x44, 0x41, 0x54, 0x03, 0x00, 0xf5, 0xfe, 0x91, 0x81, 0x4a,
- 0x65, 0xdb, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae,
- 0x42, 0x60, 0x82
-};
-unsigned int skip_next_png_len = 651;
diff --git a/src/icons/skip_previous.h b/src/icons/skip_previous.h
deleted file mode 100644
index 7158272..0000000
--- a/src/icons/skip_previous.h
+++ /dev/null
@@ -1,61 +0,0 @@
-unsigned char skip_previous_png[] = {
- 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
- 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40,
- 0x08, 0x06, 0x00, 0x00, 0x00, 0xaa, 0x69, 0x71, 0xde, 0x00, 0x00, 0x02,
- 0x64, 0x49, 0x44, 0x41, 0x54, 0x78, 0x01, 0xec, 0x97, 0x2f, 0x6f, 0xc3,
- 0x30, 0x10, 0xc5, 0x9b, 0xa8, 0xa0, 0x65, 0x1d, 0x6f, 0xab, 0x06, 0x8e,
- 0x0d, 0x96, 0x96, 0x8d, 0x8e, 0x8d, 0x8e, 0x6d, 0x68, 0xfb, 0x26, 0x1b,
- 0xdc, 0xd8, 0xd8, 0xe8, 0xd8, 0x68, 0x59, 0xe9, 0xe0, 0x58, 0xd6, 0x3f,
- 0xbc, 0x63, 0x2b, 0xa8, 0x94, 0xbd, 0x93, 0x6a, 0x29, 0x8a, 0x34, 0x35,
- 0xb5, 0x9d, 0xf3, 0x45, 0xbe, 0x2a, 0x27, 0xc7, 0x6a, 0x1d, 0xdf, 0xfb,
- 0xf9, 0xf9, 0xea, 0xa4, 0x9d, 0xc8, 0x3f, 0x0a, 0x20, 0x72, 0x03, 0x74,
- 0xd4, 0x01, 0xea, 0x80, 0xc8, 0x09, 0xe8, 0x16, 0x88, 0xdc, 0x00, 0x5a,
- 0x04, 0x75, 0x0b, 0xe8, 0x16, 0x88, 0x9c, 0x80, 0x6e, 0x81, 0xc8, 0x0d,
- 0xa0, 0xff, 0x02, 0xd1, 0x6d, 0x81, 0xaa, 0xe3, 0x9d, 0x01, 0xac, 0xd7,
- 0xeb, 0xa2, 0x1a, 0x79, 0x9e, 0x5f, 0x54, 0x27, 0x6a, 0xa2, 0x8f, 0x79,
- 0x17, 0xae, 0xcf, 0x75, 0x06, 0xe0, 0x9a, 0x80, 0xcd, 0x78, 0x00, 0x1e,
- 0x40, 0x7c, 0x81, 0xb1, 0x53, 0x84, 0xd3, 0xd5, 0x3a, 0x00, 0x10, 0xfe,
- 0xd4, 0xed, 0x76, 0xb7, 0x4e, 0xaa, 0x4b, 0x83, 0x5b, 0x05, 0x00, 0xe2,
- 0x7f, 0x91, 0xfb, 0x3d, 0xc2, 0xdb, 0xd5, 0x0a, 0x00, 0xab, 0xd5, 0xea,
- 0x1a, 0xe2, 0xc9, 0xf2, 0x3d, 0x6f, 0xca, 0x0f, 0x0f, 0x12, 0x0f, 0x60,
- 0xb3, 0xd9, 0xe4, 0x49, 0x92, 0xbc, 0x1d, 0xf2, 0xf5, 0xde, 0x88, 0x05,
- 0x80, 0x42, 0x37, 0xa1, 0x55, 0x2f, 0x8a, 0x62, 0xe2, 0x5d, 0x75, 0xe9,
- 0x81, 0x22, 0x01, 0x40, 0xf8, 0x07, 0x0a, 0x5d, 0x5e, 0xca, 0xb3, 0xb1,
- 0x5b, 0x51, 0x00, 0xb0, 0xea, 0x3d, 0x88, 0x2f, 0xa0, 0xf6, 0x12, 0xc1,
- 0x72, 0x89, 0x01, 0xb0, 0x5c, 0x2e, 0x1f, 0xb0, 0xea, 0x54, 0xe5, 0x59,
- 0x84, 0x9b, 0x49, 0x44, 0x00, 0xc0, 0xaa, 0x6f, 0xd3, 0x34, 0x7d, 0x34,
- 0x49, 0x71, 0xb6, 0x41, 0x01, 0xc0, 0xf2, 0x53, 0x88, 0x27, 0xcb, 0x0f,
- 0x38, 0x45, 0x97, 0xe7, 0x0a, 0x06, 0x00, 0xc2, 0x17, 0xb0, 0xbc, 0xf3,
- 0x59, 0xbe, 0x2c, 0xc6, 0xe6, 0x9e, 0x1d, 0x00, 0x56, 0xdd, 0xdb, 0x39,
- 0xde, 0x46, 0x70, 0x75, 0x0c, 0x2b, 0x00, 0xac, 0xba, 0xd7, 0x73, 0x7c,
- 0x55, 0x8c, 0x4d, 0x9f, 0x0d, 0x00, 0xc4, 0x53, 0x85, 0xf7, 0x7a, 0x8e,
- 0xaf, 0x23, 0xf8, 0xd8, 0x6f, 0x58, 0x00, 0xc0, 0xf6, 0xde, 0xcf, 0xf0,
- 0xc7, 0x84, 0xd5, 0xfd, 0x9e, 0x05, 0x40, 0x96, 0x65, 0xbb, 0xd1, 0x68,
- 0xd4, 0xc7, 0xb1, 0xf6, 0xa5, 0x6e, 0x62, 0x5c, 0xbf, 0x63, 0x01, 0x60,
- 0xc4, 0x8c, 0xc7, 0xe3, 0xdb, 0xfd, 0x7e, 0x7f, 0x66, 0xfa, 0x12, 0x5a,
- 0x56, 0x00, 0x24, 0x18, 0x6e, 0xf8, 0x81, 0x1b, 0x12, 0xdc, 0xcf, 0x11,
- 0xc1, 0x2f, 0x76, 0x00, 0x46, 0x31, 0x20, 0xcc, 0xe0, 0x86, 0x99, 0xe9,
- 0x87, 0x6a, 0x83, 0x01, 0x20, 0xc1, 0x70, 0xc3, 0x1c, 0x20, 0xf0, 0xba,
- 0x9f, 0x7c, 0x53, 0x3f, 0x44, 0x04, 0x05, 0x60, 0x04, 0x0f, 0x87, 0xc3,
- 0x0c, 0x05, 0xf2, 0xce, 0xf4, 0x39, 0x5b, 0x11, 0x00, 0x48, 0x30, 0x0a,
- 0xe4, 0x33, 0xb6, 0x44, 0x9f, 0xee, 0x39, 0x43, 0x0c, 0x00, 0x12, 0x8d,
- 0x2d, 0x41, 0x7f, 0x97, 0xb4, 0x25, 0x5e, 0xa9, 0xcf, 0x11, 0xa2, 0x00,
- 0x18, 0xc1, 0xd8, 0x12, 0x37, 0x70, 0xc3, 0xb9, 0xe9, 0x37, 0xd9, 0x8a,
- 0x04, 0x40, 0x82, 0xe1, 0x86, 0x2f, 0x8e, 0x02, 0x29, 0x16, 0x00, 0x41,
- 0xa0, 0x80, 0x1b, 0xa8, 0x40, 0x5e, 0xd1, 0x7d, 0x13, 0x21, 0x1e, 0x00,
- 0x89, 0x46, 0x81, 0x7c, 0xc7, 0x96, 0xa0, 0x02, 0xb9, 0xa3, 0xbe, 0xcf,
- 0x68, 0x05, 0x00, 0x12, 0x8c, 0x2d, 0x41, 0x05, 0xd2, 0xfb, 0xfb, 0x44,
- 0x6b, 0x00, 0x10, 0x04, 0x0a, 0xb8, 0xc1, 0xeb, 0xfb, 0x44, 0xeb, 0x00,
- 0x10, 0x04, 0xb8, 0xe1, 0xdf, 0xf7, 0x09, 0xfa, 0xfe, 0x94, 0x70, 0x06,
- 0x40, 0x95, 0xba, 0x1a, 0x48, 0xf0, 0xf3, 0x94, 0x24, 0x6c, 0x7f, 0x8b,
- 0x79, 0x9d, 0xdf, 0x25, 0x9c, 0x01, 0xd8, 0x26, 0x2f, 0x65, 0x9c, 0x02,
- 0x90, 0xb2, 0x12, 0xa1, 0xf2, 0x50, 0x07, 0x84, 0x22, 0x2f, 0x65, 0x5e,
- 0x75, 0x80, 0x94, 0x95, 0x08, 0x95, 0x87, 0x3a, 0x20, 0x14, 0x79, 0x29,
- 0xf3, 0xaa, 0x03, 0xa4, 0xac, 0x44, 0xa8, 0x3c, 0xd4, 0x01, 0xa1, 0xc8,
- 0x4b, 0x99, 0x57, 0x1d, 0x20, 0x65, 0x25, 0x6c, 0xf3, 0x70, 0x1d, 0xf7,
- 0x07, 0x00, 0x00, 0xff, 0xff, 0xf5, 0x69, 0xe5, 0xf3, 0x00, 0x00, 0x00,
- 0x06, 0x49, 0x44, 0x41, 0x54, 0x03, 0x00, 0x5a, 0x14, 0xa5, 0x81, 0x40,
- 0x32, 0x74, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae,
- 0x42, 0x60, 0x82
-};
-unsigned int skip_previous_png_len = 687;
diff --git a/src/icons/zoom_in.h b/src/icons/zoom_in.h
deleted file mode 100644
index f5b4647..0000000
--- a/src/icons/zoom_in.h
+++ /dev/null
@@ -1,121 +0,0 @@
-unsigned char zoom_in_png[] = {
- 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
- 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40,
- 0x08, 0x06, 0x00, 0x00, 0x00, 0xaa, 0x69, 0x71, 0xde, 0x00, 0x00, 0x05,
- 0x32, 0x49, 0x44, 0x41, 0x54, 0x78, 0x01, 0xec, 0x9a, 0x3d, 0x54, 0x15,
- 0x39, 0x14, 0x80, 0xdf, 0xe3, 0xe7, 0x1c, 0xe8, 0x76, 0x3b, 0xf7, 0xf0,
- 0x27, 0x1d, 0x74, 0xd0, 0x69, 0xb7, 0x74, 0x52, 0xad, 0xdb, 0xb9, 0xd5,
- 0xae, 0xd5, 0x52, 0xae, 0x1d, 0x56, 0x8b, 0x95, 0x58, 0xa9, 0x95, 0x5a,
- 0x69, 0xa7, 0x56, 0xda, 0x89, 0x95, 0x74, 0xda, 0x41, 0x07, 0x1d, 0xf2,
- 0x77, 0xd0, 0x4a, 0x3a, 0x5e, 0x01, 0x3c, 0xbf, 0xfb, 0xce, 0x04, 0xc7,
- 0x4c, 0x32, 0x49, 0x66, 0x32, 0x1c, 0xe6, 0xf1, 0x38, 0x73, 0xcd, 0xcc,
- 0x4d, 0x6e, 0x26, 0xf7, 0xcb, 0xcd, 0xcf, 0xe4, 0xd9, 0xd7, 0xb8, 0xe4,
- 0x7f, 0x3d, 0x00, 0x97, 0x3c, 0x00, 0x1a, 0xbd, 0x08, 0xe8, 0x45, 0xc0,
- 0x25, 0x27, 0xd0, 0x1b, 0x02, 0x97, 0x3c, 0x00, 0xe2, 0x4f, 0x82, 0x5b,
- 0x5b, 0x5b, 0x57, 0xf7, 0xf6, 0xf6, 0x5e, 0xee, 0xee, 0xee, 0xb6, 0x2d,
- 0x72, 0xb0, 0xbd, 0xbd, 0xfd, 0xdf, 0x45, 0x01, 0x1f, 0x6d, 0x08, 0x28,
- 0xa7, 0x07, 0x06, 0x06, 0xb6, 0xda, 0xed, 0xf6, 0xad, 0x1c, 0x07, 0xaf,
- 0xf4, 0xf5, 0xf5, 0x3d, 0x54, 0x70, 0x76, 0x76, 0x76, 0xf2, 0xca, 0xe6,
- 0x54, 0x13, 0x27, 0xab, 0x34, 0x00, 0x1c, 0x7f, 0x2e, 0xce, 0x38, 0x9c,
- 0xb6, 0xb6, 0xb6, 0xd9, 0x6c, 0xaa, 0x68, 0xf9, 0xdd, 0x5a, 0xa8, 0xc2,
- 0x8c, 0xc2, 0x00, 0x08, 0xf5, 0xa1, 0xc4, 0xf1, 0x7f, 0x22, 0xb5, 0xef,
- 0x03, 0xf5, 0x6d, 0x44, 0xaa, 0xcb, 0xbb, 0x9a, 0x42, 0x00, 0x70, 0x7e,
- 0x86, 0x50, 0x3f, 0xf2, 0x7e, 0x8b, 0x7f, 0xc1, 0x29, 0x20, 0x54, 0x51,
- 0xaf, 0xb5, 0x05, 0xc1, 0x00, 0x12, 0xe7, 0xd7, 0xac, 0x35, 0xfe, 0xc8,
- 0xd8, 0x64, 0xac, 0x2f, 0x1e, 0x1f, 0x1f, 0xcf, 0x21, 0xb3, 0xa8, 0xe7,
- 0x19, 0x26, 0xcf, 0x48, 0x5d, 0x57, 0x27, 0xb2, 0x5c, 0x85, 0x62, 0xe5,
- 0x07, 0x01, 0xc0, 0xf9, 0x21, 0x7a, 0x3e, 0xd7, 0x79, 0x9c, 0x9d, 0x1e,
- 0x1b, 0x1b, 0x6b, 0x22, 0xd3, 0x23, 0x23, 0x23, 0x0f, 0x26, 0x27, 0x27,
- 0x57, 0x91, 0x75, 0x9e, 0x57, 0xc6, 0xc7, 0xc7, 0x17, 0x48, 0x25, 0xaf,
- 0x79, 0x7a, 0x7a, 0x7a, 0x2f, 0xcf, 0x89, 0xf3, 0x8a, 0x84, 0x20, 0x00,
- 0x38, 0x6f, 0x0d, 0x4f, 0x1c, 0xba, 0x23, 0xce, 0xe1, 0xec, 0x66, 0x9e,
- 0x63, 0x2a, 0x6f, 0x62, 0x62, 0x62, 0x49, 0xca, 0x13, 0x15, 0x9f, 0x95,
- 0x4e, 0x4b, 0x25, 0x12, 0xde, 0x69, 0xba, 0xd2, 0x8f, 0x7a, 0x05, 0xde,
- 0x00, 0x58, 0xae, 0x9e, 0xea, 0xc6, 0xea, 0x99, 0x5e, 0xff, 0x0d, 0x87,
- 0x1e, 0xa9, 0xe7, 0x90, 0x94, 0xa8, 0x98, 0x04, 0xde, 0x03, 0x8b, 0xcd,
- 0x0d, 0xa2, 0xee, 0x17, 0x4b, 0x5e, 0x14, 0xb5, 0x37, 0x00, 0x96, 0xab,
- 0x7f, 0x4d, 0x6f, 0x14, 0xe7, 0xe9, 0xf5, 0x2f, 0xa6, 0x3c, 0x5f, 0x1d,
- 0xf0, 0x16, 0xa9, 0xff, 0x95, 0xa9, 0x3c, 0x51, 0x77, 0x60, 0xd2, 0xc7,
- 0xd2, 0x79, 0x01, 0x60, 0x3c, 0xbe, 0x31, 0xbd, 0x90, 0x9e, 0xbb, 0xe3,
- 0x72, 0x9e, 0x1e, 0x9c, 0x49, 0x64, 0xca, 0x54, 0x87, 0xd2, 0x8d, 0x8e,
- 0x8e, 0xfe, 0xc5, 0xfd, 0x21, 0xa2, 0x5f, 0x43, 0xd8, 0x57, 0x16, 0x05,
- 0x5e, 0x00, 0x68, 0xd1, 0x4d, 0x24, 0x73, 0xd1, 0x73, 0xce, 0xb0, 0xa7,
- 0x07, 0xd7, 0x44, 0x06, 0x07, 0x07, 0x5f, 0x66, 0x2a, 0xd0, 0x14, 0x12,
- 0x4d, 0x9a, 0xaa, 0xf3, 0x88, 0x7d, 0x65, 0x73, 0x81, 0x13, 0x00, 0xf4,
- 0xaf, 0x74, 0x5a, 0xa1, 0xfd, 0x43, 0x63, 0x67, 0x35, 0x55, 0xe9, 0x47,
- 0xa2, 0xa9, 0x65, 0x99, 0x14, 0xaf, 0x95, 0xae, 0xdc, 0x52, 0x81, 0x13,
- 0x40, 0x7f, 0x7f, 0xff, 0x92, 0xc9, 0x96, 0xc6, 0xae, 0x9b, 0xf4, 0x65,
- 0x75, 0x27, 0x27, 0x27, 0x73, 0x65, 0xeb, 0x08, 0xb1, 0x77, 0x02, 0x60,
- 0x72, 0xfa, 0xdb, 0x50, 0x61, 0xa9, 0x49, 0xcf, 0x50, 0xdf, 0x99, 0x0a,
- 0xb0, 0xc6, 0x65, 0x91, 0x55, 0xc8, 0x38, 0x0c, 0xcf, 0x0c, 0x0b, 0xde,
- 0x38, 0x01, 0x50, 0xef, 0x10, 0xf2, 0xd3, 0xc5, 0xe4, 0xe7, 0xb3, 0xa3,
- 0xfb, 0xc9, 0xa6, 0xec, 0x03, 0xbb, 0xca, 0x3f, 0xca, 0xd6, 0x61, 0xb2,
- 0xf7, 0x01, 0x90, 0xb1, 0x03, 0xc0, 0xaa, 0xae, 0x64, 0xae, 0x98, 0x61,
- 0xb5, 0xc8, 0x9c, 0x01, 0xa8, 0x72, 0x8c, 0xed, 0xdc, 0x7c, 0x55, 0x2e,
- 0x49, 0x33, 0x11, 0x86, 0xfd, 0xd5, 0x24, 0x2f, 0x6a, 0x52, 0x08, 0x00,
- 0x2d, 0x30, 0x2d, 0x57, 0xa8, 0xa3, 0x5d, 0x99, 0xfa, 0x19, 0x8a, 0x95,
- 0x2c, 0x85, 0x85, 0x00, 0xb0, 0x2c, 0x99, 0x56, 0x86, 0x16, 0x8d, 0x5c,
- 0xd7, 0x25, 0x85, 0xc4, 0x95, 0x9f, 0x2a, 0xda, 0xc8, 0xf4, 0x36, 0x51,
- 0xf7, 0xb5, 0x51, 0xc1, 0x5f, 0x21, 0x00, 0x84, 0x63, 0x66, 0x42, 0x62,
- 0xf2, 0xda, 0x64, 0x33, 0x33, 0xab, 0x8b, 0x6a, 0x33, 0x60, 0x72, 0xf3,
- 0x55, 0xb9, 0x24, 0xcd, 0xcc, 0x3b, 0xac, 0x46, 0xb9, 0x1f, 0x61, 0x89,
- 0x5d, 0x70, 0xe2, 0x03, 0x20, 0x33, 0x1e, 0x71, 0xc6, 0xb4, 0x32, 0x04,
- 0xbf, 0x3c, 0xc4, 0x80, 0x08, 0x78, 0x1f, 0x52, 0xde, 0xb7, 0xac, 0x0f,
- 0x00, 0xd3, 0x6e, 0x2f, 0xd3, 0x43, 0xbe, 0x2f, 0x74, 0x95, 0x63, 0xb9,
- 0x33, 0x9e, 0x11, 0xf2, 0xe5, 0x98, 0x99, 0x78, 0x5d, 0x75, 0xf9, 0xe4,
- 0x3b, 0x01, 0xb0, 0xe3, 0x7b, 0x6c, 0xaa, 0x68, 0x7f, 0x7f, 0xff, 0xbe,
- 0x49, 0x5f, 0x56, 0x47, 0x74, 0x39, 0xb7, 0xcc, 0x65, 0xdf, 0x91, 0xb6,
- 0x77, 0x02, 0x60, 0x6c, 0xb7, 0xd2, 0x06, 0xea, 0x9e, 0x90, 0x5c, 0x54,
- 0xf7, 0xb1, 0x52, 0x96, 0x51, 0xe3, 0xc1, 0x28, 0x73, 0x4e, 0xe1, 0x7d,
- 0x87, 0xab, 0x6d, 0x4e, 0x00, 0x52, 0x01, 0x0d, 0xb8, 0x2d, 0xa9, 0x2e,
- 0x34, 0xd8, 0x79, 0x88, 0x49, 0xe8, 0x76, 0x4e, 0x80, 0x64, 0x72, 0xd4,
- 0xed, 0x0d, 0xcf, 0x1f, 0x0c, 0xba, 0x06, 0x67, 0x06, 0x0b, 0x26, 0x7d,
- 0x0c, 0x9d, 0x17, 0x00, 0x1a, 0xf0, 0xc2, 0xf2, 0xb2, 0x29, 0x8e, 0xc5,
- 0x9f, 0x58, 0xf2, 0x82, 0xd4, 0xc0, 0x6c, 0x5b, 0x0c, 0xde, 0x5a, 0xf4,
- 0x51, 0xd4, 0x5e, 0x00, 0xe4, 0x4d, 0xcc, 0x05, 0xd7, 0x25, 0xd5, 0x85,
- 0xe8, 0x58, 0xa0, 0xf1, 0xc6, 0xf3, 0x02, 0xbd, 0xac, 0xed, 0x19, 0x7b,
- 0x9b, 0xf3, 0x0d, 0x22, 0xe8, 0x4f, 0x9b, 0x5d, 0x0c, 0xbd, 0x37, 0x00,
- 0xe6, 0x82, 0x4f, 0xbc, 0xd0, 0x76, 0xde, 0x77, 0x13, 0x27, 0x8e, 0xd8,
- 0x0e, 0x07, 0xad, 0x0e, 0x94, 0xbf, 0x86, 0x9d, 0xd5, 0x79, 0x1b, 0x74,
- 0xda, 0x11, 0xed, 0xf2, 0x06, 0x20, 0x6f, 0xa4, 0x37, 0xa6, 0x25, 0xb5,
- 0x88, 0x9c, 0x18, 0x1f, 0xe1, 0xd0, 0x06, 0x8e, 0x99, 0x76, 0x8a, 0x67,
- 0x66, 0xb2, 0xd4, 0x51, 0xae, 0xcd, 0x8e, 0xf2, 0xe3, 0x99, 0x32, 0x7b,
- 0xb3, 0x92, 0x40, 0xcf, 0xe6, 0x44, 0xd4, 0x04, 0x01, 0x90, 0xf7, 0x02,
- 0xa1, 0x29, 0x69, 0x8e, 0x4c, 0xe1, 0xd8, 0x81, 0x38, 0x98, 0xc8, 0x37,
- 0xd2, 0x0d, 0x44, 0xe0, 0x74, 0x3e, 0x96, 0x3c, 0x97, 0xba, 0x1b, 0xcc,
- 0x2f, 0x6b, 0x22, 0xfc, 0x98, 0x6a, 0x3c, 0x93, 0xc8, 0x69, 0x83, 0x77,
- 0x56, 0x30, 0x00, 0xa9, 0x39, 0x81, 0x60, 0x5c, 0x1e, 0x25, 0x5f, 0x13,
- 0xf9, 0x88, 0x91, 0xf3, 0x40, 0xdf, 0xe1, 0xb1, 0xa2, 0xec, 0x99, 0x5f,
- 0x66, 0x44, 0xf8, 0x14, 0xfe, 0xbf, 0x2a, 0x08, 0x85, 0x00, 0x48, 0x03,
- 0x81, 0x30, 0x4c, 0x1a, 0x75, 0x86, 0x26, 0x32, 0xae, 0x53, 0xef, 0x3c,
- 0x69, 0xe6, 0xb4, 0xa9, 0x2a, 0x08, 0x85, 0x01, 0xe0, 0x7c, 0x67, 0x86,
- 0x66, 0xa2, 0x12, 0x10, 0xbe, 0xd1, 0x20, 0x66, 0x26, 0x59, 0xc1, 0xf1,
- 0x26, 0x7b, 0x05, 0x99, 0x68, 0x4d, 0xf9, 0x1d, 0x5d, 0x15, 0x10, 0x4a,
- 0x01, 0x90, 0x56, 0x31, 0x51, 0xb5, 0x68, 0xfc, 0x30, 0x20, 0x7e, 0xe5,
- 0x39, 0xd7, 0x01, 0xf2, 0xf5, 0xeb, 0x31, 0xb6, 0xb2, 0x51, 0x9a, 0x4f,
- 0x67, 0x70, 0x2e, 0xf8, 0x3a, 0xfd, 0x9c, 0xbe, 0x8f, 0x0d, 0xa1, 0x34,
- 0x00, 0xd5, 0x38, 0x40, 0x1c, 0xe2, 0x8c, 0x84, 0x70, 0x13, 0x18, 0xc3,
- 0x34, 0xf4, 0x16, 0xa1, 0x2c, 0x1b, 0xa8, 0x55, 0x52, 0x09, 0xe9, 0x55,
- 0x74, 0xcb, 0xe4, 0xcd, 0x51, 0x4e, 0x9c, 0x16, 0x31, 0xfe, 0x4f, 0x11,
- 0x8e, 0xdb, 0x97, 0xd9, 0x6a, 0x5b, 0x7f, 0x3b, 0xa4, 0x9e, 0x68, 0x73,
- 0x42, 0x34, 0x00, 0x0a, 0x84, 0xa4, 0xc0, 0x68, 0xf1, 0xc3, 0xe8, 0x6b,
- 0x42, 0xfa, 0x36, 0xce, 0xce, 0x91, 0xce, 0x4a, 0x8a, 0xee, 0x2e, 0x79,
- 0x5e, 0x5f, 0x75, 0x40, 0x58, 0x3a, 0x0f, 0x08, 0x95, 0x00, 0x10, 0x08,
- 0x31, 0xe4, 0x3c, 0x20, 0x5c, 0x68, 0x00, 0x02, 0xb1, 0x6a, 0x08, 0x17,
- 0x1e, 0x40, 0xd5, 0x10, 0x6a, 0x01, 0x20, 0x04, 0x82, 0x94, 0x0d, 0x91,
- 0xda, 0x00, 0x10, 0xa7, 0xaa, 0x18, 0x0e, 0xb5, 0x02, 0x50, 0x05, 0x84,
- 0xda, 0x01, 0x08, 0x80, 0xe0, 0x75, 0x64, 0x57, 0x4b, 0x00, 0x1e, 0x10,
- 0x0e, 0x19, 0x2e, 0xcb, 0x52, 0xce, 0x25, 0xb5, 0x05, 0x20, 0x8e, 0xe1,
- 0xa4, 0x69, 0xb3, 0x24, 0x3b, 0x52, 0xd9, 0x96, 0x4b, 0x11, 0xa7, 0xd4,
- 0x1a, 0x80, 0x78, 0xa7, 0x41, 0x08, 0x72, 0x5e, 0xec, 0x6b, 0x0f, 0x40,
- 0x9c, 0x48, 0x20, 0xdc, 0x65, 0xbb, 0xed, 0xdd, 0xf3, 0x62, 0x27, 0xd2,
- 0x15, 0x00, 0xc4, 0x11, 0x20, 0x78, 0x8d, 0x79, 0x29, 0x9b, 0x96, 0xae,
- 0x01, 0x90, 0x76, 0x2a, 0xe4, 0xbe, 0x07, 0x20, 0x84, 0x56, 0x37, 0x96,
- 0xed, 0x45, 0x40, 0x37, 0xf6, 0x6a, 0x88, 0x4f, 0xbd, 0x08, 0x08, 0xa1,
- 0xd5, 0x8d, 0x65, 0x6b, 0x1f, 0x01, 0x65, 0x3b, 0xe5, 0x3b, 0x00, 0x00,
- 0x00, 0xff, 0xff, 0x51, 0xf8, 0x77, 0x07, 0x00, 0x00, 0x00, 0x06, 0x49,
- 0x44, 0x41, 0x54, 0x03, 0x00, 0x1b, 0xcd, 0x54, 0x9f, 0x16, 0x6a, 0xa8,
- 0x66, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60,
- 0x82
-};
-unsigned int zoom_in_png_len = 1405;
diff --git a/src/icons/zoom_out.h b/src/icons/zoom_out.h
deleted file mode 100644
index 2c6f933..0000000
--- a/src/icons/zoom_out.h
+++ /dev/null
@@ -1,117 +0,0 @@
-unsigned char zoom_out_png[] = {
- 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
- 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40,
- 0x08, 0x06, 0x00, 0x00, 0x00, 0xaa, 0x69, 0x71, 0xde, 0x00, 0x00, 0x05,
- 0x06, 0x49, 0x44, 0x41, 0x54, 0x78, 0x01, 0xec, 0x9a, 0x3d, 0x54, 0xd4,
- 0x40, 0x10, 0x80, 0x39, 0xa0, 0x80, 0x4e, 0x3b, 0x7c, 0xfc, 0x77, 0xd0,
- 0x41, 0x07, 0x9d, 0x74, 0x50, 0x89, 0x1d, 0x56, 0x4a, 0x25, 0xa5, 0x74,
- 0x50, 0x89, 0x95, 0x50, 0x29, 0x95, 0x5a, 0x69, 0x87, 0x54, 0xda, 0x89,
- 0x15, 0xd7, 0x69, 0x07, 0x1d, 0x74, 0x27, 0x7f, 0x0f, 0xad, 0xa4, 0xe3,
- 0x0a, 0xe0, 0xfc, 0xe6, 0x5e, 0x72, 0xde, 0x25, 0xbb, 0xc9, 0x6e, 0xb2,
- 0xe1, 0x91, 0xbb, 0xf3, 0x65, 0xdc, 0x64, 0x76, 0x77, 0xb2, 0xf3, 0xed,
- 0xec, 0x4f, 0xf6, 0xe8, 0xec, 0x68, 0xf1, 0x7f, 0x6d, 0x00, 0x2d, 0x1e,
- 0x00, 0x1d, 0xed, 0x08, 0x68, 0x47, 0x40, 0x8b, 0x13, 0x68, 0x0f, 0x81,
- 0x16, 0x0f, 0x00, 0xf7, 0x93, 0x60, 0xa9, 0x54, 0x1a, 0x39, 0x3d, 0x3d,
- 0xdd, 0x3a, 0x39, 0x39, 0xa9, 0x68, 0xe4, 0xfc, 0xe8, 0xe8, 0xe8, 0xc5,
- 0x5d, 0x01, 0xef, 0x6c, 0x08, 0xf8, 0x4e, 0x77, 0x77, 0x77, 0x97, 0x2a,
- 0x95, 0xca, 0x42, 0x84, 0x83, 0x7d, 0x9d, 0x9d, 0x9d, 0x6f, 0x7c, 0x38,
- 0xc7, 0xc7, 0xc7, 0x51, 0x65, 0x23, 0xcc, 0xb8, 0xc9, 0x4a, 0x0d, 0x00,
- 0xc7, 0x3f, 0x8a, 0x33, 0x31, 0x4e, 0x6b, 0x5b, 0x5b, 0x28, 0x14, 0xfc,
- 0x68, 0x79, 0xa8, 0x2d, 0x94, 0x61, 0x46, 0x62, 0x00, 0x84, 0x7a, 0x8f,
- 0xe7, 0xf8, 0x33, 0x47, 0xed, 0xdb, 0xc5, 0xde, 0x81, 0x23, 0x5b, 0xc6,
- 0x66, 0x12, 0x01, 0xc0, 0xf9, 0x09, 0x42, 0xfd, 0xd2, 0xf8, 0x2d, 0xe6,
- 0x05, 0xc7, 0x80, 0x90, 0x85, 0x5d, 0x6d, 0x0b, 0xac, 0x01, 0x78, 0xce,
- 0xef, 0x69, 0x2d, 0xfe, 0xcf, 0x38, 0x64, 0xac, 0xaf, 0x5c, 0x5d, 0x5d,
- 0xcd, 0x20, 0x93, 0xa8, 0xe7, 0x18, 0x26, 0x1f, 0x48, 0xe3, 0xae, 0x6a,
- 0x64, 0xc5, 0x15, 0x72, 0x95, 0x6f, 0x05, 0x00, 0xe7, 0x7b, 0xe8, 0xf9,
- 0x48, 0xe7, 0x71, 0x76, 0x7c, 0x70, 0x70, 0xb0, 0x80, 0x8c, 0xf7, 0xf7,
- 0xf7, 0x6f, 0x8c, 0x8e, 0x8e, 0x16, 0x91, 0x7d, 0x9e, 0x77, 0x86, 0x86,
- 0x86, 0x96, 0x48, 0x25, 0xaf, 0x70, 0x73, 0x73, 0xf3, 0x2a, 0xca, 0x89,
- 0xdb, 0x8a, 0x04, 0x2b, 0x00, 0x38, 0xaf, 0x0d, 0x4f, 0x1c, 0x5a, 0x16,
- 0xe7, 0x70, 0xf6, 0x30, 0xca, 0x31, 0x3f, 0x6f, 0x78, 0x78, 0x78, 0x4d,
- 0xca, 0x13, 0x15, 0xbf, 0x7c, 0x5d, 0x20, 0x95, 0x48, 0xf8, 0x16, 0xd0,
- 0xa5, 0x7e, 0x0c, 0x1a, 0x30, 0x06, 0xc0, 0x72, 0xf5, 0x3e, 0x58, 0xd9,
- 0x7f, 0xa6, 0xd7, 0x1f, 0xe0, 0xd0, 0x5b, 0xff, 0xd9, 0x26, 0x25, 0x2a,
- 0x46, 0x81, 0xb7, 0xa1, 0xa9, 0x33, 0x4b, 0xd4, 0xdd, 0xd3, 0xe4, 0x39,
- 0x51, 0x1b, 0x03, 0x60, 0xb9, 0x7a, 0xae, 0x7a, 0xa3, 0x38, 0x4f, 0xaf,
- 0xff, 0x56, 0xe5, 0x99, 0xea, 0x80, 0xb7, 0x82, 0xfd, 0xcf, 0xaa, 0xf2,
- 0x44, 0xdd, 0xb9, 0x4a, 0xef, 0x4a, 0x67, 0x04, 0x80, 0xf1, 0xf8, 0x45,
- 0xf5, 0x42, 0x7a, 0x6e, 0x39, 0xad, 0xf3, 0xbe, 0xdd, 0x81, 0x81, 0x81,
- 0x27, 0xdc, 0x5f, 0x20, 0xc1, 0xab, 0x27, 0xcb, 0x28, 0x30, 0x02, 0x40,
- 0x8b, 0xe6, 0x91, 0xd0, 0x45, 0xcf, 0x25, 0x0a, 0xfb, 0x90, 0x21, 0x4f,
- 0x21, 0xd1, 0xe4, 0xdd, 0x36, 0x24, 0x44, 0x41, 0x66, 0x73, 0x41, 0x2c,
- 0x00, 0xe8, 0xf7, 0x35, 0xb4, 0xc6, 0x7b, 0xa0, 0xb1, 0x93, 0xde, 0xad,
- 0xb3, 0x84, 0x68, 0x2a, 0x6b, 0x26, 0xc5, 0x29, 0x67, 0x2f, 0x09, 0x18,
- 0x8a, 0x05, 0xd0, 0xd5, 0xd5, 0xb5, 0x16, 0xa8, 0x53, 0x7d, 0xa4, 0xb1,
- 0xfb, 0xd5, 0x1b, 0xc7, 0xff, 0x5d, 0x5f, 0x5f, 0xcf, 0x38, 0x36, 0x19,
- 0x69, 0x2e, 0x16, 0x00, 0x93, 0xd3, 0x53, 0x85, 0x85, 0x54, 0x93, 0x9e,
- 0xc2, 0x5e, 0x4d, 0x05, 0x58, 0xe5, 0xb2, 0xc8, 0x2a, 0xa4, 0x1c, 0x86,
- 0xb5, 0x8a, 0x09, 0x6f, 0x62, 0x01, 0x60, 0xb7, 0x07, 0x69, 0xb8, 0x98,
- 0xfc, 0x4c, 0x76, 0x74, 0x0d, 0x75, 0xd2, 0x3e, 0xb0, 0xab, 0x7c, 0x94,
- 0xd6, 0x86, 0xaa, 0xbe, 0x09, 0x80, 0x50, 0x3d, 0x00, 0x14, 0x83, 0x4a,
- 0xe6, 0x8a, 0x09, 0x56, 0x0b, 0xdd, 0x19, 0x80, 0x56, 0x1f, 0xb4, 0xe3,
- 0x3d, 0x87, 0x22, 0x8c, 0xb9, 0x61, 0xc4, 0xcb, 0x73, 0x9a, 0x24, 0x02,
- 0x40, 0x0b, 0x54, 0xcb, 0x15, 0x6a, 0x67, 0x57, 0xc8, 0x3e, 0x43, 0x31,
- 0x93, 0x0d, 0x51, 0x22, 0x00, 0x2c, 0x4b, 0xaa, 0x95, 0xa1, 0x4c, 0x23,
- 0xf7, 0x6d, 0x45, 0x83, 0x2c, 0xd4, 0xdb, 0x44, 0xdd, 0x1f, 0x4d, 0xd9,
- 0x54, 0xea, 0x44, 0x00, 0x08, 0xc7, 0xd0, 0x84, 0xc4, 0xe4, 0x75, 0xc8,
- 0x66, 0x66, 0xd2, 0x56, 0x34, 0xad, 0x0f, 0xcd, 0x3b, 0xac, 0x46, 0x91,
- 0x1f, 0x61, 0x1a, 0x3b, 0xb1, 0x6a, 0x13, 0x00, 0xa1, 0xf1, 0x48, 0x2f,
- 0xab, 0x56, 0x86, 0xd8, 0x97, 0xa5, 0x29, 0x40, 0x04, 0x7c, 0x4f, 0x53,
- 0x5f, 0x57, 0xd7, 0x04, 0x80, 0x6a, 0xb7, 0x17, 0xea, 0x21, 0xdd, 0x0b,
- 0x6c, 0xf5, 0x2c, 0x77, 0xca, 0x33, 0x42, 0xbe, 0x1c, 0x43, 0x13, 0xaf,
- 0xad, 0x6d, 0x55, 0xf9, 0x58, 0x00, 0xec, 0xf8, 0x36, 0x55, 0x15, 0xcf,
- 0xce, 0xce, 0x5e, 0xab, 0xf4, 0x69, 0x75, 0x44, 0xd7, 0x56, 0x5a, 0x1b,
- 0x36, 0xf5, 0x63, 0x01, 0x30, 0xb6, 0xcb, 0x2a, 0x83, 0x84, 0xe4, 0x8a,
- 0x4a, 0x9f, 0x46, 0xc7, 0x32, 0xaa, 0x3c, 0x18, 0x65, 0xce, 0x49, 0xbc,
- 0xef, 0x88, 0x6b, 0x4f, 0x2c, 0x00, 0x31, 0x40, 0x03, 0x16, 0x25, 0x0d,
- 0x0a, 0x0d, 0x76, 0x7d, 0x88, 0xb9, 0x1b, 0x7c, 0x87, 0x3c, 0x73, 0x66,
- 0xb0, 0x24, 0x69, 0x16, 0x62, 0x04, 0x80, 0x06, 0x7c, 0xd2, 0xbc, 0x7c,
- 0x8c, 0x63, 0xf1, 0x77, 0x9a, 0x3c, 0x2b, 0x35, 0x30, 0x2b, 0x9a, 0x0a,
- 0x5f, 0x35, 0x7a, 0x27, 0x6a, 0x23, 0x00, 0xf2, 0x26, 0xe6, 0x82, 0x69,
- 0x49, 0x83, 0x42, 0x74, 0x2c, 0xd1, 0x78, 0xe5, 0x79, 0x41, 0xb0, 0xac,
- 0xee, 0x99, 0xfa, 0x3a, 0xe7, 0x3b, 0x98, 0xfc, 0x1e, 0xeb, 0xea, 0xb9,
- 0xd0, 0x1b, 0x03, 0x60, 0x2e, 0xf8, 0xc9, 0x0b, 0x75, 0xe7, 0x7d, 0xf3,
- 0x38, 0x71, 0xc9, 0x76, 0xd8, 0x6a, 0x75, 0xa0, 0xfc, 0x14, 0xf5, 0xb4,
- 0xce, 0xeb, 0xa0, 0xd3, 0x0e, 0x67, 0x97, 0x31, 0x00, 0x79, 0x23, 0xbd,
- 0x31, 0x2e, 0xa9, 0x46, 0xe4, 0xc4, 0xf8, 0x12, 0x87, 0x0e, 0x70, 0x4c,
- 0xb5, 0x53, 0xac, 0x55, 0x93, 0xa5, 0x8e, 0x72, 0x15, 0x76, 0x94, 0x3f,
- 0x6a, 0xca, 0xf0, 0xcd, 0x8e, 0x07, 0x3d, 0x9c, 0xe3, 0x50, 0x63, 0x05,
- 0x40, 0xde, 0x0b, 0x84, 0x82, 0xa4, 0x11, 0x32, 0x86, 0x63, 0xe7, 0xe2,
- 0xa0, 0x27, 0x7f, 0x49, 0x0f, 0x10, 0x81, 0x53, 0xfd, 0x28, 0x32, 0x5c,
- 0xea, 0x66, 0x99, 0x5f, 0xf6, 0x44, 0xf8, 0x31, 0x55, 0x79, 0x26, 0x11,
- 0xd1, 0x06, 0xe3, 0x2c, 0x6b, 0x00, 0x62, 0xd9, 0x83, 0xa0, 0x5c, 0x1e,
- 0x25, 0x3f, 0x20, 0xf2, 0x11, 0x33, 0x86, 0xce, 0x74, 0x78, 0xec, 0x50,
- 0xb6, 0x7a, 0x31, 0xbf, 0x4c, 0x88, 0xf0, 0x29, 0xfc, 0x32, 0x2b, 0x08,
- 0x89, 0x00, 0x48, 0xeb, 0x80, 0xd0, 0x4b, 0xea, 0x74, 0x86, 0x26, 0x32,
- 0xa6, 0xb1, 0x3b, 0x47, 0x1a, 0x3a, 0x6d, 0xca, 0x0a, 0x42, 0x62, 0x00,
- 0x38, 0x5f, 0x9d, 0xa1, 0x99, 0xa8, 0x04, 0x84, 0x69, 0x34, 0x48, 0x35,
- 0x95, 0xec, 0xe0, 0x78, 0x81, 0x0f, 0x29, 0x99, 0x68, 0x55, 0xf9, 0x55,
- 0x5d, 0x16, 0x10, 0x52, 0x01, 0x90, 0x56, 0x31, 0x51, 0x95, 0x69, 0x7c,
- 0x2f, 0x20, 0xee, 0xf3, 0x1c, 0xe9, 0x00, 0xf9, 0xc1, 0x6b, 0x93, 0xba,
- 0xf2, 0x53, 0xd9, 0x5c, 0x7d, 0x06, 0xe7, 0x82, 0xdb, 0xf5, 0xcf, 0xf5,
- 0xf7, 0xae, 0x21, 0xa4, 0x06, 0xe0, 0x37, 0x0e, 0x10, 0x17, 0x38, 0x23,
- 0x21, 0x5c, 0x00, 0x46, 0x2f, 0x0d, 0x5d, 0x20, 0x94, 0x65, 0x03, 0x55,
- 0x24, 0x95, 0x90, 0x2e, 0xa2, 0x5b, 0x27, 0x6f, 0x86, 0x72, 0xe2, 0xb4,
- 0x88, 0xf2, 0x2f, 0x45, 0x38, 0x6e, 0x5f, 0x67, 0xab, 0xad, 0xfd, 0xed,
- 0x10, 0x3b, 0xce, 0xe6, 0x04, 0x67, 0x00, 0x7c, 0x10, 0x92, 0x02, 0xa3,
- 0xcc, 0x0f, 0xa3, 0xdb, 0x84, 0xf4, 0x22, 0xce, 0xce, 0x90, 0x4e, 0x4a,
- 0x8a, 0x6e, 0x95, 0x3c, 0xa3, 0xaf, 0x3a, 0x20, 0xac, 0xdd, 0x06, 0x84,
- 0x4c, 0x00, 0x08, 0x04, 0x17, 0x72, 0x1b, 0x10, 0xee, 0x34, 0x00, 0x81,
- 0x98, 0x35, 0x84, 0x3b, 0x0f, 0x20, 0x6b, 0x08, 0xb9, 0x00, 0x60, 0x03,
- 0x41, 0xca, 0xda, 0x48, 0x6e, 0x00, 0x88, 0x53, 0x59, 0x0c, 0x87, 0x5c,
- 0x01, 0xc8, 0x02, 0x42, 0xee, 0x00, 0x58, 0x40, 0x30, 0x3a, 0xb2, 0xcb,
- 0x25, 0x00, 0x03, 0x08, 0x17, 0x0c, 0x97, 0x75, 0x29, 0x17, 0x27, 0xb9,
- 0x05, 0x20, 0x8e, 0xe1, 0xa4, 0x6a, 0xb3, 0x24, 0x3b, 0x52, 0xd9, 0x96,
- 0x4b, 0x91, 0x58, 0xc9, 0x35, 0x00, 0xf1, 0x2e, 0x00, 0xc1, 0xca, 0x79,
- 0xa9, 0x9f, 0x7b, 0x00, 0xe2, 0x84, 0x07, 0x61, 0x95, 0xed, 0xb6, 0x71,
- 0xcf, 0x4b, 0x3d, 0x91, 0xa6, 0x00, 0x20, 0x8e, 0x00, 0xc1, 0x68, 0xcc,
- 0x4b, 0xd9, 0x7a, 0x69, 0x1a, 0x00, 0xf5, 0x4e, 0xd9, 0xdc, 0xb7, 0x01,
- 0xd8, 0xd0, 0x6a, 0xc6, 0xb2, 0xed, 0x08, 0x68, 0xc6, 0x5e, 0xb5, 0xf1,
- 0xa9, 0x1d, 0x01, 0x36, 0xb4, 0x9a, 0xb1, 0x6c, 0xee, 0x23, 0x20, 0x6d,
- 0xa7, 0xfc, 0x03, 0x00, 0x00, 0xff, 0xff, 0x23, 0x51, 0x17, 0x5c, 0x00,
- 0x00, 0x00, 0x06, 0x49, 0x44, 0x41, 0x54, 0x03, 0x00, 0x9b, 0xeb, 0x52,
- 0x9f, 0x91, 0xa9, 0x83, 0x89, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e,
- 0x44, 0xae, 0x42, 0x60, 0x82
-};
-unsigned int zoom_out_png_len = 1361;
diff --git a/src/import_dialog.cpp b/src/import_dialog.cpp
new file mode 100644
index 0000000..7584b41
--- /dev/null
+++ b/src/import_dialog.cpp
@@ -0,0 +1,307 @@
+#include "import_dialog.h"
+#include "sprite_converter.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+ImportDialog::ImportDialog(QWidget* parent, AppState& state) : QDialog(parent), m_state(state)
+{
+ setWindowTitle("Create SPR");
+ setFixedSize(450, 580);
+
+ auto* lay = new QVBoxLayout(this);
+ lay->setContentsMargins(24, 24, 24, 24);
+ lay->setSpacing(0);
+
+ auto* inLbl = new QLabel("Input Images");
+ inLbl->setStyleSheet(QString("font-size: 14px; font-weight: 500; color: %1;").arg(SpriteColors::TextSection.name()));
+ lay->addWidget(inLbl);
+ lay->addSpacing(8);
+
+ auto* btnRow = new QHBoxLayout;
+ btnRow->setSpacing(8);
+ auto* addBtn = new QPushButton("Add Images...");
+ addBtn->setCursor(Qt::PointingHandCursor);
+ addBtn->setStyleSheet(QString(
+ "QPushButton { background: %1; color: %2; border: none; border-radius: 4px; padding: 6px 12px; }"
+ "QPushButton:hover { background: %3; }"
+ ).arg(SpriteColors::BgLighter.name(), SpriteColors::TextPrimary.name(), SpriteColors::BgLight.name()));
+
+ auto* clrBtn = new QPushButton("Clear");
+ clrBtn->setCursor(Qt::PointingHandCursor);
+ clrBtn->setStyleSheet(addBtn->styleSheet());
+
+ btnRow->addWidget(addBtn);
+ btnRow->addWidget(clrBtn);
+ btnRow->addStretch();
+ lay->addLayout(btnRow);
+ lay->addSpacing(8);
+
+ m_fileList = new QListWidget;
+ m_fileList->setFixedHeight(130);
+ m_fileList->setStyleSheet(QString(
+ "QListWidget { background: %1; border: 1px solid %2; border-radius: 4px; }"
+ ).arg(SpriteColors::BgDark.name(), SpriteColors::Border.name()));
+ lay->addWidget(m_fileList);
+ lay->addSpacing(24);
+
+ auto* setLbl = new QLabel("Sprite Parameters");
+ setLbl->setStyleSheet(inLbl->styleSheet());
+ lay->addWidget(setLbl);
+ lay->addSpacing(12);
+
+ auto addRow = [&](const QString& label, QWidget* w) {
+ auto* r = new QHBoxLayout;
+ auto* l = new QLabel(label);
+ l->setFixedWidth(80);
+ l->setStyleSheet(QString("color: %1;").arg(SpriteColors::TextDim.name()));
+ r->addWidget(l);
+ r->addWidget(w);
+ lay->addLayout(r);
+ lay->addSpacing(8);
+ };
+
+ auto* verW = new QWidget;
+ auto* verL = new QHBoxLayout(verW);
+ verL->setContentsMargins(0,0,0,0);
+ auto* v1 = new QRadioButton("Quake (v1)");
+ auto* v2 = new QRadioButton("Half-Life (v2)");
+ verL->addWidget(v1);
+ verL->addWidget(v2);
+ verL->addStretch();
+
+ m_versionCombo = new QComboBox;
+ m_versionCombo->addItem("v1", 1);
+ m_versionCombo->addItem("v2", 2);
+ m_versionCombo->setVisible(false);
+
+ addRow("Version:", verW);
+
+ m_typeCombo = new QComboBox;
+ m_typeCombo->addItems({"Parallel Upright", "Facing Upright", "Parallel", "Oriented", "Parallel Oriented"});
+ m_typeCombo->setCurrentIndex(2);
+ addRow("Type:", m_typeCombo);
+
+ m_renderCombo = new QComboBox;
+ m_renderCombo->addItems({"Normal", "Additive", "Index Alpha", "Alpha Test"});
+ addRow("Render:", m_renderCombo);
+
+ auto* intW = new QWidget;
+ auto* intL = new QHBoxLayout(intW);
+ intL->setContentsMargins(0,0,0,0);
+
+ m_intervalSpin = new QDoubleSpinBox;
+ m_intervalSpin->setRange(0.01, 1.0);
+ m_intervalSpin->setVisible(false);
+
+ auto* intSl = new QSlider(Qt::Horizontal);
+ intSl->setRange(1, 100);
+ intSl->setFixedHeight(40);
+ auto* intLbl = new QLabel("0.10s");
+ intLbl->setFixedWidth(40);
+ intLbl->setStyleSheet(QString("color: %1;").arg(SpriteColors::TextPrimary.name()));
+
+ intL->addWidget(intSl);
+ intL->addWidget(intLbl);
+ addRow("Interval:", intW);
+
+ connect(v1, &QRadioButton::toggled, [this](bool c) {
+ if(c) {
+ m_versionCombo->setCurrentIndex(0);
+ m_renderCombo->setCurrentIndex(0);
+ m_renderCombo->setEnabled(false);
+ }
+ });
+ connect(v2, &QRadioButton::toggled, [this](bool c) {
+ if(c) {
+ m_versionCombo->setCurrentIndex(1);
+ m_renderCombo->setEnabled(true);
+ }
+ });
+
+ connect(intSl, &QSlider::valueChanged, [this, intLbl](int v) {
+ float val = v / 100.0f;
+ m_intervalSpin->setValue((double)val);
+ intLbl->setText(QString::number(val, 'f', 2) + "s");
+ });
+
+ v2->setChecked(true);
+ intSl->setValue(10);
+ m_intervalSpin->setValue(0.1);
+
+ lay->addStretch();
+
+ auto* footRow = new QHBoxLayout;
+ footRow->setSpacing(8);
+ footRow->addStretch();
+
+ auto* cancelBtn = new QPushButton("Cancel");
+ cancelBtn->setCursor(Qt::PointingHandCursor);
+ cancelBtn->setStyleSheet(QString(
+ "QPushButton { background: transparent; color: %1; border: none; font-weight: 500; padding: 0 12px; height: 36px; }"
+ "QPushButton:hover { background: rgba(255, 255, 255, 0.05); border-radius: 4px; }"
+ ).arg(SpriteColors::Accent.name()));
+
+ auto* createBtn = new QPushButton("Create");
+ createBtn->setCursor(Qt::PointingHandCursor);
+ createBtn->setStyleSheet(QString(
+ "QPushButton { background: %1; color: #FFFFFF; border: none; border-radius: 4px; font-weight: 500; padding: 0 16px; height: 36px; }"
+ "QPushButton:hover { background: %2; }"
+ ).arg(SpriteColors::Accent.name(), SpriteColors::AccentLit.name()));
+
+ footRow->addWidget(cancelBtn);
+ footRow->addWidget(createBtn);
+ lay->addLayout(footRow);
+
+ connect(addBtn, &QPushButton::clicked, this, &ImportDialog::onAddImages);
+ connect(clrBtn, &QPushButton::clicked, this, &ImportDialog::onClearAll);
+ connect(createBtn, &QPushButton::clicked, this, &ImportDialog::onCreate);
+ connect(cancelBtn, &QPushButton::clicked, this, &QDialog::reject);
+}
+
+void ImportDialog::onAddImages()
+{
+ QSettings settings("Sprite-Tools");
+ QString lastDir = settings.value("lastImportDir", QString::fromStdString(m_state.last_dir)).toString();
+ QStringList files = QFileDialog::getOpenFileNames(this, "Select Images", lastDir, "Images (*.png *.bmp);;All (*)");
+
+ if (files.isEmpty())
+ return;
+
+ QString newDir = QFileInfo(files.first()).absolutePath();
+
+ m_state.last_dir = newDir.toStdString();
+
+ settings.setValue("lastImportDir", newDir);
+
+ for (const auto& f : files)
+ {
+ m_files.append(f);
+ auto* item = new QListWidgetItem(m_fileList);
+ auto* w = new FileListItem(QFileInfo(f).fileName(), [this, item]() {
+ int row = m_fileList->row(item);
+ m_files.removeAt(row);
+ delete m_fileList->takeItem(row);
+ });
+ item->setSizeHint(w->sizeHint());
+ m_fileList->setItemWidget(item, w);
+ }
+}
+
+void ImportDialog::onClearAll()
+{
+ m_files.clear();
+ m_fileList->clear();
+}
+
+void ImportDialog::onRemoveSelected()
+{
+ auto items = m_fileList->selectedItems();
+ for (auto* item : items)
+ {
+ int row = m_fileList->row(item);
+ m_files.removeAt(row);
+ delete m_fileList->takeItem(row);
+ }
+}
+
+void ImportDialog::onCreate()
+{
+ if (m_files.isEmpty())
+ {
+ QMessageBox::warning(this, "No Images", "Please add at least one image.");
+ return;
+ }
+
+ QString savePath = QFileDialog::getSaveFileName(this, "Save Sprite", QString::fromStdString(m_state.last_dir), "Sprite (*.spr)");
+ if (savePath.isEmpty()) return;
+ if (!savePath.endsWith(".spr", Qt::CaseInsensitive)) savePath += ".spr";
+
+ int version = m_versionCombo->itemData(m_versionCombo->currentIndex()).toInt();
+ int type = m_typeCombo->currentIndex();
+ int texFmt = m_renderCombo->currentIndex();
+ float interval = (float)m_intervalSpin->value();
+
+ if (version == 1) texFmt = 0;
+
+ QProgressDialog prog("Creating sprite...", "Cancel", 0, 100, this);
+ prog.setWindowTitle("Creating Sprite");
+ prog.setWindowModality(Qt::WindowModal);
+ prog.setMinimumDuration(0);
+
+ int total = m_files.size();
+ std::vector> storage;
+ std::vector ptrs;
+ std::vector widths, heights;
+
+ for (int i = 0; i < total; i++)
+ {
+ if (prog.wasCanceled()) { reject(); return; }
+ prog.setLabelText(QString("Loading image %1 / %2...").arg(i + 1).arg(total));
+ prog.setValue((int)((float)(i + 1) / total * 40));
+ QApplication::processEvents();
+
+ int w, h, ch;
+ uint8_t* px = stbi_load(m_files[i].toStdString().c_str(), &w, &h, &ch, 4);
+ if (!px)
+ {
+ QMessageBox::critical(this, "Error", "Failed to load: " + QFileInfo(m_files[i]).fileName());
+ return;
+ }
+ storage.emplace_back(px, px + (size_t)w * h * 4);
+ stbi_image_free(px);
+ widths.push_back(w);
+ heights.push_back(h);
+ }
+
+ for (auto& v : storage) ptrs.push_back(v.data());
+
+ prog.setLabelText("Building sprite...");
+ prog.setValue(50);
+ QApplication::processEvents();
+
+ ImageToSprParams p;
+ p.version = version;
+ p.type = (uint32_t)type;
+ p.tex_format = (uint32_t)texFmt;
+ p.interval = interval;
+
+ auto result = SpriteConverter::RGBAToSprMemory(ptrs, widths, heights, p);
+ if (!result.success)
+ {
+ QMessageBox::critical(this, "Error", QString::fromStdString(result.error));
+ return;
+ }
+
+ prog.setLabelText("Saving...");
+ prog.setValue(80);
+ QApplication::processEvents();
+
+ std::ofstream file(savePath.toStdString(), std::ios::binary);
+ if (!file.is_open())
+ {
+ QMessageBox::critical(this, "Error", "Failed to create file");
+ return;
+ }
+ file.write(reinterpret_cast(result.data.data()), (std::streamsize)result.data.size());
+ file.close();
+
+ prog.setValue(100);
+ QMessageBox::information(this, "Success", QString("Created: %1\n%2 frame(s), %3 bytes").arg(QFileInfo(savePath).fileName()).arg(total).arg(result.data.size()));
+
+ m_outputFile = savePath;
+ accept();
+}
\ No newline at end of file
diff --git a/src/import_dialog.h b/src/import_dialog.h
new file mode 100644
index 0000000..2ecfee4
--- /dev/null
+++ b/src/import_dialog.h
@@ -0,0 +1,59 @@
+#pragma once
+
+#include "theme.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "app_state.h"
+
+class ImportDialog : public QDialog
+{
+ Q_OBJECT
+public:
+ ImportDialog(QWidget* parent, AppState& state);
+ QString outputFile() const { return m_outputFile; }
+
+private slots:
+ void onAddImages();
+ void onClearAll();
+ void onRemoveSelected();
+ void onCreate();
+
+private:
+ AppState& m_state;
+ QString m_outputFile;
+ QListWidget* m_fileList;
+ QComboBox* m_versionCombo;
+ QComboBox* m_typeCombo;
+ QComboBox* m_renderCombo;
+ QDoubleSpinBox* m_intervalSpin;
+ QStringList m_files;
+};
+
+class FileListItem : public QWidget {
+public:
+ FileListItem(const QString& name, std::function onRemove) {
+ auto* l = new QHBoxLayout(this);
+
+ auto* txt = new QLabel(name);
+ txt->setStyleSheet(QString("color: %1;").arg(SpriteColors::TextPrimary.name()));
+
+ auto* btn = new QToolButton;
+ btn->setIcon(QIcon(":/icons/close.png"));
+ btn->setStyleSheet("border: none; background: transparent;");
+ btn->setCursor(Qt::PointingHandCursor);
+ QObject::connect(btn, &QToolButton::clicked, onRemove);
+
+ l->addWidget(txt, 1);
+ l->addWidget(btn);
+ }
+};
\ No newline at end of file
diff --git a/src/main.cpp b/src/main.cpp
index acadb03..d02ce4e 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,99 +1,21 @@
-#include
-
-#include "ui.h"
-
-#include "imgui.h"
-#include "imgui_impl_opengl3.h"
-#include "imgui_impl_glfw.h"
-
-#include
-
-static UI* g_ui = nullptr;
-
-static void DropCallback(GLFWwindow*, int count, const char** paths)
-{
- if (g_ui && count > 0)
- g_ui->SetPendingFile(paths[0]);
-}
+#include
+#include "mainwindow.h"
+#include "theme.h"
int main(int argc, char* argv[])
{
- glfwSetErrorCallback([](int err, const char* desc)
- {
- fprintf(stderr, "GLFW %d: %s\n", err, desc);
- });
-
- if (!glfwInit())
- return -1;
-
- glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
- glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
-
- GLFWwindow* window = glfwCreateWindow(1280, 720, "Sprite-Tools", nullptr, nullptr);
- if (!window)
- {
- glfwTerminate();
- return -1;
- }
-
- glfwMakeContextCurrent(window);
- glfwSwapInterval(1);
-
- IMGUI_CHECKVERSION();
- ImGui::CreateContext();
+ QApplication app(argc, argv);
+ QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath());
+ app.setApplicationName("Sprite-Tools");
+ app.setPalette(Theme::darkPalette());
+ app.setStyleSheet(Theme::globalStyleSheet());
- ImGui_ImplGlfw_InitForOpenGL(window, true);
- ImGui_ImplOpenGL3_Init("#version 130");
-
- UI ui;
- g_ui = &ui;
-
- ui.LoadIcons();
- ui.SetupTheme();
-
- glfwSetDropCallback(window, DropCallback);
+ MainWindow w;
+ w.resize(1280, 720);
+ w.show();
if (argc > 1)
- ui.SetPendingFile(argv[1]);
-
- while (!glfwWindowShouldClose(window))
- {
- glfwPollEvents();
-
- ui.ProcessPendingFile();
-
- if (ui.IsTitleChanged())
- glfwSetWindowTitle(window, ui.ConsumeTitle().c_str());
-
- if (ui.IsExitRequested())
- glfwSetWindowShouldClose(window, GLFW_TRUE);
-
- ImGui_ImplOpenGL3_NewFrame();
- ImGui_ImplGlfw_NewFrame();
- ImGui::NewFrame();
-
- ui.RenderFrame();
-
- ImGui::Render();
- int dw, dh;
- glfwGetFramebufferSize(window, &dw, &dh);
- glViewport(0, 0, dw, dh);
- glClearColor(0.08f, 0.08f, 0.10f, 1.0f);
- glClear(GL_COLOR_BUFFER_BIT);
- glEnable(GL_BLEND);
- glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
- ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
- glfwSwapBuffers(window);
- }
-
- ui.CleanupResources();
-
- ImGui_ImplOpenGL3_Shutdown();
- ImGui_ImplGlfw_Shutdown();
- ImGui::DestroyContext();
- glfwDestroyWindow(window);
- glfwTerminate();
- g_ui = nullptr;
+ w.openFile(QString::fromLocal8Bit(argv[1]));
- return 0;
+ return app.exec();
}
\ No newline at end of file
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
new file mode 100644
index 0000000..9eba8da
--- /dev/null
+++ b/src/mainwindow.cpp
@@ -0,0 +1,708 @@
+#include "mainwindow.h"
+#include "sprite_viewport.h"
+#include "properties_panel.h"
+#include "export_dialog.h"
+#include "import_dialog.h"
+#include "about_dialog.h"
+#include "frame_slider.h"
+#include "theme.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+
+MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent)
+{
+ setWindowTitle("Sprite-Tools");
+ setAcceptDrops(true);
+
+ m_viewport = new SpriteViewport(this);
+ setCentralWidget(m_viewport);
+
+ createActions();
+ createMenus();
+ createToolBar();
+ createStatusBar();
+ createDockWidgets();
+
+ m_animTimer = new QTimer(this);
+ m_animTimer->setInterval(16);
+ connect(m_animTimer, &QTimer::timeout, this, &MainWindow::animTick);
+
+ connect(this, &MainWindow::spriteLoaded, m_viewport, &SpriteViewport::onSpriteLoaded);
+ connect(this, &MainWindow::spriteClosed, m_viewport, &SpriteViewport::onSpriteClosed);
+ connect(this, &MainWindow::frameChanged, m_viewport, &SpriteViewport::onFrameChanged);
+ connect(this, &MainWindow::zoomChanged, m_viewport, &SpriteViewport::onZoomChanged);
+
+ connect(this, &MainWindow::spriteLoaded, m_props, &PropertiesPanel::onSpriteLoaded);
+ connect(this, &MainWindow::spriteClosed, m_props, &PropertiesPanel::onSpriteClosed);
+ connect(this, &MainWindow::frameChanged, m_props, &PropertiesPanel::onFrameChanged);
+
+ connect(m_viewport, &SpriteViewport::zoomRequested, this, &MainWindow::onZoomChanged);
+
+ updatePlayback();
+}
+
+MainWindow::~MainWindow()
+{
+ if (m_state.sprite_loaded)
+ {
+ m_viewport->deleteTextures();
+ m_loader.Unload();
+ }
+}
+
+QIcon MainWindow::icon(const QString& name)
+{
+ return QIcon(":/icons/" + name + ".png");
+}
+
+QToolButton* MainWindow::tinyBtn(const QString& iconName, const QString& tooltip, bool enabled, bool highlighted)
+{
+ auto* btn = new QToolButton;
+ QIcon ic = icon(iconName);
+
+ btn->setProperty("origIcon", QVariant::fromValue(ic));
+
+ btn->setIcon(ic);
+ btn->setToolTip(tooltip);
+ btn->setIconSize(QSize(18, 18));
+ btn->setFixedSize(34, 34);
+ btn->setAutoRaise(true);
+
+ updateBtnState(btn, enabled, highlighted);
+
+ return btn;
+}
+
+void MainWindow::updateBtnState(QToolButton* btn, bool enabled, bool highlighted)
+{
+ btn->setEnabled(enabled);
+
+ btn->setStyleSheet(QString(R"(
+ QToolButton {
+ border: 1px solid transparent;
+ border-radius: 6px;
+ padding: 4px;
+ background: transparent;
+ }
+ QToolButton:hover {
+ background-color: rgba(%1, %2, %3, 46);
+ border: 1px solid rgba(%1, %2, %3, 64);
+ }
+ QToolButton:pressed {
+ background-color: rgba(%1, %2, %3, 89);
+ }
+ QToolButton:disabled {
+ background: transparent;
+ border: none;
+ }
+ )").arg(SpriteColors::Accent.red())
+ .arg(SpriteColors::Accent.green())
+ .arg(SpriteColors::Accent.blue()));
+
+ QColor tint;
+ if (!enabled)
+ {
+ tint = SpriteColors::TextDim;
+ }
+ else if (highlighted)
+ {
+ tint = SpriteColors::AccentLit;
+ }
+ else
+ {
+ tint = SpriteColors::TextPrimary;
+ }
+
+ QIcon origIcon = btn->property("origIcon").value();
+ if (origIcon.isNull())
+ {
+ origIcon = btn->icon();
+ btn->setProperty("origIcon", QVariant::fromValue(origIcon));
+ }
+
+ if (!origIcon.isNull())
+ {
+ QPixmap pm = origIcon.pixmap(18, 18);
+ if (!pm.isNull())
+ {
+ QPainter painter(&pm);
+ painter.setCompositionMode(QPainter::CompositionMode_SourceIn);
+ painter.fillRect(pm.rect(), tint);
+ painter.end();
+ btn->setIcon(QIcon(pm));
+ }
+ }
+}
+
+void MainWindow::createActions()
+{
+ m_actOpen = new QAction(icon("folder"), "&Open...", this);
+ m_actOpen->setShortcut(QKeySequence::Open);
+ connect(m_actOpen, &QAction::triggered, this, &MainWindow::onOpenFile);
+
+ m_actClose = new QAction(icon("close"), "&Close", this);
+ m_actClose->setVisible(false);
+ connect(m_actClose, &QAction::triggered, this, &MainWindow::onCloseFile);
+
+ m_actExport = new QAction(icon("save_alt"), "&Export Frames...", this);
+ m_actExport->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_E));
+ m_actExport->setVisible(false);
+ connect(m_actExport, &QAction::triggered, this, &MainWindow::onExport);
+
+ m_actImport = new QAction(icon("add_photo"), "&Import Images to SPR...", this);
+ m_actImport->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_I));
+ connect(m_actImport, &QAction::triggered, this, &MainWindow::onImport);
+
+ m_actExit = new QAction("E&xit", this);
+ m_actExit->setShortcut(QKeySequence(Qt::ALT | Qt::Key_F4));
+ connect(m_actExit, &QAction::triggered, this, &QWidget::close);
+
+ m_actShowToolbar = new QAction("Toolbar", this);
+ m_actShowToolbar->setCheckable(true);
+ m_actShowToolbar->setChecked(true);
+ connect(m_actShowToolbar, &QAction::toggled, this, [this](bool v) {
+ m_toolbar->setVisible(v);
+ m_state.show_toolbar = v;
+ });
+
+ m_actShowProps = new QAction("Properties", this);
+ m_actShowProps->setCheckable(true);
+ m_actShowProps->setChecked(true);
+ connect(m_actShowProps, &QAction::toggled, this, [this](bool v) {
+ m_propsDock->setVisible(v);
+ m_state.show_properties = v;
+ });
+
+ m_actShowChecker = new QAction("Transparency Grid", this);
+ m_actShowChecker->setCheckable(true);
+ m_actShowChecker->setChecked(true);
+ connect(m_actShowChecker, &QAction::toggled, this, [this](bool v) {
+ m_state.show_checker = v;
+ m_viewport->update();
+ });
+}
+
+void MainWindow::createMenus()
+{
+ QMenu* file = menuBar()->addMenu("&File");
+ file->addAction(m_actOpen);
+ file->addAction(m_actClose);
+ file->addSeparator();
+ file->addAction(m_actExport);
+ file->addAction(m_actImport);
+ file->addSeparator();
+ file->addAction(m_actExit);
+
+ QMenu* view = menuBar()->addMenu("&View");
+ view->addAction(m_actShowToolbar);
+ view->addAction(m_actShowProps);
+ view->addSeparator();
+ view->addAction(m_actShowChecker);
+ view->addSeparator();
+
+ QMenu* zm = view->addMenu("Zoom");
+ float zvals[] = {0.25f, 0.5f, 1.0f, 2.0f, 4.0f, 8.0f};
+ const char* zlbl[] = {"25%", "50%", "100%", "200%", "400%", "800%"};
+ for (int i = 0; i < 6; i++)
+ {
+ float z = zvals[i];
+ zm->addAction(zlbl[i], this, [this, z]() { onZoomChanged(z); });
+ }
+
+ m_menuPlayback = menuBar()->addMenu("&Playback");
+ m_menuPlayback->addAction(icon("play"), "Play/Pause (Space)", this, &MainWindow::onPlayPause);
+ m_menuPlayback->addSeparator();
+ m_menuPlayback->addAction(icon("skip_previous"), "First (Home)", this, &MainWindow::onFirstFrame);
+ m_menuPlayback->addAction(icon("navigate_before"),"Previous (Left)", this, &MainWindow::onPrevFrame);
+ m_menuPlayback->addAction(icon("navigate_next"), "Next (Right)", this, &MainWindow::onNextFrame);
+ m_menuPlayback->addAction(icon("skip_next"), "Last (End)", this, &MainWindow::onLastFrame);
+
+ m_menuPlayback->menuAction()->setVisible(false);
+
+ QMenu* help = menuBar()->addMenu("&Help");
+ help->addAction(icon("info"), "About", this, &MainWindow::onAbout);
+}
+
+void MainWindow::createToolBar()
+{
+ m_toolbar = addToolBar("Main");
+ m_toolbar->setMovable(false);
+ m_toolbar->setIconSize(QSize(18, 18));
+ m_toolbar->setStyleSheet(QString(R"(
+ QToolBar {
+ background-color: %1;
+ border-bottom: 1px solid %2;
+ spacing: 2px;
+ padding: 4px;
+ }
+ )").arg(SpriteColors::ToolbarBg.name(), SpriteColors::Border.name()));
+
+ QString sliderStyle = QString(R"(
+ QSlider { min-height: 28px; max-height: 28px; }
+ QSlider::groove:horizontal {
+ height: 4px; background: %1; border-radius: 2px;
+ }
+ QSlider::sub-page:horizontal {
+ background: %2; border-radius: 2px;
+ }
+ QSlider::handle:horizontal {
+ background: %3; width: 14px; height: 14px;
+ margin: -5px 0; border-radius: 7px;
+ }
+ QSlider::handle:horizontal:hover { background: %4; }
+ QSlider::handle:horizontal:disabled { background: %5; }
+ QSlider::groove:horizontal:disabled { background: %6; }
+ QSlider::sub-page:horizontal:disabled { background: %6; }
+ )").arg(SpriteColors::BgLight.name(), SpriteColors::AccentDim.name(),
+ SpriteColors::Accent.name(), SpriteColors::AccentLit.name(),
+ SpriteColors::TextDim.name(), SpriteColors::BgDark.name());
+
+ m_btnFirst = tinyBtn("skip_previous", "First (Home)");
+ m_btnPrev = tinyBtn("navigate_before", "Previous (Left)");
+ m_btnPlayPause = tinyBtn("play", "Play/Pause (Space)");
+ m_btnNext = tinyBtn("navigate_next", "Next (Right)");
+ m_btnLast = tinyBtn("skip_next", "Last (End)");
+
+ connect(m_btnFirst, &QToolButton::clicked, this, &MainWindow::onFirstFrame);
+ connect(m_btnPrev, &QToolButton::clicked, this, &MainWindow::onPrevFrame);
+ connect(m_btnPlayPause, &QToolButton::clicked, this, &MainWindow::onPlayPause);
+ connect(m_btnNext, &QToolButton::clicked, this, &MainWindow::onNextFrame);
+ connect(m_btnLast, &QToolButton::clicked, this, &MainWindow::onLastFrame);
+
+ m_toolbar->addWidget(m_btnFirst);
+ m_toolbar->addWidget(m_btnPrev);
+ m_toolbar->addWidget(m_btnPlayPause);
+ m_toolbar->addWidget(m_btnNext);
+ m_toolbar->addWidget(m_btnLast);
+
+ m_speedLabel = new QLabel("1.0x");
+ m_speedLabel->setFixedWidth(32);
+ m_speedLabel->setAlignment(Qt::AlignCenter);
+ m_speedLabel->setStyleSheet(
+ QString("font-size: 10px; color: %1;").arg(SpriteColors::TextDim.name()));
+
+ m_speedSlider = new QSlider(Qt::Horizontal);
+ m_speedSlider->setRange(1, 80);
+ m_speedSlider->setValue(10);
+ m_speedSlider->setFixedWidth(60);
+ m_speedSlider->setStyleSheet(sliderStyle);
+ m_speedSlider->setToolTip("Playback speed");
+ connect(m_speedSlider, &QSlider::valueChanged, this, [this](int v) {
+ m_state.anim_speed = v / 10.0f;
+ m_speedLabel->setText(QString::number(m_state.anim_speed, 'f', 1) + "x");
+ });
+
+ m_toolbar->addWidget(m_speedLabel);
+ m_toolbar->addWidget(m_speedSlider);
+
+ auto* spacer = new QWidget;
+ spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
+ m_toolbar->addWidget(spacer);
+
+ m_btnZoomOut = tinyBtn("zoom_out", "Zoom Out (-)");
+ m_btnZoomIn = tinyBtn("zoom_in", "Zoom In (+)");
+
+ m_zoomLabel = new QLabel("100%");
+ m_zoomLabel->setFixedWidth(45);
+ m_zoomLabel->setAlignment(Qt::AlignCenter);
+ m_zoomLabel->setStyleSheet(QString("color: %1; font-weight: 500; font-size: 11px;").arg(SpriteColors::TextPrimary.name()));
+
+ m_btnZoomReset = tinyBtn("center_focus", "Reset Zoom (1)");
+
+ connect(m_btnZoomOut, &QToolButton::clicked, this, &MainWindow::onZoomOut);
+ connect(m_btnZoomIn, &QToolButton::clicked, this, &MainWindow::onZoomIn);
+ connect(m_btnZoomReset, &QToolButton::clicked, this, &MainWindow::onZoomReset);
+
+ m_toolbar->addWidget(m_btnZoomOut);
+ m_toolbar->addWidget(m_btnZoomIn);
+ m_toolbar->addWidget(m_zoomLabel);
+ m_toolbar->addWidget(m_btnZoomReset);
+
+ m_toolbar->addSeparator();
+
+ m_framePosLabel = new QLabel;
+ m_framePosLabel->setFixedWidth(48);
+ m_framePosLabel->setAlignment(Qt::AlignCenter);
+ m_framePosLabel->setStyleSheet(QString("font-size: 11px; color: %1;").arg(SpriteColors::TextDim.name()));
+ m_toolbar->addWidget(m_framePosLabel);
+
+ m_frameSlider = new FrameSlider;
+ m_frameSlider->setToolTip("Current frame");
+ connect(m_frameSlider, &FrameSlider::valueChanged, this, [this](int v) {
+ if (v != m_state.current_frame) setFrame(v);
+ });
+ m_toolbar->addWidget(m_frameSlider);
+}
+
+void MainWindow::createStatusBar()
+{
+ m_statusMsg = new QLabel;
+ m_statusInfo = new QLabel;
+ m_statusInfo->setStyleSheet(QString("font-size: 10px; color: %1;").arg(SpriteColors::TextDim.name()));
+ statusBar()->addWidget(m_statusMsg, 1);
+ statusBar()->addPermanentWidget(m_statusInfo);
+}
+
+void MainWindow::createDockWidgets()
+{
+ m_props = new PropertiesPanel(this);
+ m_propsDock = new QDockWidget("Properties", this);
+ m_propsDock->setWidget(m_props);
+
+ m_propsDock->setFeatures(QDockWidget::NoDockWidgetFeatures);
+ m_propsDock->setMinimumWidth(280);
+
+ addDockWidget(Qt::RightDockWidgetArea, m_propsDock);
+
+ connect(m_propsDock, &QDockWidget::visibilityChanged,
+ m_actShowProps, &QAction::setChecked);
+}
+
+void MainWindow::keyPressEvent(QKeyEvent* event)
+{
+ if (event->key() == Qt::Key_Space)
+ {
+ onPlayPause();
+ event->accept();
+ return;
+ }
+ if (event->key() == Qt::Key_Left) { onPrevFrame(); event->accept(); return; }
+ if (event->key() == Qt::Key_Right) { onNextFrame(); event->accept(); return; }
+ if (event->key() == Qt::Key_Home) { onFirstFrame(); event->accept(); return; }
+ if (event->key() == Qt::Key_End) { onLastFrame(); event->accept(); return; }
+
+ if (event->key() == Qt::Key_Plus || event->key() == Qt::Key_Equal)
+ { onZoomIn(); event->accept(); return; }
+ if (event->key() == Qt::Key_Minus)
+ { onZoomOut(); event->accept(); return; }
+ if (event->key() == Qt::Key_1)
+ { onZoomReset(); event->accept(); return; }
+
+ QMainWindow::keyPressEvent(event);
+}
+
+void MainWindow::openFile(const QString& path)
+{
+ if (m_state.sprite_loaded)
+ {
+ m_viewport->deleteTextures();
+ m_loader.Unload();
+ m_state.sprite_loaded = false;
+ }
+
+ std::string sp = path.toStdString();
+ if (!m_loader.Load(sp))
+ {
+ statusBar()->showMessage("Failed to load: " + QFileInfo(path).fileName(), 4000);
+ return;
+ }
+
+ m_state.sprite_loaded = true;
+ m_state.filepath = sp;
+ m_state.fileName = QFileInfo(path).fileName().toStdString();
+ m_state.current_frame = 0;
+ m_state.total_frames = m_loader.GetTotalFrameCount();
+ m_state.animating = false;
+ m_state.anim_time = 0.0;
+ m_state.scroll_x = m_state.scroll_y = 0;
+ m_state.last_dir = QFileInfo(path).absolutePath().toStdString();
+
+ SpriteFrame* f = m_loader.GetFrame(0);
+ if (f)
+ {
+ float m = (float)std::max(f->width, f->height);
+ float z = (m < 64) ? 4.0f : (m < 128) ? 2.0f : 1.0f;
+ onZoomChanged(z);
+ }
+
+ m_frameSlider->setRange(0, std::max(0, m_state.total_frames - 1));
+ m_frameSlider->setValue(0);
+
+ m_actClose->setEnabled(true);
+ m_actExport->setEnabled(true);
+
+ updateTitle();
+ updatePlayback();
+ updateStatusInfo();
+
+ statusBar()->showMessage(
+ QString("Loaded: %1 (%2 frames)")
+ .arg(QFileInfo(path).fileName())
+ .arg(m_state.total_frames), 4000);
+
+ emit spriteLoaded();
+}
+
+void MainWindow::onOpenFile()
+{
+ QSettings settings("Sprite-Tools");
+ QString lastDir = settings.value("lastDir", QString::fromStdString(m_state.last_dir)).toString();
+
+ QString p = QFileDialog::getOpenFileName(this, "Open Sprite", lastDir, "Sprite files (*.spr);;All (*)");
+
+ if (!p.isEmpty()) {
+ QString newDir = QFileInfo(p).absolutePath();
+ settings.setValue("lastDir", newDir);
+ m_state.last_dir = newDir.toStdString();
+
+ openFile(p);
+ }
+}
+
+void MainWindow::onCloseFile()
+{
+ if (!m_state.sprite_loaded)
+ return;
+ m_animTimer->stop();
+ m_state.animating = false;
+ m_viewport->deleteTextures();
+ m_loader.Unload();
+ m_state.sprite_loaded = false;
+ m_state.current_frame = 0;
+ m_state.total_frames = 0;
+ m_actClose->setEnabled(false);
+ m_actExport->setEnabled(false);
+ m_frameSlider->setRange(0, 0);
+ updateTitle();
+ updatePlayback();
+ updateStatusInfo();
+ statusBar()->showMessage("Closed", 3000);
+ emit spriteClosed();
+ onZoomChanged(1.0f);
+}
+
+void MainWindow::onExport()
+{
+ if (!m_state.sprite_loaded)
+ return;
+ ExportDialog dlg(this, m_state, m_loader);
+ dlg.exec();
+}
+
+void MainWindow::onImport()
+{
+ ImportDialog dlg(this, m_state);
+ if (dlg.exec() == QDialog::Accepted && !dlg.outputFile().isEmpty())
+ openFile(dlg.outputFile());
+}
+
+void MainWindow::onAbout()
+{
+ AboutDialog dlg(this);
+ dlg.exec();
+}
+
+void MainWindow::onPlayPause()
+{
+ if (!m_state.sprite_loaded || m_state.total_frames <= 1) return;
+ m_state.animating = !m_state.animating;
+ if (m_state.animating)
+ {
+ m_elapsed.start();
+ m_state.anim_time = 0.0;
+ m_animTimer->start();
+ }
+ else
+ {
+ m_animTimer->stop();
+ }
+ updatePlayback();
+}
+
+void MainWindow::onFirstFrame()
+{
+ setFrame(0);
+}
+
+void MainWindow::onLastFrame()
+{
+ setFrame(std::max(0, m_state.total_frames - 1));
+}
+
+void MainWindow::onPrevFrame()
+{
+ if (m_state.total_frames <= 0)
+ return;
+ setFrame((m_state.current_frame - 1 + m_state.total_frames) % m_state.total_frames);
+}
+
+void MainWindow::onNextFrame()
+{
+ if (m_state.total_frames <= 0)
+ return;
+ setFrame((m_state.current_frame + 1) % m_state.total_frames);
+}
+
+void MainWindow::onZoomIn()
+{
+ onZoomChanged(std::min(16.0f, m_state.zoom * 2.0f));
+}
+
+void MainWindow::onZoomOut()
+{
+ onZoomChanged(std::max(0.25f, m_state.zoom * 0.5f));
+}
+
+void MainWindow::onZoomReset()
+{
+ m_state.scroll_x = m_state.scroll_y = 0;
+
+ SpriteFrame* f = m_loader.GetFrame(0);
+ if (f)
+ {
+ float m = (float)std::max(f->width, f->height);
+ float z = (m < 64) ? 4.0f : (m < 128) ? 2.0f : 1.0f;
+ onZoomChanged(z);
+ }
+ else
+ {
+ onZoomChanged(1.0f);
+ }
+
+}
+
+void MainWindow::onZoomChanged(float z)
+{
+ m_state.zoom = std::clamp(z, 0.25f, 16.0f);
+
+ if (m_zoomLabel)
+ {
+ m_zoomLabel->setText(QString("%1%").arg(qRound(m_state.zoom * 100)));
+ }
+
+ emit zoomChanged(m_state.zoom);
+}
+
+void MainWindow::setFrame(int f)
+{
+ if (f < 0 || f >= m_state.total_frames) return;
+ m_state.current_frame = f;
+
+ m_frameSlider->blockSignals(true);
+ m_frameSlider->setValue(f);
+ m_frameSlider->blockSignals(false);
+
+ m_framePosLabel->setText(
+ QString("%1/%2").arg(f + 1).arg(m_state.total_frames));
+
+ emit frameChanged(f);
+ updateStatusInfo();
+}
+
+void MainWindow::animTick()
+{
+ if (!m_state.animating || !m_state.sprite_loaded) return;
+
+ double dt = m_elapsed.elapsed() / 1000.0;
+ m_elapsed.restart();
+ m_state.anim_time += dt * m_state.anim_speed;
+
+ SpriteFrame* fr = m_loader.GetFrame(m_state.current_frame);
+ if (!fr) return;
+ float iv = fr->interval > 0.0f ? fr->interval : 0.1f;
+
+ while (m_state.anim_time >= iv)
+ {
+ m_state.anim_time -= iv;
+ m_state.current_frame = (m_state.current_frame + 1) % m_state.total_frames;
+ fr = m_loader.GetFrame(m_state.current_frame);
+ if (!fr) return;
+ iv = fr->interval > 0.0f ? fr->interval : 0.1f;
+ }
+
+ m_frameSlider->blockSignals(true);
+ m_frameSlider->setValue(m_state.current_frame);
+ m_frameSlider->blockSignals(false);
+
+ m_framePosLabel->setText(
+ QString("%1/%2").arg(m_state.current_frame + 1).arg(m_state.total_frames));
+
+ emit frameChanged(m_state.current_frame);
+ updateStatusInfo();
+}
+
+void MainWindow::updateTitle()
+{
+ setWindowTitle(m_state.sprite_loaded
+ ? QString::fromUtf8("Sprite-Tools \u2014 ")
+ + QFileInfo(QString::fromStdString(m_state.filepath)).fileName()
+ : "Sprite-Tools");
+}
+
+void MainWindow::updateStatusInfo()
+{
+ if (!m_state.sprite_loaded) { m_statusInfo->clear(); return; }
+ const SpriteData& d = m_loader.GetData();
+ m_statusInfo->setText(
+ QString("v%1 %2 %3 frames")
+ .arg(d.version)
+ .arg(SpriteLoader::GetTexFormatString(d.texFormat))
+ .arg(m_state.total_frames));
+}
+
+void MainWindow::updatePlayback()
+{
+ bool loaded = m_state.sprite_loaded;
+ bool multi = loaded && m_state.total_frames > 1;
+ bool playing = m_state.animating;
+
+ m_actClose->setVisible(loaded);
+ m_actClose->setEnabled(loaded);
+
+ m_actExport->setVisible(loaded);
+ m_actExport->setEnabled(loaded);
+
+
+ updateBtnState(m_btnFirst, multi, false);
+ updateBtnState(m_btnPrev, multi, false);
+ updateBtnState(m_btnNext, multi, false);
+ updateBtnState(m_btnLast, multi, false);
+
+ QIcon playIcon = icon(playing ? "pause" : "play");
+ m_btnPlayPause->setProperty("origIcon", QVariant::fromValue(playIcon));
+ updateBtnState(m_btnPlayPause, multi, playing);
+
+ updateBtnState(m_btnZoomOut, true, false);
+ updateBtnState(m_btnZoomIn, true, false);
+ updateBtnState(m_btnZoomReset, true, false);
+
+ m_speedSlider->setEnabled(multi);
+ m_frameSlider->setEnabled(multi);
+
+ if (loaded)
+ m_framePosLabel->setText(QString("%1/%2").arg(m_state.current_frame + 1).arg(m_state.total_frames));
+ else
+ m_framePosLabel->clear();
+}
+
+void MainWindow::dragEnterEvent(QDragEnterEvent* event)
+{
+ if (event->mimeData()->hasUrls())
+ for (const QUrl& u : event->mimeData()->urls())
+ if (u.toLocalFile().endsWith(".spr", Qt::CaseInsensitive))
+ { event->acceptProposedAction(); return; }
+}
+
+void MainWindow::dropEvent(QDropEvent* event)
+{
+ for (const QUrl& u : event->mimeData()->urls())
+ {
+ QString p = u.toLocalFile();
+ if (p.endsWith(".spr", Qt::CaseInsensitive))
+ {
+ openFile(p);
+ return;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/mainwindow.h b/src/mainwindow.h
new file mode 100644
index 0000000..e1c7aa1
--- /dev/null
+++ b/src/mainwindow.h
@@ -0,0 +1,115 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "app_state.h"
+#include "sprite_loader.h"
+
+class SpriteViewport;
+class PropertiesPanel;
+class FrameSlider;
+
+class MainWindow : public QMainWindow
+{
+ Q_OBJECT
+
+public:
+ explicit MainWindow(QWidget* parent = nullptr);
+ ~MainWindow();
+
+ void openFile(const QString& path);
+ AppState& state() { return m_state; }
+ SpriteLoader& loader() { return m_loader; }
+
+signals:
+ void spriteLoaded();
+ void spriteClosed();
+ void frameChanged(int frame);
+ void zoomChanged(float zoom);
+
+public slots:
+ void onOpenFile();
+ void onCloseFile();
+ void onExport();
+ void onImport();
+ void onAbout();
+ void onPlayPause();
+ void onFirstFrame();
+ void onLastFrame();
+ void onPrevFrame();
+ void onNextFrame();
+ void onZoomIn();
+ void onZoomOut();
+ void onZoomReset();
+ void onZoomChanged(float z);
+ void setFrame(int frame);
+
+protected:
+ void dragEnterEvent(QDragEnterEvent* event) override;
+ void dropEvent(QDropEvent* event) override;
+ void keyPressEvent(QKeyEvent* event) override;
+
+private:
+ QIcon icon(const QString& name);
+ QToolButton* tinyBtn(const QString& iconName, const QString& tooltip, bool enabled = true, bool highlighted = false);
+ void updateBtnState(QToolButton* btn, bool enabled, bool highlighted = false);
+
+ void createActions();
+ void createMenus();
+ void createToolBar();
+ void createStatusBar();
+ void createDockWidgets();
+ void updateTitle();
+ void updateStatusInfo();
+ void updatePlayback();
+ void animTick();
+
+ AppState m_state;
+ SpriteLoader m_loader;
+
+ SpriteViewport* m_viewport;
+ PropertiesPanel* m_props;
+ QDockWidget* m_propsDock;
+
+ QToolBar* m_toolbar;
+ QSlider* m_speedSlider;
+ QLabel* m_speedLabel;
+ QLabel* m_framePosLabel;
+ FrameSlider* m_frameSlider;
+
+ QToolButton* m_btnFirst;
+ QToolButton* m_btnPrev;
+ QToolButton* m_btnPlayPause;
+ QToolButton* m_btnNext;
+ QToolButton* m_btnLast;
+ QToolButton* m_btnZoomOut;
+ QToolButton* m_btnZoomIn;
+ QToolButton* m_btnZoomReset;
+
+ QLabel* m_zoomLabel;
+
+ QTimer* m_animTimer;
+ QElapsedTimer m_elapsed;
+
+ QLabel* m_statusInfo;
+ QLabel* m_statusMsg;
+
+ QAction* m_actOpen;
+ QAction* m_actClose;
+ QAction* m_actExport;
+ QAction* m_actImport;
+ QAction* m_actExit;
+ QAction* m_actShowToolbar;
+ QAction* m_actShowProps;
+ QAction* m_actShowChecker;
+
+ QMenu* m_menuPlayback;
+};
\ No newline at end of file
diff --git a/src/properties_panel.cpp b/src/properties_panel.cpp
new file mode 100644
index 0000000..3108558
--- /dev/null
+++ b/src/properties_panel.cpp
@@ -0,0 +1,219 @@
+#include "properties_panel.h"
+#include "mainwindow.h"
+#include "sprite_loader.h"
+#include "theme.h"
+
+#include
+#include
+#include
+#include
+#include
+
+QWidget* sectionHeader(const QString& title)
+{
+ auto* w = new QWidget;
+ auto* lay = new QHBoxLayout(w);
+ lay->setContentsMargins(0, 12, 0, 4);
+ lay->setSpacing(8);
+
+ auto* line1 = new QFrame; line1->setFrameShape(QFrame::HLine);
+ line1->setStyleSheet(QString("color: %1;").arg(SpriteColors::Border.name()));
+ line1->setFixedWidth(12);
+
+ auto* lbl = new QLabel(title);
+ lbl->setStyleSheet(QString("font-size: 13px; font-weight: 500; color: %1;").arg(SpriteColors::TextSection.name()));
+
+ auto* line2 = new QFrame; line2->setFrameShape(QFrame::HLine);
+ line2->setStyleSheet(QString("color: %1;").arg(SpriteColors::Border.name()));
+
+ lay->addWidget(line1);
+ lay->addWidget(lbl);
+ lay->addWidget(line2);
+ return w;
+}
+
+class PaletteWidget : public QWidget
+{
+public:
+ PaletteWidget(const uint8_t* pal, int count, QWidget* parent = nullptr)
+ : QWidget(parent), m_pal(pal), m_count(count)
+ {
+ setMouseTracking(true);
+ int cols = 16, cs = 10;
+ int rows = (count + cols - 1) / cols;
+ setFixedSize(cols * (cs + 1), rows * (cs + 1));
+ }
+
+protected:
+ void paintEvent(QPaintEvent*) override
+ {
+ QPainter p(this);
+ int cols = 16, cs = 10;
+ for (int i = 0; i < m_count && i < 256; i++)
+ {
+ int r = i / cols, c = i % cols;
+ p.fillRect(c * (cs + 1), r * (cs + 1), cs, cs, QColor(m_pal[i*3], m_pal[i*3+1], m_pal[i*3+2]));
+ }
+ }
+
+ void mouseMoveEvent(QMouseEvent* e) override
+ {
+ int cols = 16, cs = 10;
+ int c = e->pos().x() / (cs + 1);
+ int r = e->pos().y() / (cs + 1);
+ int idx = r * cols + c;
+ if (idx >= 0 && idx < m_count)
+ {
+ uint8_t rv = m_pal[idx*3], g = m_pal[idx*3+1], b = m_pal[idx*3+2];
+ QToolTip::showText(e->globalPosition().toPoint(),
+ QString("#%1: %2 %3 %4 (#%5%6%7)")
+ .arg(idx).arg(rv).arg(g).arg(b)
+ .arg(rv, 2, 16, QChar('0'))
+ .arg(g, 2, 16, QChar('0'))
+ .arg(b, 2, 16, QChar('0')));
+ }
+ }
+
+private:
+ const uint8_t* m_pal;
+ int m_count;
+};
+
+PropertiesPanel::PropertiesPanel(MainWindow* mainWin, QWidget* parent) : QScrollArea(parent), m_main(mainWin)
+{
+ setWidgetResizable(true);
+ setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+
+ m_content = new QWidget;
+ m_layout = new QVBoxLayout(m_content);
+ m_layout->setAlignment(Qt::AlignTop);
+ m_layout->setContentsMargins(10, 10, 10, 10);
+ m_layout->setSpacing(4);
+
+ auto* ph = new QLabel("No file loaded");
+ ph->setStyleSheet(QString("color: %1;").arg(SpriteColors::TextDim.name()));
+ ph->setAlignment(Qt::AlignCenter);
+ m_layout->addWidget(ph);
+
+ setWidget(m_content);
+}
+
+QLabel* PropertiesPanel::propLabel(const QString& name, const QString& value)
+{
+ return new QLabel(QString("%2 %3").arg(SpriteColors::TextDim.name(), name, value));
+}
+
+void PropertiesPanel::onSpriteLoaded()
+{
+ rebuild();
+}
+
+void PropertiesPanel::onSpriteClosed()
+{
+ QLayoutItem* item;
+ while ((item = m_layout->takeAt(0))) { delete item->widget(); delete item; }
+ auto* ph = new QLabel("No file loaded");
+ ph->setStyleSheet(QString("color: %1;").arg(SpriteColors::TextDim.name()));
+ ph->setAlignment(Qt::AlignCenter);
+ m_layout->addWidget(ph);
+}
+void PropertiesPanel::onFrameChanged(int f)
+{
+ updateFrame(f);
+}
+
+void PropertiesPanel::rebuild()
+{
+ QLayoutItem* item;
+ while ((item = m_layout->takeAt(0))) { delete item->widget(); delete item; }
+
+ AppState& st = m_main->state();
+ SpriteLoader& ld = m_main->loader();
+ const SpriteData& d = ld.GetData();
+
+ auto* fileGrp = new QGroupBox("File");
+ auto* fileL = new QVBoxLayout(fileGrp);
+ auto* fnLbl = new QLabel(QFileInfo(QString::fromStdString(d.filepath)).fileName());
+ fnLbl->setToolTip(QString::fromStdString(d.filepath));
+ fnLbl->setWordWrap(true);
+ fileL->addWidget(fnLbl);
+ m_layout->addWidget(fileGrp);
+
+ auto* sprGrp = new QGroupBox("Sprite");
+ auto* sprL = new QVBoxLayout(sprGrp);
+ const char* vn = (d.version == 1) ? "Quake" : (d.version == 2) ? "Half-Life" : "Unknown";
+ sprL->addWidget(propLabel("Version:", QString("%1 (%2)").arg(d.version).arg(vn)));
+ sprL->addWidget(propLabel("Type:", SpriteLoader::GetTypeString(d.type)));
+ sprL->addWidget(propLabel("Render:", SpriteLoader::GetTexFormatString(d.texFormat)));
+ sprL->addWidget(propLabel("Cull:", SpriteLoader::GetFaceTypeString(d.facetype)));
+ sprL->addWidget(propLabel("Bounds:", QString("%1 x %2").arg(d.bounds[0]).arg(d.bounds[1])));
+ sprL->addWidget(propLabel("Frames:", QString::number(st.total_frames)));
+ m_layout->addWidget(sprGrp);
+
+ auto* frGrp = new QGroupBox("Frame");
+ auto* frL = new QVBoxLayout(frGrp);
+ m_frameIndex = new QLabel;
+ m_frameSize = new QLabel;
+ m_frameOrigin = new QLabel;
+ m_frameInterval = new QLabel;
+ frL->addWidget(m_frameIndex);
+ frL->addWidget(m_frameSize);
+ frL->addWidget(m_frameOrigin);
+ frL->addWidget(m_frameInterval);
+ m_layout->addWidget(frGrp);
+
+ auto* grpGrp = new QGroupBox("Groups");
+ auto* grpL = new QVBoxLayout(grpGrp);
+ auto* tree = new QTreeWidget;
+ tree->setHeaderHidden(true);
+ tree->setMaximumHeight(200);
+ tree->setAlternatingRowColors(true);
+
+ int gi = 0;
+ for (const auto& g : d.groups)
+ {
+ const char* ts = (g.type == FRAME_SINGLE) ? "Single" : (g.type == FRAME_GROUP) ? "Group" : "Angled";
+ auto* gItem = new QTreeWidgetItem(tree);
+ gItem->setText(0, QString("%1 #%2 (%3)").arg(ts).arg(gi).arg(g.frames.size()));
+ for (int fi = 0; fi < (int)g.frames.size(); fi++)
+ {
+ const auto& f = g.frames[fi];
+ auto* fItem = new QTreeWidgetItem(gItem);
+ fItem->setText(0, QString("%1x%2 (%3,%4) %5s")
+ .arg(f.width).arg(f.height)
+ .arg(f.origin[0]).arg(f.origin[1])
+ .arg(f.interval, 0, 'f', 3));
+ }
+ gi++;
+ }
+ grpL->addWidget(tree);
+ m_layout->addWidget(grpGrp);
+
+ if (d.version == SPRITE_VERSION_HL && d.palette_colors > 0)
+ {
+ auto* palGrp = new QGroupBox("Palette");
+ auto* palL = new QVBoxLayout(palGrp);
+ palL->addWidget(new PaletteWidget(d.palette, d.palette_colors));
+ m_layout->addWidget(palGrp);
+ }
+
+ m_layout->addStretch();
+ updateFrame(st.current_frame);
+}
+
+void PropertiesPanel::updateFrame(int frame)
+{
+ if (!m_frameIndex)
+ return;
+
+ AppState& st = m_main->state();
+ SpriteFrame* fr = m_main->loader().GetFrame(frame);
+
+ if (!fr)
+ return;
+
+ m_frameIndex->setText(propLabel("Index:", QString("%1 / %2").arg(frame + 1).arg(st.total_frames))->text());
+ m_frameSize->setText(propLabel("Size:", QString("%1 x %2").arg(fr->width).arg(fr->height))->text());
+ m_frameOrigin->setText(propLabel("Origin:", QString("%1, %2").arg(fr->origin[0]).arg(fr->origin[1]))->text());
+ m_frameInterval->setText(propLabel("Interval:", QString("%1 s").arg(fr->interval, 0, 'f', 4))->text());
+}
\ No newline at end of file
diff --git a/src/properties_panel.h b/src/properties_panel.h
new file mode 100644
index 0000000..3a84c8a
--- /dev/null
+++ b/src/properties_panel.h
@@ -0,0 +1,36 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+
+class MainWindow;
+
+class PropertiesPanel : public QScrollArea
+{
+ Q_OBJECT
+
+public:
+ explicit PropertiesPanel(MainWindow* mainWin, QWidget* parent = nullptr);
+
+public slots:
+ void onSpriteLoaded();
+ void onSpriteClosed();
+ void onFrameChanged(int frame);
+
+private:
+ void rebuild();
+ void updateFrame(int frame);
+ QLabel* propLabel(const QString& name, const QString& value);
+
+ MainWindow* m_main;
+ QWidget* m_content = nullptr;
+ QVBoxLayout* m_layout = nullptr;
+
+ QLabel* m_frameIndex = nullptr;
+ QLabel* m_frameSize = nullptr;
+ QLabel* m_frameOrigin = nullptr;
+ QLabel* m_frameInterval = nullptr;
+ QLabel* m_playingLabel = nullptr;
+};
\ No newline at end of file
diff --git a/src/renderer.cpp b/src/renderer.cpp
deleted file mode 100644
index 0aa4e72..0000000
--- a/src/renderer.cpp
+++ /dev/null
@@ -1,52 +0,0 @@
-#include "renderer.h"
-#include
-
-void SpriteRenderer::UploadFrame(SpriteFrame& frame)
-{
- if (frame.gl_texture != 0)
- return;
-
- if (frame.rgba.empty() || frame.width <= 0 || frame.height <= 0)
- return;
-
- GLuint tex;
- glGenTextures(1, &tex);
- glBindTexture(GL_TEXTURE_2D, tex);
-
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
- // glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
- // glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
-
- glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, frame.width, frame.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, frame.rgba.data());
- glBindTexture(GL_TEXTURE_2D, 0);
-
- frame.gl_texture = tex;
-}
-
-void SpriteRenderer::DeleteTextures(SpriteData& data)
-{
- for (auto& group : data.groups)
- {
- for (auto& frame : group.frames)
- {
- if (frame.gl_texture != 0)
- {
- GLuint tex = frame.gl_texture;
- glDeleteTextures(1, &tex);
- frame.gl_texture = 0;
- }
- }
- }
-}
-
-void SpriteRenderer::UploadAllFrames(SpriteData& data)
-{
- for (auto& group : data.groups)
- {
- for (auto& frame : group.frames)
- {
- UploadFrame(frame);
- }
- }
-}
\ No newline at end of file
diff --git a/src/renderer.h b/src/renderer.h
deleted file mode 100644
index a4cb560..0000000
--- a/src/renderer.h
+++ /dev/null
@@ -1,21 +0,0 @@
-#pragma once
-
-#include "sprite_loader.h"
-
-#ifdef PLATFORM_WIN32
-#define NOMINMAX
-#define WIN32_LEAN_AND_MEAN
-#include
-#endif
-#include
-
-class SpriteRenderer
-{
-public:
- SpriteRenderer() = default;
- ~SpriteRenderer() = default;
-
- void UploadFrame(SpriteFrame& frame);
- void DeleteTextures(SpriteData& data);
- void UploadAllFrames(SpriteData& data);
-};
diff --git a/src/sprite_viewport.cpp b/src/sprite_viewport.cpp
new file mode 100644
index 0000000..5ec977c
--- /dev/null
+++ b/src/sprite_viewport.cpp
@@ -0,0 +1,214 @@
+#include "sprite_viewport.h"
+#include "mainwindow.h"
+#include "sprite_loader.h"
+#include "theme.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+SpriteViewport::SpriteViewport(MainWindow* mainWin, QWidget* parent) : QWidget(parent), m_main(mainWin)
+{
+ setMouseTracking(true);
+ setFocusPolicy(Qt::StrongFocus);
+}
+
+void SpriteViewport::rebuildImages()
+{
+ m_images.clear();
+ SpriteLoader& loader = m_main->loader();
+ int total = loader.GetTotalFrameCount();
+
+ for (int i = 0; i < total; i++)
+ {
+ SpriteFrame* f = loader.GetFrame(i);
+ if (!f || f->rgba.empty())
+ {
+ m_images.emplace_back();
+ continue;
+ }
+ QImage img(f->rgba.data(), f->width, f->height,
+ f->width * 4, QImage::Format_RGBA8888);
+ m_images.push_back(img.copy());
+ }
+}
+
+void SpriteViewport::drawChecker(QPainter& painter, const QRectF& rect)
+{
+ const float cell = 10.0f;
+ painter.save();
+ painter.setClipRect(rect);
+
+ QColor color1(24, 24, 32);
+ QColor color2(40, 40, 50);
+
+ int startX = static_cast(rect.left() / cell);
+ int startY = static_cast(rect.top() / cell);
+ int endX = static_cast(rect.right() / cell) + 1;
+ int endY = static_cast(rect.bottom() / cell) + 1;
+
+ for (int y = startY; y < endY; y++) {
+ for (int x = startX; x < endX; x++) {
+ QColor c = ((x + y) % 2 == 0) ? color1 : color2;
+ painter.fillRect(QRectF(x * cell, y * cell, cell, cell), c);
+ }
+ }
+
+ painter.restore();
+}
+
+
+void SpriteViewport::drawPlaceholder(QPainter& painter)
+{
+ QFont titleFont = painter.font();
+ titleFont.setPointSize(28);
+ titleFont.setBold(true);
+ painter.setFont(titleFont);
+ painter.setPen(SpriteColors::Accent);
+ painter.drawText(rect(), Qt::AlignCenter, "Sprite-Tools");
+
+ QFont subFont = painter.font();
+ subFont.setPointSize(12);
+ subFont.setBold(false);
+ painter.setFont(subFont);
+ painter.setPen(SpriteColors::TextDim);
+
+ QRect sub = rect();
+ sub.moveTop(sub.top() + 44);
+ painter.drawText(sub, Qt::AlignCenter, "Drop .spr file here or press Ctrl+O");
+}
+
+void SpriteViewport::paintEvent(QPaintEvent*)
+{
+ QPainter painter(this);
+ painter.setRenderHint(QPainter::SmoothPixmapTransform, false);
+ painter.fillRect(rect(), SpriteColors::BgDark);
+
+ AppState& s = m_main->state();
+
+ if (!s.sprite_loaded || m_images.empty())
+ {
+ drawPlaceholder(painter);
+ return;
+ }
+
+ int idx = s.current_frame;
+ if (idx < 0 || idx >= (int)m_images.size()) return;
+
+ const QImage& img = m_images[idx];
+ if (img.isNull())
+ return;
+
+ float iw = img.width() * s.zoom;
+ float ih = img.height() * s.zoom;
+ float vw = (float)width();
+ float vh = (float)height();
+
+ float maxSx = std::max(0.0f, iw - vw);
+ float maxSy = std::max(0.0f, ih - vh);
+ s.scroll_x = std::clamp(s.scroll_x, 0.0f, maxSx);
+ s.scroll_y = std::clamp(s.scroll_y, 0.0f, maxSy);
+
+ float ox = (vw > iw) ? (vw - iw) * 0.5f : -s.scroll_x;
+ float oy = (vh > ih) ? (vh - ih) * 0.5f : -s.scroll_y;
+
+ QRectF sprRect(ox, oy, iw, ih);
+
+ if (s.show_checker)
+ {
+ QRectF vis = sprRect.intersected(QRectF(0, 0, vw, vh));
+ if (vis.isValid())
+ drawChecker(painter, vis);
+ }
+
+ painter.drawImage(sprRect, img);
+
+ painter.setPen(QPen(SpriteColors::Border, 1.0));
+ painter.setBrush(Qt::NoBrush);
+ painter.drawRect(sprRect);
+}
+
+void SpriteViewport::onSpriteLoaded()
+{
+ rebuildImages();
+ update();
+}
+
+void SpriteViewport::onSpriteClosed()
+{
+ m_images.clear();
+ update();
+}
+
+void SpriteViewport::onFrameChanged(int)
+{
+ update();
+}
+
+void SpriteViewport::onZoomChanged(float)
+{
+ update();
+}
+
+void SpriteViewport::wheelEvent(QWheelEvent* event)
+{
+ float delta = event->angleDelta().y() / 120.0f;
+ AppState& s = m_main->state();
+
+ if (event->modifiers() & Qt::ControlModifier)
+ {
+ float oldZ = s.zoom;
+ float newZ = std::clamp(oldZ * (1.0f + delta * 0.15f), 0.25f, 16.0f);
+ float r = newZ / oldZ;
+ float cw = (float)width(), ch = (float)height();
+ s.scroll_x = (s.scroll_x + cw * 0.5f) * r - cw * 0.5f;
+ s.scroll_y = (s.scroll_y + ch * 0.5f) * r - ch * 0.5f;
+ emit zoomRequested(newZ);
+ }
+ else if (event->modifiers() & Qt::ShiftModifier)
+ {
+ s.scroll_x -= delta * 50;
+ update();
+ }
+ else
+ {
+ s.scroll_y -= delta * 50;
+ update();
+ }
+ event->accept();
+}
+
+void SpriteViewport::mousePressEvent(QMouseEvent* event)
+{
+ if (event->button() == Qt::MiddleButton)
+ {
+ m_dragging = true;
+ m_dragStart = event->pos();
+ m_dragScrollX = m_main->state().scroll_x;
+ m_dragScrollY = m_main->state().scroll_y;
+ setCursor(Qt::ClosedHandCursor);
+ }
+}
+
+void SpriteViewport::mouseMoveEvent(QMouseEvent* event)
+{
+ if (m_dragging)
+ {
+ QPoint d = event->pos() - m_dragStart;
+ m_main->state().scroll_x = m_dragScrollX - d.x();
+ m_main->state().scroll_y = m_dragScrollY - d.y();
+ update();
+ }
+}
+
+void SpriteViewport::mouseReleaseEvent(QMouseEvent* event)
+{
+ if (event->button() == Qt::MiddleButton && m_dragging)
+ {
+ m_dragging = false;
+ setCursor(Qt::ArrowCursor);
+ }
+}
\ No newline at end of file
diff --git a/src/sprite_viewport.h b/src/sprite_viewport.h
new file mode 100644
index 0000000..f19369e
--- /dev/null
+++ b/src/sprite_viewport.h
@@ -0,0 +1,48 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+
+class MainWindow;
+
+class SpriteViewport : public QWidget
+{
+ Q_OBJECT
+
+public:
+ explicit SpriteViewport(MainWindow* mainWin, QWidget* parent = nullptr);
+ ~SpriteViewport() = default;
+
+ void deleteTextures() { m_images.clear(); }
+
+signals:
+ void zoomRequested(float newZoom);
+
+public slots:
+ void onSpriteLoaded();
+ void onSpriteClosed();
+ void onFrameChanged(int frame);
+ void onZoomChanged(float zoom);
+
+protected:
+ void paintEvent(QPaintEvent* event) override;
+ void wheelEvent(QWheelEvent* event) override;
+ void mousePressEvent(QMouseEvent* event) override;
+ void mouseMoveEvent(QMouseEvent* event) override;
+ void mouseReleaseEvent(QMouseEvent* event) override;
+
+private:
+ void rebuildImages();
+ void drawChecker(QPainter& painter, const QRectF& rect);
+ void drawPlaceholder(QPainter& painter);
+
+ MainWindow* m_main;
+ std::vector m_images;
+
+ bool m_dragging = false;
+ QPoint m_dragStart;
+ float m_dragScrollX = 0;
+ float m_dragScrollY = 0;
+};
\ No newline at end of file
diff --git a/src/theme.h b/src/theme.h
new file mode 100644
index 0000000..5229512
--- /dev/null
+++ b/src/theme.h
@@ -0,0 +1,393 @@
+#pragma once
+
+#include
+#include
+#include
+
+namespace SpriteColors
+{
+ inline const QColor BgDark {0x1C, 0x1C, 0x24};
+ inline const QColor BgMid {0x24, 0x24, 0x2B};
+ inline const QColor BgLight {0x2E, 0x2E, 0x38};
+ inline const QColor BgLighter {0x38, 0x38, 0x45};
+ inline const QColor ToolbarBg = BgMid;
+ inline const QColor StatusBg {0x17, 0x17, 0x1F};
+ inline const QColor Accent {0x66, 0x99, 0xF2};
+ inline const QColor AccentDim {0x4D, 0x73, 0xBF};
+ inline const QColor AccentLit {0x8C, 0xB8, 0xFF};
+ inline const QColor TextPrimary {0xE0, 0xE0, 0xEB};
+ inline const QColor TextDim {0x80, 0x80, 0x94};
+ inline const QColor TextSection {0x8C, 0xA6, 0xE6};
+ inline const QColor Border {0x47, 0x47, 0x59, 0x80};
+ inline const QColor Playing {0x73, 0xD9, 0x73};
+ inline const QColor Success {0x4C, 0xAF, 0x50};
+ inline const QColor Error {0xE5, 0x39, 0x35};
+}
+
+namespace Theme
+{
+ inline QString globalStyleSheet()
+ {
+ return QStringLiteral(R"(
+ * {
+ font-family: "Segoe UI", "Noto Sans", "Ubuntu", sans-serif;
+ }
+ QMainWindow, QDialog {
+ background-color: #1C1C24;
+ }
+ QToolTip {
+ color: #E0E0EB;
+ background-color: #21212B;
+ border: 1px solid #474759;
+ padding: 4px 8px;
+ border-radius: 4px;
+ }
+ QMenuBar {
+ background-color: #24242B;
+ border-bottom: 1px solid #3A3A48;
+ padding: 2px;
+ color: #E0E0EB;
+ }
+ QMenuBar::item {
+ padding: 4px 10px;
+ border-radius: 4px;
+ }
+ QMenuBar::item:selected {
+ background-color: rgba(102, 153, 242, 0.2);
+ }
+ QMenu {
+ background-color: #21212B;
+ border: 1px solid #3A3A48;
+ border-radius: 8px;
+ padding: 4px;
+ color: #E0E0EB;
+ }
+ QMenu::item {
+ padding: 6px 24px 6px 12px;
+ border-radius: 4px;
+ }
+ QMenu::item:selected {
+ background-color: rgba(102, 153, 242, 0.3);
+ }
+ QMenu::separator {
+ height: 1px;
+ background-color: #3A3A48;
+ margin: 4px 8px;
+ }
+ QMenu::icon {
+ padding-left: 8px;
+ }
+ QStatusBar {
+ background-color: #17171F;
+ border-top: 1px solid #3A3A48;
+ color: #808094;
+ }
+ QStatusBar QLabel {
+ padding: 2px 4px;
+ }
+ QToolBar {
+ background-color: #24242B;
+ border-bottom: 1px solid #3A3A48;
+ spacing: 2px;
+ padding: 2px 4px;
+ }
+ QToolBar QToolButton {
+ border: 1px solid transparent;
+ border-radius: 6px;
+ padding: 4px;
+ color: #E0E0EB;
+ }
+ QToolBar QToolButton:hover {
+ background-color: rgba(102, 153, 242, 0.18);
+ border: 1px solid rgba(102, 153, 242, 0.25);
+ }
+ QToolBar QToolButton:pressed {
+ background-color: rgba(102, 153, 242, 0.35);
+ }
+ QToolBar QToolButton:checked {
+ background-color: rgba(102, 153, 242, 0.25);
+ border: 1px solid rgba(102, 153, 242, 0.4);
+ }
+ QToolBar QToolButton:disabled {
+ color: #505060;
+ }
+ QToolBar QLabel {
+ color: #808094;
+ padding: 0 2px;
+ }
+ QDockWidget {
+ color: #8CA6E6;
+ font-weight: bold;
+ }
+ QDockWidget::title {
+ background-color: #24242B;
+ padding: 8px 12px;
+ border-bottom: 1px solid #3A3A48;
+ text-align: left;
+ }
+ QDockWidget::close-button, QDockWidget::float-button {
+ border: none;
+ padding: 2px;
+ }
+ QScrollBar:vertical {
+ background: #1C1C24;
+ width: 10px;
+ border: none;
+ }
+ QScrollBar::handle:vertical {
+ background: #505064;
+ min-height: 24px;
+ border-radius: 4px;
+ margin: 2px;
+ }
+ QScrollBar::handle:vertical:hover {
+ background: #6A6A86;
+ }
+ QScrollBar:horizontal {
+ background: #1C1C24;
+ height: 10px;
+ border: none;
+ }
+ QScrollBar::handle:horizontal {
+ background: #505064;
+ min-width: 24px;
+ border-radius: 4px;
+ margin: 2px;
+ }
+ QScrollBar::handle:horizontal:hover {
+ background: #6A6A86;
+ }
+ QScrollBar::add-line, QScrollBar::sub-line,
+ QScrollBar::add-page, QScrollBar::sub-page {
+ background: none;
+ height: 0px;
+ width: 0px;
+ }
+ QSlider::groove:horizontal {
+ height: 4px;
+ background: #3A3A48;
+ border-radius: 2px;
+ }
+ QSlider::handle:horizontal {
+ background: #6699F2;
+ width: 16px;
+ height: 16px;
+ margin: -6px 0;
+ border-radius: 8px;
+ }
+ QSlider::handle:horizontal:hover {
+ background: #88B4FF;
+ }
+ QSlider::handle:horizontal:disabled {
+ background: #505060;
+ }
+ QSlider::groove:horizontal:disabled {
+ background: #2E2E38;
+ }
+ QGroupBox {
+ font-weight: bold;
+ border: 1px solid #3A3A48;
+ border-radius: 8px;
+ margin-top: 12px;
+ padding: 12px 8px 8px 8px;
+ color: #8CA6E6;
+ }
+ QGroupBox::title {
+ subcontrol-origin: margin;
+ left: 12px;
+ padding: 0 6px;
+ }
+ QTreeWidget {
+ background-color: #24242C;
+ border: 1px solid #3A3A48;
+ border-radius: 6px;
+ alternate-background-color: #2A2A34;
+ color: #E0E0EB;
+ outline: none;
+ }
+ QTreeWidget::item {
+ padding: 3px 4px;
+ border-radius: 4px;
+ }
+ QTreeWidget::item:hover {
+ background-color: rgba(102, 153, 242, 0.12);
+ }
+ QTreeWidget::item:selected {
+ background-color: rgba(102, 153, 242, 0.25);
+ }
+ QHeaderView::section {
+ background-color: #24242C;
+ color: #808094;
+ border: none;
+ padding: 4px;
+ }
+ QComboBox {
+ background-color: #2E2E38;
+ border: 1px solid #3A3A48;
+ border-radius: 6px;
+ padding: 4px 10px;
+ color: #E0E0EB;
+ min-height: 28px;
+ }
+ QComboBox:hover {
+ border: 1px solid #6699F2;
+ }
+ QComboBox::drop-down {
+ border: none;
+ width: 24px;
+ }
+ QComboBox QAbstractItemView {
+ background-color: #21212B;
+ border: 1px solid #3A3A48;
+ border-radius: 6px;
+ color: #E0E0EB;
+ selection-background-color: rgba(102, 153, 242, 0.3);
+ }
+ QSpinBox, QDoubleSpinBox {
+ background-color: #2E2E38;
+ border: 1px solid #3A3A48;
+ border-radius: 6px;
+ padding: 4px 8px;
+ color: #E0E0EB;
+ min-height: 28px;
+ }
+ QSpinBox:hover, QDoubleSpinBox:hover {
+ border: 1px solid #6699F2;
+ }
+ QSpinBox::up-button, QSpinBox::down-button,
+ QDoubleSpinBox::up-button, QDoubleSpinBox::down-button {
+ border: none;
+ width: 20px;
+ }
+ QPushButton {
+ background-color: #2E2E38;
+ border: 1px solid #3A3A48;
+ border-radius: 8px;
+ padding: 6px 20px;
+ color: #E0E0EB;
+ min-height: 32px;
+ font-weight: 500;
+ }
+ QPushButton:hover {
+ background-color: rgba(102, 153, 242, 0.18);
+ border: 1px solid rgba(102, 153, 242, 0.4);
+ }
+ QPushButton:pressed {
+ background-color: rgba(102, 153, 242, 0.35);
+ }
+ QPushButton:disabled {
+ color: #505060;
+ background-color: #24242B;
+ border-color: #2E2E38;
+ }
+ QPushButton#primaryButton {
+ background-color: #4D73BF;
+ border: none;
+ color: white;
+ }
+ QPushButton#primaryButton:hover {
+ background-color: #6699F2;
+ }
+ QPushButton#primaryButton:pressed {
+ background-color: #3D5C99;
+ }
+ QProgressBar {
+ border: none;
+ border-radius: 4px;
+ text-align: center;
+ background-color: #2E2E38;
+ color: #E0E0EB;
+ min-height: 8px;
+ max-height: 8px;
+ }
+ QProgressBar::chunk {
+ background-color: #6699F2;
+ border-radius: 4px;
+ }
+ QRadioButton, QCheckBox {
+ color: #E0E0EB;
+ spacing: 8px;
+ }
+ QRadioButton::indicator, QCheckBox::indicator {
+ width: 18px;
+ height: 18px;
+ }
+ QLineEdit {
+ background-color: #2E2E38;
+ border: 1px solid #3A3A48;
+ border-radius: 6px;
+ padding: 4px 8px;
+ color: #E0E0EB;
+ min-height: 28px;
+ }
+ QLineEdit:hover {
+ border: 1px solid #6699F2;
+ }
+ QLineEdit:focus {
+ border: 2px solid #6699F2;
+ }
+ QListWidget {
+ background-color: #24242C;
+ border: 1px solid #3A3A48;
+ border-radius: 6px;
+ color: #E0E0EB;
+ outline: none;
+ }
+ QListWidget::item {
+ padding: 4px 8px;
+ border-radius: 4px;
+ }
+ QListWidget::item:hover {
+ background-color: rgba(102, 153, 242, 0.12);
+ }
+ QListWidget::item:selected {
+ background-color: rgba(102, 153, 242, 0.25);
+ }
+ QTabWidget::pane {
+ border: 1px solid #3A3A48;
+ border-radius: 6px;
+ background-color: #1C1C24;
+ }
+ QTabBar::tab {
+ background-color: #24242B;
+ color: #808094;
+ padding: 6px 16px;
+ border: none;
+ border-bottom: 2px solid transparent;
+ }
+ QTabBar::tab:selected {
+ color: #6699F2;
+ border-bottom: 2px solid #6699F2;
+ }
+ QTabBar::tab:hover {
+ color: #E0E0EB;
+ background-color: rgba(102, 153, 242, 0.1);
+ }
+ QDialog {
+ background-color: #24242B;
+ border-radius: 12px;
+ }
+ )");
+ }
+
+ inline QPalette darkPalette()
+ {
+ QPalette p;
+ p.setColor(QPalette::Window, SpriteColors::BgDark);
+ p.setColor(QPalette::WindowText, SpriteColors::TextPrimary);
+ p.setColor(QPalette::Base, SpriteColors::BgLight);
+ p.setColor(QPalette::AlternateBase, SpriteColors::BgLighter);
+ p.setColor(QPalette::ToolTipBase, SpriteColors::BgMid);
+ p.setColor(QPalette::ToolTipText, SpriteColors::TextPrimary);
+ p.setColor(QPalette::Text, SpriteColors::TextPrimary);
+ p.setColor(QPalette::Button, SpriteColors::BgLighter);
+ p.setColor(QPalette::ButtonText, SpriteColors::TextPrimary);
+ p.setColor(QPalette::BrightText, QColor(255, 80, 80));
+ p.setColor(QPalette::Link, SpriteColors::Accent);
+ p.setColor(QPalette::Highlight, SpriteColors::Accent);
+ p.setColor(QPalette::HighlightedText, QColor(0, 0, 0));
+ p.setColor(QPalette::Disabled, QPalette::Text, SpriteColors::TextDim);
+ p.setColor(QPalette::Disabled, QPalette::ButtonText, SpriteColors::TextDim);
+ return p;
+ }
+}
\ No newline at end of file
diff --git a/src/ui.cpp b/src/ui.cpp
deleted file mode 100644
index b74be81..0000000
--- a/src/ui.cpp
+++ /dev/null
@@ -1,1700 +0,0 @@
-#include "ui.h"
-#include "sprite_converter.h"
-#include "icons.h"
-
-ImGuiImage m_pCenterFocus;
-ImGuiImage m_pFolder;
-ImGuiImage m_pNavigateBefore;
-ImGuiImage m_pNavigateNext;
-ImGuiImage m_pPause;
-ImGuiImage m_pPlay;
-ImGuiImage m_pSkipNext;
-ImGuiImage m_pSkipPrevious;
-ImGuiImage m_pZoomIn;
-ImGuiImage m_pZoomOut;
-
-#include
-#include
-#include
-#include
-#include
-
-#include "portable-file-dialogs.h"
-#include "stb_image.h"
-
-void UI::LoadIcons()
-{
- m_pCenterFocus = UI_utils::LoadImageFromMemory(center_focus_strong_png, center_focus_strong_png_len);
- m_pFolder = UI_utils::LoadImageFromMemory(folder_png, folder_png_len);
- m_pNavigateBefore = UI_utils::LoadImageFromMemory(navigate_before_png, navigate_before_png_len);
- m_pNavigateNext = UI_utils::LoadImageFromMemory(navigate_next_png, navigate_next_png_len);
- m_pPause = UI_utils::LoadImageFromMemory(pause_png, pause_png_len);
- m_pPlay = UI_utils::LoadImageFromMemory(play_png, play_png_len);
- m_pSkipNext= UI_utils::LoadImageFromMemory(skip_next_png, skip_next_png_len);
- m_pSkipPrevious = UI_utils::LoadImageFromMemory(skip_previous_png, skip_previous_png_len);
- m_pZoomIn = UI_utils::LoadImageFromMemory(zoom_in_png, zoom_in_png_len);
- m_pZoomOut = UI_utils::LoadImageFromMemory(zoom_out_png, zoom_out_png_len);
-}
-
-void UI::SetupTheme()
-{
- ImGuiStyle& s = ImGui::GetStyle();
-
- s.WindowRounding = 4.0f;
- s.FrameRounding = 3.0f;
- s.GrabRounding = 3.0f;
- s.TabRounding = 3.0f;
- s.ScrollbarRounding = 4.0f;
- s.PopupRounding = 4.0f;
- s.ChildRounding = 3.0f;
-
- s.WindowPadding = ImVec2(8, 8);
- s.FramePadding = ImVec2(6, 3);
- s.ItemSpacing = ImVec2(6, 4);
- s.ItemInnerSpacing = ImVec2(4, 4);
- s.WindowBorderSize = 1.0f;
- s.FrameBorderSize = 0.0f;
- s.PopupBorderSize = 1.0f;
- s.ScrollbarSize = 12.0f;
- s.GrabMinSize = 10.0f;
- s.WindowTitleAlign = ImVec2(0.5f, 0.5f);
-
- ImVec4* c = s.Colors;
-
- ImVec4 bg_dark = ImVec4(0.11f, 0.11f, 0.14f, 1.00f);
- ImVec4 bg_mid = ImVec4(0.14f, 0.14f, 0.17f, 1.00f);
- ImVec4 bg_light = ImVec4(0.18f, 0.18f, 0.22f, 1.00f);
- ImVec4 bg_lighter = ImVec4(0.22f, 0.22f, 0.27f, 1.00f);
-
- ImVec4 accent = ImVec4(0.40f, 0.60f, 0.95f, 1.00f);
- ImVec4 accent_dim = ImVec4(0.30f, 0.45f, 0.75f, 1.00f);
- ImVec4 accent_lit = ImVec4(0.55f, 0.72f, 1.00f, 1.00f);
-
- ImVec4 text = ImVec4(0.88f, 0.88f, 0.92f, 1.00f);
- ImVec4 text_dim = ImVec4(0.50f, 0.50f, 0.58f, 1.00f);
-
- c[ImGuiCol_WindowBg] = bg_dark;
- c[ImGuiCol_ChildBg] = ImVec4(0, 0, 0, 0);
- c[ImGuiCol_PopupBg] = ImVec4(0.13f, 0.13f, 0.16f, 0.97f);
- c[ImGuiCol_MenuBarBg] = bg_mid;
-
- c[ImGuiCol_TitleBg] = bg_dark;
- c[ImGuiCol_TitleBgActive] = bg_mid;
- c[ImGuiCol_TitleBgCollapsed] = bg_dark;
-
- c[ImGuiCol_Border] = ImVec4(0.28f, 0.28f, 0.35f, 0.50f);
- c[ImGuiCol_BorderShadow] = ImVec4(0, 0, 0, 0);
-
- c[ImGuiCol_FrameBg] = bg_light;
- c[ImGuiCol_FrameBgHovered] = bg_lighter;
- c[ImGuiCol_FrameBgActive] = ImVec4(accent.x, accent.y, accent.z, 0.30f);
-
- c[ImGuiCol_Button] = bg_lighter;
- c[ImGuiCol_ButtonHovered] = ImVec4(accent.x, accent.y, accent.z, 0.45f);
- c[ImGuiCol_ButtonActive] = ImVec4(accent.x, accent.y, accent.z, 0.65f);
-
- c[ImGuiCol_Header] = ImVec4(accent.x, accent.y, accent.z, 0.18f);
- c[ImGuiCol_HeaderHovered] = ImVec4(accent.x, accent.y, accent.z, 0.35f);
- c[ImGuiCol_HeaderActive] = ImVec4(accent.x, accent.y, accent.z, 0.50f);
-
- c[ImGuiCol_Tab] = bg_mid;
- c[ImGuiCol_TabHovered] = ImVec4(accent.x, accent.y, accent.z, 0.50f);
- c[ImGuiCol_TabActive] = accent_dim;
- c[ImGuiCol_TabUnfocused] = bg_dark;
- c[ImGuiCol_TabUnfocusedActive] = bg_mid;
-
- c[ImGuiCol_SliderGrab] = accent_dim;
- c[ImGuiCol_SliderGrabActive] = accent;
-
- c[ImGuiCol_CheckMark] = accent_lit;
-
- c[ImGuiCol_ScrollbarBg] = bg_dark;
- c[ImGuiCol_ScrollbarGrab] = ImVec4(0.32f, 0.32f, 0.40f, 1.00f);
- c[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.42f, 0.42f, 0.52f, 1.00f);
- c[ImGuiCol_ScrollbarGrabActive] = accent_dim;
-
- c[ImGuiCol_Separator] = ImVec4(0.28f, 0.28f, 0.35f, 0.60f);
- c[ImGuiCol_SeparatorHovered] = accent_dim;
- c[ImGuiCol_SeparatorActive] = accent;
-
- c[ImGuiCol_ResizeGrip] = ImVec4(0.28f, 0.28f, 0.35f, 0.30f);
- c[ImGuiCol_ResizeGripHovered] = accent_dim;
- c[ImGuiCol_ResizeGripActive] = accent;
-
- c[ImGuiCol_Text] = text;
- c[ImGuiCol_TextDisabled] = text_dim;
- c[ImGuiCol_TextSelectedBg] = ImVec4(accent.x, accent.y, accent.z, 0.30f);
-
- c[ImGuiCol_DragDropTarget] = accent_lit;
- c[ImGuiCol_NavHighlight] = accent;
- c[ImGuiCol_PlotHistogram] = accent;
- c[ImGuiCol_PlotHistogramHovered] = accent_lit;
-}
-
-void UI::Tooltip(const char* text)
-{
- if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayShort))
- {
- ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8, 6));
- ImGui::BeginTooltip();
- ImGui::TextUnformatted(text);
- ImGui::EndTooltip();
- ImGui::PopStyleVar();
- }
-}
-
-bool UI::ImgToolBtn(const char* id, ImGuiImage& img, const char* tip, bool active, bool enabled, float size)
-{
- if (!enabled)
- ImGui::BeginDisabled();
-
- if (active)
- {
- ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.40f, 0.60f, 0.95f, 0.70f));
- ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.50f, 0.68f, 1.00f, 0.80f));
- ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.55f, 0.72f, 1.00f, 0.90f));
- }
-
- ImVec2 btn_size(size + 8, size + 8);
- bool pressed = ImGui::ImageButton(id, (ImTextureID)(intptr_t)img.texture, ImVec2(size, size), ImVec2(0, 0), ImVec2(1, 1), ImVec4(0, 0, 0, 0), ImVec4(1, 1, 1, 1));
-
- if (tip)
- Tooltip(tip);
-
- if (active)
- ImGui::PopStyleColor(3);
-
- if (!enabled)
- ImGui::EndDisabled();
-
- return pressed;
-}
-
-void UI::ToolSep()
-{
- ImGui::SameLine(0, 2);
- ImVec2 p = ImGui::GetCursorScreenPos();
- float h = ImGui::GetFrameHeight();
- ImGui::GetWindowDrawList()->AddLine(ImVec2(p.x + 1, p.y + 3), ImVec2(p.x + 1, p.y + h - 3), IM_COL32(255, 255, 255, 30), 1.0f);
- ImGui::Dummy(ImVec2(4, h));
- ImGui::SameLine(0, 2);
-}
-
-void UI::Section(const char* text)
-{
- ImGui::Spacing();
- ImGui::Spacing();
- float w = ImGui::GetContentRegionAvail().x;
- ImVec2 p = ImGui::GetCursorScreenPos();
- float text_w = ImGui::CalcTextSize(text).x;
-
- ImDrawList* dl = ImGui::GetWindowDrawList();
- float y = p.y + ImGui::GetTextLineHeight() * 0.5f;
-
- dl->AddLine(ImVec2(p.x, y), ImVec2(p.x + 6, y), IM_COL32(100, 100, 120, 100), 1.0f);
-
- ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 10);
- ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.55f, 0.65f, 0.90f, 1.0f));
- ImGui::TextUnformatted(text);
- ImGui::PopStyleColor();
-
- float after_text = p.x + 10 + text_w + 6;
- dl->AddLine(ImVec2(after_text, y), ImVec2(p.x + w, y), IM_COL32(100, 100, 120, 100), 1.0f);
-
- ImGui::Spacing();
-}
-
-void UI::PropRow(const char* label, const char* fmt, ...)
-{
- float label_w = 110.0f;
- ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.55f, 0.55f, 0.62f, 1.0f));
- ImGui::Text("%s", label);
- ImGui::PopStyleColor();
- ImGui::SameLine(label_w);
-
- va_list args;
- va_start(args, fmt);
- ImGui::TextV(fmt, args);
- va_end(args);
-}
-
-std::string UI::GetDir(const std::string& path)
-{
- size_t p = path.find_last_of("/\\");
- return (p != std::string::npos) ? path.substr(0, p) : "";
-}
-
-std::string UI::GetFilename(const std::string& path)
-{
- size_t p = path.find_last_of("/\\");
- return (p != std::string::npos) ? path.substr(p + 1) : path;
-}
-
-void UI::SetStatus(const std::string& msg)
-{
- m_app.status_msg = msg;
- auto now = std::chrono::high_resolution_clock::now();
- m_app.status_time = std::chrono::duration(now.time_since_epoch()).count();
-}
-
-void UI::OpenFileDialog()
-{
- auto sel = pfd::open_file("Open Sprite", m_app.last_dir, { "Sprite files (*.spr)", "*.spr", "All files", "*" }, pfd::opt::none).result();
-
- if (!sel.empty())
- m_app.pending_file = sel[0];
-}
-
-void UI::LoadSpriteFile(const std::string& path)
-{
- if (m_app.sprite_loaded)
- {
- m_app.renderer.DeleteTextures(m_app.loader.GetData());
- m_app.loader.Unload();
- m_app.sprite_loaded = false;
- }
-
- if (m_app.loader.Load(path))
- {
- m_app.renderer.UploadAllFrames(m_app.loader.GetData());
- m_app.sprite_loaded = true;
- m_app.current_frame = 0;
- m_app.total_frames = m_app.loader.GetTotalFrameCount();
- m_app.animating = false;
- m_app.anim_time = 0.0;
- m_app.scroll_x = m_app.scroll_y = 0;
- m_app.last_dir = GetDir(path);
-
- SpriteFrame* f = m_app.loader.GetFrame(0);
- if (f)
- {
- float m = (float)std::max(f->width, f->height);
- m_app.zoom = (m < 64) ? 4.0f : (m < 128) ? 2.0f : 1.0f;
- }
-
- m_app.window_title = "Sprite-Tools — " + GetFilename(path);
- m_app.title_changed = true;
-
- SetStatus("Loaded: " + GetFilename(path) + " (" + std::to_string(m_app.total_frames) + " frames)");
- }
- else
- {
- SetStatus("Failed to load: " + GetFilename(path));
- }
-}
-
-void UI::CloseSprite()
-{
- if (!m_app.sprite_loaded)
- return;
-
- m_app.renderer.DeleteTextures(m_app.loader.GetData());
- m_app.loader.Unload();
- m_app.sprite_loaded = false;
- m_app.current_frame = 0;
- m_app.total_frames = 0;
-
- m_app.window_title = "Sprite-Tools";
- m_app.title_changed = true;
-
- SetStatus("Closed");
-}
-
-void UI::SetPendingFile(const std::string& path)
-{
- m_app.pending_file = path;
-}
-
-void UI::ProcessPendingFile()
-{
- if (m_app.pending_file.empty())
- return;
-
- LoadSpriteFile(m_app.pending_file);
- m_app.pending_file.clear();
-}
-
-std::string UI::ConsumeTitle()
-{
- m_app.title_changed = false;
- return m_app.window_title;
-}
-
-void UI::CleanupResources()
-{
- if (m_app.sprite_loaded)
- m_app.renderer.DeleteTextures(m_app.loader.GetData());
-}
-
-void UI::DrawMenuBar()
-{
- if (!ImGui::BeginMainMenuBar())
- return;
-
- if (ImGui::BeginMenu("File"))
- {
- if (ImGui::MenuItem("Open", "Ctrl+O"))
- OpenFileDialog();
-
- if (ImGui::MenuItem("Close", nullptr, false, m_app.sprite_loaded))
- CloseSprite();
-
- ImGui::Separator();
-
- if (ImGui::MenuItem("Export Frames...", "Ctrl+E",
- false, m_app.sprite_loaded))
- m_conv.show_export = true;
-
- if (ImGui::MenuItem("Import Images to SPR...", "Ctrl+I"))
- m_conv.show_import = true;
-
- ImGui::Separator();
-
- if (ImGui::MenuItem("Exit", "Alt+F4"))
- m_app.request_exit = true;
-
- ImGui::EndMenu();
- }
-
- if (ImGui::BeginMenu("View"))
- {
- ImGui::MenuItem("Toolbar", nullptr, &m_app.show_toolbar);
- ImGui::MenuItem("Properties", nullptr, &m_app.show_info);
- ImGui::Separator();
- ImGui::MenuItem("Transparency grid", nullptr, &m_app.show_checker);
-
- ImGui::Separator();
-
- if (ImGui::BeginMenu("Zoom"))
- {
- const float zooms[] = { 0.25f, 0.5f, 1.0f, 2.0f, 4.0f, 8.0f };
- const char* labels[] = { "25%", "50%", "100%", "200%", "400%", "800%" };
- for (int i = 0; i < 6; i++)
- {
- bool sel = (std::abs(m_app.zoom - zooms[i]) < 0.01f);
- if (ImGui::MenuItem(labels[i], nullptr, sel))
- {
- m_app.zoom = zooms[i];
- m_app.scroll_x = m_app.scroll_y = 0;
- }
- }
- ImGui::EndMenu();
- }
-
- ImGui::EndMenu();
- }
-
- bool can_play = m_app.sprite_loaded && m_app.total_frames > 1;
- if (ImGui::BeginMenu("Playback", can_play))
- {
- if (ImGui::MenuItem(m_app.animating ? "Pause" : "Play", "Space"))
- {
- m_app.animating = !m_app.animating;
- if (m_app.animating)
- {
- auto now = std::chrono::high_resolution_clock::now();
- m_app.last_time = std::chrono::duration(
- now.time_since_epoch()).count();
- }
- }
- ImGui::Separator();
-
- if (ImGui::MenuItem("First", "Home"))
- m_app.current_frame = 0;
- if (ImGui::MenuItem("Previous", "Left"))
- m_app.current_frame = (m_app.current_frame - 1 + m_app.total_frames) % m_app.total_frames;
- if (ImGui::MenuItem("Next", "Right"))
- m_app.current_frame = (m_app.current_frame + 1) % m_app.total_frames;
- if (ImGui::MenuItem("Last", "End"))
- m_app.current_frame = m_app.total_frames - 1;
-
- ImGui::Separator();
- ImGui::Text("Speed:");
- ImGui::SetNextItemWidth(120);
- ImGui::SliderFloat("##spd", &m_app.anim_speed, 0.1f, 8.0f, "%.1fx");
-
- ImGui::EndMenu();
- }
-
- if (ImGui::BeginMenu("Help"))
- {
- if (ImGui::MenuItem("About"))
- m_app.show_about = true;
- ImGui::EndMenu();
- }
-
- if (m_app.sprite_loaded)
- {
- SpriteFrame* f = m_app.loader.GetFrame(m_app.current_frame);
- if (f)
- {
- char buf[96];
- snprintf(buf, sizeof(buf), "%dx%d | %d/%d | %.0f%%", f->width, f->height, m_app.current_frame + 1, m_app.total_frames, m_app.zoom * 100.0f);
- float tw = ImGui::CalcTextSize(buf).x;
- ImGui::SameLine(ImGui::GetWindowWidth() - tw - 16);
- ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.50f, 0.50f, 0.58f, 1.0f));
- ImGui::TextUnformatted(buf);
- ImGui::PopStyleColor();
- }
- }
-
- ImGui::EndMainMenuBar();
-}
-
-void UI::DrawToolbar()
-{
- if (!m_app.show_toolbar)
- return;
-
- ImGuiViewport* vp = ImGui::GetMainViewport();
- float h = 38.0f;
- float iconSize = 20.0f;
-
- ImGui::SetNextWindowPos(ImVec2(vp->WorkPos.x, vp->WorkPos.y));
- ImGui::SetNextWindowSize(ImVec2(vp->WorkSize.x, h));
-
- ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(6, 5));
- ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(3, 0));
- ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.13f, 0.13f, 0.16f, 1.0f));
-
- ImGui::Begin("##tb", nullptr,
- ImGuiWindowFlags_NoTitleBar |
- ImGuiWindowFlags_NoResize |
- ImGuiWindowFlags_NoMove |
- ImGuiWindowFlags_NoScrollbar |
- ImGuiWindowFlags_NoSavedSettings);
-
- bool loaded = m_app.sprite_loaded;
- bool multi = loaded && m_app.total_frames > 1;
-
- if (ImgToolBtn("##open", m_pFolder, "Open (Ctrl+O)"))
- OpenFileDialog();
- ImGui::SameLine();
-
- ToolSep();
-
- if (ImgToolBtn("##first", m_pSkipPrevious, "First (Home)", false, multi, iconSize))
- m_app.current_frame = 0;
- ImGui::SameLine();
-
- if (ImgToolBtn("##prev", m_pNavigateBefore, "Previous (Left)", false, multi, iconSize))
- m_app.current_frame = (m_app.current_frame - 1 + std::max(1, m_app.total_frames)) % std::max(1, m_app.total_frames);
- ImGui::SameLine();
- {
- bool playing = m_app.animating;
- ImGuiImage& playIcon = playing ? m_pPause : m_pPlay;
- if (ImgToolBtn("##playpause", playIcon, "Play/Pause (Space)", playing, multi, iconSize))
- {
- m_app.animating = !m_app.animating;
- if (m_app.animating)
- {
- auto now = std::chrono::high_resolution_clock::now();
- m_app.last_time = std::chrono::duration(now.time_since_epoch()).count();
- }
- }
- }
- ImGui::SameLine();
-
- if (ImgToolBtn("##next", m_pNavigateNext, "Next (Right)", false, multi, iconSize))
- m_app.current_frame = (m_app.current_frame + 1) % std::max(1, m_app.total_frames);
- ImGui::SameLine();
-
- if (ImgToolBtn("##last", m_pSkipNext, "Last (End)", false, multi, iconSize))
- m_app.current_frame = std::max(0, m_app.total_frames - 1);
-
- ImGui::SameLine();
- ToolSep();
-
- ImGui::BeginDisabled(!multi);
- ImGui::SetNextItemWidth(70);
- ImGui::SliderFloat("##spd", &m_app.anim_speed, 0.1f, 8.0f, "%.1fx");
- Tooltip("Playback speed");
- ImGui::EndDisabled();
-
- ImGui::SameLine();
- ToolSep();
-
- if (ImgToolBtn("##zoomout", m_pZoomOut, "Zoom out (-)", false, true, iconSize))
- {
- m_app.zoom = std::max(0.25f, m_app.zoom * 0.5f);
- m_app.scroll_x = m_app.scroll_y = 0;
- }
- ImGui::SameLine();
-
- ImGui::SetNextItemWidth(70);
- float zoom_pct = m_app.zoom * 100.0f;
- if (ImGui::SliderFloat("##zm", &zoom_pct, 25, 1600, "%.0f%%", ImGuiSliderFlags_Logarithmic))
- {
- m_app.zoom = zoom_pct / 100.0f;
- }
- Tooltip("Zoom (Ctrl+Wheel)");
- ImGui::SameLine();
-
- if (ImgToolBtn("##zoomin", m_pZoomIn, "Zoom in (+)", false, true, iconSize))
- m_app.zoom = std::min(16.0f, m_app.zoom * 2.0f);
- ImGui::SameLine();
-
- if (ImgToolBtn("##center", m_pCenterFocus, "100% (1)", false, true, iconSize))
- {
- m_app.zoom = 1.0f;
- m_app.scroll_x = m_app.scroll_y = 0;
- }
-
- if (multi)
- {
- ImGui::SameLine();
- ToolSep();
-
- ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.55f, 0.55f, 0.62f, 1.0f));
- ImGui::Text("Frame");
- ImGui::PopStyleColor();
- ImGui::SameLine();
-
- float remaining = ImGui::GetContentRegionAvail().x - 4;
- ImGui::SetNextItemWidth(std::max(60.0f, remaining));
- int f = m_app.current_frame;
- char frame_label[32];
- snprintf(frame_label, sizeof(frame_label), "%d / %d", f + 1, m_app.total_frames);
- if (ImGui::SliderInt("##fr", &f, 0, m_app.total_frames - 1, frame_label, ImGuiSliderFlags_AlwaysClamp))
- m_app.current_frame = f;
- }
-
- ImGui::End();
- ImGui::PopStyleColor();
- ImGui::PopStyleVar(2);
-}
-
-void UI::DrawChecker(ImDrawList* dl, ImVec2 pos, ImVec2 sz, float cell)
-{
- ImU32 a = IM_COL32(160, 160, 160, 255);
- ImU32 b = IM_COL32(110, 110, 110, 255);
- int nx = (int)(sz.x / cell) + 1;
- int ny = (int)(sz.y / cell) + 1;
- for (int y = 0; y < ny; y++)
- for (int x = 0; x < nx; x++)
- {
- float x0 = pos.x + x * cell;
- float y0 = pos.y + y * cell;
- dl->AddRectFilled(ImVec2(x0, y0), ImVec2(std::min(x0 + cell, pos.x + sz.x), std::min(y0 + cell, pos.y + sz.y)), ((x + y) & 1) ? b : a);
- }
-}
-
-void UI::DrawViewport()
-{
- float tb_h = m_app.show_toolbar ? 38.0f : 0.0f;
- float sb_h = 26.0f;
- float info_w = m_app.show_info ? 300.0f : 0.0f;
-
- ImGuiViewport* vp = ImGui::GetMainViewport();
- float vw = vp->WorkSize.x - info_w;
- float vh = vp->WorkSize.y - tb_h - sb_h;
-
- ImGui::SetNextWindowPos(ImVec2(vp->WorkPos.x, vp->WorkPos.y + tb_h));
- ImGui::SetNextWindowSize(ImVec2(vw, vh));
-
- ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
- ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.11f, 0.11f, 0.14f, 1.0f));
-
- ImGui::Begin("##viewport", nullptr,
- ImGuiWindowFlags_NoTitleBar |
- ImGuiWindowFlags_NoResize |
- ImGuiWindowFlags_NoMove |
- ImGuiWindowFlags_NoCollapse |
- ImGuiWindowFlags_NoBringToFrontOnFocus |
- ImGuiWindowFlags_NoScrollbar |
- ImGuiWindowFlags_NoScrollWithMouse);
-
- ImGui::PopStyleColor();
- ImGui::PopStyleVar();
-
- if (!m_app.sprite_loaded)
- {
- ImVec2 ws = ImGui::GetWindowSize();
-
- const char* t1 = "Sprite-Tools";
-
- ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.40f, 0.55f, 0.88f, 0.80f));
- float old_scale = ImGui::GetFont()->Scale;
- ImGui::GetFont()->Scale = 1.5f;
- ImGui::PushFont(ImGui::GetFont());
- ImVec2 ts_big = ImGui::CalcTextSize(t1);
- ImGui::SetCursorPos(ImVec2((ws.x - ts_big.x) * 0.5f, ws.y * 0.36f));
- ImGui::TextUnformatted(t1);
- ImGui::GetFont()->Scale = old_scale;
- ImGui::PopFont();
- ImGui::PopStyleColor();
-
- const char* t2 = "Drop .spr file here or press Ctrl+O";
- ImVec2 ts2 = ImGui::CalcTextSize(t2);
- ImGui::SetCursorPos(ImVec2((ws.x - ts2.x) * 0.5f, ws.y * 0.36f + ts_big.y + 12));
- ImGui::TextDisabled("%s", t2);
-
- ImGui::End();
- return;
- }
-
- SpriteFrame* frame = m_app.loader.GetFrame(m_app.current_frame);
- if (!frame)
- {
- ImGui::End();
- return;
- }
-
- if (m_app.animating && m_app.total_frames > 1)
- {
- auto now = std::chrono::high_resolution_clock::now();
- double t = std::chrono::duration(now.time_since_epoch()).count();
- m_app.anim_time += (t - m_app.last_time) * m_app.anim_speed;
- m_app.last_time = t;
- float iv = frame->interval > 0.0f ? frame->interval : 0.1f;
- while (m_app.anim_time >= iv)
- {
- m_app.anim_time -= iv;
- m_app.current_frame = (m_app.current_frame + 1) % m_app.total_frames;
- frame = m_app.loader.GetFrame(m_app.current_frame);
- if (!frame) { ImGui::End(); return; }
- iv = frame->interval > 0.0f ? frame->interval : 0.1f;
- }
- }
-
- ImVec2 avail = ImGui::GetContentRegionAvail();
- float iw = frame->width * m_app.zoom;
- float ih = frame->height * m_app.zoom;
-
- float sbt = 12.0f;
- bool sx = iw > avail.x;
- bool sy = ih > avail.y;
- float cw = avail.x - (sy ? sbt : 0);
- float ch = avail.y - (sx ? sbt : 0);
- sx = iw > cw; sy = ih > ch;
- cw = avail.x - (sy ? sbt : 0);
- ch = avail.y - (sx ? sbt : 0);
-
- float msx = std::max(0.0f, iw - cw);
- float msy = std::max(0.0f, ih - ch);
-
- if (ImGui::IsWindowHovered())
- {
- float wh = ImGui::GetIO().MouseWheel;
- if (wh != 0)
- {
- if (ImGui::GetIO().KeyCtrl)
- {
- float oz = m_app.zoom;
- m_app.zoom = std::clamp(m_app.zoom * (1.0f + wh * 0.15f), 0.25f, 16.0f);
- float r = m_app.zoom / oz;
- m_app.scroll_x = (m_app.scroll_x + cw * 0.5f) * r - cw * 0.5f;
- m_app.scroll_y = (m_app.scroll_y + ch * 0.5f) * r - ch * 0.5f;
- }
- else if (ImGui::GetIO().KeyShift)
- m_app.scroll_x -= wh * 50;
- else
- m_app.scroll_y -= wh * 50;
- }
- if (ImGui::IsMouseClicked(ImGuiMouseButton_Middle))
- {
- m_app.dragging = true;
- m_app.drag_start = ImGui::GetMousePos();
- m_app.drag_sx = m_app.scroll_x;
- m_app.drag_sy = m_app.scroll_y;
- }
- }
- if (m_app.dragging)
- {
- if (ImGui::IsMouseDown(ImGuiMouseButton_Middle))
- {
- ImVec2 m = ImGui::GetMousePos();
- m_app.scroll_x = m_app.drag_sx - (m.x - m_app.drag_start.x);
- m_app.scroll_y = m_app.drag_sy - (m.y - m_app.drag_start.y);
- }
- else
- m_app.dragging = false;
- }
-
- m_app.scroll_x = std::clamp(m_app.scroll_x, 0.0f, msx);
- m_app.scroll_y = std::clamp(m_app.scroll_y, 0.0f, msy);
-
- ImVec2 wp = ImGui::GetCursorScreenPos();
- float ox = sx ? -m_app.scroll_x : (cw - iw) * 0.5f;
- float oy = sy ? -m_app.scroll_y : (ch - ih) * 0.5f;
- ImVec2 p0(wp.x + ox, wp.y + oy);
- ImVec2 p1(p0.x + iw, p0.y + ih);
-
- ImDrawList* dl = ImGui::GetWindowDrawList();
- dl->PushClipRect(wp, ImVec2(wp.x + cw, wp.y + ch), true);
-
- if (m_app.show_checker)
- {
- float vx0 = std::max(p0.x, wp.x), vy0 = std::max(p0.y, wp.y);
- float vx1 = std::min(p1.x, wp.x + cw), vy1 = std::min(p1.y, wp.y + ch);
- if (vx0 < vx1 && vy0 < vy1)
- DrawChecker(dl, ImVec2(vx0, vy0), ImVec2(vx1 - vx0, vy1 - vy0));
- }
-
- if (frame->gl_texture)
- dl->AddImage((ImTextureID)(intptr_t)frame->gl_texture, p0, p1);
-
- dl->AddRect(p0, p1, IM_COL32(120, 140, 200, 60), 0, 0, 1.0f);
- dl->PopClipRect();
-
- auto DrawScrollbar = [&](bool horizontal)
- {
- float bar_x, bar_y, bar_w, bar_h;
- float content_sz, img_sz, scroll_val, max_scroll;
-
- if (horizontal)
- {
- bar_x = wp.x; bar_y = wp.y + ch;
- bar_w = cw; bar_h = sbt;
- content_sz = cw; img_sz = iw;
- scroll_val = m_app.scroll_x; max_scroll = msx;
- }
- else
- {
- bar_x = wp.x + cw; bar_y = wp.y;
- bar_w = sbt; bar_h = ch;
- content_sz = ch; img_sz = ih;
- scroll_val = m_app.scroll_y; max_scroll = msy;
- }
-
- dl->AddRectFilled(ImVec2(bar_x, bar_y), ImVec2(bar_x + bar_w, bar_y + bar_h), IM_COL32(20, 20, 25, 200), 2.0f);
-
- float vis = content_sz / img_sz;
- float track = horizontal ? bar_w : bar_h;
- float thumb = std::max(20.0f, track * vis);
- float ratio = (max_scroll > 0) ? (scroll_val / max_scroll) : 0;
- float thumb_pos = ratio * (track - thumb);
-
- ImVec2 tmin, tmax;
- if (horizontal)
- {
- tmin = ImVec2(bar_x + thumb_pos, bar_y + 2);
- tmax = ImVec2(bar_x + thumb_pos + thumb, bar_y + bar_h - 2);
- }
- else
- {
- tmin = ImVec2(bar_x + 2, bar_y + thumb_pos);
- tmax = ImVec2(bar_x + bar_w - 2, bar_y + thumb_pos + thumb);
- }
-
- bool hov = ImGui::IsMouseHoveringRect(tmin, tmax);
- ImU32 tc = hov ? IM_COL32(140, 160, 220, 200) : IM_COL32(80, 80, 100, 180);
- dl->AddRectFilled(tmin, tmax, tc, 3.0f);
-
- ImVec2 bmin(bar_x, bar_y), bmax(bar_x + bar_w, bar_y + bar_h);
- if (ImGui::IsMouseHoveringRect(bmin, bmax) && ImGui::IsMouseClicked(0))
- {
- float click = horizontal ? (ImGui::GetMousePos().x - bar_x) : (ImGui::GetMousePos().y - bar_y);
- float& sv = horizontal ? m_app.scroll_x : m_app.scroll_y;
- sv = (click / track) * max_scroll;
- }
- if (hov && ImGui::IsMouseDragging(0))
- {
- float delta = horizontal ? ImGui::GetIO().MouseDelta.x : ImGui::GetIO().MouseDelta.y;
- float& sv = horizontal ? m_app.scroll_x : m_app.scroll_y;
- sv += delta * (max_scroll / (track - thumb));
- }
- };
-
- if (sx)
- DrawScrollbar(true);
-
- if (sy)
- DrawScrollbar(false);
-
- if (sx && sy)
- dl->AddRectFilled( ImVec2(wp.x + cw, wp.y + ch), ImVec2(wp.x + cw + sbt, wp.y + ch + sbt), IM_COL32(20, 20, 25, 200), 2.0f);
-
- ImGui::End();
-}
-
-void UI::DrawProperties()
-{
- if (!m_app.show_info)
- return;
-
- float tb_h = m_app.show_toolbar ? 38.0f : 0.0f;
- float sb_h = 26.0f;
- float pw = 300.0f;
-
- ImGuiViewport* vp = ImGui::GetMainViewport();
-
- ImGui::SetNextWindowPos(ImVec2(vp->WorkPos.x + vp->WorkSize.x - pw, vp->WorkPos.y + tb_h));
- ImGui::SetNextWindowSize(ImVec2(pw, vp->WorkSize.y - tb_h - sb_h));
-
- ImGui::Begin("Properties", &m_app.show_info,
- ImGuiWindowFlags_NoMove |
- ImGuiWindowFlags_NoResize |
- ImGuiWindowFlags_NoCollapse |
- ImGuiWindowFlags_NoBringToFrontOnFocus);
-
- if (!m_app.sprite_loaded)
- {
- ImGui::TextDisabled("No file loaded");
- ImGui::End();
- return;
- }
-
- const SpriteData& d = m_app.loader.GetData();
-
- Section("File");
- {
- std::string fn = GetFilename(d.filepath);
- ImGui::TextUnformatted(fn.c_str());
- Tooltip(d.filepath.c_str());
- }
-
- Section("Sprite");
- {
- const char* vn = (d.version == 1) ? "Quake" : (d.version == 2) ? "Half-Life" : "Unknown";
- PropRow("Version", "%d (%s)", d.version, vn);
- PropRow("Type", "%s", SpriteLoader::GetTypeString(d.type));
- PropRow("Render", "%s", SpriteLoader::GetTexFormatString(d.texFormat));
- PropRow("Cull", "%s", SpriteLoader::GetFaceTypeString(d.facetype));
- PropRow("Bounds", "%d x %d", d.bounds[0], d.bounds[1]);
- PropRow("Frames", "%d", m_app.total_frames);
- }
-
- Section("Frame");
- SpriteFrame* fr = m_app.loader.GetFrame(m_app.current_frame);
- if (fr)
- {
- PropRow("Index", "%d / %d", m_app.current_frame + 1, m_app.total_frames);
- PropRow("Size", "%d x %d", fr->width, fr->height);
- PropRow("Origin", "%d, %d", fr->origin[0], fr->origin[1]);
- PropRow("Interval", "%.4f s", fr->interval);
-
- if (m_app.animating)
- {
- ImGui::SameLine();
- ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.45f, 0.85f, 0.45f, 1.0f));
- ImGui::Text(" Playing");
- ImGui::PopStyleColor();
- }
- }
-
- Section("Groups");
- int gi = 0;
- for (const auto& g : d.groups)
- {
- const char* ts = (g.type == FRAME_SINGLE) ? "Single" : (g.type == FRAME_GROUP) ? "Group" : "Angled";
- char lbl[64];
- snprintf(lbl, sizeof(lbl), "%s #%d (%d)###g%d", ts, gi, (int)g.frames.size(), gi);
-
- if (ImGui::TreeNode(lbl))
- {
- for (int fi = 0; fi < (int)g.frames.size(); fi++)
- {
- const auto& f = g.frames[fi];
- ImGui::BulletText("%dx%d (%d,%d) %.3fs",
- f.width, f.height, f.origin[0], f.origin[1], f.interval);
- }
- ImGui::TreePop();
- }
- gi++;
- }
-
- if (d.version == SPRITE_VERSION_HL && d.palette_colors > 0)
- {
- Section("Palette");
-
- ImVec2 cs(9, 9);
- int cols = 16;
- ImDrawList* pdl = ImGui::GetWindowDrawList();
- ImVec2 base = ImGui::GetCursorScreenPos();
-
- for (int i = 0; i < d.palette_colors && i < 256; i++)
- {
- int row = i / cols, col = i % cols;
- ImVec2 a(base.x + col * (cs.x + 1), base.y + row * (cs.y + 1));
- ImVec2 b(a.x + cs.x, a.y + cs.y);
-
- uint8_t r = d.palette[i * 3];
- uint8_t g = d.palette[i * 3 + 1];
- uint8_t bl = d.palette[i * 3 + 2];
- pdl->AddRectFilled(a, b, IM_COL32(r, g, bl, 255));
-
- if (ImGui::IsMouseHoveringRect(a, b))
- {
- pdl->AddRect(a, b, IM_COL32(255, 255, 100, 255), 0, 0, 1.5f);
- ImGui::BeginTooltip();
- ImGui::ColorButton("##c",ImVec4(r / 255.f, g / 255.f, bl / 255.f, 1.f), ImGuiColorEditFlags_NoTooltip, ImVec2(28, 28));
- ImGui::SameLine();
- ImGui::Text("#%d\n%d %d %d\n#%02X%02X%02X", i, r, g, bl, r, g, bl);
- ImGui::EndTooltip();
- }
- }
-
- int rows = (std::min((int)d.palette_colors, 256) + cols - 1) / cols;
- ImGui::Dummy(ImVec2(cols * (cs.x + 1), rows * (cs.y + 1)));
- }
-
- ImGui::End();
-}
-
-void UI::DrawStatusBar()
-{
- float h = 26.0f;
- ImGuiViewport* vp = ImGui::GetMainViewport();
-
- ImGui::SetNextWindowPos(ImVec2(vp->WorkPos.x, vp->WorkPos.y + vp->WorkSize.y - h));
- ImGui::SetNextWindowSize(ImVec2(vp->WorkSize.x, h));
-
- ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(10, 3));
- ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.09f, 0.09f, 0.12f, 1.0f));
-
- ImGui::Begin("##sb", nullptr,
- ImGuiWindowFlags_NoTitleBar |
- ImGuiWindowFlags_NoResize |
- ImGuiWindowFlags_NoMove |
- ImGuiWindowFlags_NoScrollbar |
- ImGuiWindowFlags_NoSavedSettings |
- ImGuiWindowFlags_NoBringToFrontOnFocus);
-
- if (!m_app.status_msg.empty())
- {
- auto now = std::chrono::high_resolution_clock::now();
- double age = std::chrono::duration(
- now.time_since_epoch()).count() - m_app.status_time;
- if (age < 4.0)
- {
- float a = (age < 3.0) ? 1.0f : (float)(4.0 - age);
- ImGui::PushStyleColor(ImGuiCol_Text,
- ImVec4(0.75f, 0.80f, 0.92f, a));
- ImGui::TextUnformatted(m_app.status_msg.c_str());
- ImGui::PopStyleColor();
- }
- else
- m_app.status_msg.clear();
- }
-
- if (m_app.sprite_loaded)
- {
- const SpriteData& d = m_app.loader.GetData();
- char buf[96];
- snprintf(buf, sizeof(buf), "v%d %s %d frames", d.version, SpriteLoader::GetTexFormatString(d.texFormat), m_app.total_frames);
- float tw = ImGui::CalcTextSize(buf).x;
- ImGui::SameLine(ImGui::GetWindowWidth() - tw - 12);
- ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.42f, 0.42f, 0.50f, 1.0f));
- ImGui::TextUnformatted(buf);
- ImGui::PopStyleColor();
- }
-
- ImGui::End();
- ImGui::PopStyleColor();
- ImGui::PopStyleVar();
-}
-
-void UI::DrawAbout()
-{
- if (!m_app.show_about)
- return;
-
- ImGui::OpenPopup("About##dlg");
- ImGui::SetNextWindowSize(ImVec2(400, 200), ImGuiCond_Appearing);
-
- if (ImGui::BeginPopupModal("About##dlg", &m_app.show_about,
- ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar))
- {
- float footer_height = ImGui::GetFrameHeightWithSpacing() + 10.0f;
-
- ImGui::BeginChild("about_content", ImVec2(0, -footer_height), false);
-
- ImGui::Spacing();
-
- ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.45f, 0.62f, 0.95f, 1.0f));
- ImGui::Text("Sprite-Tools");
- ImGui::PopStyleColor();
-
- ImGui::Spacing();
- ImGui::TextWrapped("Sprite viewer and creator for Quake / Half-Life sprites");
-
- ImGui::Spacing();
- ImGui::Spacing();
-
- ImGui::Text("Github:");
- ImGui::SameLine();
-
- ImGui::TextColored(ImVec4(0.45f, 0.62f, 0.95f, 1.0f), "https://github.com/Elinsrc/Sprite-Tools");
-
- if (ImGui::IsItemHovered())
- {
- ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
- ImGui::SetTooltip("Open GitHub repository");
-
- if (ImGui::IsItemClicked())
- {
-#ifdef _WIN32
- ShellExecuteA(0, "open", "https://github.com/Elinsrc/Sprite-Tools", 0, 0, SW_SHOWNORMAL);
-#else
- system("xdg-open https://github.com/Elinsrc/Sprite-Tools");
-#endif
- }
- }
-
- ImGui::EndChild();
-
- ImGui::Separator();
-
- float button_width = 100.0f;
- ImGui::SetCursorPosX((ImGui::GetWindowWidth() - button_width) * 0.5f);
-
- if (ImGui::Button("OK", ImVec2(button_width, 0)))
- {
- m_app.show_about = false;
- ImGui::CloseCurrentPopup();
- }
-
- ImGui::EndPopup();
- }
-}
-void UI::HandleKeys()
-{
- ImGuiIO& io = ImGui::GetIO();
-
- if (io.WantTextInput || ImGui::IsAnyItemActive())
- return;
-
- if (io.KeyCtrl && ImGui::IsKeyPressed(ImGuiKey_O))
- {
- OpenFileDialog();
- return;
- }
-
- if (!m_app.sprite_loaded)
- return;
-
- if (ImGui::IsKeyPressed(ImGuiKey_Space) && m_app.total_frames > 1)
- {
- m_app.animating = !m_app.animating;
- if (m_app.animating)
- {
- auto now = std::chrono::high_resolution_clock::now();
- m_app.last_time = std::chrono::duration(
- now.time_since_epoch()).count();
- }
- }
-
- if (!m_app.animating && m_app.total_frames > 1)
- {
- if (ImGui::IsKeyPressed(ImGuiKey_LeftArrow))
- m_app.current_frame = (m_app.current_frame - 1 + m_app.total_frames) % m_app.total_frames;
-
- if (ImGui::IsKeyPressed(ImGuiKey_RightArrow))
- m_app.current_frame = (m_app.current_frame + 1) % m_app.total_frames;
- }
-
- if (ImGui::IsKeyPressed(ImGuiKey_Home))
- m_app.current_frame = 0;
-
- if (ImGui::IsKeyPressed(ImGuiKey_End))
- m_app.current_frame = std::max(0, m_app.total_frames - 1);
-
- if (ImGui::IsKeyPressed(ImGuiKey_Equal) ||
- ImGui::IsKeyPressed(ImGuiKey_KeypadAdd))
- m_app.zoom = std::min(16.0f, m_app.zoom * 2.0f);
-
- if (ImGui::IsKeyPressed(ImGuiKey_Minus) ||
- ImGui::IsKeyPressed(ImGuiKey_KeypadSubtract))
- m_app.zoom = std::max(0.25f, m_app.zoom * 0.5f);
-
- if (ImGui::IsKeyPressed(ImGuiKey_1))
- {
- m_app.zoom = 1.0f;
- m_app.scroll_x = m_app.scroll_y = 0;
- }
-
- if (ImGui::IsKeyPressed(ImGuiKey_I))
- m_app.show_info = !m_app.show_info;
-
- if (ImGui::IsKeyPressed(ImGuiKey_T))
- m_app.show_toolbar = !m_app.show_toolbar;
-
- if (io.KeyCtrl && ImGui::IsKeyPressed(ImGuiKey_E) && m_app.sprite_loaded)
- {
- m_conv.show_export = true;
- return;
- }
-
- if (io.KeyCtrl && ImGui::IsKeyPressed(ImGuiKey_I))
- {
- m_conv.show_import = true;
- return;
- }
-}
-
-void UI::DrawExportDialog()
-{
- if (!m_conv.show_export)
- return;
-
- if (!m_app.sprite_loaded)
- {
- m_conv.show_export = false;
- return;
- }
-
- if (m_task.running.load())
- return;
-
- ImGui::OpenPopup("Export Frames##dlg");
- ImGui::SetNextWindowSize(ImVec2(360, 200), ImGuiCond_Appearing);
-
- if (ImGui::BeginPopupModal("Export Frames##dlg", &m_conv.show_export, ImGuiWindowFlags_NoResize))
- {
- Section("Output Format");
-
- const char* fmts[] = { "PNG (with alpha)", "BMP (no alpha)" };
- ImGui::Combo("Format", &m_conv.export_format, fmts, 2);
-
- Section("Frame Selection");
-
- ImGui::RadioButton("All frames", &m_conv.export_frame, -1);
- ImGui::SameLine();
- ImGui::RadioButton("Current only", &m_conv.export_frame, m_app.current_frame);
-
- ImGui::Spacing();
- ImGui::Separator();
- ImGui::Spacing();
-
- float bw = 110;
- ImGui::SetCursorPosX((ImGui::GetWindowWidth() - bw * 2 - 8) * 0.5f);
-
- if (ImGui::Button("Export...", ImVec2(bw, 0)))
- {
- ImGui::CloseCurrentPopup();
- StartExport();
- }
-
- ImGui::SameLine();
- if (ImGui::Button("Cancel", ImVec2(bw, 0)))
- {
- m_conv.show_export = false;
- ImGui::CloseCurrentPopup();
- }
-
- ImGui::EndPopup();
- }
-}
-
-void UI::DrawImportDialog()
-{
- if (!m_conv.show_import)
- return;
-
- if (m_task.running.load())
- return;
-
- ImGui::OpenPopup("Create SPR##dlg");
- ImGui::SetNextWindowSize(ImVec2(480, 420), ImGuiCond_Appearing);
-
- if (ImGui::BeginPopupModal("Create SPR##dlg", &m_conv.show_import, ImGuiWindowFlags_NoResize))
- {
- Section("Input Images");
-
- if (ImGui::Button("Add Images..."))
- {
- auto sel = pfd::open_file("Select Images", m_app.last_dir, { "Images", "*.png *.bmp", "All", "*" }, pfd::opt::multiselect).result();
-
- for (const auto& s : sel)
- m_conv.import_files.push_back(s);
- }
- ImGui::SameLine();
- if (ImGui::Button("Clear All"))
- m_conv.import_files.clear();
-
- ImGui::Text("%d file(s)", (int)m_conv.import_files.size());
-
- if (!m_conv.import_files.empty())
- {
- ImGui::BeginChild("##filelist", ImVec2(0, 90), ImGuiChildFlags_Borders);
-
- int rm = -1;
- for (int i = 0; i < (int)m_conv.import_files.size(); i++)
- {
- ImGui::PushID(i);
- if (ImGui::SmallButton("X")) rm = i;
- ImGui::SameLine();
- ImGui::TextUnformatted(GetFilename(m_conv.import_files[i]).c_str());
- ImGui::PopID();
- }
- if (rm >= 0)
- m_conv.import_files.erase(m_conv.import_files.begin() + rm);
-
- ImGui::EndChild();
- }
-
- Section("Sprite Parameters");
-
- const char* vers[] = { "Quake (v1)", "Half-Life (v2)" };
- int vi = m_conv.import_version - 1;
- if (ImGui::Combo("Version", &vi, vers, 2))
- m_conv.import_version = vi + 1;
-
- const char* types[] = {
- "Parallel Upright",
- "Facing Upright",
- "Parallel",
- "Oriented",
- "Parallel Oriented"
- };
- ImGui::Combo("Type", &m_conv.import_type, types, 5);
-
- if (m_conv.import_version == 2)
- {
- const char* tfmts[] = {
- "Normal",
- "Additive",
- "Index Alpha",
- "Alpha Test"
- };
- ImGui::Combo("Render Mode",&m_conv.import_tex_format, tfmts, 4);
- }
-
- ImGui::SliderFloat("Interval", &m_conv.import_interval, 0.01f, 1.0f, "%.3f s");
-
- ImGui::Spacing();
- ImGui::Separator();
- ImGui::Spacing();
-
- bool can = !m_conv.import_files.empty();
- if (!can)
- ImGui::BeginDisabled();
-
- float bw = 110;
- ImGui::SetCursorPosX((ImGui::GetWindowWidth() - bw * 2 - 8) * 0.5f);
-
- if (ImGui::Button("Create SPR...", ImVec2(bw, 0)))
- {
- ImGui::CloseCurrentPopup();
- StartImport();
- }
-
- if (!can) ImGui::EndDisabled();
-
- ImGui::SameLine();
- if (ImGui::Button("Cancel", ImVec2(bw, 0)))
- {
- m_conv.show_import = false;
- m_conv.import_files.clear();
- ImGui::CloseCurrentPopup();
- }
-
- ImGui::EndPopup();
- }
-}
-
-void UI::StartTask(const std::string& title, std::function work)
-{
- if (m_task.running.load())
- return;
-
- if (m_task.worker.joinable())
- m_task.worker.join();
-
- m_task.running.store(true);
- m_task.done.store(false);
- m_task.cancel_requested.store(false);
- m_task.progress.store(0.0f);
- m_task.result_success = false;
- m_task.pending_open_file.clear();
-
- {
- std::lock_guard lock(m_task.mutex);
- m_task.title = title;
- m_task.status = "Starting...";
- m_task.result_message.clear();
- }
-
- m_task.worker = std::thread([this, work]()
- {
- work(m_task);
-
- m_task.progress.store(1.0f);
- m_task.running.store(false);
- m_task.done.store(true);
- });
-}
-
-void UI::DrawProgressDialog()
-{
- if (!m_task.running.load() && !m_task.done.load())
- return;
-
- std::string title, status, result;
- {
- std::lock_guard lock(m_task.mutex);
- title = m_task.title;
- status = m_task.status;
- result = m_task.result_message;
- }
-
- float progress = m_task.progress.load();
- bool is_running = m_task.running.load();
- bool is_done = m_task.done.load();
-
- std::string popup_id = title + "##progress";
-
- ImGui::SetNextWindowSizeConstraints(ImVec2(400, -1), ImVec2(400, -1));
-
- if (!ImGui::IsPopupOpen(popup_id.c_str()))
- ImGui::OpenPopup(popup_id.c_str());
-
- ImGuiWindowFlags flags = ImGuiWindowFlags_NoResize |
- ImGuiWindowFlags_NoMove |
- ImGuiWindowFlags_AlwaysAutoResize |
- ImGuiWindowFlags_NoSavedSettings;
-
- bool open = true;
- if (ImGui::BeginPopupModal(popup_id.c_str(), nullptr, flags))
- {
- if (is_running)
- {
- ImGui::TextWrapped("%s", status.c_str());
- ImGui::Spacing();
-
- char pct[32];
- snprintf(pct, sizeof(pct), "%.0f%%", progress * 100.0f);
- ImGui::ProgressBar(progress, ImVec2(-1, 0), pct);
-
- ImGui::Spacing();
-
- double t = ImGui::GetTime();
- int dots = ((int)(t * 3.0)) % 4;
- char anim[8] = " ";
- for (int i = 0; i < dots; i++) anim[i] = '.';
- anim[dots] = '\0';
-
- ImGui::TextDisabled("Working%s", anim);
-
- ImGui::Spacing();
- ImGui::Separator();
- ImGui::Spacing();
-
- float bw = 120.0f;
- ImGui::SetCursorPosX((ImGui::GetWindowWidth() - bw) * 0.5f);
- if (ImGui::Button("Cancel", ImVec2(bw, 0)))
- {
- m_task.cancel_requested.store(true);
- std::lock_guard lock(m_task.mutex);
- m_task.status = "Cancelling...";
- }
- }
- else if (is_done)
- {
- if (m_task.result_success)
- ImGui::TextColored(ImVec4(0.3f, 1.0f, 0.3f, 1.0f), "Success!");
- else
- ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), "Failed!");
-
- ImGui::Spacing();
-
- if (!result.empty())
- {
- ImGui::TextWrapped("%s", result.c_str());
- ImGui::Spacing();
- }
-
- ImGui::ProgressBar(1.0f, ImVec2(-1, 0), "Done");
-
- ImGui::Spacing();
- ImGui::Separator();
- ImGui::Spacing();
-
- float bw = 120.0f;
- ImGui::SetCursorPosX((ImGui::GetWindowWidth() - bw) * 0.5f);
- if (ImGui::Button("OK", ImVec2(bw, 0)))
- {
- m_task.done.store(false);
- ImGui::CloseCurrentPopup();
-
- if (!m_task.pending_open_file.empty())
- {
- m_app.pending_file = m_task.pending_open_file;
- m_task.pending_open_file.clear();
- }
-
- if (m_task.worker.joinable())
- m_task.worker.join();
- }
- }
-
- ImGui::EndPopup();
- }
-}
-
-void UI::StartExport()
-{
- std::string spr_path = m_app.loader.GetData().filepath;
- int format = m_conv.export_format;
- int frame_idx = m_conv.export_frame;
- int total = m_app.total_frames;
-
- auto dir = pfd::select_folder("Output Directory", m_app.last_dir).result();
-
- if (dir.empty())
- return;
-
- std::string prefix = GetFilename(spr_path);
- size_t dot = prefix.rfind('.');
- if (dot != std::string::npos) prefix = prefix.substr(0, dot);
-
- m_conv.show_export = false;
-
- StartTask("Exporting Frames", [=](TaskState& task)
- {
- SpriteLoader loader;
-
- {
- std::lock_guard lock(task.mutex);
- task.status = "Loading sprite...";
- }
-
- if (!loader.Load(spr_path))
- {
- std::lock_guard lock(task.mutex);
- task.result_message = "Failed to load sprite";
- task.result_success = false;
- return;
- }
-
- int t = loader.GetTotalFrameCount();
- int start = 0, end_idx = t;
-
- if (frame_idx >= 0 && frame_idx < t)
- {
- start = frame_idx;
- end_idx = frame_idx + 1;
- }
-
- int count = end_idx - start;
- int exported = 0;
- std::string ext = SpriteConverter::GetFormatExtension(static_cast(format));
-
- for (int i = start; i < end_idx; i++)
- {
- if (task.cancel_requested.load())
- {
- std::lock_guard lock(task.mutex);
- task.result_message = "Cancelled. Exported " +
- std::to_string(exported) + " file(s)";
- task.result_success = false;
- return;
- }
-
- {
- std::lock_guard lock(task.mutex);
- task.status = "Exporting frame " +
- std::to_string(i + 1) + " / " +
- std::to_string(end_idx) + "...";
- }
-
- SpriteFrame* frame = loader.GetFrame(i);
- if (!frame || frame->rgba.empty())
- {
- std::lock_guard lock(task.mutex);
- task.result_message = "Failed to get frame " + std::to_string(i);
- task.result_success = false;
- return;
- }
-
- char fname[512];
- if (t == 1 && frame_idx < 0)
- snprintf(fname, sizeof(fname), "%s/%s%s", dir.c_str(), prefix.c_str(), ext.c_str());
- else
- snprintf(fname, sizeof(fname), "%s/%s_%03d%s", dir.c_str(), prefix.c_str(), i, ext.c_str());
-
- SprToImageParams p;
- p.output_dir = dir;
- p.output_prefix = prefix;
- p.format = static_cast(format);
- p.frame_index = i;
-
- if (!SpriteConverter::SaveImageRGBA(fname, frame->rgba.data(), frame->width, frame->height, static_cast(format)))
- {
- std::lock_guard lock(task.mutex);
- task.result_message = "Failed to save: " + std::string(fname);
- task.result_success = false;
- return;
- }
-
- exported++;
- task.progress.store((float)(i - start + 1) / (float)count);
- }
-
- std::lock_guard lock(task.mutex);
- task.result_message = "Exported " + std::to_string(exported) + " file(s) to:\n" + dir;
- task.result_success = true;
- });
-}
-
-void UI::StartImport()
-{
- auto save = pfd::save_file("Save Sprite", m_app.last_dir, { "Sprite (*.spr)", "*.spr" }).result();
-
- if (save.empty()) return;
-
- if (save.size() < 4 || save.substr(save.size() - 4) != ".spr")
- save += ".spr";
-
- std::vector files = m_conv.import_files;
- int version = m_conv.import_version;
- int type = m_conv.import_type;
- int tex_format = m_conv.import_tex_format;
- float interval = m_conv.import_interval;
-
- m_conv.show_import = false;
- m_conv.import_files.clear();
-
- StartTask("Creating Sprite", [=](TaskState& task)
- {
- int total = (int)files.size();
-
- std::vector> rgba_storage;
- std::vector rgba_ptrs;
- std::vector widths, heights;
-
- for (int i = 0; i < total; i++)
- {
- if (task.cancel_requested.load())
- {
- std::lock_guard lock(task.mutex);
- task.result_message = "Cancelled";
- task.result_success = false;
- return;
- }
-
- {
- std::lock_guard lock(task.mutex);
- task.status = "Loading image " +
- std::to_string(i + 1) + " / " +
- std::to_string(total) + "...";
- }
-
- std::vector rgba;
- int w, h, ch;
- uint8_t* px = stbi_load(files[i].c_str(), &w, &h, &ch, 4);
-
- if (!px)
- {
- std::lock_guard lock(task.mutex);
- std::string fn = files[i];
- size_t sl = fn.find_last_of("/\\");
- if (sl != std::string::npos) fn = fn.substr(sl + 1);
- task.result_message = "Failed to load: " + fn;
- task.result_success = false;
- return;
- }
-
- rgba.assign(px, px + (size_t)w * h * 4);
- stbi_image_free(px);
-
- if (w > 4096 || h > 4096 || w <= 0 || h <= 0)
- {
- std::lock_guard lock(task.mutex);
- task.result_message = "Bad image size: " +
- std::to_string(w) + "x" + std::to_string(h);
- task.result_success = false;
- return;
- }
-
- widths.push_back(w);
- heights.push_back(h);
- rgba_storage.push_back(std::move(rgba));
-
- task.progress.store((float)(i + 1) / (float)(total * 3));
- }
-
- if (task.cancel_requested.load())
- {
- std::lock_guard lock(task.mutex);
- task.result_message = "Cancelled";
- task.result_success = false;
- return;
- }
-
- for (auto& v : rgba_storage)
- rgba_ptrs.push_back(v.data());
-
- {
- std::lock_guard lock(task.mutex);
- task.status = "Building palette...";
- }
-
- task.progress.store(0.4f);
-
- ImageToSprParams p;
- p.version = version;
- p.type = (uint32_t)type;
- p.tex_format = (uint32_t)tex_format;
- p.interval = interval;
-
- auto result = SpriteConverter::RGBAToSprMemory(rgba_ptrs, widths, heights, p);
-
- if (task.cancel_requested.load())
- {
- std::lock_guard lock(task.mutex);
- task.result_message = "Cancelled";
- task.result_success = false;
- return;
- }
-
- task.progress.store(0.8f);
-
- if (!result.success)
- {
- std::lock_guard lock(task.mutex);
- task.result_message = result.error;
- task.result_success = false;
- return;
- }
-
- {
- std::lock_guard lock(task.mutex);
- task.status = "Saving sprite...";
- }
-
- std::ofstream file(save, std::ios::binary);
- if (!file.is_open())
- {
- std::lock_guard lock(task.mutex);
- task.result_message = "Failed to create file";
- task.result_success = false;
- return;
- }
-
- file.write(reinterpret_cast(result.data.data()),
- (std::streamsize)result.data.size());
- file.close();
-
- task.progress.store(1.0f);
-
- std::string fn = save;
- size_t sl = fn.find_last_of("/\\");
- if (sl != std::string::npos) fn = fn.substr(sl + 1);
-
- {
- std::lock_guard lock(task.mutex);
- task.result_message = "Created: " + fn + "\n" +
- std::to_string(total) + " frame(s), " +
- std::to_string(result.data.size()) + " bytes";
- task.result_success = true;
- task.pending_open_file = save;
- }
- });
-}
-
-void UI::RenderFrame()
-{
- HandleKeys();
- ProcessPendingFile();
- DrawMenuBar();
- DrawToolbar();
- DrawViewport();
- DrawProperties();
- DrawStatusBar();
- DrawAbout();
- DrawExportDialog();
- DrawImportDialog();
- DrawProgressDialog();
-}
diff --git a/src/ui.h b/src/ui.h
deleted file mode 100644
index 33d2a3a..0000000
--- a/src/ui.h
+++ /dev/null
@@ -1,145 +0,0 @@
-#pragma once
-
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include "sprite_loader.h"
-#include "renderer.h"
-
-#include "imgui.h"
-#include "imgui_internal.h"
-
-#include "ui_utils.h"
-
-struct AppState
-{
- SpriteLoader loader;
- SpriteRenderer renderer;
-
- bool sprite_loaded = false;
- int current_frame = 0;
- int total_frames = 0;
-
- bool animating = false;
- float anim_speed = 1.0f;
- double anim_time = 0.0;
- double last_time = 0.0;
-
- float zoom = 1.0f;
- bool show_checker = true;
- bool show_info = true;
- bool show_toolbar = true;
- bool show_about = false;
-
- float scroll_x = 0.0f;
- float scroll_y = 0.0f;
- bool dragging = false;
- ImVec2 drag_start = ImVec2(0, 0);
- float drag_sx = 0.0f;
- float drag_sy = 0.0f;
-
- std::string pending_file;
- std::string status_msg;
- double status_time = 0.0;
- std::string last_dir;
-
- bool request_exit = false;
- std::string window_title;
- bool title_changed = false;
-};
-
-class UI
-{
-public:
- UI() = default;
- ~UI() = default;
-
- void LoadIcons();
- void SetupTheme();
- void RenderFrame();
-
- void SetPendingFile(const std::string& path);
- void ProcessPendingFile();
-
- bool IsExitRequested() const { return m_app.request_exit; }
- bool IsTitleChanged() const { return m_app.title_changed; }
- std::string ConsumeTitle();
-
- bool IsSpriteLoaded() const { return m_app.sprite_loaded; }
- void CleanupResources();
-
- AppState& GetState() { return m_app; }
- const AppState& GetState() const { return m_app; }
-
-private:
- void Tooltip(const char* text);
- bool ImgToolBtn(const char* id, ImGuiImage& img, const char* tip = nullptr, bool active = false, bool enabled = true, float size = 20.0f);
- void ToolSep();
- void Section(const char* text);
- void PropRow(const char* label, const char* fmt, ...);
-
- void SetStatus(const std::string& msg);
- void OpenFileDialog();
- void LoadSpriteFile(const std::string& path);
- void CloseSprite();
-
- void DrawMenuBar();
- void DrawToolbar();
- void DrawChecker(ImDrawList* dl, ImVec2 pos, ImVec2 sz, float cell = 12.0f);
- void DrawViewport();
- void DrawProperties();
- void DrawStatusBar();
- void DrawAbout();
- void HandleKeys();
-
- static std::string GetDir(const std::string& path);
- static std::string GetFilename(const std::string& path);
-
- AppState m_app;
-
- void DrawExportDialog();
- void DrawImportDialog();
-
- struct ConverterState
- {
- bool show_export = false;
- bool show_import = false;
- int export_format = 0;
- int export_frame = -1;
-
- std::vector import_files;
- int import_version = 2;
- int import_type = 2;
- int import_tex_format = 0;
- float import_interval = 0.1f;
- } m_conv;
-
- struct TaskState
- {
- std::atomic running{false};
- std::atomic done{false};
- std::atomic cancel_requested{false};
- std::atomic progress{0.0f};
-
- std::mutex mutex;
- std::string title;
- std::string status;
- std::string result_message;
- bool result_success = false;
-
- std::string pending_open_file;
-
- std::thread worker;
- } m_task;
-
- void DrawProgressDialog();
- void StartTask(const std::string& title, std::function work);
-
- void StartExport();
- void StartImport();
-
-};
\ No newline at end of file
diff --git a/src/ui_utils.cpp b/src/ui_utils.cpp
deleted file mode 100644
index 5f5de2b..0000000
--- a/src/ui_utils.cpp
+++ /dev/null
@@ -1,29 +0,0 @@
-#include "ui_utils.h"
-
-#include
-
-#ifdef PLATFORM_WIN32
-#define NOMINMAX
-#define WIN32_LEAN_AND_MEAN
-#include
-#endif
-#include
-
-ImGuiImage UI_utils::LoadImageFromMemory(const unsigned char* buffer, int bufferSize)
-{
- ImGuiImage result;
-
- int channels;
- unsigned char* data = stbi_load_from_memory(buffer, bufferSize, &result.width, &result.height, &channels, 4);
-
- GLuint texture;
- glGenTextures(1, &texture);
- glBindTexture(GL_TEXTURE_2D, texture);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
- glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, result.width, result.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
- stbi_image_free(data);
-
- result.texture = (ImTextureID)(intptr_t)texture;
- return result;
-}
\ No newline at end of file
diff --git a/src/ui_utils.h b/src/ui_utils.h
deleted file mode 100644
index 5f5f36a..0000000
--- a/src/ui_utils.h
+++ /dev/null
@@ -1,19 +0,0 @@
-#pragma once
-
-#include "imgui.h"
-
-struct ImGuiImage
-{
- ImTextureID texture = 0;
- int width = 0;
- int height = 0;
-};
-
-class UI_utils
-{
-public:
- UI_utils() = default;
- ~UI_utils() = default;
-
- static ImGuiImage LoadImageFromMemory(const unsigned char* buffer, int bufferSize);
-};