Refactor + render animation tweaks + updated frame properties + bug fixes

This commit is contained in:
2025-12-17 23:02:00 -05:00
parent b4b4fe3714
commit 119bbc4081
63 changed files with 1964 additions and 1701 deletions
+5 -4
View File
@@ -4,10 +4,11 @@
#include <filesystem>
#include <unordered_map>
#include "filesystem_.h"
#include "file_.h"
#include "map_.h"
#include "time_.h"
#include "vector_.h"
#include "working_directory.h"
#include "xml_.h"
using namespace tinyxml2;
@@ -23,7 +24,7 @@ namespace anm2ed::anm2
{
XMLDocument document;
filesystem::File file(path, "rb");
File file(path, "rb");
if (!file)
{
if (errorString) *errorString = localize.get(ERROR_FILE_NOT_FOUND);
@@ -38,7 +39,7 @@ namespace anm2ed::anm2
return;
}
filesystem::WorkingDirectory workingDirectory(path, true);
WorkingDirectory workingDirectory(path, WorkingDirectory::FILE);
const XMLElement* element = document.RootElement();
@@ -65,7 +66,7 @@ namespace anm2ed::anm2
XMLDocument document;
document.InsertFirstChild(to_element(document));
filesystem::File file(path, "wb");
File file(path, "wb");
if (!file)
{
if (errorString) *errorString = localize.get(ERROR_FILE_NOT_FOUND);
+4 -3
View File
@@ -1,7 +1,8 @@
#include "anm2.h"
#include "filesystem_.h"
#include "map_.h"
#include "path_.h"
#include "working_directory.h"
using namespace anm2ed::types;
using namespace anm2ed::util;
@@ -22,7 +23,7 @@ namespace anm2ed::anm2
labels.emplace_back(localize.get(BASIC_NONE));
for (auto& [id, sound] : content.sounds)
{
auto pathString = filesystem::path_to_utf8(sound.path);
auto pathString = path::to_utf8(sound.path);
labels.emplace_back(std::vformat(localize.get(FORMAT_SOUND), std::make_format_args(id, pathString)));
}
return labels;
@@ -57,7 +58,7 @@ namespace anm2ed::anm2
return false;
}
filesystem::WorkingDirectory workingDirectory(directory);
WorkingDirectory workingDirectory(directory);
for (auto element = document.FirstChildElement("Sound"); element; element = element->NextSiblingElement("Sound"))
{
+4 -3
View File
@@ -2,8 +2,9 @@
#include <ranges>
#include "filesystem_.h"
#include "map_.h"
#include "path_.h"
#include "working_directory.h"
using namespace anm2ed::types;
using namespace anm2ed::util;
@@ -41,7 +42,7 @@ namespace anm2ed::anm2
labels.emplace_back(localize.get(BASIC_NONE));
for (auto& [id, spritesheet] : content.spritesheets)
{
auto pathString = filesystem::path_to_utf8(spritesheet.path);
auto pathString = path::to_utf8(spritesheet.path);
labels.emplace_back(std::vformat(localize.get(FORMAT_SPRITESHEET), std::make_format_args(id, pathString)));
}
return labels;
@@ -62,7 +63,7 @@ namespace anm2ed::anm2
return false;
}
filesystem::WorkingDirectory workingDirectory(directory);
WorkingDirectory workingDirectory(directory);
for (auto element = document.FirstChildElement("Spritesheet"); element;
element = element->NextSiblingElement("Spritesheet"))
-2
View File
@@ -117,7 +117,5 @@ namespace anm2ed::anm2
}
void Frame::shorten() { duration = glm::clamp(--duration, FRAME_DURATION_MIN, FRAME_DURATION_MAX); }
void Frame::extend() { duration = glm::clamp(++duration, FRAME_DURATION_MIN, FRAME_DURATION_MAX); }
}
+25 -4
View File
@@ -35,6 +35,8 @@ namespace anm2ed::anm2
MEMBERS
#undef X
#undef MEMBERS
Frame() = default;
Frame(tinyxml2::XMLElement*, Type);
tinyxml2::XMLElement* to_element(tinyxml2::XMLDocument&, Type);
@@ -46,10 +48,29 @@ namespace anm2ed::anm2
struct FrameChange
{
#define X(name, type, ...) std::optional<type> name{};
MEMBERS
#undef X
std::optional<bool> isVisible{};
std::optional<bool> isInterpolated{};
std::optional<float> rotation{};
std::optional<int> duration{};
std::optional<int> atFrame{};
std::optional<int> eventID{};
std::optional<float> pivotX{};
std::optional<float> pivotY{};
std::optional<float> cropX{};
std::optional<float> cropY{};
std::optional<float> positionX{};
std::optional<float> positionY{};
std::optional<float> sizeX{};
std::optional<float> sizeY{};
std::optional<float> scaleX{};
std::optional<float> scaleY{};
std::optional<float> colorOffsetR{};
std::optional<float> colorOffsetG{};
std::optional<float> colorOffsetB{};
std::optional<float> tintR{};
std::optional<float> tintG{};
std::optional<float> tintB{};
std::optional<float> tintA{};
};
#undef MEMBERS
}
+55 -59
View File
@@ -132,6 +132,39 @@ namespace anm2ed::anm2
auto end = numberFrames > -1 ? start + numberFrames : (int)frames.size();
end = glm::clamp(end, start, (int)frames.size());
const auto clamp_identity = [](auto value) { return value; };
const auto clamp01 = [](auto value) { return glm::clamp(value, 0.0f, 1.0f); };
const auto clamp_duration = [](int value) { return std::max(FRAME_DURATION_MIN, value); };
auto apply_scalar_with_clamp = [&](auto& target, const auto& optionalValue, auto clampFunc)
{
if (!optionalValue) return;
auto value = *optionalValue;
switch (type)
{
case ADJUST:
target = clampFunc(value);
break;
case ADD:
target = clampFunc(target + value);
break;
case SUBTRACT:
target = clampFunc(target - value);
break;
case MULTIPLY:
target = clampFunc(target * value);
break;
case DIVIDE:
if (value == decltype(value){}) return;
target = clampFunc(target / value);
break;
}
};
auto apply_scalar = [&](auto& target, const auto& optionalValue)
{ apply_scalar_with_clamp(target, optionalValue, clamp_identity); };
for (int i = useStart; i < end; i++)
{
Frame& frame = frames[i];
@@ -139,69 +172,32 @@ namespace anm2ed::anm2
if (change.isVisible) frame.isVisible = *change.isVisible;
if (change.isInterpolated) frame.isInterpolated = *change.isInterpolated;
switch (type)
{
case ADJUST:
if (change.rotation) frame.rotation = *change.rotation;
if (change.duration) frame.duration = std::max(FRAME_DURATION_MIN, *change.duration);
if (change.crop) frame.crop = *change.crop;
if (change.pivot) frame.pivot = *change.pivot;
if (change.position) frame.position = *change.position;
if (change.size) frame.size = *change.size;
if (change.scale) frame.scale = *change.scale;
if (change.colorOffset) frame.colorOffset = glm::clamp(*change.colorOffset, 0.0f, 1.0f);
if (change.tint) frame.tint = glm::clamp(*change.tint, 0.0f, 1.0f);
break;
apply_scalar(frame.rotation, change.rotation);
apply_scalar_with_clamp(frame.duration, change.duration, clamp_duration);
case ADD:
if (change.rotation) frame.rotation += *change.rotation;
if (change.duration) frame.duration = std::max(FRAME_DURATION_MIN, frame.duration + *change.duration);
if (change.crop) frame.crop += *change.crop;
if (change.pivot) frame.pivot += *change.pivot;
if (change.position) frame.position += *change.position;
if (change.size) frame.size += *change.size;
if (change.scale) frame.scale += *change.scale;
if (change.colorOffset) frame.colorOffset = glm::clamp(frame.colorOffset + *change.colorOffset, 0.0f, 1.0f);
if (change.tint) frame.tint = glm::clamp(frame.tint + *change.tint, 0.0f, 1.0f);
break;
apply_scalar(frame.crop.x, change.cropX);
apply_scalar(frame.crop.y, change.cropY);
case SUBTRACT:
if (change.rotation) frame.rotation -= *change.rotation;
if (change.duration) frame.duration = std::max(FRAME_DURATION_MIN, frame.duration - *change.duration);
if (change.crop) frame.crop -= *change.crop;
if (change.pivot) frame.pivot -= *change.pivot;
if (change.position) frame.position -= *change.position;
if (change.size) frame.size -= *change.size;
if (change.scale) frame.scale -= *change.scale;
if (change.colorOffset) frame.colorOffset = glm::clamp(frame.colorOffset - *change.colorOffset, 0.0f, 1.0f);
if (change.tint) frame.tint = glm::clamp(frame.tint - *change.tint, 0.0f, 1.0f);
break;
apply_scalar(frame.pivot.x, change.pivotX);
apply_scalar(frame.pivot.y, change.pivotY);
case MULTIPLY:
if (change.rotation) frame.rotation *= *change.rotation;
if (change.duration) frame.duration = std::max(FRAME_DURATION_MIN, frame.duration * *change.duration);
if (change.crop) frame.crop *= *change.crop;
if (change.pivot) frame.pivot *= *change.pivot;
if (change.position) frame.position *= *change.position;
if (change.size) frame.size *= *change.size;
if (change.scale) frame.scale *= *change.scale;
if (change.colorOffset) frame.colorOffset = glm::clamp(frame.colorOffset * *change.colorOffset, 0.0f, 1.0f);
if (change.tint) frame.tint = glm::clamp(frame.tint * *change.tint, 0.0f, 1.0f);
break;
apply_scalar(frame.position.x, change.positionX);
apply_scalar(frame.position.y, change.positionY);
case DIVIDE:
if (change.rotation && *change.rotation != 0.0f) frame.rotation /= *change.rotation;
if (change.duration && *change.duration != 0)
frame.duration = std::max(FRAME_DURATION_MIN, frame.duration / *change.duration);
if (change.crop) frame.crop /= *change.crop;
if (change.pivot) frame.pivot /= *change.pivot;
if (change.position) frame.position /= *change.position;
if (change.size) frame.size /= *change.size;
if (change.scale) frame.scale /= *change.scale;
if (change.colorOffset) frame.colorOffset = glm::clamp(frame.colorOffset / *change.colorOffset, 0.0f, 1.0f);
if (change.tint) frame.tint = glm::clamp(frame.tint / *change.tint, 0.0f, 1.0f);
break;
}
apply_scalar(frame.size.x, change.sizeX);
apply_scalar(frame.size.y, change.sizeY);
apply_scalar(frame.scale.x, change.scaleX);
apply_scalar(frame.scale.y, change.scaleY);
apply_scalar_with_clamp(frame.colorOffset.x, change.colorOffsetR, clamp01);
apply_scalar_with_clamp(frame.colorOffset.y, change.colorOffsetG, clamp01);
apply_scalar_with_clamp(frame.colorOffset.z, change.colorOffsetB, clamp01);
apply_scalar_with_clamp(frame.tint.x, change.tintR, clamp01);
apply_scalar_with_clamp(frame.tint.y, change.tintG, clamp01);
apply_scalar_with_clamp(frame.tint.z, change.tintB, clamp01);
apply_scalar_with_clamp(frame.tint.w, change.tintA, clamp01);
}
}
+7 -20
View File
@@ -1,6 +1,7 @@
#include "sound.h"
#include "filesystem_.h"
#include "path_.h"
#include "working_directory.h"
#include "xml_.h"
using namespace anm2ed::resource;
@@ -21,23 +22,11 @@ namespace anm2ed::anm2
return *this;
}
namespace
{
std::filesystem::path make_relative_or_keep(const std::filesystem::path& input)
{
if (input.empty()) return input;
std::error_code ec{};
auto relative = std::filesystem::relative(input, ec);
if (!ec) return relative;
return input;
}
}
Sound::Sound(const std::filesystem::path& directory, const std::filesystem::path& path)
{
filesystem::WorkingDirectory workingDirectory(directory);
this->path = !path.empty() ? make_relative_or_keep(path) : this->path;
this->path = filesystem::path_lower_case_backslash_handle(this->path);
WorkingDirectory workingDirectory(directory);
this->path = !path.empty() ? path::make_relative(path) : this->path;
this->path = path::lower_case_backslash_handle(this->path);
audio = Audio(this->path);
}
@@ -46,7 +35,7 @@ namespace anm2ed::anm2
if (!element) return;
element->QueryIntAttribute("Id", &id);
xml::query_path_attribute(element, "Path", &path);
path = filesystem::path_lower_case_backslash_handle(path);
path = path::lower_case_backslash_handle(path);
audio = Audio(path);
}
@@ -54,7 +43,7 @@ namespace anm2ed::anm2
{
auto element = document.NewElement("Sound");
element->SetAttribute("Id", id);
auto pathString = filesystem::path_to_utf8(path);
auto pathString = path::to_utf8(path);
element->SetAttribute("Path", pathString.c_str());
return element;
}
@@ -72,8 +61,6 @@ namespace anm2ed::anm2
}
void Sound::reload(const std::filesystem::path& directory) { *this = Sound(directory, this->path); }
bool Sound::is_valid() { return audio.is_valid(); }
void Sound::play() { audio.play(); }
}
+9 -20
View File
@@ -1,6 +1,7 @@
#include "spritesheet.h"
#include "filesystem_.h"
#include "path_.h"
#include "working_directory.h"
#include "xml_.h"
using namespace anm2ed::resource;
@@ -17,27 +18,15 @@ namespace anm2ed::anm2
// Spritesheet paths from Isaac Rebirth are made with the assumption that paths are case-insensitive
// However when using the resource dumper, the spritesheet paths are all lowercase (on Linux anyway)
// This will handle this case and make the paths OS-agnostic
path = filesystem::path_lower_case_backslash_handle(path);
path = path::lower_case_backslash_handle(path);
texture = Texture(path);
}
namespace
{
std::filesystem::path make_relative_or_keep(const std::filesystem::path& input)
{
if (input.empty()) return input;
std::error_code ec{};
auto relative = std::filesystem::relative(input, ec);
if (!ec) return relative;
return input;
}
}
Spritesheet::Spritesheet(const std::filesystem::path& directory, const std::filesystem::path& path)
{
filesystem::WorkingDirectory workingDirectory(directory);
this->path = !path.empty() ? make_relative_or_keep(path) : this->path;
this->path = filesystem::path_lower_case_backslash_handle(this->path);
WorkingDirectory workingDirectory(directory);
this->path = !path.empty() ? path::make_relative(path) : this->path;
this->path = path::lower_case_backslash_handle(this->path);
texture = Texture(this->path);
}
@@ -45,7 +34,7 @@ namespace anm2ed::anm2
{
auto element = document.NewElement("Spritesheet");
element->SetAttribute("Id", id);
auto pathString = filesystem::path_to_utf8(path);
auto pathString = path::to_utf8(path);
element->SetAttribute("Path", pathString.c_str());
return element;
}
@@ -64,8 +53,8 @@ namespace anm2ed::anm2
bool Spritesheet::save(const std::filesystem::path& directory, const std::filesystem::path& path)
{
filesystem::WorkingDirectory workingDirectory(directory);
this->path = !path.empty() ? make_relative_or_keep(path) : this->path;
WorkingDirectory workingDirectory(directory);
this->path = !path.empty() ? path::make_relative(path) : this->path;
return texture.write_png(this->path);
}
-6
View File
@@ -11,15 +11,9 @@
using namespace glm;
using namespace anm2ed::resource;
using namespace anm2ed::util;
using namespace anm2ed::canvas;
namespace anm2ed
{
constexpr float AXIS_VERTICES[] = {-1.0f, 0.0f, 1.0f, 0.0f};
constexpr float GRID_VERTICES[] = {-1.f, -1.f, 0.f, 0.f, 3.f, -1.f, 2.f, 0.f, -1.f, 3.f, 0.f, 2.f};
constexpr float RECT_VERTICES[] = {0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f};
constexpr GLuint TEXTURE_INDICES[] = {0, 1, 2, 2, 3, 0};
Canvas::Canvas() = default;
Canvas::Canvas(vec2 size)
+27 -26
View File
@@ -6,35 +6,37 @@
#include "framebuffer.h"
#include "shader.h"
namespace anm2ed::canvas
{
constexpr float TEXTURE_VERTICES[] = {0, 0, 0.0f, 0.0f, 1, 0, 1.0f, 0.0f, 1, 1, 1.0f, 1.0f, 0, 1, 0.0f, 1.0f};
constexpr auto PIVOT_SIZE = glm::vec2(8, 8);
constexpr auto ZOOM_MIN = 1.0f;
constexpr auto ZOOM_MAX = 2000.0f;
constexpr auto DASH_LENGTH = 4.0f;
constexpr auto DASH_GAP = 1.0f;
constexpr auto DASH_OFFSET = 1.0f;
constexpr auto STEP = 1.0f;
constexpr auto STEP_FAST = 5.0f;
constexpr auto GRID_SIZE_MIN = 1;
constexpr auto GRID_SIZE_MAX = 10000;
constexpr auto GRID_OFFSET_MIN = 0;
constexpr auto GRID_OFFSET_MAX = 10000;
constexpr auto CHECKER_SIZE = 32.0f;
}
#include "types.h"
namespace anm2ed
{
class Canvas : public Framebuffer
{
public:
static constexpr float TEXTURE_VERTICES[] = {0, 0, 0.0f, 0.0f, 1, 0, 1.0f, 0.0f,
1, 1, 1.0f, 1.0f, 0, 1, 0.0f, 1.0f};
static constexpr auto PIVOT_SIZE = glm::vec2(8, 8);
static constexpr auto ZOOM_MIN = 1.0f;
static constexpr auto ZOOM_MAX = 2000.0f;
static constexpr auto DASH_LENGTH = 4.0f;
static constexpr auto DASH_GAP = 1.0f;
static constexpr auto DASH_OFFSET = 1.0f;
static constexpr auto STEP = 1.0f;
static constexpr auto STEP_FAST = 5.0f;
static constexpr auto GRID_SIZE_MIN = 1;
static constexpr auto GRID_SIZE_MAX = 10000;
static constexpr auto GRID_OFFSET_MIN = 0;
static constexpr auto GRID_OFFSET_MAX = 10000;
static constexpr auto CHECKER_SIZE = 32.0f;
static constexpr float AXIS_VERTICES[] = {-1.0f, 0.0f, 1.0f, 0.0f};
static constexpr float GRID_VERTICES[] = {-1.f, -1.f, 0.f, 0.f, 3.f, -1.f, 2.f, 0.f, -1.f, 3.f, 0.f, 2.f};
static constexpr float RECT_VERTICES[] = {0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f};
static constexpr GLuint TEXTURE_INDICES[] = {0, 1, 2, 2, 3, 0};
static constexpr auto BORDER_DASH_LENGTH = 1.0f;
static constexpr auto BORDER_DASH_GAP = 0.5f;
static constexpr auto BORDER_DASH_OFFSET = 0.0f;
static constexpr auto PIVOT_COLOR = types::color::PINK;
GLuint axisVAO{};
GLuint axisVBO{};
GLuint rectVAO{};
@@ -53,10 +55,9 @@ namespace anm2ed
void grid_render(resource::Shader&, float, glm::vec2, glm::ivec2 = glm::ivec2(32, 32), glm::ivec2 = {},
glm::vec4 = glm::vec4(1.0f)) const;
void texture_render(resource::Shader&, GLuint&, glm::mat4 = {1.0f}, glm::vec4 = glm::vec4(1.0f), glm::vec3 = {},
float* = (float*)canvas::TEXTURE_VERTICES) const;
float* = (float*)TEXTURE_VERTICES) const;
void rect_render(resource::Shader&, const glm::mat4&, const glm::mat4&, glm::vec4 = glm::vec4(1.0f),
float dashLength = canvas::DASH_LENGTH, float dashGap = canvas::DASH_GAP,
float dashOffset = canvas::DASH_OFFSET) const;
float dashLength = DASH_LENGTH, float dashGap = DASH_GAP, float dashOffset = DASH_OFFSET) const;
void zoom_set(float&, glm::vec2&, glm::vec2, float) const;
glm::vec2 position_translate(float&, glm::vec2&, glm::vec2) const;
void set_to_rect(float& zoom, glm::vec2& pan, glm::vec4 rect) const;
+10 -15
View File
@@ -13,9 +13,11 @@
#include <cstring>
#include <format>
#include "filesystem_.h"
#include "path_.h"
namespace anm2ed::dialog
using namespace anm2ed::util;
namespace anm2ed
{
static void callback(void* userData, const char* const* filelist, int filter)
{
@@ -23,7 +25,7 @@ namespace anm2ed::dialog
if (filelist && filelist[0] && strlen(filelist[0]) > 0)
{
self->path = anm2ed::util::filesystem::path_from_utf8(filelist[0]);
self->path = path::from_utf8(filelist[0]);
self->selectedFilter = filter;
}
else
@@ -32,13 +34,6 @@ namespace anm2ed::dialog
self->path.clear();
}
}
}
using namespace anm2ed::dialog;
namespace filesystem = anm2ed::util::filesystem;
namespace anm2ed
{
Dialog::Dialog(SDL_Window* window)
{
@@ -46,21 +41,21 @@ namespace anm2ed
this->window = window;
}
void Dialog::file_open(dialog::Type type)
void Dialog::file_open(Type type)
{
SDL_ShowOpenFileDialog(callback, this, window, FILTERS[TYPE_FILTERS[type]], std::size(FILTERS[TYPE_FILTERS[type]]),
nullptr, false);
this->type = type;
}
void Dialog::file_save(dialog::Type type)
void Dialog::file_save(Type type)
{
SDL_ShowSaveFileDialog(callback, this, window, FILTERS[TYPE_FILTERS[type]], std::size(FILTERS[TYPE_FILTERS[type]]),
nullptr);
this->type = type;
}
void Dialog::folder_open(dialog::Type type)
void Dialog::folder_open(Type type)
{
SDL_ShowOpenFolderDialog(callback, this, window, nullptr, false);
this->type = type;
@@ -72,7 +67,7 @@ namespace anm2ed
#ifdef _WIN32
ShellExecuteW(nullptr, L"open", path.native().c_str(), nullptr, nullptr, SW_SHOWNORMAL);
#elif __unix__
auto pathUtf8 = filesystem::path_to_utf8(path);
auto pathUtf8 = path::to_utf8(path);
system(std::format("xdg-open \"{}\" &", pathUtf8).c_str());
#else
toasts.push(localize.get(TOAST_NOT_SUPPORTED));
@@ -82,6 +77,6 @@ namespace anm2ed
void Dialog::reset() { *this = Dialog(this->window); }
bool Dialog::is_selected(dialog::Type type) const { return this->type == type && !path.empty(); }
bool Dialog::is_selected(Type type) const { return this->type == type && !path.empty(); }
};
+28 -32
View File
@@ -4,8 +4,11 @@
#include <SDL3/SDL.h>
namespace anm2ed::dialog
namespace anm2ed
{
class Dialog
{
public:
#if defined(_WIN32)
#define EXECUTABLE_FILTER {"Executable", "exe"}
#else
@@ -22,18 +25,18 @@ namespace anm2ed::dialog
X(MP4, {"MP4 video", "MP4"}) \
X(EXECUTABLE, EXECUTABLE_FILTER)
enum Filter
{
enum Filter
{
#define X(symbol, ...) symbol,
FILTER_LIST
#undef X
};
constexpr SDL_DialogFileFilter FILTERS[][1] = {
#define X(symbol, ...) {__VA_ARGS__},
FILTER_LIST
#undef X
};
};
static constexpr SDL_DialogFileFilter FILTERS[][1] = {
#define X(symbol, ...) {__VA_ARGS__},
FILTER_LIST
#undef X
};
#undef FILTER_LIST
@@ -53,40 +56,33 @@ namespace anm2ed::dialog
X(WEBM_PATH_SET, WEBM) \
X(MP4_PATH_SET, MP4)
enum Type
{
enum Type
{
#define X(symbol, filter) symbol,
DIALOG_LIST
#undef X
};
constexpr Filter TYPE_FILTERS[] = {
#define X(symbol, filter) filter,
DIALOG_LIST
#undef X
};
};
static constexpr Filter TYPE_FILTERS[] = {
#define X(symbol, filter) filter,
DIALOG_LIST
#undef X
};
#undef DIALOG_LIST
}
namespace anm2ed
{
class Dialog
{
public:
SDL_Window* window{};
std::filesystem::path path{};
dialog::Type type{dialog::NONE};
Type type{NONE};
int selectedFilter{-1};
Dialog() = default;
Dialog(SDL_Window*);
void file_open(dialog::Type type);
void file_save(dialog::Type type);
void folder_open(dialog::Type type);
bool is_selected(dialog::Type type) const;
void file_open(Type type);
void file_save(Type type);
void folder_open(Type type);
bool is_selected(Type type) const;
void reset();
void file_explorer_open(const std::filesystem::path&);
};
}
}
+16 -18
View File
@@ -4,18 +4,17 @@
#include <format>
#include "filesystem_.h"
#include "log.h"
#include "path_.h"
#include "strings.h"
#include "toast.h"
using namespace anm2ed::anm2;
using namespace anm2ed::imgui;
using namespace anm2ed::types;
using namespace anm2ed::util;
using namespace glm;
namespace filesystem = anm2ed::util::filesystem;
namespace anm2ed
{
Document::Document(Anm2& anm2, const std::filesystem::path& path)
@@ -99,12 +98,12 @@ namespace anm2ed
this->path = !path.empty() ? path : this->path;
auto absolutePath = this->path;
auto absolutePathUtf8 = filesystem::path_to_utf8(absolutePath);
auto absolutePathUtf8 = path::to_utf8(absolutePath);
if (anm2.serialize(absolutePath, errorString))
{
toasts.push(std::vformat(localize.get(TOAST_SAVE_DOCUMENT), std::make_format_args(absolutePathUtf8)));
logger.info(std::vformat(localize.get(TOAST_SAVE_DOCUMENT, anm2ed::ENGLISH),
std::make_format_args(absolutePathUtf8)));
logger.info(
std::vformat(localize.get(TOAST_SAVE_DOCUMENT, anm2ed::ENGLISH), std::make_format_args(absolutePathUtf8)));
clean();
return true;
}
@@ -121,14 +120,14 @@ namespace anm2ed
std::filesystem::path Document::autosave_path_get()
{
auto fileNameUtf8 = filesystem::path_to_utf8(filename_get());
auto fileNameUtf8 = path::to_utf8(filename_get());
auto autosaveNameUtf8 = "." + fileNameUtf8 + ".autosave";
return directory_get() / filesystem::path_from_utf8(autosaveNameUtf8);
return directory_get() / path::from_utf8(autosaveNameUtf8);
}
std::filesystem::path Document::path_from_autosave_get(std::filesystem::path& path)
{
auto fileName = path.filename().u8string();
auto fileName = path::to_utf8(path.filename());
if (!fileName.empty() && fileName.front() == '.') fileName.erase(fileName.begin());
auto restorePath = path.parent_path() / std::filesystem::path(std::u8string(fileName.begin(), fileName.end()));
@@ -140,7 +139,7 @@ namespace anm2ed
bool Document::autosave(std::string* errorString)
{
auto autosavePath = autosave_path_get();
auto autosavePathUtf8 = filesystem::path_to_utf8(autosavePath);
auto autosavePathUtf8 = path::to_utf8(autosavePath);
if (anm2.serialize(autosavePath, errorString))
{
autosaveHash = hash;
@@ -152,8 +151,8 @@ namespace anm2ed
}
else if (errorString)
{
toasts.push(std::vformat(localize.get(TOAST_AUTOSAVE_FAILED),
std::make_format_args(autosavePathUtf8, *errorString)));
toasts.push(
std::vformat(localize.get(TOAST_AUTOSAVE_FAILED), std::make_format_args(autosavePathUtf8, *errorString)));
logger.error(std::vformat(localize.get(TOAST_AUTOSAVE_FAILED, anm2ed::ENGLISH),
std::make_format_args(autosavePathUtf8, *errorString)));
}
@@ -265,17 +264,16 @@ namespace anm2ed
if (anm2.spritesheet_add(directory_get(), path, id))
{
anm2::Spritesheet& spritesheet = anm2.content.spritesheets[id];
auto pathString = filesystem::path_to_utf8(spritesheet.path);
auto pathString = path::to_utf8(spritesheet.path);
this->spritesheet.selection = {id};
this->spritesheet.reference = id;
toasts.push(
std::vformat(localize.get(TOAST_SPRITESHEET_INITIALIZED), std::make_format_args(id, pathString)));
toasts.push(std::vformat(localize.get(TOAST_SPRITESHEET_INITIALIZED), std::make_format_args(id, pathString)));
logger.info(std::vformat(localize.get(TOAST_SPRITESHEET_INITIALIZED, anm2ed::ENGLISH),
std::make_format_args(id, pathString)));
}
else
{
auto pathUtf8 = filesystem::path_to_utf8(pathCopy);
auto pathUtf8 = path::to_utf8(pathCopy);
toasts.push(std::vformat(localize.get(TOAST_SPRITESHEET_INIT_FAILED), std::make_format_args(pathUtf8)));
logger.error(std::vformat(localize.get(TOAST_SPRITESHEET_INIT_FAILED, anm2ed::ENGLISH),
std::make_format_args(pathUtf8)));
@@ -294,7 +292,7 @@ namespace anm2ed
if (anm2.sound_add(directory_get(), path, id))
{
auto& soundInfo = anm2.content.sounds[id];
auto soundPath = filesystem::path_to_utf8(soundInfo.path);
auto soundPath = path::to_utf8(soundInfo.path);
sound.selection = {id};
sound.reference = id;
toasts.push(std::vformat(localize.get(TOAST_SOUND_INITIALIZED), std::make_format_args(id, soundPath)));
@@ -303,7 +301,7 @@ namespace anm2ed
}
else
{
auto pathUtf8 = filesystem::path_to_utf8(pathCopy);
auto pathUtf8 = path::to_utf8(pathCopy);
toasts.push(std::vformat(localize.get(TOAST_SOUND_INITIALIZE_FAILED), std::make_format_args(pathUtf8)));
logger.error(std::vformat(localize.get(TOAST_SOUND_INITIALIZE_FAILED, anm2ed::ENGLISH),
std::make_format_args(pathUtf8)));
+4 -5
View File
@@ -3,14 +3,13 @@
#include <format>
#include <vector>
#include "filesystem_.h"
#include "path_.h"
#include "strings.h"
#include "time_.h"
using namespace anm2ed::resource;
using namespace anm2ed::types;
using namespace anm2ed::util;
namespace filesystem = anm2ed::util::filesystem;
namespace anm2ed::imgui
{
@@ -88,7 +87,7 @@ namespace anm2ed::imgui
auto isRequested = i == manager.pendingSelected;
auto font = isDirty ? font::ITALICS : font::REGULAR;
auto filename = filesystem::path_to_utf8(document.filename_get());
auto filename = path::to_utf8(document.filename_get());
auto string =
isDirty ? std::vformat(localize.get(FORMAT_NOT_SAVED), std::make_format_args(filename)) : filename;
auto label = std::format("{}###Document{}", string, i);
@@ -105,7 +104,7 @@ namespace anm2ed::imgui
ImGui::EndTabItem();
}
auto pathUtf8 = filesystem::path_to_utf8(document.path);
auto pathUtf8 = path::to_utf8(document.path);
ImGui::SetItemTooltip("%s", pathUtf8.c_str());
ImGui::PopFont();
@@ -128,7 +127,7 @@ namespace anm2ed::imgui
{
auto& closeDocument = manager.documents[closeDocumentIndex];
auto filename = filesystem::path_to_utf8(closeDocument.filename_get());
auto filename = path::to_utf8(closeDocument.filename_get());
auto prompt = std::vformat(localize.get(LABEL_DOCUMENT_MODIFIED_PROMPT), std::make_format_args(filename));
ImGui::TextUnformatted(prompt.c_str());
+16 -3
View File
@@ -1,6 +1,3 @@
#include "imgui_.h"
#include "strings.h"
#include <imgui/imgui_internal.h>
#include <cmath>
@@ -8,7 +5,12 @@
#include <sstream>
#include <unordered_map>
#include "imgui_.h"
#include "path_.h"
#include "strings.h"
using namespace anm2ed::types;
using namespace anm2ed::util;
using namespace glm;
namespace anm2ed::imgui
@@ -84,6 +86,17 @@ namespace anm2ed::imgui
return ImGui::InputText(label, string->data(), string->capacity() + 1, flags, input_text_callback, string);
}
bool input_text_path(const char* label, std::filesystem::path* path, ImGuiInputTextFlags flags)
{
if (!path) return false;
auto pathUtf8 = path::to_utf8(*path);
auto edited = input_text_string(label, &pathUtf8, flags);
if (edited) *path = path::from_utf8(pathUtf8);
return edited;
}
bool combo_negative_one_indexed(const std::string& label, int* index, std::vector<const char*>& strings)
{
*index += 1;
+2
View File
@@ -1,5 +1,6 @@
#pragma once
#include <filesystem>
#include <glm/glm.hpp>
#include <imgui/imgui.h>
#include <set>
@@ -176,6 +177,7 @@ namespace anm2ed::imgui
ImVec2 child_size_get(int = 1);
int input_text_callback(ImGuiInputTextCallbackData*);
bool input_text_string(const char*, std::string*, ImGuiInputTextFlags = 0);
bool input_text_path(const char*, std::filesystem::path*, ImGuiInputTextFlags = 0);
bool input_int_range(const char*, int&, int, int, int = STEP, int = STEP_FAST, ImGuiInputTextFlags = 0);
bool input_int2_range(const char*, glm::ivec2&, glm::ivec2, glm::ivec2, ImGuiInputTextFlags = 0);
bool input_float_range(const char*, float&, float, float, float = STEP, float = STEP_FAST, const char* = "%.3f",
+35 -935
View File
File diff suppressed because it is too large Load Diff
+12 -4
View File
@@ -8,12 +8,23 @@
#include "settings.h"
#include "strings.h"
#include "wizard/about.h"
#include "wizard/change_all_frame_properties.h"
#include "wizard/configure.h"
#include "wizard/generate_animation_from_grid.h"
#include "wizard/render_animation.h"
namespace anm2ed::imgui
{
class Taskbar
{
wizard::ChangeAllFrameProperties changeAllFrameProperties{};
wizard::About about{};
wizard::Configure configure{};
wizard::GenerateAnimationFromGrid generateAnimationFromGrid{};
wizard::RenderAnimation renderAnimation{};
Canvas generate;
float generateTime{};
PopupHelper generatePopup{PopupHelper(LABEL_TASKBAR_GENERATE_ANIMATION_FROM_GRID)};
PopupHelper changePopup{PopupHelper(LABEL_CHANGE_ALL_FRAME_PROPERTIES, imgui::POPUP_NORMAL_NO_HEIGHT)};
PopupHelper overwritePopup{PopupHelper(LABEL_TASKBAR_OVERWRITE_FILE, imgui::POPUP_SMALL_NO_HEIGHT)};
@@ -21,14 +32,11 @@ namespace anm2ed::imgui
PopupHelper configurePopup{PopupHelper(LABEL_TASKBAR_CONFIGURE)};
PopupHelper aboutPopup{PopupHelper(LABEL_TASKBAR_ABOUT)};
Settings editSettings{};
int selectedShortcut{-1};
int creditsIndex{};
bool isQuittingMode{};
public:
float height{};
Taskbar();
void update(Manager&, Settings&, Resources&, Dialog&, bool&);
};
};
+26 -19
View File
@@ -11,12 +11,12 @@
#include "imgui_.h"
#include "log.h"
#include "math_.h"
#include "path_.h"
#include "strings.h"
#include "toast.h"
#include "tool.h"
#include "types.h"
using namespace anm2ed::canvas;
using namespace anm2ed::types;
using namespace anm2ed::util;
using namespace anm2ed::resource;
@@ -48,6 +48,7 @@ namespace anm2ed::imgui
{
auto& ffmpegPath = settings.renderFFmpegPath;
auto& path = settings.renderPath;
auto pathString = path::to_utf8(path);
auto& type = settings.renderType;
if (playback.time > end || playback.isFinished)
@@ -55,11 +56,11 @@ namespace anm2ed::imgui
if (type == render::PNGS)
{
auto& format = settings.renderFormat;
auto formatString = path::to_utf8(format);
bool isSuccess{true};
for (auto [i, frame] : std::views::enumerate(renderFrames))
{
std::filesystem::path outputPath =
std::filesystem::path(path) / std::vformat(format, std::make_format_args(i));
auto outputPath = path / std::vformat(formatString, std::make_format_args(i));
if (!frame.write_png(outputPath))
{
@@ -71,15 +72,16 @@ namespace anm2ed::imgui
if (isSuccess)
{
toasts.push(std::vformat(localize.get(TOAST_EXPORT_RENDERED_FRAMES), std::make_format_args(path)));
logger.info(
std::vformat(localize.get(TOAST_EXPORT_RENDERED_FRAMES, anm2ed::ENGLISH), std::make_format_args(path)));
toasts.push(std::vformat(localize.get(TOAST_EXPORT_RENDERED_FRAMES), std::make_format_args(pathString)));
logger.info(std::vformat(localize.get(TOAST_EXPORT_RENDERED_FRAMES, anm2ed::ENGLISH),
std::make_format_args(pathString)));
}
else
{
toasts.push(std::vformat(localize.get(TOAST_EXPORT_RENDERED_FRAMES_FAILED), std::make_format_args(path)));
toasts.push(
std::vformat(localize.get(TOAST_EXPORT_RENDERED_FRAMES_FAILED), std::make_format_args(pathString)));
logger.error(std::vformat(localize.get(TOAST_EXPORT_RENDERED_FRAMES_FAILED, anm2ed::ENGLISH),
std::make_format_args(path)));
std::make_format_args(pathString)));
}
}
else if (type == render::SPRITESHEET)
@@ -129,15 +131,16 @@ namespace anm2ed::imgui
Texture spritesheetTexture(spritesheet.data(), spritesheetSize);
if (spritesheetTexture.write_png(path))
{
toasts.push(std::vformat(localize.get(TOAST_EXPORT_SPRITESHEET), std::make_format_args(path)));
logger.info(
std::vformat(localize.get(TOAST_EXPORT_SPRITESHEET, anm2ed::ENGLISH), std::make_format_args(path)));
toasts.push(std::vformat(localize.get(TOAST_EXPORT_SPRITESHEET), std::make_format_args(pathString)));
logger.info(std::vformat(localize.get(TOAST_EXPORT_SPRITESHEET, anm2ed::ENGLISH),
std::make_format_args(pathString)));
}
else
{
toasts.push(std::vformat(localize.get(TOAST_EXPORT_SPRITESHEET_FAILED), std::make_format_args(path)));
toasts.push(
std::vformat(localize.get(TOAST_EXPORT_SPRITESHEET_FAILED), std::make_format_args(pathString)));
logger.error(std::vformat(localize.get(TOAST_EXPORT_SPRITESHEET_FAILED, anm2ed::ENGLISH),
std::make_format_args(path)));
std::make_format_args(pathString)));
}
}
}
@@ -146,16 +149,16 @@ namespace anm2ed::imgui
{
if (animation_render(ffmpegPath, path, renderFrames, audioStream, (render::Type)type, size, anm2.info.fps))
{
toasts.push(std::vformat(localize.get(TOAST_EXPORT_RENDERED_ANIMATION), std::make_format_args(path)));
toasts.push(std::vformat(localize.get(TOAST_EXPORT_RENDERED_ANIMATION), std::make_format_args(pathString)));
logger.info(std::vformat(localize.get(TOAST_EXPORT_RENDERED_ANIMATION, anm2ed::ENGLISH),
std::make_format_args(path)));
std::make_format_args(pathString)));
}
else
{
toasts.push(
std::vformat(localize.get(TOAST_EXPORT_RENDERED_ANIMATION_FAILED), std::make_format_args(path)));
std::vformat(localize.get(TOAST_EXPORT_RENDERED_ANIMATION_FAILED), std::make_format_args(pathString)));
logger.error(std::vformat(localize.get(TOAST_EXPORT_RENDERED_ANIMATION_FAILED, anm2ed::ENGLISH),
std::make_format_args(path)));
std::make_format_args(pathString)));
}
}
@@ -163,7 +166,6 @@ namespace anm2ed::imgui
if (settings.renderIsRawAnimation)
{
settings = savedSettings;
pan = savedPan;
@@ -290,8 +292,12 @@ namespace anm2ed::imgui
auto zoom_in = [&]() { zoom_adjust(zoomStep); };
auto zoom_out = [&]() { zoom_adjust(-zoomStep); };
manager.isAbleToRecord = false;
if (ImGui::Begin(localize.get(LABEL_ANIMATION_PREVIEW_WINDOW), &settings.windowIsAnimationPreview))
{
manager.isAbleToRecord = true;
auto childSize = ImVec2(row_widget_width_get(4),
(ImGui::GetTextLineHeightWithSpacing() * 4) + (ImGui::GetStyle().WindowPadding.y * 2));
@@ -402,6 +408,7 @@ namespace anm2ed::imgui
settings.previewBackgroundColor = vec4();
settings.previewIsGrid = false;
settings.previewIsAxes = false;
settings.previewIsPivots = false;
settings.previewIsBorder = false;
settings.timelineIsOnlyShowLayers = true;
settings.onionskinIsEnabled = false;
@@ -729,7 +736,7 @@ namespace anm2ed::imgui
auto frame = document.frame_get();
auto useTool = tool;
auto step = isMod ? canvas::STEP_FAST : canvas::STEP;
auto step = isMod ? STEP_FAST : STEP;
mousePos = position_translate(zoom, pan, to_vec2(ImGui::GetMousePos()) - to_vec2(cursorScreenPos));
if (isMouseMiddleDown) useTool = tool::PAN;
+9 -145
View File
@@ -13,7 +13,6 @@ using namespace glm;
namespace anm2ed::imgui
{
void FrameProperties::update(Manager& manager, Settings& settings)
{
if (ImGui::Begin(localize.get(LABEL_FRAME_PROPERTIES_WINDOW), &settings.windowIsFrameProperties))
@@ -180,151 +179,16 @@ namespace anm2ed::imgui
ImGui::EndDisabled();
}
else
{
auto& isCrop = settings.changeIsCrop;
auto& isCropX = settings.changeIsCropX;
auto& isCropY = settings.changeIsCropY;
auto& isSize = settings.changeIsSize;
auto& isPosition = settings.changeIsPosition;
auto& isPivot = settings.changeIsPivot;
auto& isScale = settings.changeIsScale;
auto& isRotation = settings.changeIsRotation;
auto& isDuration = settings.changeIsDuration;
auto& isTint = settings.changeIsTint;
auto& isColorOffset = settings.changeIsColorOffset;
auto& isVisibleSet = settings.changeIsVisibleSet;
auto& isInterpolatedSet = settings.changeIsInterpolatedSet;
auto& crop = settings.changeCrop;
auto& size = settings.changeSize;
auto& position = settings.changePosition;
auto& pivot = settings.changePivot;
auto& scale = settings.changeScale;
auto& rotation = settings.changeRotation;
auto& duration = settings.changeDuration;
auto& tint = settings.changeTint;
auto& colorOffset = settings.changeColorOffset;
auto& isVisible = settings.changeIsVisible;
auto& isInterpolated = settings.changeIsInterpolated;
#define PROPERTIES_WIDGET(body, checkboxLabel, isEnabled) \
ImGui::Checkbox(checkboxLabel, &isEnabled); \
ImGui::SameLine(); \
ImGui::BeginDisabled(!isEnabled); \
body; \
ImGui::EndDisabled();
auto bool_value = [&](const char* checkboxLabel, const char* valueLabel, bool& isEnabled, bool& value)
{ PROPERTIES_WIDGET(ImGui::Checkbox(valueLabel, &value), checkboxLabel, isEnabled) };
auto color3_value = [&](const char* checkboxLabel, const char* valueLabel, bool& isEnabled, vec3& value)
{ PROPERTIES_WIDGET(ImGui::ColorEdit3(valueLabel, value_ptr(value)), checkboxLabel, isEnabled); };
auto color4_value = [&](const char* checkboxLabel, const char* valueLabel, bool& isEnabled, vec4& value)
{ PROPERTIES_WIDGET(ImGui::ColorEdit4(valueLabel, value_ptr(value)), checkboxLabel, isEnabled); };
auto float2_value = [&](const char* checkboxLabel, const char* valueLabel, bool& isEnabled, vec2& value)
{
PROPERTIES_WIDGET(ImGui::InputFloat2(valueLabel, value_ptr(value), vec2_format_get(value)), checkboxLabel,
isEnabled);
};
auto float2_value_new = [&](const char* checkboxXLabel, const char* checkboxYLabel, const char* valueXLabel,
const char* valueYLabel, bool& isXEnabled, bool& isYEnabled, vec2& value)
{
auto width =
(ImGui::CalcItemWidth() - ImGui::GetTextLineHeight() - (ImGui::GetStyle().ItemInnerSpacing.x * 6)) / 2;
PROPERTIES_WIDGET(ImGui::PushItemWidth(width);
ImGui::DragFloat(valueXLabel, &value.x, DRAG_SPEED, 0.0f, 0.0f, float_format_get(value.x));
ImGui::PopItemWidth(), checkboxXLabel, isXEnabled);
ImGui::SameLine();
PROPERTIES_WIDGET(ImGui::PushItemWidth(width);
ImGui::DragFloat(valueYLabel, &value.y, DRAG_SPEED, 0.0f, 0.0f, float_format_get(value.y));
ImGui::PopItemWidth(), checkboxYLabel, isYEnabled);
};
auto float_value = [&](const char* checkboxLabel, const char* valueLabel, bool& isEnabled, float& value)
{
PROPERTIES_WIDGET(ImGui::InputFloat(valueLabel, &value, STEP, STEP_FAST, float_format_get(value)),
checkboxLabel, isEnabled);
};
auto duration_value = [&](const char* checkboxLabel, const char* valueLabel, bool& isEnabled, int& value)
{
PROPERTIES_WIDGET(
input_int_range(valueLabel, value, anm2::FRAME_DURATION_MIN, anm2::FRAME_DURATION_MAX, STEP, STEP_FAST),
checkboxLabel, isEnabled);
};
#undef PROPERTIES_WIDGET
float2_value_new("##Is Crop X", "##Is Crop Y", "##Crop X", localize.get(BASIC_CROP), isCropX, isCropY, crop);
float2_value("##Is Size", localize.get(BASIC_SIZE), isSize, size);
float2_value("##Is Position", localize.get(BASIC_POSITION), isPosition, position);
float2_value("##Is Pivot", localize.get(BASIC_PIVOT), isPivot, pivot);
float2_value("##Is Scale", localize.get(BASIC_SCALE), isScale, scale);
float_value("##Is Rotation", localize.get(BASIC_ROTATION), isRotation, rotation);
duration_value("##Is Duration", localize.get(BASIC_DURATION), isDuration, duration);
color4_value("##Is Tint", localize.get(BASIC_TINT), isTint, tint);
color3_value("##Is Color Offset", localize.get(BASIC_COLOR_OFFSET), isColorOffset, colorOffset);
bool_value("##Is Visible", localize.get(BASIC_VISIBLE), isVisibleSet, isVisible);
ImGui::SameLine();
bool_value("##Is Interpolated", localize.get(BASIC_INTERPOLATED), isInterpolatedSet, isInterpolated);
auto frame_change = [&](anm2::ChangeType type)
{
anm2::FrameChange frameChange;
if (isCrop) frameChange.crop = std::make_optional(crop);
if (isSize) frameChange.size = std::make_optional(size);
if (isPosition) frameChange.position = std::make_optional(position);
if (isPivot) frameChange.pivot = std::make_optional(pivot);
if (isScale) frameChange.scale = std::make_optional(scale);
if (isRotation) frameChange.rotation = std::make_optional(rotation);
if (isDuration) frameChange.duration = std::make_optional(duration);
if (isTint) frameChange.tint = std::make_optional(tint);
if (isColorOffset) frameChange.colorOffset = std::make_optional(colorOffset);
if (isVisibleSet) frameChange.isVisible = std::make_optional(isVisible);
if (isInterpolatedSet) frameChange.isInterpolated = std::make_optional(isInterpolated);
DOCUMENT_EDIT(document, localize.get(EDIT_CHANGE_FRAME_PROPERTIES), Document::FRAMES,
document.item_get()->frames_change(frameChange, type, *frames.begin(), (int)frames.size()));
};
ImGui::Separator();
bool isAnyProperty = isCrop || isSize || isPosition || isPivot || isScale || isRotation || isDuration ||
isTint || isColorOffset || isVisibleSet || isInterpolatedSet;
auto rowWidgetSize = widget_size_with_row_get(5);
ImGui::BeginDisabled(!isAnyProperty);
if (ImGui::Button(localize.get(LABEL_ADJUST), rowWidgetSize)) frame_change(anm2::ADJUST);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_ADJUST));
ImGui::SameLine();
if (ImGui::Button(localize.get(BASIC_ADD), rowWidgetSize)) frame_change(anm2::ADD);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_ADD_VALUES));
ImGui::SameLine();
if (ImGui::Button(localize.get(LABEL_SUBTRACT), rowWidgetSize)) frame_change(anm2::SUBTRACT);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_SUBTRACT_VALUES));
ImGui::SameLine();
if (ImGui::Button(localize.get(LABEL_MULTIPLY), rowWidgetSize)) frame_change(anm2::MULTIPLY);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_MULTIPLY_VALUES));
ImGui::SameLine();
if (ImGui::Button(localize.get(LABEL_DIVIDE), rowWidgetSize)) frame_change(anm2::DIVIDE);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_DIVIDE_VALUES));
ImGui::EndDisabled();
}
changeAllFrameProperties.update(document, settings);
}
ImGui::End();
dummy_value_negative<int>() = -1;
dummy_value<float>() = 0;
dummy_value<int>() = 0;
dummy_value<bool>() = 0;
dummy_value<vec2>() = vec2();
dummy_value<vec3>() = vec3();
dummy_value<vec4>() = vec4();
}
}
+3 -11
View File
@@ -3,23 +3,15 @@
#include <glm/vec2.hpp>
#include "manager.h"
#include "settings.h"
namespace anm2
{
struct Frame;
}
#include "wizard/change_all_frame_properties.h"
namespace anm2ed::imgui
{
class FrameProperties
{
wizard::ChangeAllFrameProperties changeAllFrameProperties{};
public:
void update(Manager&, Settings&);
private:
glm::vec2 cropEditingValue{};
const anm2::Frame* cropEditingFrame{};
bool isCropEditing{};
};
}
+11 -12
View File
@@ -2,12 +2,11 @@
#include <ranges>
#include "filesystem_.h"
#include "log.h"
#include "path_.h"
#include "strings.h"
#include "toast.h"
using namespace anm2ed::dialog;
using namespace anm2ed::util;
using namespace anm2ed::types;
using namespace anm2ed::resource;
@@ -24,8 +23,8 @@ namespace anm2ed::imgui
auto& selection = document.sound.selection;
auto style = ImGui::GetStyle();
auto add_open = [&]() { dialog.file_open(dialog::SOUND_OPEN); };
auto replace_open = [&]() { dialog.file_open(dialog::SOUND_REPLACE); };
auto add_open = [&]() { dialog.file_open(Dialog::SOUND_OPEN); };
auto replace_open = [&]() { dialog.file_open(Dialog::SOUND_REPLACE); };
auto play = [&](anm2::Sound& sound) { sound.play(); };
@@ -34,7 +33,7 @@ namespace anm2ed::imgui
auto behavior = [&]()
{
int id{};
auto pathString = filesystem::path_to_utf8(path);
auto pathString = path::to_utf8(path);
if (anm2.sound_add(document.directory_get(), path, id))
{
selection = {id};
@@ -76,7 +75,7 @@ namespace anm2ed::imgui
{
anm2::Sound& sound = anm2.content.sounds[id];
sound.reload(document.directory_get());
auto pathString = filesystem::path_to_utf8(sound.path);
auto pathString = path::to_utf8(sound.path);
toasts.push(std::vformat(localize.get(TOAST_RELOAD_SOUND), std::make_format_args(id, pathString)));
logger.info(
std::vformat(localize.get(TOAST_RELOAD_SOUND, anm2ed::ENGLISH), std::make_format_args(id, pathString)));
@@ -95,7 +94,7 @@ namespace anm2ed::imgui
auto& id = *selection.begin();
anm2::Sound& sound = anm2.content.sounds[id];
sound = anm2::Sound(document.directory_get(), path);
auto pathString = filesystem::path_to_utf8(sound.path);
auto pathString = path::to_utf8(sound.path);
toasts.push(std::vformat(localize.get(TOAST_REPLACE_SOUND), std::make_format_args(id, pathString)));
logger.info(
std::vformat(localize.get(TOAST_REPLACE_SOUND, anm2ed::ENGLISH), std::make_format_args(id, pathString)));
@@ -215,7 +214,7 @@ namespace anm2ed::imgui
bool isValid = sound.is_valid();
auto& soundIcon = isValid ? resources.icons[icon::SOUND] : resources.icons[icon::NONE];
auto tintColor = !isValid ? ImVec4(1.0f, 0.25f, 0.25f, 1.0f) : ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
auto pathString = filesystem::path_to_utf8(sound.path);
auto pathString = path::to_utf8(sound.path);
ImGui::SetNextItemSelectionUserData(id);
ImGui::SetNextItemStorageID(id);
@@ -271,9 +270,9 @@ namespace anm2ed::imgui
ImGui::EndChild();
ImGui::PopID();
}
context_menu();
context_menu();
}
ImGui::PopStyleVar();
selection.finish();
@@ -288,7 +287,7 @@ namespace anm2ed::imgui
if (ImGui::Button(localize.get(BASIC_ADD), widgetSize)) add_open();
imgui::set_item_tooltip_shortcut(localize.get(TOOLTIP_SOUND_ADD), settings.shortcutAdd);
if (dialog.is_selected(dialog::SOUND_OPEN))
if (dialog.is_selected(Dialog::SOUND_OPEN))
{
add(dialog.path);
dialog.reset();
@@ -316,7 +315,7 @@ namespace anm2ed::imgui
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_REPLACE_SOUND));
ImGui::EndDisabled();
if (dialog.is_selected(dialog::SOUND_REPLACE))
if (dialog.is_selected(Dialog::SOUND_REPLACE))
{
replace(dialog.path);
dialog.reset();
+2 -9
View File
@@ -11,7 +11,6 @@
#include "tool.h"
#include "types.h"
using namespace anm2ed::canvas;
using namespace anm2ed::types;
using namespace anm2ed::resource;
using namespace anm2ed::util;
@@ -19,12 +18,6 @@ using namespace glm;
namespace anm2ed::imgui
{
constexpr auto BORDER_DASH_LENGTH = 1.0f;
constexpr auto BORDER_DASH_GAP = 0.5f;
constexpr auto BORDER_DASH_OFFSET = 0.0f;
constexpr auto PIVOT_COLOR = color::PINK;
SpritesheetEditor::SpritesheetEditor() : Canvas(vec2()) {}
void SpritesheetEditor::update(Manager& manager, Settings& settings, Resources& resources)
@@ -218,7 +211,7 @@ namespace anm2ed::imgui
rect_render(dashedShader, cropTransform, cropModel, color::RED);
auto pivotTransform =
transform * math::quad_model_get(canvas::PIVOT_SIZE, frame->crop + frame->pivot, PIVOT_SIZE * 0.5f);
transform * math::quad_model_get(PIVOT_SIZE, frame->crop + frame->pivot, PIVOT_SIZE * 0.5f);
texture_render(shaderTexture, resources.icons[icon::PIVOT].id, pivotTransform, PIVOT_COLOR);
}
}
@@ -278,7 +271,7 @@ namespace anm2ed::imgui
auto frame = document.frame_get();
auto useTool = tool;
auto step = isMod ? canvas::STEP_FAST : canvas::STEP;
auto step = isMod ? STEP_FAST : STEP;
auto stepX = isGridSnap ? step * gridSize.x : step;
auto stepY = isGridSnap ? step * gridSize.y : step;
previousMousePos = mousePos;
+15 -16
View File
@@ -6,8 +6,8 @@
#include <format>
#include "document.h"
#include "filesystem_.h"
#include "log.h"
#include "path_.h"
#include "strings.h"
#include "toast.h"
@@ -28,8 +28,8 @@ namespace anm2ed::imgui
auto& reference = document.spritesheet.reference;
auto style = ImGui::GetStyle();
auto add_open = [&]() { dialog.file_open(dialog::SPRITESHEET_OPEN); };
auto replace_open = [&]() { dialog.file_open(dialog::SPRITESHEET_REPLACE); };
auto add_open = [&]() { dialog.file_open(Dialog::SPRITESHEET_OPEN); };
auto replace_open = [&]() { dialog.file_open(Dialog::SPRITESHEET_REPLACE); };
auto add = [&](const std::filesystem::path& path)
{
@@ -47,7 +47,7 @@ namespace anm2ed::imgui
for (auto& id : unused)
{
anm2::Spritesheet& spritesheet = anm2.content.spritesheets[id];
auto pathString = filesystem::path_to_utf8(spritesheet.path);
auto pathString = path::to_utf8(spritesheet.path);
toasts.push(std::vformat(localize.get(TOAST_REMOVE_SPRITESHEET), std::make_format_args(id, pathString)));
logger.info(std::vformat(localize.get(TOAST_REMOVE_SPRITESHEET, anm2ed::ENGLISH),
std::make_format_args(id, pathString)));
@@ -69,7 +69,7 @@ namespace anm2ed::imgui
{
anm2::Spritesheet& spritesheet = anm2.content.spritesheets[id];
spritesheet.reload(document.directory_get());
auto pathString = filesystem::path_to_utf8(spritesheet.path);
auto pathString = path::to_utf8(spritesheet.path);
toasts.push(std::vformat(localize.get(TOAST_RELOAD_SPRITESHEET), std::make_format_args(id, pathString)));
logger.info(std::vformat(localize.get(TOAST_RELOAD_SPRITESHEET, anm2ed::ENGLISH),
std::make_format_args(id, pathString)));
@@ -88,7 +88,7 @@ namespace anm2ed::imgui
auto& id = *selection.begin();
anm2::Spritesheet& spritesheet = anm2.content.spritesheets[id];
spritesheet = anm2::Spritesheet(document.directory_get(), path);
auto pathString = filesystem::path_to_utf8(spritesheet.path);
auto pathString = path::to_utf8(spritesheet.path);
toasts.push(std::vformat(localize.get(TOAST_REPLACE_SPRITESHEET), std::make_format_args(id, pathString)));
logger.info(std::vformat(localize.get(TOAST_REPLACE_SPRITESHEET, anm2ed::ENGLISH),
std::make_format_args(id, pathString)));
@@ -104,7 +104,7 @@ namespace anm2ed::imgui
for (auto& id : selection)
{
anm2::Spritesheet& spritesheet = anm2.content.spritesheets[id];
auto pathString = filesystem::path_to_utf8(spritesheet.path);
auto pathString = path::to_utf8(spritesheet.path);
if (spritesheet.save(document.directory_get()))
{
toasts.push(std::vformat(localize.get(TOAST_SAVE_SPRITESHEET), std::make_format_args(id, pathString)));
@@ -126,10 +126,9 @@ namespace anm2ed::imgui
std::error_code ec{};
auto absolutePath = std::filesystem::weakly_canonical(document.directory_get() / spritesheet.path, ec);
if (ec) absolutePath = document.directory_get() / spritesheet.path;
auto target = std::filesystem::is_directory(absolutePath)
? absolutePath
: std::filesystem::is_directory(absolutePath.parent_path()) ? absolutePath.parent_path()
: document.directory_get();
auto target = std::filesystem::is_directory(absolutePath) ? absolutePath
: std::filesystem::is_directory(absolutePath.parent_path()) ? absolutePath.parent_path()
: document.directory_get();
dialog.file_explorer_open(target);
};
@@ -232,7 +231,7 @@ namespace anm2ed::imgui
bool isValid = spritesheet.texture.is_valid();
auto& texture = isValid ? spritesheet.texture : resources.icons[icon::NONE];
auto tintColor = !isValid ? ImVec4(1.0f, 0.25f, 0.25f, 1.0f) : ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
auto pathString = filesystem::path_to_utf8(spritesheet.path);
auto pathString = path::to_utf8(spritesheet.path);
auto pathCStr = pathString.c_str();
ImGui::SetNextItemSelectionUserData(id);
@@ -322,12 +321,12 @@ namespace anm2ed::imgui
}
ImGui::EndChild();
ImGui::PopID();
context_menu();
}
ImGui::PopStyleVar();
context_menu();
selection.finish();
}
ImGui::EndChild();
@@ -339,7 +338,7 @@ namespace anm2ed::imgui
if (ImGui::Button(localize.get(BASIC_ADD), rowOneWidgetSize)) add_open();
set_item_tooltip_shortcut(localize.get(TOOLTIP_ADD_SPRITESHEET), settings.shortcutAdd);
if (dialog.is_selected(dialog::SPRITESHEET_OPEN))
if (dialog.is_selected(Dialog::SPRITESHEET_OPEN))
{
add(dialog.path);
dialog.reset();
@@ -359,7 +358,7 @@ namespace anm2ed::imgui
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_REPLACE_SPRITESHEET));
ImGui::EndDisabled();
if (dialog.is_selected(dialog::SPRITESHEET_REPLACE))
if (dialog.is_selected(Dialog::SPRITESHEET_REPLACE))
{
replace(dialog.path);
dialog.reset();
+3 -2
View File
@@ -1069,7 +1069,7 @@ namespace anm2ed::imgui
drawList->AddRectFilled(frameScreenPos, frameRectMax, ImGui::GetColorU32(frameMultipleOverlayColor));
}
frames.selection.start(item->frames.size(), ImGuiMultiSelectFlags_ClearOnEscape);
if (type != anm2::TRIGGER) frames.selection.start(item->frames.size(), ImGuiMultiSelectFlags_ClearOnEscape);
for (auto [i, frame] : std::views::enumerate(item->frames))
{
@@ -1400,7 +1400,8 @@ namespace anm2ed::imgui
ImGui::PopID();
}
frames.selection.finish();
if (type != anm2::TRIGGER) frames.selection.finish();
if (isFrameSelectionLocked)
{
frames.selection.clear();
+6 -8
View File
@@ -2,11 +2,11 @@
#include <ranges>
#include "filesystem_.h"
#include "path_.h"
#include "strings.h"
using namespace anm2ed::util;
using namespace anm2ed::resource;
namespace filesystem = anm2ed::util::filesystem;
namespace anm2ed::imgui
{
@@ -34,10 +34,10 @@ namespace anm2ed::imgui
auto widgetSize = widget_size_with_row_get(2);
if (ImGui::Button(localize.get(BASIC_NEW), widgetSize))
dialog.file_save(dialog::ANM2_NEW); // handled in taskbar.cpp
dialog.file_save(Dialog::ANM2_NEW); // handled in taskbar.cpp
ImGui::SameLine();
if (ImGui::Button(localize.get(BASIC_OPEN), widgetSize))
dialog.file_open(dialog::ANM2_OPEN); // handled in taskbar.cpp
dialog.file_open(Dialog::ANM2_OPEN); // handled in taskbar.cpp
if (ImGui::BeginChild("##Recent Files Child", {}, ImGuiChildFlags_Borders))
{
@@ -46,8 +46,7 @@ namespace anm2ed::imgui
{
ImGui::PushID(i);
auto label = std::format(FILE_LABEL_FORMAT, filesystem::path_to_utf8(file.filename()),
filesystem::path_to_utf8(file));
auto label = std::format(FILE_LABEL_FORMAT, path::to_utf8(file.filename()), path::to_utf8(file));
if (ImGui::Selectable(label.c_str()))
{
@@ -78,8 +77,7 @@ namespace anm2ed::imgui
{
for (auto& file : manager.autosaveFiles)
{
auto label = std::format(FILE_LABEL_FORMAT, filesystem::path_to_utf8(file.filename()),
filesystem::path_to_utf8(file));
auto label = std::format(FILE_LABEL_FORMAT, path::to_utf8(file.filename()), path::to_utf8(file));
ImGui::TextUnformatted(label.c_str());
}
}
+191
View File
@@ -0,0 +1,191 @@
#include "about.h"
#include <cmath>
#include <imgui.h>
#include <vector>
using namespace anm2ed::resource;
namespace anm2ed::imgui::wizard
{
static constexpr auto CREDIT_DELAY = 1.0f;
static constexpr auto CREDIT_SCROLL_SPEED = 25.0f;
static constexpr About::Credit CREDITS[] = {
{"Anm2Ed", font::BOLD},
{"License: GPLv3"},
{""},
{"Designer", font::BOLD},
{"Shweet"},
{""},
{"Additional Help", font::BOLD},
{"im-tem"},
{""},
{"Localization", font::BOLD},
{"Gabriel Asencio (Spanish (Latin America))"},
{"ExtremeThreat (Russian)"},
{"CxRedix (Chinese)"},
{"sawalk/사왈이 (Korean)"},
{""},
{"Based on the work of:", font::BOLD},
{"Adrian Gavrilita"},
{"Simon Parzer"},
{"Matt Kapuszczak"},
{""},
{"XM Music", font::BOLD},
{"Drozerix"},
{"\"Keygen Wraith\""},
{"https://modarchive.org/module.php?207854"},
{"License: CC0"},
{""},
{"Libraries", font::BOLD},
{"Dear ImGui"},
{"https://github.com/ocornut/imgui"},
{"License: MIT"},
{""},
{"SDL"},
{"https://github.com/libsdl-org/SDL"},
{"License: zlib"},
{""},
{"SDL_mixer"},
{"https://github.com/libsdl-org/SDL_mixer"},
{"License: zlib"},
{""},
{"tinyxml2"},
{"https://github.com/leethomason/tinyxml2"},
{"License: zlib"},
{""},
{"glm"},
{"https://github.com/g-truc/glm"},
{"License: MIT"},
{""},
{"lunasvg"},
{"https://github.com/sammycage/lunasvg"},
{"License: MIT"},
{""},
{"Icons", font::BOLD},
{"Remix Icons"},
{"remixicon.com"},
{"License: Apache"},
{""},
{"Font", font::BOLD},
{"Noto Sans"},
{"https://fonts.google.com/noto/specimen/Noto+Sans"},
{"License: OFL"},
{""},
{"Special Thanks", font::BOLD},
{"Edmund McMillen"},
{"Florian Himsl"},
{"Tyrone Rodriguez"},
{"The-Vinh Truong (_kilburn)"},
{"Isaac Reflashed team"},
{"Everyone who waited patiently for this to be finished"},
{"Everyone else who has worked on The Binding of Isaac!"},
{""},
{""},
{""},
{""},
{""},
{""},
{""},
{""},
{""},
{"enjoy the jams :)"},
{""},
{""},
{""},
{""},
{""},
{""},
{""},
{""},
{""},
{""},
};
static constexpr auto CREDIT_COUNT = (int)(sizeof(CREDITS) / sizeof(About::Credit));
void About::reset(Resources& resources)
{
resources.music_track().play(true);
creditsState = {};
creditsState.spawnTimer = CREDIT_DELAY;
}
void About::update(Resources& resources)
{
auto size = ImGui::GetContentRegionAvail();
auto applicationLabel = localize.get(LABEL_APPLICATION_NAME);
auto versionLabel = localize.get(LABEL_APPLICATION_VERSION);
ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE_LARGE);
ImGui::SetCursorPosX((size.x - ImGui::CalcTextSize(applicationLabel).x) / 2);
ImGui::TextUnformatted(applicationLabel);
ImGui::SetCursorPosX((size.x - ImGui::CalcTextSize(versionLabel).x) / 2);
ImGui::TextUnformatted(versionLabel);
ImGui::PopFont();
auto creditRegionPos = ImGui::GetCursorScreenPos();
auto creditRegionSize = ImGui::GetContentRegionAvail();
if (creditRegionSize.y > 0.0f && creditRegionSize.x > 0.0f)
{
auto fontSize = ImGui::GetFontSize();
auto drawList = ImGui::GetWindowDrawList();
auto clipMax = ImVec2(creditRegionPos.x + creditRegionSize.x, creditRegionPos.y + creditRegionSize.y);
drawList->PushClipRect(creditRegionPos, clipMax, true);
auto delta = ImGui::GetIO().DeltaTime;
creditsState.spawnTimer -= delta;
auto maxVisible = std::max(1, (int)std::floor(creditRegionSize.y / (float)fontSize));
while (creditsState.active.size() < (size_t)maxVisible && creditsState.spawnTimer <= 0.0f)
{
creditsState.active.push_back({creditsState.nextIndex, 0.0f});
creditsState.nextIndex = (creditsState.nextIndex + 1) % CREDIT_COUNT;
creditsState.spawnTimer += CREDIT_DELAY;
}
auto baseY = clipMax.y - (float)fontSize;
auto& baseColor = ImGui::GetStyleColorVec4(ImGuiCol_Text);
auto fadeSpan = (float)fontSize * 2.0f;
for (auto it = creditsState.active.begin(); it != creditsState.active.end();)
{
it->offset += CREDIT_SCROLL_SPEED * delta;
auto yPos = baseY - it->offset;
if (yPos + fontSize < creditRegionPos.y)
{
it = creditsState.active.erase(it);
continue;
}
auto& credit = CREDITS[it->index];
auto fontPtr = resources.fonts[credit.font].get();
auto textSize = fontPtr->CalcTextSizeA((float)fontSize, FLT_MAX, 0.0f, credit.string);
auto xPos = creditRegionPos.x + (creditRegionSize.x - textSize.x) * 0.5f;
auto alpha = 1.0f;
auto topDist = yPos - creditRegionPos.y;
if (topDist < fadeSpan) alpha *= std::clamp(topDist / fadeSpan, 0.0f, 1.0f);
auto bottomDist = (creditRegionPos.y + creditRegionSize.y) - (yPos + fontSize);
if (bottomDist < fadeSpan) alpha *= std::clamp(bottomDist / fadeSpan, 0.0f, 1.0f);
if (alpha <= 0.0f)
{
++it;
continue;
}
auto color = baseColor;
color.w *= alpha;
drawList->AddText(fontPtr, fontSize, ImVec2(xPos, yPos), ImGui::GetColorU32(color), credit.string);
++it;
}
drawList->PopClipRect();
}
}
}
+36
View File
@@ -0,0 +1,36 @@
#pragma once
#include "../../resources.h"
namespace anm2ed::imgui::wizard
{
class About
{
public:
struct Credit
{
const char* string{};
resource::font::Type font{resource::font::REGULAR};
};
struct ScrollingCredit
{
int index{};
float offset{};
};
struct CreditsState
{
std::vector<ScrollingCredit> active{};
float spawnTimer{1.0f};
int nextIndex{};
};
int creditsIndex{};
CreditsState creditsState{};
void reset(Resources& resources);
void update(Resources& resources);
};
}
@@ -0,0 +1,270 @@
#include "change_all_frame_properties.h"
#include <algorithm>
#include <cmath>
#include <string>
#include "math_.h"
using namespace anm2ed::util::math;
using namespace glm;
namespace anm2ed::imgui::wizard
{
void ChangeAllFrameProperties::update(Document& document, Settings& settings)
{
isChanged = false;
auto& frames = document.frames.selection;
auto& isCropX = settings.changeIsCropX;
auto& isCropY = settings.changeIsCropY;
auto& isSizeX = settings.changeIsSizeX;
auto& isSizeY = settings.changeIsSizeY;
auto& isPositionX = settings.changeIsPositionX;
auto& isPositionY = settings.changeIsPositionY;
auto& isPivotX = settings.changeIsPivotX;
auto& isPivotY = settings.changeIsPivotY;
auto& isScaleX = settings.changeIsScaleX;
auto& isScaleY = settings.changeIsScaleY;
auto& isRotation = settings.changeIsRotation;
auto& isDuration = settings.changeIsDuration;
auto& isTintR = settings.changeIsTintR;
auto& isTintG = settings.changeIsTintG;
auto& isTintB = settings.changeIsTintB;
auto& isTintA = settings.changeIsTintA;
auto& isColorOffsetR = settings.changeIsColorOffsetR;
auto& isColorOffsetG = settings.changeIsColorOffsetG;
auto& isColorOffsetB = settings.changeIsColorOffsetB;
auto& isVisibleSet = settings.changeIsVisibleSet;
auto& isInterpolatedSet = settings.changeIsInterpolatedSet;
auto& crop = settings.changeCrop;
auto& size = settings.changeSize;
auto& position = settings.changePosition;
auto& pivot = settings.changePivot;
auto& scale = settings.changeScale;
auto& rotation = settings.changeRotation;
auto& duration = settings.changeDuration;
auto& tint = settings.changeTint;
auto& colorOffset = settings.changeColorOffset;
auto& isVisible = settings.changeIsVisible;
auto& isInterpolated = settings.changeIsInterpolated;
#define PROPERTIES_WIDGET(body, checkboxLabel, isEnabled) \
ImGui::Checkbox(checkboxLabel, &isEnabled); \
ImGui::SameLine(); \
ImGui::BeginDisabled(!isEnabled); \
body; \
ImGui::EndDisabled();
auto bool_value = [&](const char* checkboxLabel, const char* valueLabel, bool& isEnabled, bool& value)
{ PROPERTIES_WIDGET(ImGui::Checkbox(valueLabel, &value), checkboxLabel, isEnabled) };
auto color3_value = [&](const char* checkboxRLabel, const char* checkboxGLabel, const char* checkboxBLabel,
const char* valueRLabel, const char* valueGLabel, const char* valueBLabel,
const char* label, bool& isREnabled, bool& isGEnabled, bool& isBEnabled, vec3& value)
{
auto style = ImGui::GetStyle();
auto width = (ImGui::CalcItemWidth() - (ImGui::GetFrameHeightWithSpacing() * 2) - (style.ItemSpacing.x * 2) -
ImGui::GetFrameHeight()) /
3;
ivec3 valueAlt = {float_to_uint8(value.r), float_to_uint8(value.g), float_to_uint8(value.b)};
ImGui::PushItemWidth(width);
PROPERTIES_WIDGET(ImGui::DragInt(valueRLabel, &valueAlt.r, DRAG_SPEED, 0, 255, "R:%d"), checkboxRLabel,
isREnabled);
ImGui::SameLine();
PROPERTIES_WIDGET(ImGui::DragInt(valueGLabel, &valueAlt.g, DRAG_SPEED, 0, 255, "G:%d"), checkboxGLabel,
isGEnabled);
ImGui::SameLine();
PROPERTIES_WIDGET(ImGui::DragInt(valueBLabel, &valueAlt.b, DRAG_SPEED, 0, 255, "B:%d"), checkboxBLabel,
isBEnabled);
ImGui::PopItemWidth();
ImGui::SameLine();
value = vec3(uint8_to_float(valueAlt.r), uint8_to_float(valueAlt.g), uint8_to_float(valueAlt.b));
ImVec4 buttonColor = {isREnabled ? value.r : 0, isGEnabled ? value.g : 0, isBEnabled ? value.b : 0, 1};
ImGui::ColorButton(label, buttonColor);
ImGui::SameLine();
ImGui::TextUnformatted(label);
};
auto color4_value = [&](const char* checkboxRLabel, const char* checkboxGLabel, const char* checkboxBLabel,
const char* checkboxALabel, const char* valueRLabel, const char* valueGLabel,
const char* valueBLabel, const char* valueALabel, const char* label, bool& isREnabled,
bool& isGEnabled, bool& isBEnabled, bool& isAEnabled, vec4& value)
{
auto style = ImGui::GetStyle();
auto width = (ImGui::CalcItemWidth() - (ImGui::GetFrameHeightWithSpacing() * 3) - (style.ItemSpacing.x * 3) -
ImGui::GetFrameHeight()) /
4;
ivec4 valueAlt = {float_to_uint8(value.r), float_to_uint8(value.g), float_to_uint8(value.b),
float_to_uint8(value.a)};
ImGui::PushItemWidth(width);
PROPERTIES_WIDGET(ImGui::DragInt(valueRLabel, &valueAlt.r, DRAG_SPEED, 0, 255, "R:%d"), checkboxRLabel,
isREnabled);
ImGui::SameLine();
PROPERTIES_WIDGET(ImGui::DragInt(valueGLabel, &valueAlt.g, DRAG_SPEED, 0, 255, "G:%d"), checkboxGLabel,
isGEnabled);
ImGui::SameLine();
PROPERTIES_WIDGET(ImGui::DragInt(valueBLabel, &valueAlt.b, DRAG_SPEED, 0, 255, "B:%d"), checkboxBLabel,
isBEnabled);
ImGui::SameLine();
PROPERTIES_WIDGET(ImGui::DragInt(valueALabel, &valueAlt.a, DRAG_SPEED, 0, 255, "A:%d"), checkboxALabel,
isAEnabled);
ImGui::PopItemWidth();
ImGui::SameLine();
value = vec4(uint8_to_float(valueAlt.r), uint8_to_float(valueAlt.g), uint8_to_float(valueAlt.b),
uint8_to_float(valueAlt.a));
ImVec4 buttonColor = {isREnabled ? value.r : 0, isGEnabled ? value.g : 0, isBEnabled ? value.b : 0,
isAEnabled ? value.a : 1};
ImGui::ColorButton(label, buttonColor);
ImGui::SameLine();
ImGui::TextUnformatted(label);
};
auto float_value = [&](const char* checkboxLabel, const char* valueLabel, bool& isEnabled, float& value)
{
PROPERTIES_WIDGET(ImGui::DragFloat(valueLabel, &value, DRAG_SPEED, 0.0f, 0.0f, float_format_get(value)),
checkboxLabel, isEnabled);
};
auto float2_value = [&](const char* checkboxXLabel, const char* checkboxYLabel, const char* valueXLabel,
const char* valueYLabel, bool& isXEnabled, bool& isYEnabled, vec2& value)
{
auto style = ImGui::GetStyle();
auto width = (ImGui::CalcItemWidth() - ImGui::GetFrameHeightWithSpacing() - style.ItemSpacing.x) / 2;
ImGui::PushItemWidth(width);
PROPERTIES_WIDGET(ImGui::DragFloat(valueXLabel, &value.x, DRAG_SPEED, 0.0f, 0.0f, float_format_get(value.x)),
checkboxXLabel, isXEnabled);
ImGui::SameLine();
PROPERTIES_WIDGET(ImGui::DragFloat(valueYLabel, &value.y, DRAG_SPEED, 0.0f, 0.0f, float_format_get(value.y)),
checkboxYLabel, isYEnabled);
ImGui::PopItemWidth();
};
auto duration_value = [&](const char* checkboxLabel, const char* valueLabel, bool& isEnabled, int& value)
{
PROPERTIES_WIDGET(
input_int_range(valueLabel, value, anm2::FRAME_DURATION_MIN, anm2::FRAME_DURATION_MAX, STEP, STEP_FAST),
checkboxLabel, isEnabled);
};
#undef PROPERTIES_WIDGET
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImGui::GetStyle().ItemInnerSpacing);
float2_value("##Is Crop X", "##Is Crop Y", "##Crop X", localize.get(BASIC_CROP), isCropX, isCropY, crop);
float2_value("##Is Size X", "##Is Size Y", "##Size X", localize.get(BASIC_SIZE), isSizeX, isSizeY, size);
float2_value("##Is Position X", "##Is Position Y", "##Position X", localize.get(BASIC_POSITION), isPositionX,
isPositionY, position);
float2_value("##Is Pivot X", "##Is Pivot Y", "##Pivot X", localize.get(BASIC_PIVOT), isPivotX, isPivotY, pivot);
float2_value("##Is Scale X", "##Is Scale Y", "##Scale X", localize.get(BASIC_SCALE), isScaleX, isScaleY, scale);
float_value("##Is Rotation", localize.get(BASIC_ROTATION), isRotation, rotation);
duration_value("##Is Duration", localize.get(BASIC_DURATION), isDuration, duration);
color4_value("##Is Tint R", "##Is Tint G", "##Is Tint B", "##Is Tint A", "##Tint R", "##Tint G", "##Tint B",
"##Tint A", localize.get(BASIC_TINT), isTintR, isTintG, isTintB, isTintA, tint);
color3_value("##Is Color Offset R", "##Is Color Offset G", "##Is Color Offset B", "##Color Offset R",
"##Color Offset B", "##Color Offset G", localize.get(BASIC_COLOR_OFFSET), isColorOffsetR,
isColorOffsetG, isColorOffsetB, colorOffset);
bool_value("##Is Visible", localize.get(BASIC_VISIBLE), isVisibleSet, isVisible);
ImGui::SameLine();
bool_value("##Is Interpolated", localize.get(BASIC_INTERPOLATED), isInterpolatedSet, isInterpolated);
ImGui::PopStyleVar();
auto frame_change = [&](anm2::ChangeType type)
{
anm2::FrameChange frameChange;
if (isCropX) frameChange.cropX = crop.x;
if (isCropY) frameChange.cropY = crop.y;
if (isSizeX) frameChange.sizeX = size.x;
if (isSizeY) frameChange.sizeY = size.y;
if (isPositionX) frameChange.positionX = position.x;
if (isPositionY) frameChange.positionY = position.y;
if (isPivotX) frameChange.pivotX = pivot.x;
if (isPivotY) frameChange.pivotY = pivot.y;
if (isScaleX) frameChange.scaleX = scale.x;
if (isScaleY) frameChange.scaleY = scale.y;
if (isRotation) frameChange.rotation = std::make_optional(rotation);
if (isDuration) frameChange.duration = std::make_optional(duration);
if (isTintR) frameChange.tintR = tint.r;
if (isTintG) frameChange.tintG = tint.g;
if (isTintB) frameChange.tintB = tint.b;
if (isTintA) frameChange.tintA = tint.a;
if (isColorOffsetR) frameChange.colorOffsetR = colorOffset.r;
if (isColorOffsetG) frameChange.colorOffsetG = colorOffset.g;
if (isColorOffsetB) frameChange.colorOffsetB = colorOffset.b;
if (isVisibleSet) frameChange.isVisible = std::make_optional(isVisible);
if (isInterpolatedSet) frameChange.isInterpolated = std::make_optional(isInterpolated);
DOCUMENT_EDIT(document, localize.get(EDIT_CHANGE_FRAME_PROPERTIES), Document::FRAMES,
document.item_get()->frames_change(frameChange, type, *frames.begin(), (int)frames.size()));
isChanged = true;
};
ImGui::Separator();
bool isAnyProperty = isCropX || isCropY || isSizeX || isSizeY || isPositionX || isPositionY || isPivotX ||
isPivotY || isScaleX || isScaleY || isRotation || isDuration || isTintR || isTintG ||
isTintB || isTintA || isColorOffsetR || isColorOffsetG || isColorOffsetB || isVisibleSet ||
isInterpolatedSet;
auto rowWidgetSize = widget_size_with_row_get(5);
ImGui::BeginDisabled(!isAnyProperty);
if (ImGui::Button(localize.get(LABEL_ADJUST), rowWidgetSize)) frame_change(anm2::ADJUST);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_ADJUST));
ImGui::SameLine();
if (ImGui::Button(localize.get(BASIC_ADD), rowWidgetSize)) frame_change(anm2::ADD);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_ADD_VALUES));
ImGui::SameLine();
if (ImGui::Button(localize.get(LABEL_SUBTRACT), rowWidgetSize)) frame_change(anm2::SUBTRACT);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_SUBTRACT_VALUES));
ImGui::SameLine();
if (ImGui::Button(localize.get(LABEL_MULTIPLY), rowWidgetSize)) frame_change(anm2::MULTIPLY);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_MULTIPLY_VALUES));
ImGui::SameLine();
if (ImGui::Button(localize.get(LABEL_DIVIDE), rowWidgetSize)) frame_change(anm2::DIVIDE);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_DIVIDE_VALUES));
ImGui::EndDisabled();
}
}
@@ -0,0 +1,15 @@
#pragma once
#include "document.h"
#include "settings.h"
namespace anm2ed::imgui::wizard
{
class ChangeAllFrameProperties
{
public:
bool isChanged{};
void update(Document&, Settings&);
};
}
+201
View File
@@ -0,0 +1,201 @@
#include "configure.h"
#include "imgui_.h"
using namespace anm2ed::types;
namespace anm2ed::imgui::wizard
{
void Configure::reset(Settings& settings) { temporary = settings; }
void Configure::update(Manager& manager, Settings& settings)
{
isSet = false;
auto childSize = size_without_footer_get(2);
if (ImGui::BeginTabBar("##Configure Tabs"))
{
if (ImGui::BeginTabItem(localize.get(LABEL_DISPLAY)))
{
if (ImGui::BeginChild("##Tab Child", childSize, true))
{
ImGui::SeparatorText(localize.get(LABEL_WINDOW_MENU));
input_float_range(localize.get(LABEL_UI_SCALE), temporary.uiScale, 0.5f, 2.0f, 0.25f, 0.25f, "%.2f");
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_UI_SCALE));
ImGui::Checkbox(localize.get(LABEL_VSYNC), &temporary.isVsync);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_VSYNC));
ImGui::SeparatorText(localize.get(LABEL_LOCALIZATION));
ImGui::Combo(localize.get(LABEL_LANGUAGE), &temporary.language, LANGUAGE_STRINGS, LANGUAGE_COUNT);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_LANGUAGE));
ImGui::SeparatorText(localize.get(LABEL_THEME));
for (int i = 0; i < theme::COUNT; i++)
{
ImGui::RadioButton(localize.get(theme::STRINGS[i]), &temporary.theme, i);
ImGui::SameLine();
}
}
ImGui::EndChild();
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem(localize.get(LABEL_FILE_MENU)))
{
if (ImGui::BeginChild("##Tab Child", childSize, true))
{
ImGui::SeparatorText(localize.get(LABEL_AUTOSAVE));
ImGui::Checkbox(localize.get(BASIC_ENABLED), &temporary.fileIsAutosave);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_AUTOSAVE_ENABLED));
ImGui::BeginDisabled(!temporary.fileIsAutosave);
input_int_range(localize.get(LABEL_TIME_MINUTES), temporary.fileAutosaveTime, 0, 10);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_AUTOSAVE_INTERVAL));
ImGui::EndDisabled();
ImGui::SeparatorText(localize.get(LABEL_SNAPSHOTS));
input_int_range(localize.get(LABEL_STACK_SIZE), temporary.fileSnapshotStackSize, 0, 100);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_STACK_SIZE));
ImGui::SeparatorText(localize.get(LABEL_OPTIONS));
ImGui::Checkbox(localize.get(LABEL_OVERWRITE_WARNING), &temporary.fileIsWarnOverwrite);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_OVERWRITE_WARNING));
}
ImGui::EndChild();
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem(localize.get(LABEL_INPUT)))
{
if (ImGui::BeginChild("##Tab Child", childSize, true))
{
ImGui::SeparatorText(localize.get(LABEL_KEYBOARD));
input_float_range(localize.get(LABEL_REPEAT_DELAY), temporary.keyboardRepeatDelay, 0.05f, 1.0f, 0.05f, 0.05f,
"%.2f");
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_REPEAT_DELAY));
input_float_range(localize.get(LABEL_REPEAT_RATE), temporary.keyboardRepeatRate, 0.005f, 1.0f, 0.005f, 0.005f,
"%.3f");
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_REPEAT_DELAY));
ImGui::SeparatorText(localize.get(LABEL_ZOOM));
input_float_range(localize.get(LABEL_ZOOM_STEP), temporary.inputZoomStep, 10.0f, 250.0f, 10.0f, 10.0f,
"%.0f%%");
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_ZOOM_STEP));
ImGui::SeparatorText(localize.get(LABEL_TOOL));
ImGui::Checkbox(localize.get(LABEL_MOVE_TOOL_SNAP), &temporary.inputIsMoveToolSnapToMouse);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_MOVE_TOOL_SNAP));
}
ImGui::EndChild();
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem(localize.get(LABEL_SHORTCUTS_TAB)))
{
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2());
if (ImGui::BeginChild("##Tab Child", childSize, true))
{
if (ImGui::BeginTable(localize.get(LABEL_SHORTCUTS_TAB), 2,
ImGuiTableFlags_Borders | ImGuiTableFlags_ScrollY))
{
ImGui::TableSetupScrollFreeze(0, 1);
ImGui::TableSetupColumn(localize.get(LABEL_SHORTCUT_COLUMN));
ImGui::TableSetupColumn(localize.get(LABEL_VALUE_COLUMN));
ImGui::TableHeadersRow();
for (int i = 0; i < SHORTCUT_COUNT; ++i)
{
bool isSelected = selectedShortcut == i;
ShortcutMember member = SHORTCUT_MEMBERS[i];
std::string* settingString = &(temporary.*member);
std::string chordString = isSelected ? "" : *settingString;
ImGui::PushID(i);
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted(localize.get(::anm2ed::SHORTCUT_STRING_TYPES[i]));
ImGui::TableSetColumnIndex(1);
if (ImGui::Selectable(chordString.c_str(), isSelected)) selectedShortcut = i;
ImGui::PopID();
if (isSelected)
{
ImGuiKeyChord chord{ImGuiKey_None};
if (ImGui::IsKeyDown(ImGuiMod_Ctrl)) chord |= ImGuiMod_Ctrl;
if (ImGui::IsKeyDown(ImGuiMod_Shift)) chord |= ImGuiMod_Shift;
if (ImGui::IsKeyDown(ImGuiMod_Alt)) chord |= ImGuiMod_Alt;
if (ImGui::IsKeyDown(ImGuiMod_Super)) chord |= ImGuiMod_Super;
for (const auto& entry : KEY_MAP)
{
auto key = entry.second;
if (ImGui::IsKeyPressed(key))
{
chord |= key;
*settingString = chord_to_string(chord);
selectedShortcut = -1;
break;
}
}
}
}
ImGui::EndTable();
}
ImGui::EndChild();
ImGui::PopStyleVar();
ImGui::EndTabItem();
}
}
ImGui::EndTabBar();
}
auto widgetSize = widget_size_with_row_get(3);
if (ImGui::Button(localize.get(BASIC_SAVE), widgetSize))
{
settings = temporary;
ImGui::GetIO().KeyRepeatDelay = settings.keyboardRepeatDelay;
ImGui::GetIO().KeyRepeatRate = settings.keyboardRepeatRate;
ImGui::GetStyle().FontScaleMain = settings.uiScale;
SnapshotStack::max_size_set(settings.fileSnapshotStackSize);
imgui::theme_set((theme::Type)settings.theme);
localize.language = (Language)settings.language;
manager.chords_set(settings);
for (auto& document : manager.documents)
document.snapshots.apply_limit();
isSet = true;
}
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_SETTINGS_SAVE));
ImGui::SameLine();
if (ImGui::Button(localize.get(LABEL_USE_DEFAULT_SETTINGS), widgetSize)) temporary = Settings();
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_USE_DEFAULT_SETTINGS));
ImGui::SameLine();
if (ImGui::Button(localize.get(LABEL_CLOSE), widgetSize)) isSet = true;
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_CLOSE_SETTINGS));
}
}
+18
View File
@@ -0,0 +1,18 @@
#pragma once
#include "manager.h"
namespace anm2ed::imgui::wizard
{
class Configure
{
Settings temporary{};
int selectedShortcut{-1};
public:
bool isSet{};
void reset(Settings&);
void update(Manager&, Settings&);
};
}
@@ -0,0 +1,115 @@
#include "generate_animation_from_grid.h"
#include "math_.h"
#include "types.h"
using namespace anm2ed::types;
using namespace anm2ed::util;
using namespace glm;
namespace anm2ed::imgui::wizard
{
GenerateAnimationFromGrid::GenerateAnimationFromGrid() : Canvas(vec2()) {}
void GenerateAnimationFromGrid::update(Document& document, Resources& resources, Settings& settings)
{
isEnd = false;
auto& startPosition = settings.generateStartPosition;
auto& size = settings.generateSize;
auto& pivot = settings.generatePivot;
auto& rows = settings.generateRows;
auto& columns = settings.generateColumns;
auto& count = settings.generateCount;
auto& delay = settings.generateDuration;
auto& zoom = settings.generateZoom;
auto& zoomStep = settings.inputZoomStep;
auto childSize = ImVec2(row_widget_width_get(2), size_without_footer_get().y);
if (ImGui::BeginChild("##Options Child", childSize, ImGuiChildFlags_Borders))
{
ImGui::InputInt2(localize.get(LABEL_GENERATE_START_POSITION), value_ptr(startPosition));
ImGui::InputInt2(localize.get(LABEL_GENERATE_FRAME_SIZE), value_ptr(size));
ImGui::InputInt2(localize.get(BASIC_PIVOT), value_ptr(pivot));
ImGui::InputInt(localize.get(LABEL_GENERATE_ROWS), &rows, STEP, STEP_FAST);
ImGui::InputInt(localize.get(LABEL_GENERATE_COLUMNS), &columns, STEP, STEP_FAST);
input_int_range(localize.get(LABEL_GENERATE_COUNT), count, anm2::FRAME_NUM_MIN, rows * columns);
ImGui::InputInt(localize.get(BASIC_DURATION), &delay, STEP, STEP_FAST);
}
ImGui::EndChild();
ImGui::SameLine();
if (ImGui::BeginChild("##Preview Child", childSize, ImGuiChildFlags_Borders))
{
auto& backgroundColor = settings.previewBackgroundColor;
auto& shaderTexture = resources.shaders[resource::shader::TEXTURE];
auto previewSize = ImVec2(ImGui::GetContentRegionAvail().x, size_without_footer_get(2).y);
bind();
size_set(to_vec2(previewSize));
viewport_set();
clear(vec4(backgroundColor, 1.0f));
if (document.reference.itemType == anm2::LAYER)
{
auto& texture =
document.anm2.content.spritesheets[document.anm2.content.layers[document.reference.itemID].spritesheetID]
.texture;
auto index = std::clamp((int)(time * (count - 1)), 0, (count - 1));
auto row = index / columns;
auto column = index % columns;
auto crop = startPosition + ivec2(size.x * column, size.y * row);
auto uvMin = (vec2(crop) + vec2(0.5f)) / vec2(texture.size);
auto uvMax = (vec2(crop) + vec2(size) - vec2(0.5f)) / vec2(texture.size);
mat4 transform = transform_get(zoom) * math::quad_model_get(size, {}, pivot);
texture_render(shaderTexture, texture.id, transform, vec4(1.0f), {},
math::uv_vertices_get(uvMin, uvMax).data());
}
unbind();
ImGui::Image(texture, previewSize);
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
ImGui::SliderFloat("##Time", &time, 0.0f, 1.0f, "");
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
ImGui::InputFloat("##Zoom", &zoom, zoomStep, zoomStep, "%.0f%%");
zoom = glm::clamp(zoom, ZOOM_MIN, ZOOM_MAX);
}
ImGui::EndChild();
auto widgetSize = widget_size_with_row_get(2);
if (ImGui::Button(localize.get(LABEL_GENERATE), widgetSize))
{
auto generate_from_grid = [&]()
{
auto item = document.item_get();
auto animation = document.animation_get();
if (item && animation)
{
item->frames_generate_from_grid(startPosition, size, pivot, columns, count, delay);
animation->frameNum = animation->length();
}
};
DOCUMENT_EDIT(document, localize.get(EDIT_GENERATE_ANIMATION_FROM_GRID), Document::FRAMES, generate_from_grid());
isEnd = true;
}
ImGui::SameLine();
if (ImGui::Button(localize.get(BASIC_CANCEL), widgetSize)) isEnd = true;
}
}
@@ -0,0 +1,20 @@
#pragma once
#include "canvas.h"
#include "document.h"
#include "resources.h"
#include "settings.h"
namespace anm2ed::imgui::wizard
{
class GenerateAnimationFromGrid : public Canvas
{
float time{};
public:
bool isEnd{};
GenerateAnimationFromGrid();
void update(Document&, Resources&, Settings&);
};
}
+290
View File
@@ -0,0 +1,290 @@
#include "render_animation.h"
#include <ranges>
#include "log.h"
#include "path_.h"
#include "process_.h"
#include "toast.h"
#include <ranges>
using namespace anm2ed::resource;
using namespace anm2ed::util;
namespace anm2ed::imgui::wizard
{
void RenderAnimation::range_to_animation_set(Manager& manager, Document& document)
{
if (auto animation = document.animation_get())
{
manager.recordingStart = 0;
manager.recordingEnd = animation->frameNum - 1;
}
}
void RenderAnimation::range_to_frames_set(Manager& manager, Document& document)
{
auto& frames = document.frames.selection;
if (!frames.empty())
{
if (auto item = document.item_get())
{
int duration{};
for (auto [i, frame] : std::views::enumerate(item->frames))
{
if ((int)i == *frames.begin()) manager.recordingStart = duration;
if ((int)i == *frames.rbegin()) manager.recordingEnd = duration + frame.duration - 1;
duration += frame.duration;
}
}
}
}
void RenderAnimation::reset(Manager& manager, Document& document, Settings& settings)
{
if (!manager.isRecordingRange) range_to_animation_set(manager, document);
settings.renderPath.replace_extension(render::EXTENSIONS[settings.renderType]);
}
void RenderAnimation::update(Manager& manager, Document& document, Resources& resources, Settings& settings,
Dialog& dialog)
{
isEnd = false;
auto animation = document.animation_get();
if (!animation) return;
auto& ffmpegPath = settings.renderFFmpegPath;
auto& path = settings.renderPath;
auto& format = settings.renderFormat;
auto& scale = settings.renderScale;
auto& isRaw = settings.renderIsRawAnimation;
auto& type = settings.renderType;
auto& start = manager.recordingStart;
auto& end = manager.recordingEnd;
auto& rows = settings.renderRows;
auto& columns = settings.renderColumns;
auto& isRange = manager.isRecordingRange;
auto& frames = document.frames.selection;
auto& reference = document.reference;
auto& frameNum = animation->frameNum;
auto widgetSize = widget_size_with_row_get(2);
auto dialogType = type == render::PNGS ? Dialog::PNG_DIRECTORY_SET
: type == render::SPRITESHEET ? Dialog::PNG_PATH_SET
: type == render::GIF ? Dialog::GIF_PATH_SET
: type == render::WEBM ? Dialog::WEBM_PATH_SET
: Dialog::NONE;
if (ImGui::ImageButton("##FFmpeg Path Set", resources.icons[icon::FOLDER].id, icon_size_get()))
dialog.file_open(Dialog::FFMPEG_PATH_SET);
ImGui::SameLine();
input_text_path(localize.get(LABEL_FFMPEG_PATH), &ffmpegPath);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_FFMPEG_PATH));
if (dialog.is_selected(Dialog::FFMPEG_PATH_SET))
{
ffmpegPath = path::to_utf8(dialog.path);
dialog.reset();
}
if (ImGui::ImageButton("##Path Set", resources.icons[icon::FOLDER].id, icon_size_get()))
{
if (dialogType == Dialog::PNG_DIRECTORY_SET)
dialog.folder_open(dialogType);
else
dialog.file_save(dialogType);
}
ImGui::SameLine();
auto pathLabel = type == render::PNGS ? LABEL_OUTPUT_DIRECTORY : LABEL_OUTPUT_PATH;
input_text_path(localize.get(pathLabel), &path);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_OUTPUT_PATH));
if (dialog.is_selected(dialogType))
{
path = path::to_utf8(dialog.path);
dialog.reset();
}
if (ImGui::Combo(localize.get(LABEL_TYPE), &type, render::STRINGS, render::COUNT))
path.replace_extension(render::EXTENSIONS[type]);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_RENDER_TYPE));
if (type == render::PNGS || type == render::SPRITESHEET) ImGui::Separator();
if (type == render::PNGS)
{
input_text_path(localize.get(LABEL_FORMAT), &format);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_FORMAT));
}
else if (type == render::SPRITESHEET)
{
input_int_range(localize.get(LABEL_GENERATE_ROWS), rows, 1, frameNum - 1);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_ROWS));
input_int_range(localize.get(LABEL_GENERATE_COLUMNS), columns, 1, frameNum - 1);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_COLUMNS));
}
ImGui::Separator();
ImGui::Checkbox(localize.get(LABEL_CUSTOM_RANGE), &isRange);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_CUSTOM_RANGE));
ImGui::SameLine();
ImGui::BeginDisabled(frames.empty() || reference.itemID == anm2::TRIGGER);
if (ImGui::Button(localize.get(LABEL_TO_SELECTED_FRAMES))) range_to_frames_set(manager, document);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_TO_SELECTED_FRAMES));
ImGui::EndDisabled();
ImGui::SameLine();
if (ImGui::Button(localize.get(LABEL_TO_ANIMATION_RANGE))) range_to_animation_set(manager, document);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_TO_ANIMATION_RANGE));
ImGui::BeginDisabled(!isRange);
{
input_int_range(localize.get(LABEL_START), start, 0, frameNum - 1);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_START));
input_int_range(localize.get(LABEL_END), end, start, frameNum - 1);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_END));
}
ImGui::EndDisabled();
ImGui::Separator();
ImGui::Checkbox(localize.get(LABEL_RAW), &isRaw);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_RAW));
ImGui::BeginDisabled(!isRaw);
{
input_float_range(localize.get(BASIC_SCALE), scale, 1.0f, 100.0f, STEP, STEP_FAST, "%.1fx");
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_SCALE_OUTPUT));
}
ImGui::EndDisabled();
ImGui::Separator();
ImGui::Checkbox(localize.get(LABEL_SOUND), &settings.timelineIsSound);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_SOUND));
ImGui::Separator();
if (ImGui::Button(localize.get(LABEL_RENDER), widgetSize))
{
path.replace_extension(render::EXTENSIONS[type]);
auto render_is_valid = [&]()
{
auto ffmpeg_is_valid = [&]()
{
if (!path::is_executable(ffmpegPath)) return false;
auto testCommand = ffmpegPath.string() + " -version";
Process process(testCommand.c_str(), "r");
auto result = process.output_get_and_close();
if (!result.contains("ffmpeg version")) return false;
return true;
};
auto ffmpeg_valid_check = [&]()
{
if (!ffmpeg_is_valid())
{
toasts.push(localize.get(TOAST_INVALID_FFMPEG));
logger.error(localize.get(TOAST_INVALID_FFMPEG, anm2ed::ENGLISH));
return false;
}
return true;
};
auto png_format_valid_check = [&]()
{
if (!format.string().contains("{}"))
{
toasts.push(localize.get(TOAST_PNG_FORMAT_INVALID));
logger.error(localize.get(TOAST_PNG_FORMAT_INVALID, anm2ed::ENGLISH));
}
return true;
};
auto path_valid_check = [&]()
{
if (path.empty())
{
toasts.push(localize.get(TOAST_RENDER_PATH_EMPTY));
logger.error(localize.get(TOAST_RENDER_PATH_EMPTY, anm2ed::ENGLISH));
return false;
}
return true;
};
auto png_directory_valid_check = [&]()
{
if (!path::ensure_directory(path))
{
toasts.push(localize.get(TOAST_PNG_DIRECTORY_INVALID));
logger.error(localize.get(TOAST_PNG_DIRECTORY_INVALID, anm2ed::ENGLISH));
return false;
}
return true;
};
auto spritesheet_valid_check = [&]()
{
if (rows <= 0 && columns <= 0)
{
toasts.push(localize.get(TOAST_RENDER_PATH_EMPTY));
logger.error(localize.get(TOAST_RENDER_PATH_EMPTY, anm2ed::ENGLISH));
return false;
}
return true;
};
if (!path_valid_check()) return false;
switch (type)
{
case render::PNGS:
if (!png_directory_valid_check()) return false;
if (!png_format_valid_check()) return false;
format.replace_extension(render::EXTENSIONS[render::SPRITESHEET]);
break;
case render::SPRITESHEET:
if (!spritesheet_valid_check()) return false;
break;
case render::GIF:
case render::WEBM:
case render::MP4:
if (!ffmpeg_valid_check()) return false;
break;
default:
return false;
break;
}
return true;
};
if (render_is_valid())
{
manager.isRecordingStart = true;
manager.progressPopup.open();
}
isEnd = true;
}
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_RENDER_BUTTON));
ImGui::SameLine();
if (ImGui::Button(localize.get(BASIC_CANCEL), widgetSize)) isEnd = true;
}
}
+20
View File
@@ -0,0 +1,20 @@
#pragma once
#include "dialog.h"
#include "manager.h"
#include "resources.h"
namespace anm2ed::imgui::wizard
{
class RenderAnimation
{
public:
bool isEnd{};
void range_to_frames_set(Manager&, Document&);
void range_to_animation_set(Manager&, Document&);
void reset(Manager&, Document&, Settings&);
void update(Manager&, Document&, Resources&, Settings&, Dialog&);
};
}
+2 -2
View File
@@ -8,8 +8,8 @@
#include <SDL3_mixer/SDL_mixer.h>
#include "filesystem_.h"
#include "log.h"
#include "sdl.h"
#include "imgui_.h"
@@ -75,7 +75,7 @@ namespace anm2ed
}
}
std::filesystem::path Loader::settings_path() { return filesystem::path_preferences_get() / "settings.ini"; }
std::filesystem::path Loader::settings_path() { return sdl::preferences_directory_get() / "settings.ini"; }
Loader::Loader(int argc, const char** argv)
{
+16 -33
View File
@@ -2,53 +2,37 @@
#include <print>
#include "filesystem_.h"
#include "sdl.h"
#include "time_.h"
using namespace anm2ed::util;
namespace anm2ed
{
void Logger::write_raw(const std::string& message)
{
std::println("{}", message);
if (file.is_open()) file << message << '\n' << std::flush;
}
void Logger::write(const Level level, const std::string& message)
{
std::string formatted = std::format("{} {} {}", time::get("(%d-%B-%Y %I:%M:%S)"), LEVEL_STRINGS[level], message);
std::println("{}", formatted);
if (file.is_open()) file << formatted << '\n' << std::flush;
write_raw(formatted);
}
void Logger::info(const std::string& message)
{
write(INFO, message);
}
void Logger::info(const std::string& message) { write(INFO, message); }
void Logger::warning(const std::string& message) { write(WARNING, message); }
void Logger::error(const std::string& message) { write(ERROR, message); }
void Logger::fatal(const std::string& message) { write(FATAL, message); }
void Logger::command(const std::string& message) { write(COMMAND, message); }
void Logger::open(const std::filesystem::path& path) { file.open(path, std::ios::out | std::ios::app); }
void Logger::warning(const std::string& message)
{
write(WARNING, message);
}
void Logger::error(const std::string& message)
{
write(ERROR, message);
}
void Logger::fatal(const std::string& message)
{
write(FATAL, message);
}
void Logger::command(const std::string& message)
{
write(COMMAND, message);
}
void Logger::open(const std::filesystem::path& path)
{
file.open(path, std::ios::out | std::ios::app);
}
std::filesystem::path Logger::path() { return sdl::preferences_directory_get() / "log.txt"; }
Logger::Logger()
{
open(filesystem::path_preferences_get() / "log.txt");
open(path());
info("Initializing Anm2Ed");
}
@@ -57,7 +41,6 @@ namespace anm2ed
info("Exiting Anm2Ed");
if (file.is_open()) file.close();
}
}
anm2ed::Logger logger;
+2
View File
@@ -46,6 +46,8 @@ namespace anm2ed
std::ofstream file{};
public:
static std::filesystem::path path();
void write_raw(const std::string&);
void write(const Level, const std::string&);
void info(const std::string&);
void warning(const std::string&);
+22 -27
View File
@@ -5,8 +5,9 @@
#include <format>
#include "filesystem_.h"
#include "log.h"
#include "path_.h"
#include "sdl.h"
#include "strings.h"
#include "toast.h"
#include "vector_.h"
@@ -26,9 +27,7 @@ namespace anm2ed
if (parent.empty()) return;
std::error_code ec{};
std::filesystem::create_directories(parent, ec);
if (ec)
logger.warning(
std::format("Could not create directory for {}: {}", filesystem::path_to_utf8(path), ec.message()));
if (ec) logger.warning(std::format("Could not create directory for {}: {}", path::to_utf8(path), ec.message()));
}
}
@@ -59,8 +58,8 @@ namespace anm2ed
if (documents.empty()) selectionHistory.clear();
}
std::filesystem::path Manager::recent_files_path_get() { return filesystem::path_preferences_get() / "recent.txt"; }
std::filesystem::path Manager::autosave_path_get() { return filesystem::path_preferences_get() / "autosave.txt"; }
std::filesystem::path Manager::recent_files_path_get() { return sdl::preferences_directory_get() / "recent.txt"; }
std::filesystem::path Manager::autosave_path_get() { return sdl::preferences_directory_get() / "autosave.txt"; }
Manager::Manager()
{
@@ -74,7 +73,7 @@ namespace anm2ed
{
std::string errorString{};
documents.emplace_back(path, isNew, &errorString);
auto pathString = filesystem::path_to_utf8(path);
auto pathString = path::to_utf8(path);
auto& document = documents.back();
if (!document.is_valid())
@@ -136,8 +135,7 @@ namespace anm2ed
std::error_code ec{};
std::filesystem::remove(autosavePath, ec);
if (ec)
logger.warning(std::format("Could not remove autosave file {}: {}", filesystem::path_to_utf8(autosavePath),
ec.message()));
logger.warning(std::format("Could not remove autosave file {}: {}", path::to_utf8(autosavePath), ec.message()));
}
autosave_files_write();
@@ -252,7 +250,7 @@ namespace anm2ed
std::vector<std::filesystem::path> ordered;
ordered.reserve(orderedEntries.size());
for (const auto& [pathString, _] : orderedEntries)
ordered.emplace_back(filesystem::path_from_utf8(pathString));
ordered.emplace_back(path::from_utf8(pathString));
return ordered;
}
@@ -262,11 +260,11 @@ namespace anm2ed
std::error_code ec{};
if (!std::filesystem::exists(path, ec))
{
logger.warning(std::format("Skipping missing recent file: {}", filesystem::path_to_utf8(path)));
logger.warning(std::format("Skipping missing recent file: {}", path::to_utf8(path)));
return;
}
recentFiles[filesystem::path_to_utf8(path)] = ++recentFilesCounter;
recentFiles[path::to_utf8(path)] = ++recentFilesCounter;
recent_files_trim();
recent_files_write();
}
@@ -278,12 +276,11 @@ namespace anm2ed
std::ifstream file(path);
if (!file)
{
logger.warning(
std::format("Could not load recent files from: {}. Skipping...", filesystem::path_to_utf8(path)));
logger.warning(std::format("Could not load recent files from: {}. Skipping...", path::to_utf8(path)));
return;
}
logger.info(std::format("Loading recent files from: {}", filesystem::path_to_utf8(path)));
logger.info(std::format("Loading recent files from: {}", path::to_utf8(path)));
std::string line{};
std::vector<std::string> loaded{};
@@ -293,14 +290,14 @@ namespace anm2ed
{
if (line.empty()) continue;
if (!line.empty() && line.back() == '\r') line.pop_back();
auto entry = filesystem::path_from_utf8(line);
auto entry = path::from_utf8(line);
std::error_code ec{};
if (!std::filesystem::exists(entry, ec))
{
logger.warning(std::format("Skipping missing recent file: {}", line));
continue;
}
auto entryString = filesystem::path_to_utf8(entry);
auto entryString = path::to_utf8(entry);
if (!seen.insert(entryString).second) continue;
loaded.emplace_back(std::move(entryString));
}
@@ -324,14 +321,13 @@ namespace anm2ed
if (!file.is_open())
{
logger.warning(
std::format("Could not write recent files to: {}. Skipping...", filesystem::path_to_utf8(path)));
logger.warning(std::format("Could not write recent files to: {}. Skipping...", path::to_utf8(path)));
return;
}
auto ordered = recent_files_ordered();
for (auto& entry : ordered)
file << filesystem::path_to_utf8(entry) << '\n';
file << path::to_utf8(entry) << '\n';
}
void Manager::recent_files_clear()
@@ -346,9 +342,9 @@ namespace anm2ed
for (auto& path : autosaveFiles)
{
auto fileNamePath = path.filename();
auto fileNameUtf8 = filesystem::path_to_utf8(fileNamePath);
auto fileNameUtf8 = path::to_utf8(fileNamePath);
if (!fileNameUtf8.empty() && fileNameUtf8.front() == '.') fileNameUtf8.erase(fileNameUtf8.begin());
fileNamePath = filesystem::path_from_utf8(fileNameUtf8);
fileNamePath = path::from_utf8(fileNameUtf8);
auto restorePath = path.parent_path() / fileNamePath;
restorePath.replace_extension("");
@@ -372,12 +368,11 @@ namespace anm2ed
std::ifstream file(path);
if (!file)
{
logger.warning(
std::format("Could not load autosave files from: {}. Skipping...", filesystem::path_to_utf8(path)));
logger.warning(std::format("Could not load autosave files from: {}. Skipping...", path::to_utf8(path)));
return;
}
logger.info(std::format("Loading autosave files from: {}", filesystem::path_to_utf8(path)));
logger.info(std::format("Loading autosave files from: {}", path::to_utf8(path)));
std::string line{};
@@ -385,7 +380,7 @@ namespace anm2ed
{
if (line.empty()) continue;
if (!line.empty() && line.back() == '\r') line.pop_back();
auto entry = filesystem::path_from_utf8(line);
auto entry = path::from_utf8(line);
if (std::find(autosaveFiles.begin(), autosaveFiles.end(), entry) != autosaveFiles.end()) continue;
autosaveFiles.emplace_back(std::move(entry));
}
@@ -398,7 +393,7 @@ namespace anm2ed
autosaveWriteFile.open(autosave_path_get(), std::ofstream::out | std::ofstream::trunc);
for (auto& path : autosaveFiles)
autosaveWriteFile << filesystem::path_to_utf8(path) << "\n";
autosaveWriteFile << path::to_utf8(path) << "\n";
autosaveWriteFile.close();
}
+1
View File
@@ -35,6 +35,7 @@ namespace anm2ed
int recordingStart{};
int recordingEnd{};
bool isRecordingRange{};
bool isAbleToRecord{};
ImGuiKeyChord chords[SHORTCUT_COUNT]{};
+21 -26
View File
@@ -5,26 +5,17 @@
#include <format>
#include <fstream>
#ifdef _WIN32
#include "string_.h"
#define POPEN _popen
#define PCLOSE _pclose
constexpr auto PWRITE_MODE = "wb";
#elif __unix__
#define POPEN popen
#define PCLOSE pclose
constexpr auto PWRITE_MODE = "w";
#endif
#include "log.h"
#include "process_.h"
#include "sdl.h"
#include "string_.h"
using namespace anm2ed::resource;
using namespace anm2ed::util;
using namespace glm;
namespace anm2ed
{
constexpr auto FFMPEG_POPEN_ERROR = "popen() (for FFmpeg) failed!\n{}";
bool animation_render(const std::string& ffmpegPath, const std::string& path, std::vector<Texture>& frames,
AudioStream& audioStream, render::Type type, ivec2 size, int fps)
{
@@ -35,7 +26,7 @@ namespace anm2ed
std::string audioOutputArguments{"-an"};
std::string command{};
auto remove_audio_file = [&]()
auto audio_remove = [&]()
{
if (!audioPath.empty())
{
@@ -76,7 +67,7 @@ namespace anm2ed
else
{
logger.warning("Failed to open temporary audio file; exporting video without audio.");
remove_audio_file();
audio_remove();
}
}
@@ -108,18 +99,22 @@ namespace anm2ed
return false;
}
command += " 2>&1";
#if _WIN32
command = util::string::quote(command);
command = "powershell -Command \"& " + command + "\"";
command += " | Tee-Object -FilePath " + string::quote(Logger::path().string()) + " -Append";
#else
command += " | tee -a " + string::quote(Logger::path().string());
#endif
logger.command(command);
FILE* fp = POPEN(command.c_str(), PWRITE_MODE);
Process process(command.c_str(), "w");
if (!fp)
if (!process.get())
{
remove_audio_file();
logger.error(std::format(FFMPEG_POPEN_ERROR, strerror(errno)));
audio_remove();
return false;
}
@@ -127,16 +122,16 @@ namespace anm2ed
{
auto frameSize = frame.pixel_size_get();
if (fwrite(frame.pixels.data(), 1, frameSize, fp) != frameSize)
if (fwrite(frame.pixels.data(), 1, frameSize, process.get()) != frameSize)
{
remove_audio_file();
PCLOSE(fp);
audio_remove();
return false;
}
}
auto code = PCLOSE(fp);
remove_audio_file();
return (code == 0);
process.close();
audio_remove();
return true;
}
}
+1
View File
@@ -35,6 +35,7 @@ namespace anm2ed::render
namespace anm2ed
{
std::filesystem::path ffmpeg_log_path();
bool animation_render(const std::string&, const std::string&, std::vector<resource::Texture>&, AudioStream&,
render::Type, glm::ivec2, int);
}
+3 -6
View File
@@ -1,14 +1,11 @@
#include "audio.h"
#include <SDL3/SDL_filesystem.h>
#include <SDL3/SDL_properties.h>
#include <SDL3/SDL_stdinc.h>
#include <cstdio>
#include <utility>
#include "filesystem_.h"
#include "file_.h"
namespace filesystem = anm2ed::util::filesystem;
using namespace anm2ed::util;
namespace anm2ed::resource
{
@@ -22,7 +19,7 @@ namespace anm2ed::resource
{
if (path.empty()) return;
filesystem::File file(path, "rb");
File file(path, "rb");
if (!file) return;
if (std::fseek(file.get(), 0, SEEK_END) != 0) return;
+4 -5
View File
@@ -406,14 +406,12 @@ namespace anm2ed
X(TOAST_EXPORT_SPRITESHEET_FAILED, "Could not export spritesheet to: {0}", "No se pudo exportar spritesheet a: {0}", "Не удалось экспортировать спрайт-лист в: {0}", "无法导出图集至: {0}", "{0}에 스프라이트 시트를 내보내기 할 수 없습니다.") \
X(TOAST_ADD_SPRITESHEET_FAILED, "Failed to add spritesheet! Open a document first.", "¡Error a añadir spritesheet! Abre un documento primero.", "Не удалось добавить спрайт-лист! Сначала откройте документ.", "无法添加图集!请先打开文档。", "스프라이트 시트를 추가할 수 없습니다! 먼저 문서를 여세요.") \
X(TOAST_ADD_SOUND_FAILED, "Failed to add sound! Open a document first.", "¡Error al añadir sonido! Abre un documento primero.", "Не удалось добавить звук! Сначала откройте документ.", "无法添加声音!请先打开文档。", "사운드를 추가할 수 없습니다! 먼저 문서를 여세요.") \
X(TOAST_INVALID_FFMPEG_PATH, "FFmpeg path must point to a valid executable file.", "La ruta de FFmpeg debe apuntar a un archivo ejecutable valido.", "Путь к FFmpeg должен указывать к валидному файлу .exe.", "FFmpeg路径必须指向一个正确的可执行文件.", "FFmpeg 경로는 유효한 실행 파일을 지정해야 합니다.") \
X(TOAST_INVALID_FFMPEG, "Unable to run FFmpeg. Make sure the executable exists, has the proper permissions, and is a valid FFmpeg executable.", "No se pudo ejecutar FFmpeg. Asegúrate de que el ejecutable exista, tenga los permisos adecuados y sea un FFmpeg válido.", "Не удалось запустить FFmpeg. Убедитесь, что исполняемый файл существует, имеет нужные права и является корректным исполняемым FFmpeg.", "无法运行 FFmpeg。请确保可执行文件存在,具有正确的权限,并且是有效的 FFmpeg 可执行文件", "FFmpeg을 실행할 수 없습니다. 실행 파일이 존재하고 올바른 권한을 가지며 유효한 FFmpeg 실행 파일인지 확인하세요.") \
X(TOAST_NOT_SUPPORTED, "Operation not supported.", "Operacion no soportada.", "Операция не поддерживается.", "不支持此操作.", "해당 작업은 지원되지 않습니다.") \
X(TOAST_OPEN_DOCUMENT, "Opened document: {0}", "Documento Abierto: {0}", "Открыт документ: {0}", "已打开文件: {0}", "{0} 파일 열기") \
X(TOAST_OPEN_DOCUMENT_FAILED, "Failed to open document: {0} ({1})", "Error al abrir el documento: {0} ({1})", "Не удалось открыть документ: {0} ({1})", "打开文件失败: {0} ({1})", "{0} 파일을 여는데 실패했습니다. {1}") \
X(TOAST_PNG_DIRECTORY_ACCESS_ERROR, "Could not access directory: {0} ({1})", "No se pudo acceder al directorio: {0} ({1})", "Не удалось получить доступ к директории: {0} ({1})", "无法访问目录: {0} ({1})", "{0} 디렉터리에 접근할 수 없습니다. {1}") \
X(TOAST_PNG_DIRECTORY_CREATE_ERROR, "Could not create directory: {0} ({1})", "No se pudo crear el directorio: {0} ({1})", "Не удалось создать директорию: {0} ({1})", "无法创造目录: {0} ({1})", "{0} 디렉터리를 생성할 수 없습니다. {1}") \
X(TOAST_PNG_DIRECTORY_NOT_DIRECTORY, "PNG output path must be a directory: {0}", "La ruta de salida de PNG debe ser un directorio: {0}", "Выходной путь для PNG должен быть директорией: {0}", "PNG输出路径必须是一个目录: {0}", "PNG를 출력할 경로는 디렉터리여야 합니다. {0}") \
X(TOAST_PNG_DIRECTORY_NOT_SET, "PNG output directory must be set.", "El directorio de salida de PNG debe ser ajustado.", "Директория для вывода PNG должна быть установлена.", "必须设置PNG输出目录.", "PNG를 출력할 경로를 설정해야 합니다.") \
X(TOAST_PNG_DIRECTORY_INVALID, "Unable to create PNG directory. Make sure to check permissions.", "No se puede crear el directorio PNG. Verifica los permisos.", "Не удалось создать каталог PNG. Проверьте права доступа.", "无法创建 PNG 目录。请检查权限。", "PNG 디렉터리를 만들 수 없습니다. 권한을 확인하세요.") \
X(TOAST_PNG_FORMAT_INVALID, "PNG format invalid. Make sure it contains \"{}\".", "Formato PNG inválido. Asegúrate de que contenga \"{}\".", "Недопустимый формат PNG. Убедитесь, что он содержит \"{}\".", "PNG 格式无效。请确保其中包含 \"{}\"", "PNG 형식이 잘못되었습니다. \"{}\"을 포함하는지 확인하세요.") \
X(TOAST_REDO, "Redo: {0}", "Rehacer: {0}", "Повтор: {0}", "重做: {0}", "다시 실행: {0}") \
X(ERROR_FILE_NOT_FOUND, "File not found!", "¡Archivo no encontrado!", "Файл не найден!", "找不到文件!", "파일을 찾을 수 없습니다!") \
X(TOAST_RELOAD_SPRITESHEET, "Reloaded spritesheet #{0}: {1}", "Se ha recargado spritesheet #{0}: {1}", "Спрайт-лист #{0} перезагружен: {1}", "重新加载了图集 #{0}: {1}", "{0}번 스프라이트 시트 다시 불러옴: {1}") \
@@ -429,6 +427,7 @@ namespace anm2ed
X(TOAST_SOUNDS_PASTE, "Paste Sound(s)", "Pegar Sonido(s)", "Вставить звуки", "粘贴声音", "사운드 붙여넣기") \
X(TOAST_SOUND_INITIALIZED, "Initialized sound #{0}: {1}", "Sonido Inizializado #{0}: {1}", "Звук #{0} инициализирован: {1}", "已初始化声音 #{0}: {1}", "{0}번 사운드 초기화됨: {1}") \
X(TOAST_SOUND_INITIALIZE_FAILED, "Failed to initialize sound: {0}", "Error al Inizializar sonido: {0}", "Не удалось инициализировать звуки: {0}", "初始化声音失败: {0}", "사운드 초기화 실패: {0}") \
X(TOAST_RENDER_PATH_EMPTY, "Render path is empty!", "¡La ruta de render está vacía!", "Путь вывода пуст!", "渲染路径为空!", "렌더 경로가 비어 있습니다!") \
X(TOAST_SPRITESHEET_EMPTY, "Spritesheet export failed: captured frames are empty.", "Error al exportar spritesheet: Frames capturados estan vacios.", "Не удалось экспортировать спрайт-лист: захваченные кадры пусты.", "导出图集失败: 未捕获任何帧.", "스프라이트 내보내기 실패: 캡처된 프레임이 비어있습니다.") \
X(TOAST_SPRITESHEET_INITIALIZED, "Initialized spritesheet #{0}: {1}", "Spritesheet Inizializada #{0}: {1}", "Спрайт-лист #{0} инициализирован: {1}", "已初始化图集 #{0}: {1}", "{0}번 스프라이트 시트 초기화됨: {1}") \
X(TOAST_SPRITESHEET_INIT_FAILED, "Failed to initialize spritesheet: {0}", "Error al Inizializar spritesheet: {0}", "Не удалось инициализировать спрайт-лист: {0}", "初始化图集失败: {0}", "스프라이트 시트 초기화 실패: {0}") \
+5 -5
View File
@@ -22,13 +22,13 @@
#pragma GCC diagnostic pop
#endif
#include "filesystem_.h"
#include "file_.h"
#include "math_.h"
using namespace anm2ed::resource::texture;
using namespace anm2ed::util::math;
using namespace anm2ed::util;
using namespace glm;
namespace filesystem = anm2ed::util::filesystem;
namespace anm2ed::resource
{
@@ -119,10 +119,10 @@ namespace anm2ed::resource
Texture::Texture(const std::filesystem::path& pngPath)
{
filesystem::File file(pngPath, "rb");
File file(pngPath, "rb");
if (auto handle = file.get())
{
if (const uint8_t* data = stbi_load_from_file(handle, &size.x, &size.y, nullptr, CHANNELS); data)
if (auto data = stbi_load_from_file(handle, &size.x, &size.y, nullptr, CHANNELS); data)
{
upload(data);
stbi_image_free((void*)data);
@@ -134,7 +134,7 @@ namespace anm2ed::resource
{
if (pixels.empty()) return false;
filesystem::File file(path, "wb");
File file(path, "wb");
if (auto handle = file.get())
{
auto write_func = [](void* context, void* data, int size) { fwrite(data, 1, size, static_cast<FILE*>(context)); };
+4 -5
View File
@@ -3,12 +3,11 @@
#include <fstream>
#include <sstream>
#include "filesystem_.h"
#include "log.h"
#include "path_.h"
using namespace anm2ed::util;
using namespace glm;
namespace filesystem = anm2ed::util::filesystem;
namespace anm2ed
{
@@ -123,8 +122,8 @@ DockSpace ID=0x123F8F08 Window=0x6D581B32 Pos=8,62 Size=1902,994 Spl
Settings::Settings(const std::filesystem::path& path)
{
auto pathUtf8 = filesystem::path_to_utf8(path);
if (filesystem::path_is_exist(path))
auto pathUtf8 = path::to_utf8(path);
if (path::is_exist(path))
logger.info(std::format("Using settings from: {}", pathUtf8));
else
{
@@ -271,7 +270,7 @@ DockSpace ID=0x123F8F08 Window=0x6D581B32 Pos=8,62 Size=1902,994 Spl
std::string Settings::imgui_data_load(const std::filesystem::path& path)
{
auto pathUtf8 = filesystem::path_to_utf8(path);
auto pathUtf8 = path::to_utf8(path);
std::ifstream file(path, std::ios::in | std::ios::binary);
if (!file.is_open())
{
+12 -12
View File
@@ -29,6 +29,7 @@ namespace anm2ed
X(BOOL, bool) \
X(FLOAT, float) \
X(STRING, std::string) \
X(PATH, std::filesystem::path) \
X(IVEC2, glm::ivec2) \
X(IVEC2_WH, glm::ivec2) \
X(VEC2, glm::vec2) \
@@ -69,25 +70,26 @@ namespace anm2ed
X(PLAYBACK_IS_LOOP, playbackIsLoop, STRING_UNDEFINED, BOOL, true) \
X(PLAYBACK_IS_CLAMP, playbackIsClamp, STRING_UNDEFINED, BOOL, true) \
\
X(CHANGE_IS_CROP, changeIsCrop, STRING_UNDEFINED, BOOL, false) \
X(CHANGE_IS_CROP_X, changeIsCropX, STRING_UNDEFINED, BOOL, false) \
X(CHANGE_IS_CROP_Y, changeIsCropY, STRING_UNDEFINED, BOOL, false) \
X(CHANGE_IS_SIZE, changeIsSize, STRING_UNDEFINED, BOOL, false) \
X(CHANGE_IS_SIZE_X, changeIsSizeX, STRING_UNDEFINED, BOOL, false) \
X(CHANGE_IS_SIZE_Y, changeIsSizeY, STRING_UNDEFINED, BOOL, false) \
X(CHANGE_IS_POSITION, changeIsPosition, STRING_UNDEFINED, BOOL, false) \
X(CHANGE_IS_POSITION_X, changeIsPositionX, STRING_UNDEFINED, BOOL, false) \
X(CHANGE_IS_POSITION_Y, changeIsPositionY, STRING_UNDEFINED, BOOL, false) \
X(CHANGE_IS_PIVOT, changeIsPivot, STRING_UNDEFINED, BOOL, false) \
X(CHANGE_IS_PIVOT_X, changeIsPivotX, STRING_UNDEFINED, BOOL, false) \
X(CHANGE_IS_PIVOT_Y, changeIsPivotY, STRING_UNDEFINED, BOOL, false) \
X(CHANGE_IS_SCALE, changeIsScale, STRING_UNDEFINED, BOOL, false) \
X(CHANGE_IS_SCALE_X, changeIsScaleX, STRING_UNDEFINED, BOOL, false) \
X(CHANGE_IS_SCALE_Y, changeIsScaleY, STRING_UNDEFINED, BOOL, false) \
X(CHANGE_IS_ROTATION, changeIsRotation, STRING_UNDEFINED, BOOL, false) \
X(CHANGE_IS_DURATION, changeIsDuration, STRING_UNDEFINED, BOOL, false) \
X(CHANGE_IS_TINT, changeIsTint, STRING_UNDEFINED, BOOL, false) \
X(CHANGE_IS_COLOR_OFFSET, changeIsColorOffset, STRING_UNDEFINED, BOOL, false) \
X(CHANGE_IS_TINT_R, changeIsTintR, STRING_UNDEFINED, BOOL, false) \
X(CHANGE_IS_TINT_G, changeIsTintG, STRING_UNDEFINED, BOOL, false) \
X(CHANGE_IS_TINT_B, changeIsTintB, STRING_UNDEFINED, BOOL, false) \
X(CHANGE_IS_TINT_A, changeIsTintA, STRING_UNDEFINED, BOOL, false) \
X(CHANGE_IS_COLOR_OFFSET_R, changeIsColorOffsetR, STRING_UNDEFINED, BOOL, false) \
X(CHANGE_IS_COLOR_OFFSET_G, changeIsColorOffsetG, STRING_UNDEFINED, BOOL, false) \
X(CHANGE_IS_COLOR_OFFSET_B, changeIsColorOffsetB, STRING_UNDEFINED, BOOL, false) \
X(CHANGE_IS_COLOR_OFFSET_A, changeIsColorOffsetA, STRING_UNDEFINED, BOOL, false) \
X(CHANGE_IS_VISIBLE_SET, changeIsVisibleSet, STRING_UNDEFINED, BOOL, false) \
X(CHANGE_IS_INTERPOLATED_SET, changeIsInterpolatedSet, STRING_UNDEFINED, BOOL, false) \
X(CHANGE_CROP, changeCrop, STRING_UNDEFINED, VEC2, {}) \
@@ -101,8 +103,6 @@ namespace anm2ed
X(CHANGE_COLOR_OFFSET, changeColorOffset, STRING_UNDEFINED, VEC3, {}) \
X(CHANGE_IS_VISIBLE, changeIsVisible, STRING_UNDEFINED, BOOL, false) \
X(CHANGE_IS_INTERPOLATED, changeIsInterpolated, STRING_UNDEFINED, BOOL, false) \
X(CHANGE_NUMBER_FRAMES, changeNumberFrames, STRING_UNDEFINED, INT, 1) \
X(CHANGE_IS_FROM_SELECTED_FRAME, changeIsFromSelectedFrame, STRING_UNDEFINED, BOOL, false) \
\
X(SCALE_VALUE, scaleValue, STRING_UNDEFINED, FLOAT, 1.0f) \
\
@@ -167,13 +167,13 @@ namespace anm2ed
X(TOOL_COLOR, toolColor, STRING_UNDEFINED, VEC4, {1.0, 1.0, 1.0, 1.0}) \
\
X(RENDER_TYPE, renderType, STRING_UNDEFINED, INT, render::GIF) \
X(RENDER_PATH, renderPath, STRING_UNDEFINED, STRING, OUTPUT_PATH_DEFAULT) \
X(RENDER_PATH, renderPath, STRING_UNDEFINED, PATH, OUTPUT_PATH_DEFAULT) \
X(RENDER_ROWS, renderRows, STRING_UNDEFINED, INT, 0) \
X(RENDER_COLUMNS, renderColumns, STRING_UNDEFINED, INT, 0) \
X(RENDER_FORMAT, renderFormat, STRING_UNDEFINED, STRING, "{}.png") \
X(RENDER_FORMAT, renderFormat, STRING_UNDEFINED, PATH, "{}.png") \
X(RENDER_IS_RAW_ANIMATION, renderIsRawAnimation, STRING_UNDEFINED, BOOL, true) \
X(RENDER_SCALE, renderScale, STRING_UNDEFINED, FLOAT, 1.0f) \
X(RENDER_FFMPEG_PATH, renderFFmpegPath, STRING_UNDEFINED, STRING, FFMPEG_PATH_DEFAULT)
X(RENDER_FFMPEG_PATH, renderFFmpegPath, STRING_UNDEFINED, PATH, FFMPEG_PATH_DEFAULT)
#define SETTINGS_SHORTCUTS \
/* Symbol / Name / String / Type / Default */ \
+8 -9
View File
@@ -5,8 +5,8 @@
#include <imgui/backends/imgui_impl_opengl3.h>
#include <imgui/backends/imgui_impl_sdl3.h>
#include "filesystem_.h"
#include "log.h"
#include "path_.h"
#include "strings.h"
#include "toast.h"
@@ -26,7 +26,7 @@ namespace anm2ed
dialog = Dialog(window);
for (const auto& argument : arguments)
manager.open(filesystem::path_from_utf8(argument));
manager.open(path::from_utf8(argument));
manager.chords_set(settings);
}
@@ -46,8 +46,8 @@ namespace anm2ed
{
std::string droppedFile = event.drop.data ? event.drop.data : "";
if (droppedFile.empty()) break;
auto droppedPath = filesystem::path_from_utf8(droppedFile);
if (filesystem::path_is_extension(droppedPath, "anm2"))
auto droppedPath = path::from_utf8(droppedFile);
if (path::is_extension(droppedPath, "anm2"))
{
if (manager.documents.empty())
manager.open(droppedPath);
@@ -60,7 +60,7 @@ namespace anm2ed
}
SDL_FlashWindow(window, SDL_FLASH_UNTIL_FOCUSED);
}
else if (filesystem::path_is_extension(droppedPath, "png"))
else if (path::is_extension(droppedPath, "png"))
{
if (auto document = manager.get())
document->spritesheet_add(droppedPath);
@@ -70,8 +70,7 @@ namespace anm2ed
logger.warning(localize.get(TOAST_ADD_SPRITESHEET_FAILED, anm2ed::ENGLISH));
}
}
else if (filesystem::path_is_extension(droppedPath, "wav") ||
filesystem::path_is_extension(droppedPath, "ogg"))
else if (path::is_extension(droppedPath, "wav") || path::is_extension(droppedPath, "ogg"))
{
if (auto document = manager.get())
document->sound_add(droppedPath);
@@ -87,8 +86,8 @@ namespace anm2ed
{
std::string droppedFile = event.drop.data ? event.drop.data : "";
if (droppedFile.empty()) break;
auto droppedPath = filesystem::path_from_utf8(droppedFile);
if (filesystem::path_is_extension(droppedPath, "anm2"))
auto droppedPath = path::from_utf8(droppedFile);
if (path::is_extension(droppedPath, "anm2"))
{
manager.open(droppedPath);
SDL_FlashWindow(window, SDL_FLASH_UNTIL_FOCUSED);
+41
View File
@@ -0,0 +1,41 @@
#include "file_.h"
namespace anm2ed::util
{
File::File(const std::filesystem::path& path, const char* mode) { open(path, mode); }
File::~File() { close(); }
bool File::open(const std::filesystem::path& path, const char* mode)
{
close();
#ifdef _WIN32
std::wstring wideMode{};
if (mode)
wideMode.assign(mode, mode + std::strlen(mode));
else
wideMode = L"rb";
handle = _wfopen(path.native().c_str(), wideMode.c_str());
#else
handle = std::fopen(path.string().c_str(), mode);
#endif
return handle != nullptr;
}
int File::close()
{
if (handle)
{
auto result = std::fclose(handle);
handle = nullptr;
return result;
}
return -1;
}
FILE* File::get() const { return handle; }
File::operator bool() const { return handle != nullptr; }
}
+22
View File
@@ -0,0 +1,22 @@
#pragma once
#include <filesystem>
namespace anm2ed::util
{
class File
{
public:
File() = default;
File(const std::filesystem::path&, const char* mode);
~File();
bool open(const std::filesystem::path&, const char* mode);
int close();
FILE* get() const;
explicit operator bool() const;
private:
FILE* handle{};
};
}
-140
View File
@@ -1,140 +0,0 @@
#include "filesystem_.h"
#include <SDL3/SDL_filesystem.h>
#include <SDL3/SDL_stdinc.h>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <cwctype>
#include <filesystem>
#include <type_traits>
namespace anm2ed::util::filesystem
{
namespace
{
template <typename CharT> CharT to_lower_char(CharT character)
{
if constexpr (std::is_same_v<CharT, wchar_t>)
return static_cast<CharT>(std::towlower(static_cast<wint_t>(character)));
else
return static_cast<CharT>(std::tolower(static_cast<unsigned char>(character)));
}
}
std::filesystem::path path_preferences_get()
{
auto sdlPath = SDL_GetPrefPath(nullptr, "anm2ed");
if (!sdlPath) return {};
auto filePath = path_from_utf8(sdlPath);
SDL_free(sdlPath);
return filePath;
}
std::filesystem::path path_to_lower(const std::filesystem::path& path)
{
auto native = path.native();
for (auto& character : native)
character = to_lower_char(character);
return std::filesystem::path(native);
}
std::filesystem::path path_backslash_replace(const std::filesystem::path& path)
{
auto native = path.native();
constexpr auto backslash = static_cast<std::filesystem::path::value_type>('\\');
constexpr auto slash = static_cast<std::filesystem::path::value_type>('/');
for (auto& character : native)
if (character == backslash) character = slash;
return std::filesystem::path(native);
}
std::string path_to_utf8(const std::filesystem::path& path)
{
auto u8 = path.u8string();
return std::string(u8.begin(), u8.end());
}
std::filesystem::path path_from_utf8(const std::string& utf8)
{
return std::filesystem::path(std::u8string(utf8.begin(), utf8.end()));
}
FILE* open(const std::filesystem::path& path, const char* mode)
{
#ifdef _WIN32
std::wstring wideMode{};
if (mode)
wideMode.assign(mode, mode + std::strlen(mode));
else
wideMode = L"rb";
return _wfopen(path.native().c_str(), wideMode.c_str());
#else
return std::fopen(path.string().c_str(), mode);
#endif
}
bool path_is_exist(const std::filesystem::path& path)
{
std::error_code errorCode;
return std::filesystem::exists(path, errorCode) && ((void)std::filesystem::status(path, errorCode), !errorCode);
}
bool path_is_extension(const std::filesystem::path& path, const std::string& extension)
{
auto extensionUtf8 = path_to_utf8(std::filesystem::path(path).extension());
std::transform(extensionUtf8.begin(), extensionUtf8.end(), extensionUtf8.begin(), ::tolower);
return extensionUtf8 == ("." + extension);
}
std::filesystem::path path_lower_case_backslash_handle(const std::filesystem::path& path)
{
auto newPath = path;
if (path_is_exist(newPath)) return newPath;
newPath = path_backslash_replace(newPath);
if (path_is_exist(newPath)) return newPath;
newPath = path_to_lower(newPath);
return newPath;
}
WorkingDirectory::WorkingDirectory(const std::filesystem::path& path, bool isFile)
{
previous = std::filesystem::current_path();
if (isFile)
{
std::filesystem::path filePath = path;
std::filesystem::path parentPath = filePath.parent_path();
std::filesystem::current_path(parentPath);
}
else
std::filesystem::current_path(path);
}
WorkingDirectory::~WorkingDirectory() { std::filesystem::current_path(previous); }
File::File(const std::filesystem::path& path, const char* mode) { open(path, mode); }
File::~File() { close(); }
bool File::open(const std::filesystem::path& path, const char* mode)
{
close();
handle = filesystem::open(path, mode);
return handle != nullptr;
}
void File::close()
{
if (handle)
{
std::fclose(handle);
handle = nullptr;
}
}
FILE* File::get() const { return handle; }
File::operator bool() const { return handle != nullptr; }
}
-47
View File
@@ -1,47 +0,0 @@
#pragma once
#include <cstdio>
#include <filesystem>
#include <string>
namespace anm2ed::util::filesystem
{
std::filesystem::path path_preferences_get();
std::filesystem::path path_to_lower(const std::filesystem::path&);
std::filesystem::path path_backslash_replace(const std::filesystem::path&);
std::string path_to_utf8(const std::filesystem::path&);
std::filesystem::path path_from_utf8(const std::string&);
bool path_is_exist(const std::filesystem::path&);
bool path_is_extension(const std::filesystem::path&, const std::string&);
std::filesystem::path path_lower_case_backslash_handle(const std::filesystem::path&);
FILE* open(const std::filesystem::path&, const char*);
class File
{
public:
File() = default;
File(const std::filesystem::path&, const char* mode);
~File();
bool open(const std::filesystem::path&, const char* mode);
void close();
FILE* get() const;
explicit operator bool() const;
private:
FILE* handle{};
};
class WorkingDirectory
{
public:
std::filesystem::path previous;
WorkingDirectory(const std::filesystem::path&, bool = false);
~WorkingDirectory();
};
}
+120
View File
@@ -0,0 +1,120 @@
#include "path_.h"
#include <SDL3/SDL_filesystem.h>
#include <SDL3/SDL_stdinc.h>
#include <cctype>
#include <cwctype>
#include <filesystem>
#include <type_traits>
namespace anm2ed::util::path
{
namespace
{
template <typename CharT> CharT to_lower_char(CharT character)
{
if constexpr (std::is_same_v<CharT, wchar_t>)
return static_cast<CharT>(std::towlower(static_cast<wint_t>(character)));
else
return static_cast<CharT>(std::tolower(static_cast<unsigned char>(character)));
}
}
std::filesystem::path to_lower(const std::filesystem::path& path)
{
auto native = path.native();
for (auto& character : native)
character = to_lower_char(character);
return std::filesystem::path(native);
}
std::filesystem::path backslash_replace(const std::filesystem::path& path)
{
auto native = path.native();
constexpr auto backslash = static_cast<std::filesystem::path::value_type>('\\');
constexpr auto slash = static_cast<std::filesystem::path::value_type>('/');
for (auto& character : native)
if (character == backslash) character = slash;
return std::filesystem::path(native);
}
std::string to_utf8(const std::filesystem::path& path)
{
auto u8 = path.u8string();
return std::string(u8.begin(), u8.end());
}
std::filesystem::path from_utf8(const std::string& utf8)
{
return std::filesystem::path(std::u8string(utf8.begin(), utf8.end()));
}
bool is_exist(const std::filesystem::path& path)
{
if (path.empty()) return false;
std::error_code errorCode;
return std::filesystem::exists(path, errorCode) && ((void)std::filesystem::status(path, errorCode), !errorCode);
}
bool is_extension(const std::filesystem::path& path, const std::string& extension)
{
if (path.empty()) return false;
auto extensionUtf8 = to_utf8(std::filesystem::path(path).extension());
std::transform(extensionUtf8.begin(), extensionUtf8.end(), extensionUtf8.begin(), ::tolower);
return extensionUtf8 == ("." + extension);
}
bool is_executable(const std::filesystem::path& path)
{
if (path.empty()) return false;
std::error_code ec;
auto status = std::filesystem::status(path, ec);
if (ec) return false;
if (!std::filesystem::exists(status)) return false;
if (!std::filesystem::is_regular_file(status)) return false;
#ifdef WIN32
auto extension = to_lower(path.extension());
if (extension != ".exe" && extension != ".bat" && extension != ".cmd") return false;
#else
auto permissions = status.permissions();
auto executableMask =
std::filesystem::perms::owner_exec | std::filesystem::perms::group_exec | std::filesystem::perms::others_exec;
if ((permissions & executableMask) == std::filesystem::perms::none) return false;
#endif
return true;
}
bool ensure_directory(const std::filesystem::path& path)
{
if (path.empty()) return false;
std::error_code ec;
if (std::filesystem::create_directories(path, ec)) return true;
return is_exist(path.parent_path());
}
std::filesystem::path make_relative(const std::filesystem::path& path)
{
if (path.empty()) return path;
std::error_code ec{};
auto relative = std::filesystem::relative(path, ec);
if (!ec) return relative;
return path;
}
std::filesystem::path lower_case_backslash_handle(const std::filesystem::path& path)
{
auto newPath = path;
if (is_exist(newPath)) return newPath;
newPath = backslash_replace(newPath);
if (is_exist(newPath)) return newPath;
newPath = to_lower(newPath);
return newPath;
}
}
+21
View File
@@ -0,0 +1,21 @@
#pragma once
#include <filesystem>
#include <string>
namespace anm2ed::util::path
{
std::filesystem::path to_lower(const std::filesystem::path&);
std::filesystem::path backslash_replace(const std::filesystem::path&);
std::string to_utf8(const std::filesystem::path&);
std::filesystem::path from_utf8(const std::string&);
std::filesystem::path make_relative(const std::filesystem::path&);
bool is_exist(const std::filesystem::path&);
bool is_executable(const std::filesystem::path&);
bool is_extension(const std::filesystem::path&, const std::string&);
bool ensure_directory(const std::filesystem::path&);
std::filesystem::path lower_case_backslash_handle(const std::filesystem::path&);
}
+55
View File
@@ -0,0 +1,55 @@
#include "process_.h"
namespace anm2ed::util
{
Process::Process(const char* command, const char* mode) { open(command, mode); }
Process::~Process() { close(); }
bool Process::open(const char* command, const char* mode)
{
close();
#ifdef WIN32
pipe = _popen(command, mode);
#else
pipe = popen(command, mode);
#endif
return pipe != nullptr;
}
int Process::close()
{
if (pipe)
{
#ifdef WIN32
auto result = _pclose(pipe);
#else
auto result = pclose(pipe);
#endif
pipe = nullptr;
return result;
}
return -1;
}
std::string Process::output_get_and_close()
{
std::string output{};
if (!pipe) return {};
char buffer[256];
while (fgets(buffer, sizeof(buffer), pipe))
output += buffer;
close();
return output;
}
FILE* Process::get() const { return pipe; }
Process::operator bool() const { return pipe != nullptr; }
}
+24
View File
@@ -0,0 +1,24 @@
#pragma once
#include <string>
namespace anm2ed::util
{
class Process
{
public:
Process() = default;
Process(const char*, const char*);
~Process();
bool open(const char*, const char*);
int close();
FILE* get() const;
explicit operator bool() const;
std::string output_get_and_close();
private:
FILE* pipe{};
};
}
+17
View File
@@ -0,0 +1,17 @@
#include "sdl.h"
#include <SDL3/SDL.h>
#include "path_.h"
namespace anm2ed::util::sdl
{
std::filesystem::path preferences_directory_get()
{
auto sdlPath = SDL_GetPrefPath(nullptr, "anm2ed");
if (!sdlPath) return {};
auto filePath = path::from_utf8(sdlPath);
SDL_free(sdlPath);
return filePath;
}
}
+8
View File
@@ -0,0 +1,8 @@
#pragma once
#include <filesystem>
namespace anm2ed::util::sdl
{
std::filesystem::path preferences_directory_get();
}
+19
View File
@@ -0,0 +1,19 @@
#include "working_directory.h"
namespace anm2ed::util
{
WorkingDirectory::WorkingDirectory(const std::filesystem::path& path, Type type)
{
previous = std::filesystem::current_path();
if (type == FILE)
{
std::filesystem::path filePath = path;
std::filesystem::path parentPath = filePath.parent_path();
std::filesystem::current_path(parentPath);
}
else
std::filesystem::current_path(path);
}
WorkingDirectory::~WorkingDirectory() { std::filesystem::current_path(previous); }
}
+21
View File
@@ -0,0 +1,21 @@
#pragma once
#include <filesystem>
namespace anm2ed::util
{
class WorkingDirectory
{
public:
enum Type
{
FILE,
DIRECTORY
};
std::filesystem::path previous{};
WorkingDirectory(const std::filesystem::path&, Type type = DIRECTORY);
~WorkingDirectory();
};
}
+3 -3
View File
@@ -1,10 +1,10 @@
#include "xml_.h"
#include "filesystem_.h"
#include "math_.h"
#include "path_.h"
using namespace anm2ed::util;
using namespace tinyxml2;
namespace filesystem = anm2ed::util::filesystem;
namespace anm2ed::util::xml
{
@@ -27,7 +27,7 @@ namespace anm2ed::util::xml
{
std::string temp{};
auto result = query_string_attribute(element, attribute, &temp);
if (result == XML_SUCCESS) *out = filesystem::path_from_utf8(temp);
if (result == XML_SUCCESS) *out = path::from_utf8(temp);
return result;
}