The Omega Update(TM) Part 5 (Finishing)

This commit is contained in:
2025-08-14 21:39:17 -04:00
parent ea3498692a
commit 4029828d04
24 changed files with 966 additions and 319 deletions

View File

@@ -10,9 +10,10 @@ A reimplementation of *The Binding of Isaac: Rebirth*'s proprietary animation ed
- New features - New features
- Can output .webm or *.png sequence - Can output .webm or *.png sequence
- Cutting, copying and pasting - Cutting, copying and pasting
- Additional wizard options
- Robust snapshot (undo/redo) system - Robust snapshot (undo/redo) system
- Additional hotkeys/shortcuts - Additional hotkeys/shortcuts
- Settings that will preserve on exit - Settings that will preserve on exit (stored in %APPDATA% on Windows or ~/.local/share on Linux)
## Dependencies ## Dependencies
Download these from your package manager: Download these from your package manager:

View File

@@ -23,7 +23,9 @@
#include <ranges> #include <ranges>
#include <string> #include <string>
#include <unordered_set> #include <unordered_set>
#include <vector> #include <vector>
typedef uint8_t u8; typedef uint8_t u8;
typedef uint16_t u16; typedef uint16_t u16;
@@ -123,6 +125,65 @@ static inline bool string_to_bool(const std::string& string)
return lower == "true"; return lower == "true";
} }
static inline std::string path_canonical_resolve
(
const std::string& inputPath,
const std::string& basePath = std::filesystem::current_path().string()
)
{
auto strings_equal_ignore_case = [](std::string a, std::string b) {
auto to_lower = [](unsigned char c) { return static_cast<char>(std::tolower(c)); };
std::transform(a.begin(), a.end(), a.begin(), to_lower);
std::transform(b.begin(), b.end(), b.begin(), to_lower);
return a == b;
};
std::string sanitized = inputPath;
std::replace(sanitized.begin(), sanitized.end(), '\\', '/');
std::filesystem::path normalizedPath = sanitized;
std::filesystem::path absolutePath = normalizedPath.is_absolute()
? normalizedPath
: (std::filesystem::path(basePath) / normalizedPath);
std::error_code error;
if (std::filesystem::exists(absolutePath, error)) {
std::error_code canonicalError;
std::filesystem::path canonicalPath = std::filesystem::weakly_canonical(absolutePath, canonicalError);
return (canonicalError ? absolutePath : canonicalPath).generic_string();
}
std::filesystem::path resolvedPath = absolutePath.root_path();
std::filesystem::path remainingPath = absolutePath.relative_path();
for (const std::filesystem::path& segment : remainingPath) {
std::filesystem::path candidatePath = resolvedPath / segment;
if (std::filesystem::exists(candidatePath, error)) {
resolvedPath = candidatePath;
continue;
}
bool matched = false;
if (std::filesystem::exists(resolvedPath, error) && std::filesystem::is_directory(resolvedPath, error)) {
for (const auto& directoryEntry : std::filesystem::directory_iterator(resolvedPath, error)) {
if (strings_equal_ignore_case(directoryEntry.path().filename().string(), segment.string())) {
resolvedPath = directoryEntry.path();
matched = true;
break;
}
}
}
if (!matched) return sanitized;
}
if (!std::filesystem::exists(resolvedPath, error))
return sanitized;
std::error_code canonicalError;
std::filesystem::path canonicalPath = std::filesystem::weakly_canonical(resolvedPath, canonicalError);
return (canonicalError ? resolvedPath : canonicalPath).generic_string();
};
static inline std::string working_directory_from_file_set(const std::string& path) static inline std::string working_directory_from_file_set(const std::string& path)
{ {
std::filesystem::path filePath = path; std::filesystem::path filePath = path;
@@ -138,13 +199,20 @@ static inline std::string path_extension_change(const std::string& path, const s
return filePath.string(); return filePath.string();
} }
static inline bool path_is_extension(const std::string& path, const std::string& extension)
{
auto e = std::filesystem::path(path).extension().string();
std::transform(e.begin(), e.end(), e.begin(), ::tolower);
return e == ("." + extension);
}
static inline bool path_exists(const std::filesystem::path& pathCheck) static inline bool path_exists(const std::filesystem::path& pathCheck)
{ {
std::error_code errorCode; std::error_code errorCode;
return std::filesystem::exists(pathCheck, errorCode) && ((void)std::filesystem::status(pathCheck, errorCode), !errorCode); return std::filesystem::exists(pathCheck, errorCode) && ((void)std::filesystem::status(pathCheck, errorCode), !errorCode);
} }
static inline bool path_valid(const std::filesystem::path& pathCheck) static inline bool path_is_valid(const std::filesystem::path& pathCheck)
{ {
namespace fs = std::filesystem; namespace fs = std::filesystem;
std::error_code ec; std::error_code ec;

View File

@@ -217,6 +217,7 @@ enum ShaderType
{ {
SHADER_LINE, SHADER_LINE,
SHADER_TEXTURE, SHADER_TEXTURE,
SHADER_GRID,
SHADER_COUNT SHADER_COUNT
}; };
@@ -259,14 +260,62 @@ void main()
} }
)"; )";
const std::string SHADER_GRID_VERTEX = R"(
#version 330 core
layout ( location = 0 ) in vec2 i_position;
out vec2 clip;
void main() {
clip = i_position;
gl_Position = vec4(i_position, 0.0, 1.0);
}
)";
const std::string SHADER_GRID_FRAGMENT = R"(
#version 330 core
in vec2 clip;
uniform mat4 u_model; // inverse of your world->clip matrix (MVP)
uniform vec2 u_size; // world-space cell size (e.g. 64,64)
uniform vec2 u_offset; // world-space grid offset (shifts entire grid)
uniform vec4 u_color; // RGBA
out vec4 o_fragColor;
void main()
{
// clip -> world on z=0 plane
vec4 w = u_model * vec4(clip, 0.0, 1.0);
w /= w.w;
vec2 world = w.xy;
// grid space
vec2 g = (world - u_offset) / u_size;
vec2 d = abs(fract(g) - 0.5);
float distance = min(d.x, d.y);
float fw = min(fwidth(g.x), fwidth(g.y));
float alpha = 1.0 - smoothstep(0.0, fw, distance);
if (alpha <= 0.0) discard;
o_fragColor = vec4(u_color.rgb, u_color.a * alpha);
}
)";
#define SHADER_UNIFORM_COLOR "u_color" #define SHADER_UNIFORM_COLOR "u_color"
#define SHADER_UNIFORM_TRANSFORM "u_transform" #define SHADER_UNIFORM_TRANSFORM "u_transform"
#define SHADER_UNIFORM_TINT "u_tint" #define SHADER_UNIFORM_TINT "u_tint"
#define SHADER_UNIFORM_COLOR_OFFSET "u_color_offset" #define SHADER_UNIFORM_COLOR_OFFSET "u_color_offset"
#define SHADER_UNIFORM_OFFSET "u_offset"
#define SHADER_UNIFORM_SIZE "u_size"
#define SHADER_UNIFORM_MODEL "u_model"
#define SHADER_UNIFORM_TEXTURE "u_texture" #define SHADER_UNIFORM_TEXTURE "u_texture"
const ShaderData SHADER_DATA[SHADER_COUNT] = const ShaderData SHADER_DATA[SHADER_COUNT] =
{ {
{SHADER_VERTEX, SHADER_FRAGMENT}, {SHADER_VERTEX, SHADER_FRAGMENT},
{SHADER_VERTEX, SHADER_TEXTURE_FRAGMENT} {SHADER_VERTEX, SHADER_TEXTURE_FRAGMENT},
{SHADER_GRID_VERTEX, SHADER_GRID_FRAGMENT}
}; };

View File

@@ -588,8 +588,8 @@ bool anm2_deserialize(Anm2* self, Resources* resources, const std::string& path)
xmlAttribute = xmlAttribute->Next(); xmlAttribute = xmlAttribute->Next();
} }
if (anm2Element == ANM2_ELEMENT_SPRITESHEET) if (anm2Element == ANM2_ELEMENT_SPRITESHEET && resources)
resources_texture_init(resources, spritesheet->path , id); resources_texture_init(resources, spritesheet->path, id);
xmlChild = xmlElement->FirstChildElement(); xmlChild = xmlElement->FirstChildElement();
@@ -630,14 +630,6 @@ bool anm2_deserialize(Anm2* self, Resources* resources, const std::string& path)
void anm2_layer_add(Anm2* self) void anm2_layer_add(Anm2* self)
{ {
s32 id = map_next_id_get(self->layers); s32 id = map_next_id_get(self->layers);
self->layers[id] = Anm2Layer{}; self->layers[id] = Anm2Layer{};
@@ -1126,4 +1118,53 @@ void anm2_frame_bake(Anm2* self, Anm2Reference* reference, s32 interval, bool is
delay += baked.delay; delay += baked.delay;
} }
}
void anm2_scale(Anm2* self, f32 scale)
{
auto frame_scale = [&](Anm2Frame& frame)
{
frame.position = vec2((s32)(frame.position.x * scale), (s32)(frame.position.y * scale));
frame.size = vec2((s32)(frame.size.x * scale), (s32)(frame.size.y * scale));
frame.crop = vec2((s32)(frame.crop.x * scale), (s32)(frame.crop.y * scale));
frame.pivot = vec2((s32)(frame.pivot.x * scale), (s32)(frame.pivot.y * scale));
};
for (auto& [_, animation] : self->animations)
{
for (auto& frame : animation.rootAnimation.frames)
frame_scale(frame);
for (auto& [_, layerAnimation] : animation.layerAnimations)
for (auto& frame : layerAnimation.frames)
frame_scale(frame);
for (auto& [_, nullAnimation] : animation.nullAnimations)
for (auto& frame : nullAnimation.frames)
frame_scale(frame);
}
}
void anm2_generate_from_grid(Anm2* self, Anm2Reference* reference, vec2 startPosition, vec2 size, vec2 pivot, s32 columns, s32 count, s32 delay)
{
Anm2Item* item = anm2_item_from_reference(self, reference);
if (!item) return;
Anm2Reference frameReference = *reference;
for (s32 i = 0; i < count; i++)
{
const s32 row = i / columns;
const s32 column = i % columns;
Anm2Frame frame{};
frame.delay = delay;
frame.pivot = pivot;
frame.size = size;
frame.crop = startPosition + vec2(size.x * column, size.y * row);
anm2_frame_add(self, &frame, &frameReference);
frameReference.frameIndex++;
}
} }

View File

@@ -20,6 +20,9 @@
#define ANM2_WRITE_INFO "Wrote anm2 to file: {}" #define ANM2_WRITE_INFO "Wrote anm2 to file: {}"
#define ANM2_CREATED_ON_FORMAT "%d-%B-%Y %I:%M:%S %p" #define ANM2_CREATED_ON_FORMAT "%d-%B-%Y %I:%M:%S %p"
#define ANM2_EXTENSION "anm2"
#define ANM2_SPRITESHEET_EXTENSION "png"
/* Elements */ /* Elements */
#define ANM2_ELEMENT_LIST \ #define ANM2_ELEMENT_LIST \
X(ANIMATED_ACTOR, "AnimatedActor") \ X(ANIMATED_ACTOR, "AnimatedActor") \
@@ -151,7 +154,7 @@ struct Anm2Frame
{ {
bool isVisible = true; bool isVisible = true;
bool isInterpolated = false; bool isInterpolated = false;
f32 rotation = 1.0f; f32 rotation{};
s32 delay = ANM2_FRAME_DELAY_MIN; s32 delay = ANM2_FRAME_DELAY_MIN;
s32 atFrame = INDEX_NONE; s32 atFrame = INDEX_NONE;
s32 eventID = ID_NONE; s32 eventID = ID_NONE;
@@ -159,7 +162,7 @@ struct Anm2Frame
vec2 pivot{}; vec2 pivot{};
vec2 position{}; vec2 position{};
vec2 size{}; vec2 size{};
vec2 scale{}; vec2 scale = {100, 100};
vec3 offsetRGB{}; vec3 offsetRGB{};
vec4 tintRGBA = {1.0f, 1.0f, 1.0f, 1.0f}; vec4 tintRGBA = {1.0f, 1.0f, 1.0f, 1.0f};
}; };
@@ -269,7 +272,7 @@ Anm2Animation* anm2_animation_from_reference(Anm2* self, Anm2Reference* referenc
Anm2Item* anm2_item_from_reference(Anm2* self, Anm2Reference* reference); Anm2Item* anm2_item_from_reference(Anm2* self, Anm2Reference* reference);
Anm2Frame* anm2_frame_from_reference(Anm2* self, Anm2Reference* reference); Anm2Frame* anm2_frame_from_reference(Anm2* self, Anm2Reference* reference);
s32 anm2_frame_index_from_time(Anm2* self, Anm2Reference reference, f32 time); s32 anm2_frame_index_from_time(Anm2* self, Anm2Reference reference, f32 time);
Anm2Frame* anm2_frame_add(Anm2* self, Anm2Frame* frame, Anm2Reference* reference, s32 time); Anm2Frame* anm2_frame_add(Anm2* self, Anm2Frame* frame, Anm2Reference* reference, s32 time = 0.0f);
void anm2_frame_erase(Anm2* self, Anm2Reference* reference); void anm2_frame_erase(Anm2* self, Anm2Reference* reference);
void anm2_frame_from_time(Anm2* self, Anm2Frame* frame, Anm2Reference reference, f32 time); void anm2_frame_from_time(Anm2* self, Anm2Frame* frame, Anm2Reference reference, f32 time);
void anm2_reference_clear(Anm2Reference* self); void anm2_reference_clear(Anm2Reference* self);
@@ -279,4 +282,6 @@ s32 anm2_animation_length_get(Anm2Animation* self);
void anm2_animation_length_set(Anm2Animation* self); void anm2_animation_length_set(Anm2Animation* self);
void anm2_animation_merge(Anm2* self, s32 animationID, const std::vector<s32>& mergeIDs, Anm2MergeType type); void anm2_animation_merge(Anm2* self, s32 animationID, const std::vector<s32>& mergeIDs, Anm2MergeType type);
void anm2_frame_bake(Anm2* self, Anm2Reference* reference, s32 interval, bool isRoundScale, bool isRoundRotation); void anm2_frame_bake(Anm2* self, Anm2Reference* reference, s32 interval, bool isRoundScale, bool isRoundRotation);
void anm2_item_frame_set(Anm2* self, Anm2Reference* reference, const Anm2FrameChange& change, Anm2ChangeType type, s32 start, s32 count); void anm2_item_frame_set(Anm2* self, Anm2Reference* reference, const Anm2FrameChange& change, Anm2ChangeType type, s32 start, s32 count);
void anm2_scale(Anm2* self, f32 scale);
void anm2_generate_from_grid(Anm2* self, Anm2Reference* reference, vec2 startPosition, vec2 size, vec2 pivot, s32 columns, s32 count, s32 delay);

View File

@@ -69,6 +69,19 @@ void canvas_init(Canvas* self, const vec2& size)
glEnableVertexAttribArray(0); glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(f32), (void*)0); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(f32), (void*)0);
// Grid
glGenVertexArrays(1, &self->gridVAO);
glBindVertexArray(self->gridVAO);
glGenBuffers(1, &self->gridVBO);
glBindBuffer(GL_ARRAY_BUFFER, self->gridVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(CANVAS_GRID_VERTICES), CANVAS_GRID_VERTICES, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, (void*)0);
glBindVertexArray(0);
// Texture // Texture
glGenVertexArrays(1, &self->textureVAO); glGenVertexArrays(1, &self->textureVAO);
glGenBuffers(1, &self->textureVBO); glGenBuffers(1, &self->textureVBO);
@@ -93,7 +106,7 @@ void canvas_init(Canvas* self, const vec2& size)
_canvas_texture_init(self, size); _canvas_texture_init(self, size);
} }
mat4 canvas_transform_get(Canvas* self, vec2& pan, f32& zoom, OriginType origin) mat4 canvas_transform_get(Canvas* self, vec2 pan, f32 zoom, OriginType origin)
{ {
f32 zoomFactor = PERCENT_TO_UNIT(zoom); f32 zoomFactor = PERCENT_TO_UNIT(zoom);
mat4 projection = glm::ortho(0.0f, self->size.x, 0.0f, self->size.y, -1.0f, 1.0f); mat4 projection = glm::ortho(0.0f, self->size.x, 0.0f, self->size.y, -1.0f, 1.0f);
@@ -136,55 +149,21 @@ void canvas_texture_set(Canvas* self)
} }
} }
void canvas_grid_draw(Canvas* self, GLuint& shader, mat4& transform, f32& zoom, ivec2& size, ivec2& offset, vec4& color) void canvas_grid_draw(Canvas* self, GLuint& shader, mat4& transform, ivec2& size, ivec2& offset, vec4& color)
{ {
if (size.x <= 0 || size.y <= 0) mat4 inverseTransform = glm::inverse(transform);
return; // avoid div-by-zero
std::vector<f32> vertices;
vec2 gridSize = self->size * (PERCENT_TO_UNIT(CANVAS_ZOOM_MAX - zoom));
// First visible vertical line <= 0
s32 startX = -(offset.x % size.x);
if (startX > 0) startX -= size.x;
for (s32 x = startX; x <= gridSize.x; x += size.x)
{
vertices.push_back((f32)x);
vertices.push_back(0.0f);
vertices.push_back((f32)x);
vertices.push_back((f32)gridSize.y);
}
// First visible horizontal line <= 0
s32 startY = -(offset.y % size.y);
if (startY > 0) startY -= size.y;
for (s32 y = startY; y <= gridSize.y; y += size.y)
{
vertices.push_back(0.0f);
vertices.push_back((f32)y);
vertices.push_back((f32)gridSize.x);
vertices.push_back((f32)y);
}
s32 vertexCount = (s32)vertices.size() / 2;
if (vertexCount == 0)
return;
glBindVertexArray(self->gridVAO);
glBindBuffer(GL_ARRAY_BUFFER, self->gridVBO);
glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(f32), vertices.data(), GL_DYNAMIC_DRAW);
glUseProgram(shader); glUseProgram(shader);
glBindVertexArray(self->gridVAO);
glUniformMatrix4fv(glGetUniformLocation(shader, SHADER_UNIFORM_TRANSFORM), 1, GL_FALSE, value_ptr(transform));
glUniform4f(glGetUniformLocation(shader, SHADER_UNIFORM_COLOR), color.r, color.g, color.b, color.a);
glDrawArrays(GL_LINES, 0, vertexCount);
glUniformMatrix4fv(glGetUniformLocation(shader, SHADER_UNIFORM_MODEL), 1, GL_FALSE, glm::value_ptr(inverseTransform));
glUniform2f(glGetUniformLocation(shader, SHADER_UNIFORM_SIZE), size.x, size.y);
glUniform2f(glGetUniformLocation(shader, SHADER_UNIFORM_OFFSET), offset.x, offset.y);
glUniform4f(glGetUniformLocation(shader, SHADER_UNIFORM_COLOR), color.r, color.g, color.b, color.a);
glBindVertexArray(self->gridVAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
glBindVertexArray(0); glBindVertexArray(0);
glUseProgram(0); glUseProgram(0);
} }

View File

@@ -14,6 +14,7 @@
static const vec2 CANVAS_GRID_SIZE = {3200, 1600}; static const vec2 CANVAS_GRID_SIZE = {3200, 1600};
static const vec2 CANVAS_PIVOT_SIZE = {8, 8}; static const vec2 CANVAS_PIVOT_SIZE = {8, 8};
static const vec2 CANVAS_SCALE_DEFAULT = {1.0f, 1.0f};
const f32 CANVAS_AXIS_VERTICES[] = const f32 CANVAS_AXIS_VERTICES[] =
{ {
@@ -23,6 +24,13 @@ const f32 CANVAS_AXIS_VERTICES[] =
0.0f, CANVAS_LINE_LENGTH 0.0f, CANVAS_LINE_LENGTH
}; };
const f32 CANVAS_GRID_VERTICES[] =
{
-1.0f, -1.0f,
3.0f, -1.0f,
-1.0f, 3.0f
};
struct Canvas struct Canvas
{ {
GLuint fbo{}; GLuint fbo{};
@@ -41,13 +49,13 @@ struct Canvas
}; };
void canvas_init(Canvas* self, const vec2& size); void canvas_init(Canvas* self, const vec2& size);
mat4 canvas_transform_get(Canvas* self, vec2& pan, f32& zoom, OriginType origin); mat4 canvas_transform_get(Canvas* self, vec2 pan, f32 zoom, OriginType origin);
void canvas_clear(vec4& color); void canvas_clear(vec4& color);
void canvas_bind(Canvas* self); void canvas_bind(Canvas* self);
void canvas_viewport_set(Canvas* self); void canvas_viewport_set(Canvas* self);
void canvas_unbind(void); void canvas_unbind(void);
void canvas_texture_set(Canvas* self); void canvas_texture_set(Canvas* self);
void canvas_grid_draw(Canvas* self, GLuint& shader, mat4& transform, f32& zoom, ivec2& size, ivec2& offset, vec4& color); void canvas_grid_draw(Canvas* self, GLuint& shader, mat4& transform, ivec2& size, ivec2& offset, vec4& color);
void canvas_axes_draw(Canvas* self, GLuint& shader, mat4& transform, vec4& color); void canvas_axes_draw(Canvas* self, GLuint& shader, mat4& transform, vec4& color);
void canvas_rect_draw(Canvas* self, const GLuint& shader, const mat4& transform, const vec4& color); void canvas_rect_draw(Canvas* self, const GLuint& shader, const mat4& transform, const vec4& color);
void canvas_free(Canvas* self); void canvas_free(Canvas* self);

View File

@@ -1,5 +1,9 @@
#include "dialog.h" #include "dialog.h"
#ifdef _WIN32
#include <windows.h>
#endif
static void _dialog_callback(void* userdata, const char* const* filelist, s32 filter) static void _dialog_callback(void* userdata, const char* const* filelist, s32 filter)
{ {
Dialog* self; Dialog* self;
@@ -67,6 +71,17 @@ void dialog_ffmpeg_path_set(Dialog* self)
self->type = DIALOG_FFMPEG_PATH_SET; self->type = DIALOG_FFMPEG_PATH_SET;
} }
void dialog_explorer_open(const std::string& path)
{
#ifdef _WIN32
ShellExecuteA(NULL, "open", path.c_str(), NULL, NULL, SW_SHOWNORMAL);
#else
char cmd[512];
snprintf(cmd, sizeof(cmd), "xdg-open \"%s\" &", path.c_str());
system(cmd);
#endif
}
void void
dialog_reset(Dialog* self) dialog_reset(Dialog* self)
{ {

View File

@@ -53,4 +53,5 @@ void dialog_anm2_save(Dialog* self);
void dialog_render_path_set(Dialog* self); void dialog_render_path_set(Dialog* self);
void dialog_render_directory_set(Dialog* self); void dialog_render_directory_set(Dialog* self);
void dialog_ffmpeg_path_set(Dialog* self); void dialog_ffmpeg_path_set(Dialog* self);
void dialog_reset(Dialog* self); void dialog_reset(Dialog* self);
void dialog_explorer_open(const std::string& path);

View File

@@ -17,6 +17,7 @@ void editor_draw(Editor* self)
vec4& gridColor = self->settings->editorGridColor; vec4& gridColor = self->settings->editorGridColor;
GLuint& shaderLine = self->resources->shaders[SHADER_LINE]; GLuint& shaderLine = self->resources->shaders[SHADER_LINE];
GLuint& shaderTexture = self->resources->shaders[SHADER_TEXTURE]; GLuint& shaderTexture = self->resources->shaders[SHADER_TEXTURE];
GLuint& shaderGrid = self->resources->shaders[SHADER_GRID];
mat4 transform = canvas_transform_get(&self->canvas, self->settings->editorPan, self->settings->editorZoom, ORIGIN_TOP_LEFT); mat4 transform = canvas_transform_get(&self->canvas, self->settings->editorPan, self->settings->editorZoom, ORIGIN_TOP_LEFT);
canvas_texture_set(&self->canvas); canvas_texture_set(&self->canvas);
@@ -48,7 +49,7 @@ void editor_draw(Editor* self)
} }
if (self->settings->editorIsGrid) if (self->settings->editorIsGrid)
canvas_grid_draw(&self->canvas, shaderLine, transform, self->settings->editorZoom, gridSize, gridOffset, gridColor); canvas_grid_draw(&self->canvas, shaderGrid, transform, gridSize, gridOffset, gridColor);
canvas_unbind(); canvas_unbind();
} }

View File

@@ -34,7 +34,7 @@ struct Editor
GLuint textureVBO; GLuint textureVBO;
GLuint borderVAO; GLuint borderVAO;
GLuint borderVBO; GLuint borderVBO;
s32 spritesheetID = -1; s32 spritesheetID = ID_NONE;
}; };
void editor_init(Editor* self, Anm2* anm2, Anm2Reference* reference, Resources* resources, Settings* settings); void editor_init(Editor* self, Anm2* anm2, Anm2Reference* reference, Resources* resources, Settings* settings);

View File

@@ -12,87 +12,40 @@ void generate_preview_init(GeneratePreview* self, Anm2* anm2, Anm2Reference* ref
void generate_preview_draw(GeneratePreview* self) void generate_preview_draw(GeneratePreview* self)
{ {
static auto& columns = self->settings->generateColumns;
/* TODO static auto& count = self->settings->generateCount;
f32& zoom = self->settings->previewZoom; static GLuint& shaderTexture = self->resources->shaders[SHADER_TEXTURE];
GLuint& shaderLine = self->resources->shaders[SHADER_LINE]; const mat4 transform = canvas_transform_get(&self->canvas, {}, CANVAS_ZOOM_DEFAULT, ORIGIN_CENTER);
GLuint& shaderTexture = self->resources->shaders[SHADER_TEXTURE];
mat4 transform = canvas_transform_get(&self->canvas, self->settings->previewPan, self->settings->previewZoom, ORIGIN_CENTER);
vec2 startPosition = {self->settings->generateStartPosition.x, self->settings->generateStartPosition.y};
vec2 size = {self->settings->generateSize.x, self->settings->generateSize.y};
vec2 pivot = {self->settings->generatePivot.x, self->settings->generatePivot.y};
canvas_bind(&self->canvas); canvas_bind(&self->canvas);
canvas_viewport_set(&self->canvas); canvas_viewport_set(&self->canvas);
canvas_clear(self->settings->previewBackgroundColor); canvas_clear(self->settings->previewBackgroundColor);
self->time = std::clamp(self->time, 0.0f, 1.0f); Anm2Item* item = anm2_item_from_reference(self->anm2, self->reference);
Texture* texture = map_find(self->resources->textures, self->anm2->layers[self->reference->itemID].spritesheetID);
Anm2Animation* animation = anm2_animation_from_reference(self->anm2, self->reference);
s32& animationID = self->reference->animationID;
if (animation)
{
Anm2Frame root;
mat4 rootModel = mat4(1.0f);
anm2_frame_from_time(self->anm2, &root, Anm2Reference{animationID, ANM2_ROOT}, self->time); if (item && texture && !texture->isInvalid)
{
const s32 index = std::clamp((s32)(self->time * count), 0, count);
const s32 row = index / columns;
const s32 column = index % columns;
vec2 crop = startPosition + vec2(size.x * column, size.y * row);
if (self->settings->previewIsRootTransform) vec2 uvMin = crop / vec2(texture->size);
rootModel = quad_parent_model_get(root.position, vec2(0.0f), root.rotation, PERCENT_TO_UNIT(root.scale)); vec2 uvMax = (crop + size) / vec2(texture->size);
f32 vertices[] = UV_VERTICES(uvMin, uvMax);
// Root mat4 model = quad_model_get(size, {}, pivot, {}, CANVAS_SCALE_DEFAULT);
if (self->settings->previewIsTargets && animation->rootAnimation.isVisible && root.isVisible) mat4 generateTransform = transform * model;
{
mat4 model = quad_model_get(PREVIEW_TARGET_SIZE, root.position, PREVIEW_TARGET_SIZE * 0.5f, root.rotation, PERCENT_TO_UNIT(root.scale));
mat4 rootTransform = transform * model;
f32 vertices[] = ATLAS_UV_VERTICES(ATLAS_TARGET);
canvas_texture_draw(&self->canvas, shaderTexture, self->resources->atlas.id, rootTransform, vertices, PREVIEW_ROOT_COLOR);
}
// Layers
for (auto [i, id] : self->anm2->layerMap)
{
Anm2Frame frame;
Anm2Item& layerAnimation = animation->layerAnimations[id];
if (!layerAnimation.isVisible || layerAnimation.frames.size() <= 0)
continue;
anm2_frame_from_time(self->anm2, &frame, Anm2Reference{animationID, ANM2_LAYER, id}, self->time);
if (!frame.isVisible)
continue;
Texture* texture = map_find(self->resources->textures, self->anm2->layers[id].spritesheetID);
if (!texture || texture->isInvalid)
continue;
vec2 uvMin = frame.crop / vec2(texture->size);
vec2 uvMax = (frame.crop + frame.size) / vec2(texture->size);
f32 vertices[] = UV_VERTICES(uvMin, uvMax);
mat4 model = quad_model_get(frame.size, frame.position, frame.pivot, frame.rotation, PERCENT_TO_UNIT(frame.scale));
mat4 layerTransform = transform * (rootModel * model);
canvas_texture_draw(&self->canvas, shaderTexture, texture->id, layerTransform, vertices, frame.tintRGBA, frame.offsetRGB);
if (self->settings->previewIsBorder)
canvas_rect_draw(&self->canvas, shaderLine, layerTransform, PREVIEW_BORDER_COLOR);
if (self->settings->previewIsPivots)
{
f32 vertices[] = ATLAS_UV_VERTICES(ATLAS_PIVOT);
mat4 pivotModel = quad_model_get(CANVAS_PIVOT_SIZE, frame.position, CANVAS_PIVOT_SIZE * 0.5f, frame.rotation, PERCENT_TO_UNIT(frame.scale));
mat4 pivotTransform = transform * (rootModel * pivotModel);
canvas_texture_draw(&self->canvas, shaderTexture, self->resources->atlas.id, pivotTransform, vertices, PREVIEW_PIVOT_COLOR);
}
}
s32& animationOverlayID = self->animationOverlayID;
Anm2Animation* animationOverlay = map_find(self->anm2->animations, animationOverlayID);
canvas_texture_draw(&self->canvas, shaderTexture, texture->id, generateTransform, vertices, COLOR_OPAQUE, COLOR_OFFSET_NONE);
}
canvas_unbind(); canvas_unbind();
*/
} }
void generate_preview_free(GeneratePreview* self) void generate_preview_free(GeneratePreview* self)

View File

@@ -5,6 +5,9 @@
#include "settings.h" #include "settings.h"
#include "canvas.h" #include "canvas.h"
#define GENERATE_PREVIEW_TIME_MIN 0.0f
#define GENERATE_PREVIEW_TIME_MAX 1.0f
const vec2 GENERATE_PREVIEW_SIZE = {325, 215}; const vec2 GENERATE_PREVIEW_SIZE = {325, 215};
struct GeneratePreview struct GeneratePreview

View File

@@ -19,6 +19,34 @@ static bool _imgui_window_color_from_position_get(SDL_Window* self, const vec2&
return true; return true;
} }
static void _imgui_anm2_new(Imgui* self, const std::string& path)
{
*self->reference = Anm2Reference{};
resources_textures_free(self->resources);
if (anm2_deserialize(self->anm2, self->resources, path))
{
window_title_from_path_set(self->window, path);
snapshots_reset(self->snapshots);
imgui_log_push(self, std::format(IMGUI_LOG_FILE_OPEN_FORMAT, path));
}
else
imgui_log_push(self, std::format(IMGUI_LOG_FILE_OPEN_FORMAT, path));
}
static void _imgui_spritesheet_add(Imgui* self, const std::string& path)
{
std::filesystem::path workingPath = std::filesystem::current_path();
std::string anm2WorkingPath = working_directory_from_file_set(self->anm2->path);
std::string spritesheetPath = std::filesystem::relative(path, anm2WorkingPath);
s32 id = map_next_id_get(self->resources->textures);
self->anm2->spritesheets[id] = Anm2Spritesheet{};
self->anm2->spritesheets[id].path = spritesheetPath;
resources_texture_init(self->resources, spritesheetPath, id);
std::filesystem::current_path(workingPath);
}
template<typename T> template<typename T>
static void _imgui_clipboard_hovered_item_set(Imgui* self, const T& data) static void _imgui_clipboard_hovered_item_set(Imgui* self, const T& data)
{ {
@@ -107,6 +135,7 @@ static void _imgui_item_pre(const ImguiItem& self, ImguiItemType type)
case IMGUI_INPUT_INT: case IMGUI_INPUT_INT:
case IMGUI_INPUT_TEXT: case IMGUI_INPUT_TEXT:
case IMGUI_DRAG_FLOAT: case IMGUI_DRAG_FLOAT:
case IMGUI_SLIDER_FLOAT:
case IMGUI_COLOR_EDIT: case IMGUI_COLOR_EDIT:
ImGui::SetNextItemWidth(size.x); ImGui::SetNextItemWidth(size.x);
break; break;
@@ -360,6 +389,8 @@ IMGUI_ITEM_VALUE_FUNCTION(_imgui_color_button, IMGUI_COLOR_BUTTON, vec4, ImGui::
IMGUI_ITEM_VALUE_FUNCTION(_imgui_checkbox, IMGUI_CHECKBOX, bool, ImGui::Checkbox(self.label_get(), &value)); IMGUI_ITEM_VALUE_FUNCTION(_imgui_checkbox, IMGUI_CHECKBOX, bool, ImGui::Checkbox(self.label_get(), &value));
IMGUI_ITEM_VALUE_CLAMP_FUNCTION(_imgui_input_int, IMGUI_INPUT_INT, s32, ImGui::InputInt(self.label.c_str(), &value, self.step, self.stepFast, self.flags)); IMGUI_ITEM_VALUE_CLAMP_FUNCTION(_imgui_input_int, IMGUI_INPUT_INT, s32, ImGui::InputInt(self.label.c_str(), &value, self.step, self.stepFast, self.flags));
IMGUI_ITEM_VALUE_CLAMP_FUNCTION(_imgui_input_int2, IMGUI_INPUT_INT, ivec2, ImGui::InputInt2(self.label.c_str(), value_ptr(value), self.flags)); IMGUI_ITEM_VALUE_CLAMP_FUNCTION(_imgui_input_int2, IMGUI_INPUT_INT, ivec2, ImGui::InputInt2(self.label.c_str(), value_ptr(value), self.flags));
IMGUI_ITEM_VALUE_CLAMP_FUNCTION(_imgui_input_float, IMGUI_INPUT_FLOAT, f32, ImGui::InputFloat(self.label.c_str(), &value, self.step, self.stepFast, self.format_get(), self.flags));
IMGUI_ITEM_VALUE_FUNCTION(_imgui_slider_float, IMGUI_SLIDER_FLOAT, f32, ImGui::SliderFloat(self.label_get(), &value, self.min, self.max, self.format_get(), self.flags));
IMGUI_ITEM_VALUE_FUNCTION(_imgui_drag_float, IMGUI_DRAG_FLOAT, f32, ImGui::DragFloat(self.label_get(), &value, self.speed, self.min, self.max, self.format_get())); IMGUI_ITEM_VALUE_FUNCTION(_imgui_drag_float, IMGUI_DRAG_FLOAT, f32, ImGui::DragFloat(self.label_get(), &value, self.speed, self.min, self.max, self.format_get()));
IMGUI_ITEM_VALUE_FUNCTION(_imgui_drag_float2, IMGUI_DRAG_FLOAT, vec2, ImGui::DragFloat2(self.label_get(), value_ptr(value), self.speed, self.min, self.max, self.format_get())); IMGUI_ITEM_VALUE_FUNCTION(_imgui_drag_float2, IMGUI_DRAG_FLOAT, vec2, ImGui::DragFloat2(self.label_get(), value_ptr(value), self.speed, self.min, self.max, self.format_get()));
IMGUI_ITEM_VALUE_FUNCTION(_imgui_color_edit3, IMGUI_COLOR_EDIT, vec3, ImGui::ColorEdit3(self.label_get(), value_ptr(value), self.flags)); IMGUI_ITEM_VALUE_FUNCTION(_imgui_color_edit3, IMGUI_COLOR_EDIT, vec3, ImGui::ColorEdit3(self.label_get(), value_ptr(value), self.flags));
@@ -522,6 +553,24 @@ static bool _imgui_option_popup(ImguiItem self, Imgui* imgui)
return false; return false;
} }
static void _imgui_context_menu(Imgui* self)
{
if (!self->isContextualActionsEnabled) return;
if (_imgui_is_window_hovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Right))
imgui_open_popup(IMGUI_CONTEXT_MENU.label_get());
if (imgui_begin_popup(IMGUI_CONTEXT_MENU.label_get(), self))
{
_imgui_selectable(IMGUI_CUT, self);
_imgui_selectable(IMGUI_COPY, self);
_imgui_selectable(IMGUI_PASTE.copy({self->clipboard->item.type == CLIPBOARD_NONE}), self);
imgui_end_popup(self);
}
}
static void _imgui_spritesheet_editor_set(Imgui* self, s32 id) static void _imgui_spritesheet_editor_set(Imgui* self, s32 id)
{ {
if (self->anm2->spritesheets.contains(id)) self->editor->spritesheetID = id; if (self->anm2->spritesheets.contains(id)) self->editor->spritesheetID = id;
@@ -1036,6 +1085,11 @@ static void _imgui_timeline(Imgui* self)
timeline_item_frames(Anm2Reference(animationID, ANM2_TRIGGERS), index); timeline_item_frames(Anm2Reference(animationID, ANM2_TRIGGERS), index);
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, defaultItemSpacing);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, defaultWindowPadding);
_imgui_context_menu(self);
ImGui::PopStyleVar(2);
_imgui_end_child(); // IMGUI_TIMELINE_FRAMES_CHILD _imgui_end_child(); // IMGUI_TIMELINE_FRAMES_CHILD
}; };
@@ -1056,6 +1110,7 @@ static void _imgui_timeline(Imgui* self)
_imgui_end_child(); // IMGUI_TIMELINE_CHILD _imgui_end_child(); // IMGUI_TIMELINE_CHILD
Anm2Frame* frame = anm2_frame_from_reference(self->anm2, self->reference); Anm2Frame* frame = anm2_frame_from_reference(self->anm2, self->reference);
Anm2Item* item = anm2_item_from_reference(self->anm2, self->reference);
_imgui_begin_child(IMGUI_TIMELINE_ITEM_FOOTER_CHILD, self); _imgui_begin_child(IMGUI_TIMELINE_ITEM_FOOTER_CHILD, self);
_imgui_button(IMGUI_TIMELINE_ADD_ITEM, self); _imgui_button(IMGUI_TIMELINE_ADD_ITEM, self);
@@ -1066,7 +1121,7 @@ static void _imgui_timeline(Imgui* self)
imgui_end_popup(self); imgui_end_popup(self);
} }
if (_imgui_button(IMGUI_TIMELINE_REMOVE_ITEM.copy({itemType == ANM2_NONE || itemType == ANM2_ROOT || itemType == ANM2_TRIGGERS}), self)) if (_imgui_button(IMGUI_TIMELINE_REMOVE_ITEM.copy({!item || itemType == ANM2_ROOT || itemType == ANM2_TRIGGERS}), self))
{ {
switch (itemType) switch (itemType)
{ {
@@ -1098,8 +1153,13 @@ static void _imgui_timeline(Imgui* self)
self->preview->isPlaying = !self->preview->isPlaying; self->preview->isPlaying = !self->preview->isPlaying;
} }
if (_imgui_button(IMGUI_ADD_FRAME, self)) if (_imgui_button(IMGUI_ADD_FRAME.copy({!item}), self))
anm2_frame_add(self->anm2, nullptr, self->reference, (s32)time); {
Anm2Reference frameReference = *self->reference;
frameReference.frameIndex = std::clamp(frameReference.frameIndex, 0, (s32)item->frames.size() - 1);
Anm2Frame* addFrame = anm2_frame_from_reference(self->anm2, &frameReference);
anm2_frame_add(self->anm2, addFrame, &frameReference);
}
if(_imgui_button(IMGUI_REMOVE_FRAME.copy({!frame}), self)) if(_imgui_button(IMGUI_REMOVE_FRAME.copy({!frame}), self))
{ {
@@ -1163,23 +1223,18 @@ static void _imgui_taskbar(Imgui* self)
if (imgui_begin_popup(IMGUI_FILE.popup, self)) if (imgui_begin_popup(IMGUI_FILE.popup, self))
{ {
_imgui_selectable(IMGUI_NEW, self); // imgui_file_new _imgui_selectable(IMGUI_NEW, self);
_imgui_selectable(IMGUI_OPEN, self); // imgui_file_open _imgui_selectable(IMGUI_OPEN, self);
_imgui_selectable(IMGUI_SAVE, self);
_imgui_selectable(IMGUI_SAVE, self); // imgui_file_save _imgui_selectable(IMGUI_SAVE_AS, self);
_imgui_selectable(IMGUI_SAVE_AS, self); // imgui_file_save_as _imgui_selectable(IMGUI_EXPLORE_ANM2_LOCATION, self);
_imgui_selectable(IMGUI_EXIT, self);
imgui_end_popup(self); imgui_end_popup(self);
} }
if (self->dialog->isSelected && self->dialog->type == DIALOG_ANM2_OPEN) if (self->dialog->isSelected && self->dialog->type == DIALOG_ANM2_OPEN)
{ {
*self->reference = Anm2Reference{}; _imgui_anm2_new(self, self->dialog->path);
resources_textures_free(self->resources);
anm2_deserialize(self->anm2, self->resources, self->dialog->path);
window_title_from_path_set(self->window, self->dialog->path);
snapshots_reset(self->snapshots);
imgui_log_push(self, std::format(IMGUI_LOG_FILE_OPEN_FORMAT, self->dialog->path));
dialog_reset(self->dialog); dialog_reset(self->dialog);
} }
@@ -1190,13 +1245,21 @@ static void _imgui_taskbar(Imgui* self)
imgui_log_push(self, std::format(IMGUI_LOG_FILE_SAVE_FORMAT, self->dialog->path)); imgui_log_push(self, std::format(IMGUI_LOG_FILE_SAVE_FORMAT, self->dialog->path));
dialog_reset(self->dialog); dialog_reset(self->dialog);
} }
if (self->isTryQuit) imgui_open_popup(IMGUI_EXIT_CONFIRMATION.label);
if (_imgui_option_popup(IMGUI_EXIT_CONFIRMATION, self))
self->isQuit = true;
else
self->isTryQuit = false;
_imgui_selectable(IMGUI_WIZARD.copy({}), self); _imgui_selectable(IMGUI_WIZARD.copy({}), self);
if (imgui_begin_popup(IMGUI_WIZARD.popup, self)) if (imgui_begin_popup(IMGUI_WIZARD.popup, self))
{ {
_imgui_selectable(IMGUI_GENERATE_ANIMATION_FROM_GRID.copy({!item || (self->reference->itemType != ANM2_LAYER)}), self); _imgui_selectable(IMGUI_GENERATE_ANIMATION_FROM_GRID.copy({!item || (self->reference->itemType != ANM2_LAYER)}), self);
_imgui_selectable(IMGUI_CHANGE_ALL_FRAME_PROPERTIES.copy({!item}), self); _imgui_selectable(IMGUI_CHANGE_ALL_FRAME_PROPERTIES.copy({!item}), self);
_imgui_selectable(IMGUI_SCALE_ANM2.copy({self->anm2->animations.empty()}), self);
_imgui_selectable(IMGUI_RENDER_ANIMATION.copy({!animation}), self); _imgui_selectable(IMGUI_RENDER_ANIMATION.copy({!animation}), self);
imgui_end_popup(self); imgui_end_popup(self);
@@ -1204,27 +1267,44 @@ static void _imgui_taskbar(Imgui* self)
if (imgui_begin_popup_modal(IMGUI_GENERATE_ANIMATION_FROM_GRID.popup, self, IMGUI_GENERATE_ANIMATION_FROM_GRID.popupSize)) if (imgui_begin_popup_modal(IMGUI_GENERATE_ANIMATION_FROM_GRID.popup, self, IMGUI_GENERATE_ANIMATION_FROM_GRID.popupSize))
{ {
static auto& startPosition = self->settings->generateStartPosition;
static auto& size = self->settings->generateSize;
static auto& pivot = self->settings->generatePivot;
static auto& rows = self->settings->generateRows;
static auto& columns = self->settings->generateColumns;
static auto& count = self->settings->generateCount;
static auto& delay = self->settings->generateDelay;
static auto& time = self->generatePreview->time;
_imgui_begin_child(IMGUI_GENERATE_ANIMATION_FROM_GRID_OPTIONS_CHILD, self); _imgui_begin_child(IMGUI_GENERATE_ANIMATION_FROM_GRID_OPTIONS_CHILD, self);
_imgui_input_int2(IMGUI_GENERATE_ANIMATION_FROM_GRID_START_POSITION, self, self->settings->generateStartPosition); _imgui_input_int2(IMGUI_GENERATE_ANIMATION_FROM_GRID_START_POSITION, self, startPosition);
_imgui_input_int2(IMGUI_GENERATE_ANIMATION_FROM_GRID_FRAME_SIZE, self, self->settings->generateFrameSize); _imgui_input_int2(IMGUI_GENERATE_ANIMATION_FROM_GRID_SIZE, self, size);
_imgui_input_int2(IMGUI_GENERATE_ANIMATION_FROM_GRID_PIVOT, self, self->settings->generatePivot); _imgui_input_int2(IMGUI_GENERATE_ANIMATION_FROM_GRID_PIVOT, self, pivot);
_imgui_input_int(IMGUI_GENERATE_ANIMATION_FROM_GRID_ROWS, self, self->settings->generateRows); _imgui_input_int(IMGUI_GENERATE_ANIMATION_FROM_GRID_ROWS, self, rows);
_imgui_input_int(IMGUI_GENERATE_ANIMATION_FROM_GRID_COLUMNS, self, self->settings->generateColumns); _imgui_input_int(IMGUI_GENERATE_ANIMATION_FROM_GRID_COLUMNS, self, columns);
_imgui_input_int _imgui_input_int(IMGUI_GENERATE_ANIMATION_FROM_GRID_COUNT.copy({.max = rows * columns}), self, count);
( _imgui_input_int(IMGUI_GENERATE_ANIMATION_FROM_GRID_DELAY, self, delay);
IMGUI_GENERATE_ANIMATION_FROM_GRID_FRAME_COUNT.copy({.max = self->settings->generateRows * self->settings->generateColumns}),
self, self->settings->generateFrameCount
);
_imgui_input_int(IMGUI_GENERATE_ANIMATION_FROM_GRID_DELAY, self, self->settings->generateDelay);
_imgui_end_child(); //IMGUI_GENERATE_ANIMATION_FROM_GRID_OPTIONS_CHILD _imgui_end_child(); //IMGUI_GENERATE_ANIMATION_FROM_GRID_OPTIONS_CHILD
ImGui::SameLine(); ImGui::SameLine();
_imgui_begin_child(IMGUI_GENERATE_ANIMATION_FROM_GRID_PREVIEW_CHILD, self); _imgui_begin_child(IMGUI_GENERATE_ANIMATION_FROM_GRID_PREVIEW_CHILD, self);
generate_preview_draw(self->generatePreview);
ImGui::Image(self->generatePreview->canvas.texture, GENERATE_PREVIEW_SIZE);
_imgui_begin_child(IMGUI_GENERATE_ANIMATION_FROM_GRID_SLIDER_CHILD, self);
_imgui_slider_float(IMGUI_GENERATE_ANIMATION_FROM_GRID_SLIDER, self, time);
_imgui_end_child(); // IMGUI_GENERATE_ANIMATION_FROM_GRID_SLIDER_CHILD
_imgui_end_child(); //IMGUI_GENERATE_ANIMATION_FROM_GRID_PREVIEW_CHILD _imgui_end_child(); //IMGUI_GENERATE_ANIMATION_FROM_GRID_PREVIEW_CHILD
_imgui_begin_child(IMGUI_FOOTER_CHILD, self); _imgui_begin_child(IMGUI_FOOTER_CHILD, self);
if (_imgui_button(IMGUI_GENERATE_ANIMATION_FROM_GRID_GENERATE, self)) imgui_close_current_popup(self); if (_imgui_button(IMGUI_GENERATE_ANIMATION_FROM_GRID_GENERATE, self))
{
anm2_generate_from_grid(self->anm2, self->reference, startPosition, size, pivot, columns, count, delay);
imgui_close_current_popup(self);
}
if (_imgui_button(IMGUI_POPUP_CANCEL, self)) imgui_close_current_popup(self); if (_imgui_button(IMGUI_POPUP_CANCEL, self)) imgui_close_current_popup(self);
_imgui_end_child(); // IMGUI_FOOTER_CHILD _imgui_end_child(); // IMGUI_FOOTER_CHILD
@@ -1316,6 +1396,24 @@ static void _imgui_taskbar(Imgui* self)
imgui_end_popup(self); imgui_end_popup(self);
} }
if (imgui_begin_popup_modal(IMGUI_SCALE_ANM2.popup, self, IMGUI_SCALE_ANM2.popupSize))
{
_imgui_begin_child(IMGUI_SCALE_ANM2_OPTIONS_CHILD, self);
_imgui_input_float(IMGUI_SCALE_ANM2_VALUE, self, self->settings->scaleValue);
_imgui_end_child(); // IMGUI_SCALE_ANM2_OPTIONS_CHILD
_imgui_begin_child(IMGUI_FOOTER_CHILD, self);
if (_imgui_button(IMGUI_SCALE_ANM2_SCALE, self))
{
anm2_scale(self->anm2, self->settings->scaleValue);
imgui_close_current_popup(self);
}
if (_imgui_button(IMGUI_POPUP_CANCEL, self)) imgui_close_current_popup(self);
_imgui_end_child(); // IMGUI_FOOTER_CHILD
imgui_end_popup(self);
}
if (imgui_begin_popup_modal(IMGUI_RENDER_ANIMATION.popup, self, IMGUI_RENDER_ANIMATION.popupSize)) if (imgui_begin_popup_modal(IMGUI_RENDER_ANIMATION.popup, self, IMGUI_RENDER_ANIMATION.popupSize))
{ {
_imgui_begin_child(IMGUI_RENDER_ANIMATION_CHILD, self); _imgui_begin_child(IMGUI_RENDER_ANIMATION_CHILD, self);
@@ -1331,7 +1429,11 @@ static void _imgui_taskbar(Imgui* self)
} }
} }
if (self->dialog->isSelected && self->dialog->type == DIALOG_RENDER_PATH_SET) if
(
self->dialog->isSelected &&
(self->dialog->type == DIALOG_RENDER_PATH_SET || self->dialog->type == DIALOG_RENDER_DIRECTORY_SET)
)
{ {
self->settings->renderPath = self->dialog->path; self->settings->renderPath = self->dialog->path;
dialog_reset(self->dialog); dialog_reset(self->dialog);
@@ -1356,24 +1458,33 @@ static void _imgui_taskbar(Imgui* self)
{ {
bool isRenderStart = true; bool isRenderStart = true;
switch (self->settings->renderType) if (!std::filesystem::exists(self->settings->ffmpegPath))
{ {
case RENDER_PNG: imgui_log_push(self, IMGUI_LOG_RENDER_ANIMATION_FFMPEG_PATH_ERROR);
if (!std::filesystem::is_directory(self->settings->renderPath)) isRenderStart = false;
{ }
imgui_log_push(self, IMGUI_LOG_RENDER_ANIMATION_DIRECTORY_ERROR);
isRenderStart = false; if (isRenderStart)
} {
break; switch (self->settings->renderType)
case RENDER_GIF: {
case RENDER_WEBM: case RENDER_PNG:
if (!path_valid(self->settings->renderPath)) if (!std::filesystem::is_directory(self->settings->renderPath))
{ {
imgui_log_push(self, IMGUI_LOG_RENDER_ANIMATION_PATH_ERROR); imgui_log_push(self, IMGUI_LOG_RENDER_ANIMATION_DIRECTORY_ERROR);
isRenderStart = false; isRenderStart = false;
} }
default: break;
break; case RENDER_GIF:
case RENDER_WEBM:
if (!path_is_valid(self->settings->renderPath))
{
imgui_log_push(self, IMGUI_LOG_RENDER_ANIMATION_PATH_ERROR);
isRenderStart = false;
}
default:
break;
}
} }
if (isRenderStart) if (isRenderStart)
@@ -1577,6 +1688,8 @@ static void _imgui_animations(Imgui* self)
ImGui::PopID(); ImGui::PopID();
}; };
_imgui_context_menu(self);
_imgui_end_child(); // animationsChild _imgui_end_child(); // animationsChild
Anm2Animation* animation = anm2_animation_from_reference(self->anm2, self->reference); Anm2Animation* animation = anm2_animation_from_reference(self->anm2, self->reference);
@@ -1800,7 +1913,7 @@ static void _imgui_spritesheets(Imgui* self)
Texture* texture = &self->resources->textures[id]; Texture* texture = &self->resources->textures[id];
bool isContains = selectedIDs.contains(id); bool isContains = selectedIDs.contains(id);
_imgui_begin_child(IMGUI_SPRITESHEET_CHILD.copy({.size = {windowSize.x, IMGUI_SPRITESHEET_CHILD.size.y}}), self); _imgui_begin_child(IMGUI_SPRITESHEET_CHILD, self);
if (_imgui_checkbox(IMGUI_SPRITESHEET_SELECTED, self, isContains)) if (_imgui_checkbox(IMGUI_SPRITESHEET_SELECTED, self, isContains))
{ {
@@ -1864,17 +1977,8 @@ static void _imgui_spritesheets(Imgui* self)
if (self->dialog->isSelected && self->dialog->type == DIALOG_SPRITESHEET_ADD) if (self->dialog->isSelected && self->dialog->type == DIALOG_SPRITESHEET_ADD)
{ {
std::filesystem::path workingPath = std::filesystem::current_path(); _imgui_spritesheet_add(self, self->dialog->path);
std::string anm2WorkingPath = working_directory_from_file_set(self->anm2->path);
std::string spritesheetPath = std::filesystem::relative(self->dialog->path, anm2WorkingPath);
s32 id = map_next_id_get(self->resources->textures);
self->anm2->spritesheets[id] = Anm2Spritesheet{};
self->anm2->spritesheets[id].path = spritesheetPath;
resources_texture_init(self->resources, spritesheetPath, id);
dialog_reset(self->dialog); dialog_reset(self->dialog);
std::filesystem::current_path(workingPath);
} }
if (_imgui_button(IMGUI_SPRITESHEETS_RELOAD.copy({selectedIDs.empty()}), self)) if (_imgui_button(IMGUI_SPRITESHEETS_RELOAD.copy({selectedIDs.empty()}), self))
@@ -1935,10 +2039,10 @@ static void _imgui_spritesheets(Imgui* self)
{ {
for (auto& id : selectedIDs) for (auto& id : selectedIDs)
{ {
std::filesystem::path workingPath = std::filesystem::current_path();
working_directory_from_file_set(self->anm2->path);
Anm2Spritesheet* spritesheet = &self->anm2->spritesheets[id]; Anm2Spritesheet* spritesheet = &self->anm2->spritesheets[id];
Texture* texture = &self->resources->textures[id]; Texture* texture = &self->resources->textures[id];
std::filesystem::path workingPath = std::filesystem::current_path();
working_directory_from_file_set(self->anm2->path);
texture_from_gl_write(texture, spritesheet->path); texture_from_gl_write(texture, spritesheet->path);
imgui_log_push(self, std::format(IMGUI_LOG_SPRITESHEET_SAVE_FORMAT, id, spritesheet->path)); imgui_log_push(self, std::format(IMGUI_LOG_SPRITESHEET_SAVE_FORMAT, id, spritesheet->path));
std::filesystem::current_path(workingPath); std::filesystem::current_path(workingPath);
@@ -2064,7 +2168,7 @@ static void _imgui_animation_preview(Imgui* self)
const bool isDown = ImGui::IsKeyPressed(IMGUI_INPUT_DOWN); const bool isDown = ImGui::IsKeyPressed(IMGUI_INPUT_DOWN);
const bool isMod = ImGui::IsKeyDown(IMGUI_INPUT_SHIFT); const bool isMod = ImGui::IsKeyDown(IMGUI_INPUT_SHIFT);
const bool isZoomIn = ImGui::IsKeyDown(IMGUI_INPUT_ZOOM_IN); const bool isZoomIn = ImGui::IsKeyDown(IMGUI_INPUT_ZOOM_IN);
const bool isZoomOut = ImGui::IsKeyDown(IMGUI_INPUT_ZOOM_IN); const bool isZoomOut = ImGui::IsKeyDown(IMGUI_INPUT_ZOOM_OUT);
const bool isMouseClick = ImGui::IsMouseClicked(ImGuiMouseButton_Left); const bool isMouseClick = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
const bool isMouseDown = ImGui::IsMouseDown(ImGuiMouseButton_Left); const bool isMouseDown = ImGui::IsMouseDown(ImGuiMouseButton_Left);
const bool isMouseMiddleDown = ImGui::IsMouseDown(ImGuiMouseButton_Middle); const bool isMouseMiddleDown = ImGui::IsMouseDown(ImGuiMouseButton_Middle);
@@ -2194,7 +2298,7 @@ static void _imgui_spritesheet_editor(Imgui* self)
const bool isMouseDown = ImGui::IsMouseDown(ImGuiMouseButton_Left); const bool isMouseDown = ImGui::IsMouseDown(ImGuiMouseButton_Left);
const bool isMouseMiddleDown = ImGui::IsMouseDown(ImGuiMouseButton_Middle); const bool isMouseMiddleDown = ImGui::IsMouseDown(ImGuiMouseButton_Middle);
const bool isZoomIn = ImGui::IsKeyDown(IMGUI_INPUT_ZOOM_IN); const bool isZoomIn = ImGui::IsKeyDown(IMGUI_INPUT_ZOOM_IN);
const bool isZoomOut = ImGui::IsKeyDown(IMGUI_INPUT_ZOOM_IN); const bool isZoomOut = ImGui::IsKeyDown(IMGUI_INPUT_ZOOM_OUT);
const bool isMod = ImGui::IsKeyDown(IMGUI_INPUT_SHIFT); const bool isMod = ImGui::IsKeyDown(IMGUI_INPUT_SHIFT);
const f32 mouseWheel = ImGui::GetIO().MouseWheel; const f32 mouseWheel = ImGui::GetIO().MouseWheel;
const ImVec2 mouseDelta = ImGui::GetIO().MouseDelta; const ImVec2 mouseDelta = ImGui::GetIO().MouseDelta;
@@ -2358,22 +2462,6 @@ static void _imgui_log(Imgui* self)
} }
} }
static void _imgui_persistent(Imgui* self)
{
if (!self->isContextualActionsEnabled) return;
if (ImGui::IsMouseClicked(ImGuiMouseButton_Right))
imgui_open_popup(IMGUI_CONTEXT_MENU.label_get());
if (imgui_begin_popup(IMGUI_CONTEXT_MENU.label_get(), self))
{
_imgui_selectable(IMGUI_CUT, self);
_imgui_selectable(IMGUI_COPY, self);
_imgui_selectable(IMGUI_PASTE.copy({self->clipboard->item.type == CLIPBOARD_NONE}), self);
imgui_end_popup(self);
}
}
static void _imgui_dock(Imgui* self) static void _imgui_dock(Imgui* self)
{ {
@@ -2408,6 +2496,7 @@ void imgui_init
Anm2Reference* reference, Anm2Reference* reference,
Editor* editor, Editor* editor,
Preview* preview, Preview* preview,
GeneratePreview* generatePreview,
Settings* settings, Settings* settings,
Snapshots* snapshots, Snapshots* snapshots,
Clipboard* clipboard, Clipboard* clipboard,
@@ -2423,6 +2512,7 @@ void imgui_init
self->reference = reference; self->reference = reference;
self->editor = editor; self->editor = editor;
self->preview = preview; self->preview = preview;
self->generatePreview = generatePreview;
self->settings = settings; self->settings = settings;
self->snapshots = snapshots; self->snapshots = snapshots;
self->clipboard = clipboard; self->clipboard = clipboard;
@@ -2441,7 +2531,7 @@ void imgui_init
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
io.ConfigWindowsMoveFromTitleBarOnly = true; io.ConfigWindowsMoveFromTitleBarOnly = true;
ImGui::LoadIniSettingsFromDisk(SETTINGS_PATH); ImGui::LoadIniSettingsFromDisk(settings_path_get().c_str());
} }
void imgui_update(Imgui* self) void imgui_update(Imgui* self)
@@ -2453,7 +2543,6 @@ void imgui_update(Imgui* self)
_imgui_taskbar(self); _imgui_taskbar(self);
_imgui_dock(self); _imgui_dock(self);
_imgui_log(self); _imgui_log(self);
_imgui_persistent(self);
if (self->isContextualActionsEnabled) if (self->isContextualActionsEnabled)
{ {
@@ -2483,15 +2572,20 @@ void imgui_update(Imgui* self)
switch (event.type) switch (event.type)
{ {
case SDL_EVENT_DROP_FILE:
{
const char* droppedFile = event.drop.data;
if (path_is_extension(droppedFile, ANM2_EXTENSION))
_imgui_anm2_new(self, droppedFile);
else if (path_is_extension(droppedFile, ANM2_SPRITESHEET_EXTENSION))
_imgui_spritesheet_add(self, droppedFile);
break;
}
case SDL_EVENT_QUIT: case SDL_EVENT_QUIT:
if (!self->snapshots->undoStack.is_empty()) imgui_quit(self);
{ if (imgui_is_popup_open(IMGUI_EXIT_CONFIRMATION.popup))
if (imgui_is_popup_open(IMGUI_EXIT_CONFIRMATION.label_get()))
self->isQuit = true;
else
imgui_open_popup(IMGUI_EXIT_CONFIRMATION.label_get());
}
else
self->isQuit = true; self->isQuit = true;
break; break;
default: default:
@@ -2499,8 +2593,6 @@ void imgui_update(Imgui* self)
} }
} }
if (_imgui_option_popup(IMGUI_EXIT_CONFIRMATION, self))
self->isQuit = true;
} }
void imgui_draw(void) void imgui_draw(void)
@@ -2513,7 +2605,6 @@ void imgui_free(void)
{ {
ImGui_ImplSDL3_Shutdown(); ImGui_ImplSDL3_Shutdown();
ImGui_ImplOpenGL3_Shutdown(); ImGui_ImplOpenGL3_Shutdown();
ImGui::SaveIniSettingsToDisk(settings_path_get().c_str());
ImGui::SaveIniSettingsToDisk(SETTINGS_PATH);
ImGui::DestroyContext(); ImGui::DestroyContext();
} }

View File

@@ -5,6 +5,7 @@
#include "editor.h" #include "editor.h"
#include "ffmpeg.h" #include "ffmpeg.h"
#include "preview.h" #include "preview.h"
#include "generate_preview.h"
#include "resources.h" #include "resources.h"
#include "settings.h" #include "settings.h"
#include "snapshots.h" #include "snapshots.h"
@@ -67,13 +68,16 @@
#define IMGUI_ACTION_TRIGGER_MOVE "Trigger AtFrame" #define IMGUI_ACTION_TRIGGER_MOVE "Trigger AtFrame"
#define IMGUI_ACTION_MOVE_PLAYHEAD "Move Playhead" #define IMGUI_ACTION_MOVE_PLAYHEAD "Move Playhead"
#define IMGUI_POPUP_FLAGS ImGuiWindowFlags_NoMove
#define IMGUI_POPUP_MODAL_FLAGS ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize
#define IMGUI_LOG_FILE_OPEN_FORMAT "Opened anm2: {}" #define IMGUI_LOG_FILE_OPEN_FORMAT "Opened anm2: {}"
#define IMGUI_LOG_FILE_SAVE_FORMAT "Saved anm2 to: {}" #define IMGUI_LOG_FILE_SAVE_FORMAT "Saved anm2 to: {}"
#define IMGUI_LOG_RENDER_ANIMATION_FRAMES_SAVE_FORMAT "Saved rendered frames to: {}" #define IMGUI_LOG_RENDER_ANIMATION_FRAMES_SAVE_FORMAT "Saved rendered frames to: {}"
#define IMGUI_LOG_RENDER_ANIMATION_SAVE_FORMAT "Saved rendered animation to: {}" #define IMGUI_LOG_RENDER_ANIMATION_SAVE_FORMAT "Saved rendered animation to: {}"
#define IMGUI_LOG_RENDER_ANIMATION_NO_ANIMATION_ERROR "No animation selected; rendering cancelled." #define IMGUI_LOG_RENDER_ANIMATION_NO_ANIMATION_ERROR "No animation selected; rendering cancelled."
#define IMGUI_LOG_RENDER_ANIMATION_NO_FRAMES_ERROR "No frames to render; rendering cancelled." #define IMGUI_LOG_RENDER_ANIMATION_NO_FRAMES_ERROR "No frames to render; rendering cancelled."
#define IMGUI_LOG_RENDER_ANIMATION_DIRECTORY_ERROR "Invalid directory! Make sure it's valid and you have write permissions." #define IMGUI_LOG_RENDER_ANIMATION_DIRECTORY_ERROR "Invalid directory! Make sure it exists and you have write permissions."
#define IMGUI_LOG_RENDER_ANIMATION_PATH_ERROR "Invalid path! Make sure it's valid and you have write permissions." #define IMGUI_LOG_RENDER_ANIMATION_PATH_ERROR "Invalid path! Make sure it's valid and you have write permissions."
#define IMGUI_LOG_RENDER_ANIMATION_FFMPEG_PATH_ERROR "Invalid FFmpeg path! Make sure you have it installed and the path is correct." #define IMGUI_LOG_RENDER_ANIMATION_FFMPEG_PATH_ERROR "Invalid FFmpeg path! Make sure you have it installed and the path is correct."
#define IMGUI_LOG_RENDER_ANIMATION_FFMPEG_ERROR "FFmpeg could not render animation! Check paths or your FFmpeg installation." #define IMGUI_LOG_RENDER_ANIMATION_FFMPEG_ERROR "FFmpeg could not render animation! Check paths or your FFmpeg installation."
@@ -116,7 +120,7 @@ const ImVec4 IMGUI_TIMELINE_HEADER_FRAME_MULTIPLE_INACTIVE_COLOR = {0.113, 0.184
const ImVec4 IMGUI_ACTIVE_COLOR = {1.0, 1.0, 1.0, 1.0}; const ImVec4 IMGUI_ACTIVE_COLOR = {1.0, 1.0, 1.0, 1.0};
const ImVec4 IMGUI_INACTIVE_COLOR = {1.0, 1.0, 1.0, 0.25}; const ImVec4 IMGUI_INACTIVE_COLOR = {1.0, 1.0, 1.0, 0.25};
const ImVec2 IMGUI_SPRITESHEET_PREVIEW_SIZE = {125.0, 125.0}; const ImVec2 IMGUI_SPRITESHEET_PREVIEW_SIZE = {50.0, 50.0};
const ImVec2 IMGUI_TOOLTIP_OFFSET = {16, 8}; const ImVec2 IMGUI_TOOLTIP_OFFSET = {16, 8};
const vec2 IMGUI_SPRITESHEET_EDITOR_CROP_FORGIVENESS = {1, 1}; const vec2 IMGUI_SPRITESHEET_EDITOR_CROP_FORGIVENESS = {1, 1};
@@ -168,6 +172,7 @@ struct Imgui
Anm2Reference* reference = nullptr; Anm2Reference* reference = nullptr;
Editor* editor = nullptr; Editor* editor = nullptr;
Preview* preview = nullptr; Preview* preview = nullptr;
GeneratePreview* generatePreview = nullptr;
Settings* settings = nullptr; Settings* settings = nullptr;
Snapshots* snapshots = nullptr; Snapshots* snapshots = nullptr;
Clipboard* clipboard = nullptr; Clipboard* clipboard = nullptr;
@@ -182,6 +187,7 @@ struct Imgui
bool isCursorSet = false; bool isCursorSet = false;
bool isContextualActionsEnabled = true; bool isContextualActionsEnabled = true;
bool isQuit = false; bool isQuit = false;
bool isTryQuit = false;
}; };
typedef void(*ImguiFunction)(Imgui*); typedef void(*ImguiFunction)(Imgui*);
@@ -235,6 +241,21 @@ static inline void imgui_file_save_as(Imgui* self)
dialog_anm2_save(self->dialog); dialog_anm2_save(self->dialog);
} }
static inline void imgui_quit(Imgui* self)
{
if (!self->snapshots->undoStack.is_empty())
self->isTryQuit = true;
else
self->isQuit = true;
}
static inline void imgui_explore(Imgui* self)
{
std::filesystem::path filePath = self->anm2->path;
std::filesystem::path parentPath = filePath.parent_path();
dialog_explorer_open(parentPath);
}
static inline void imgui_undo_push(Imgui* self, const std::string& action = SNAPSHOT_ACTION) static inline void imgui_undo_push(Imgui* self, const std::string& action = SNAPSHOT_ACTION)
{ {
Snapshot snapshot = {*self->anm2, *self->reference, self->preview->time, action}; Snapshot snapshot = {*self->anm2, *self->reference, self->preview->time, action};
@@ -377,7 +398,7 @@ static inline bool imgui_begin_popup(const std::string& label, Imgui* imgui, ImV
{ {
imgui_pending_popup_process(imgui); imgui_pending_popup_process(imgui);
if (size != ImVec2()) ImGui::SetNextWindowSizeConstraints(size, ImVec2(FLT_MAX, FLT_MAX)); if (size != ImVec2()) ImGui::SetNextWindowSizeConstraints(size, ImVec2(FLT_MAX, FLT_MAX));
bool isActivated = ImGui::BeginPopup(label.c_str()); bool isActivated = ImGui::BeginPopup(label.c_str(), IMGUI_POPUP_FLAGS);
return isActivated; return isActivated;
} }
@@ -385,7 +406,7 @@ static inline bool imgui_begin_popup_modal(const std::string& label, Imgui* imgu
{ {
imgui_pending_popup_process(imgui); imgui_pending_popup_process(imgui);
if (size != ImVec2()) ImGui::SetNextWindowSizeConstraints(size, ImVec2(FLT_MAX, FLT_MAX)); if (size != ImVec2()) ImGui::SetNextWindowSizeConstraints(size, ImVec2(FLT_MAX, FLT_MAX));
bool isActivated = ImGui::BeginPopupModal(label.c_str(), nullptr, ImGuiWindowFlags_AlwaysAutoResize); bool isActivated = ImGui::BeginPopupModal(label.c_str(), nullptr, IMGUI_POPUP_MODAL_FLAGS);
if (isActivated) imgui_contextual_actions_disable(imgui); if (isActivated) imgui_contextual_actions_disable(imgui);
return isActivated; return isActivated;
} }
@@ -402,6 +423,8 @@ static inline void imgui_end_popup(Imgui* imgui)
imgui_pending_popup_process(imgui); imgui_pending_popup_process(imgui);
} }
enum ImguiItemType enum ImguiItemType
{ {
IMGUI_ITEM, IMGUI_ITEM,
@@ -417,6 +440,8 @@ enum ImguiItemType
IMGUI_CHECKBOX, IMGUI_CHECKBOX,
IMGUI_INPUT_INT, IMGUI_INPUT_INT,
IMGUI_INPUT_TEXT, IMGUI_INPUT_TEXT,
IMGUI_INPUT_FLOAT,
IMGUI_SLIDER_FLOAT,
IMGUI_DRAG_FLOAT, IMGUI_DRAG_FLOAT,
IMGUI_COLOR_EDIT, IMGUI_COLOR_EDIT,
IMGUI_COMBO, IMGUI_COMBO,
@@ -470,14 +495,15 @@ struct ImguiItem
f32 speed = 0.25f; f32 speed = 0.25f;
s32 step = 1; s32 step = 1;
s32 stepFast = 10; s32 stepFast = 10;
s32 border{};
s32 max{};
s32 min{}; s32 min{};
s32 max{};
s32 value{}; s32 value{};
vec2 atlasOffset;
s32 border{};
s32 flags{}; s32 flags{};
s32 windowFlags{}; s32 windowFlags{};
s32 rowCount = 0; s32 rowCount = 0;
vec2 atlasOffset;
void construct() void construct()
{ {
@@ -638,6 +664,28 @@ IMGUI_ITEM(IMGUI_SAVE_AS,
self.isShortcutInLabel = true self.isShortcutInLabel = true
); );
IMGUI_ITEM(IMGUI_EXPLORE_ANM2_LOCATION,
self.label = "E&xplore Anm2 Location",
self.tooltip = "Open the system's file explorer in the anm2's path.",
self.function = imgui_explore,
self.isSizeToText = true,
self.isSeparator = true
);
IMGUI_ITEM(IMGUI_EXIT,
self.label = "&Exit ",
self.tooltip = "Exits the program.",
self.function = imgui_quit,
self.chord = ImGuiMod_Alt | ImGuiKey_F4,
self.isSizeToText = true,
self.isShortcutInLabel = true
);
IMGUI_ITEM(IMGUI_EXIT_CONFIRMATION,
self.label = "Exit Confirmation",
self.text = "Unsaved changes will be lost!\nAre you sure you want to exit?"
);
IMGUI_ITEM(IMGUI_WIZARD, IMGUI_ITEM(IMGUI_WIZARD,
self.label = "&Wizard", self.label = "&Wizard",
self.tooltip = "Opens the wizard menu, for neat functions related to the .anm2.", self.tooltip = "Opens the wizard menu, for neat functions related to the .anm2.",
@@ -647,12 +695,17 @@ IMGUI_ITEM(IMGUI_WIZARD,
self.isSameLine = true self.isSameLine = true
); );
#define IMGUI_GENERATE_ANIMATION_FROM_GRID_PADDING 40
IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID, IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID,
self.label = "&Generate Animation from Grid", self.label = "&Generate Animation from Grid",
self.tooltip = "Generate a new animation from grid values.", self.tooltip = "Generate a new animation from grid values.",
self.popup = "Generate Animation from Grid", self.popup = "Generate Animation from Grid",
self.popupType = IMGUI_POPUP_CENTER_WINDOW, self.popupType = IMGUI_POPUP_CENTER_WINDOW,
self.popupSize = {650, 215} self.popupSize =
{
(GENERATE_PREVIEW_SIZE.x * 2) + IMGUI_GENERATE_ANIMATION_FROM_GRID_PADDING,
GENERATE_PREVIEW_SIZE.y + (IMGUI_FOOTER_CHILD.size.y * 2) + (IMGUI_GENERATE_ANIMATION_FROM_GRID_PADDING / 2)
}
); );
IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_OPTIONS_CHILD, IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_OPTIONS_CHILD,
@@ -670,8 +723,8 @@ IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_START_POSITION,
self.tooltip = "Set the starting position on the layer's spritesheet for the generated animation." self.tooltip = "Set the starting position on the layer's spritesheet for the generated animation."
); );
IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_FRAME_SIZE, IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_SIZE,
self.label = "Frame Size", self.label = "Size",
self.tooltip = "Set the size of each frame in the generated animation." self.tooltip = "Set the size of each frame in the generated animation."
); );
@@ -692,9 +745,10 @@ IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_COLUMNS,
self.max = 1000 self.max = 1000
); );
IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_FRAME_COUNT, IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_COUNT,
self.label = "Count", self.label = "Count",
self.tooltip = "Set how many frames will be made for the generated animation." self.tooltip = "Set how many frames will be made for the generated animation.",
self.value = ANM2_FRAME_NUM_MIN
); );
IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_DELAY, IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_DELAY,
@@ -713,9 +767,30 @@ IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_PREVIEW_CHILD,
self.flags = true self.flags = true
); );
IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_SLIDER_CHILD,
self.label = "## Generate Animation From Grid Slider Child",
self.size =
{
(IMGUI_GENERATE_ANIMATION_FROM_GRID.popupSize.x / 2) - (IMGUI_GENERATE_ANIMATION_FROM_GRID_PADDING / 2),
IMGUI_FOOTER_CHILD.size.y
},
self.flags = true
);
IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_SLIDER,
self.label = "## Generate Animation From Grid Slider",
self.tooltip = "Change the time of the generated animation preview.",
self.min = GENERATE_PREVIEW_TIME_MIN,
self.max = GENERATE_PREVIEW_TIME_MAX,
self.value = GENERATE_PREVIEW_TIME_MIN,
self.rowCount = 1,
self.flags = ImGuiSliderFlags_NoInput
);
IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_GENERATE, IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_GENERATE,
self.label = "Generate", self.label = "Generate",
self.tooltip = "Generate an animation with the used settings.", self.tooltip = "Generate an animation with the used settings.",
self.undoAction = "Generate Animation from Grid",
self.rowCount = IMGUI_OPTION_POPUP_ROW_COUNT, self.rowCount = IMGUI_OPTION_POPUP_ROW_COUNT,
self.isSameLine = true self.isSameLine = true
); );
@@ -725,8 +800,7 @@ IMGUI_ITEM(IMGUI_CHANGE_ALL_FRAME_PROPERTIES,
self.tooltip = "Change all frame properties in the selected animation item (or selected frame).", self.tooltip = "Change all frame properties in the selected animation item (or selected frame).",
self.popup = "Change All Frame Properties", self.popup = "Change All Frame Properties",
self.popupType = IMGUI_POPUP_CENTER_WINDOW, self.popupType = IMGUI_POPUP_CENTER_WINDOW,
self.popupSize = {500, 380}, self.popupSize = {500, 380}
self.isSeparator = true
); );
IMGUI_ITEM(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_CHILD, IMGUI_ITEM(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_CHILD,
@@ -796,6 +870,39 @@ IMGUI_ITEM(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_CANCEL,
self.rowCount = IMGUI_CHANGE_ALL_FRAME_PROPERTIES_OPTIONS_ROW_COUNT self.rowCount = IMGUI_CHANGE_ALL_FRAME_PROPERTIES_OPTIONS_ROW_COUNT
); );
IMGUI_ITEM(IMGUI_SCALE_ANM2,
self.label = "&Scale Anm2",
self.tooltip = "Scale up all size and position-related frame properties in the anm2.",
self.popup = "Scale Anm2",
self.popupType = IMGUI_POPUP_CENTER_WINDOW,
self.popupSize = {260, 72},
self.isSizeToText = true,
self.isSeparator = true
);
IMGUI_ITEM(IMGUI_SCALE_ANM2_OPTIONS_CHILD,
self.label = "## Scale Anm2 Options Child",
self.size = {IMGUI_SCALE_ANM2.popupSize.x, IMGUI_SCALE_ANM2.popupSize.y - IMGUI_FOOTER_CHILD.size.y},
self.flags = true
);
IMGUI_ITEM(IMGUI_SCALE_ANM2_VALUE,
self.label = "Value",
self.tooltip = "The size and position-related frame properties in the anm2 will be scaled by this value.",
self.format = "%.2f",
self.value = 1,
self.step = 0.25,
self.stepFast = 1
);
IMGUI_ITEM(IMGUI_SCALE_ANM2_SCALE,
self.label = "Scale",
self.tooltip = "Scale the anm2 with the value specified.",
self.undoAction = "Scale Anm2",
self.rowCount = IMGUI_OPTION_POPUP_ROW_COUNT,
self.isSameLine = true
);
IMGUI_ITEM(IMGUI_RENDER_ANIMATION, IMGUI_ITEM(IMGUI_RENDER_ANIMATION,
self.label = "&Render Animation", self.label = "&Render Animation",
self.tooltip = "Renders the current animation preview; output options can be customized.", self.tooltip = "Renders the current animation preview; output options can be customized.",
@@ -1043,7 +1150,8 @@ IMGUI_ITEM(IMGUI_SPRITESHEETS_CHILD, self.label = "## Spritesheets Child", self.
IMGUI_ITEM(IMGUI_SPRITESHEET_CHILD, IMGUI_ITEM(IMGUI_SPRITESHEET_CHILD,
self.label = "## Spritesheet Child", self.label = "## Spritesheet Child",
self.size = {0, 175}, self.rowCount = 1,
self.size = {0, IMGUI_SPRITESHEET_PREVIEW_SIZE.y + 40},
self.flags = true self.flags = true
); );
@@ -1253,6 +1361,7 @@ IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_POSITION,
self.label = "Position", self.label = "Position",
self.tooltip = "Change the position of the selected frame.", self.tooltip = "Change the position of the selected frame.",
self.undoAction = "Frame Position", self.undoAction = "Frame Position",
self.isUseItemActivated = true,
self.format = "%.0f" self.format = "%.0f"
); );
@@ -1260,6 +1369,7 @@ IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_CROP,
self.label = "Crop", self.label = "Crop",
self.tooltip = "Change the crop position of the selected frame.", self.tooltip = "Change the crop position of the selected frame.",
self.undoAction = "Frame Crop", self.undoAction = "Frame Crop",
self.isUseItemActivated = true,
self.format = "%.0f" self.format = "%.0f"
); );
@@ -1267,6 +1377,7 @@ IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_SIZE,
self.label = "Size", self.label = "Size",
self.tooltip = "Change the size of the crop of the selected frame.", self.tooltip = "Change the size of the crop of the selected frame.",
self.undoAction = "Frame Size", self.undoAction = "Frame Size",
self.isUseItemActivated = true,
self.format = "%.0f" self.format = "%.0f"
); );
@@ -1274,6 +1385,7 @@ IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_PIVOT,
self.label = "Pivot", self.label = "Pivot",
self.tooltip = "Change the pivot of the selected frame.", self.tooltip = "Change the pivot of the selected frame.",
self.undoAction = "Frame Pivot", self.undoAction = "Frame Pivot",
self.isUseItemActivated = true,
self.format = "%.0f" self.format = "%.0f"
); );
@@ -1282,6 +1394,7 @@ IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_SCALE,
self.tooltip = "Change the scale of the selected frame.", self.tooltip = "Change the scale of the selected frame.",
self.undoAction = "Frame Scale", self.undoAction = "Frame Scale",
self.format = "%.0f", self.format = "%.0f",
self.isUseItemActivated = true,
self.value = 100 self.value = 100
); );
@@ -1289,6 +1402,7 @@ IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_ROTATION,
self.label = "Rotation", self.label = "Rotation",
self.tooltip = "Change the rotation of the selected frame.", self.tooltip = "Change the rotation of the selected frame.",
self.undoAction = "Frame Rotation", self.undoAction = "Frame Rotation",
self.isUseItemActivated = true,
self.format = "%.0f" self.format = "%.0f"
); );
@@ -1296,6 +1410,7 @@ IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_DELAY,
self.label = "Duration", self.label = "Duration",
self.tooltip = "Change the duration of the selected frame.", self.tooltip = "Change the duration of the selected frame.",
self.undoAction = "Frame Duration", self.undoAction = "Frame Duration",
self.isUseItemActivated = true,
self.min = ANM2_FRAME_NUM_MIN, self.min = ANM2_FRAME_NUM_MIN,
self.max = ANM2_FRAME_NUM_MAX, self.max = ANM2_FRAME_NUM_MAX,
self.value = ANM2_FRAME_NUM_MIN self.value = ANM2_FRAME_NUM_MIN
@@ -1305,6 +1420,7 @@ IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_TINT,
self.label = "Tint", self.label = "Tint",
self.tooltip = "Change the tint of the selected frame.", self.tooltip = "Change the tint of the selected frame.",
self.undoAction = "Frame Tint", self.undoAction = "Frame Tint",
self.isUseItemActivated = true,
self.value = 1 self.value = 1
); );
@@ -1312,6 +1428,7 @@ IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_COLOR_OFFSET,
self.label = "Color Offset", self.label = "Color Offset",
self.tooltip = "Change the color offset of the selected frame.", self.tooltip = "Change the color offset of the selected frame.",
self.undoAction = "Frame Color Offset", self.undoAction = "Frame Color Offset",
self.isUseItemActivated = true,
self.value = 0 self.value = 0
); );
@@ -1898,10 +2015,7 @@ IMGUI_ITEM(IMGUI_CHANGE_INPUT_INT,
self.step = 0 self.step = 0
); );
IMGUI_ITEM(IMGUI_EXIT_CONFIRMATION,
self.label = "Exit Confirmation",
self.text = "Unsaved changes will be lost!\nAre you sure you want to exit?"
);
#define IMGUI_OPTION_POPUP_ROW_COUNT 2 #define IMGUI_OPTION_POPUP_ROW_COUNT 2
IMGUI_ITEM(IMGUI_POPUP_OK, IMGUI_ITEM(IMGUI_POPUP_OK,
@@ -1938,6 +2052,7 @@ void imgui_init
Anm2Reference* reference, Anm2Reference* reference,
Editor* editor, Editor* editor,
Preview* preview, Preview* preview,
GeneratePreview* generatePreview,
Settings* settings, Settings* settings,
Snapshots* snapshots, Snapshots* snapshots,
Clipboard* clipboard, Clipboard* clipboard,

View File

@@ -1,12 +1,42 @@
#include "main.h" #include "main.h"
static bool _anm2_rescale(const std::string& file, f32 scale)
{
Anm2 anm2;
if (!anm2_deserialize(&anm2, nullptr, file)) return false;
anm2_scale(&anm2, scale);
return anm2_serialize(&anm2, file);
}
s32 s32
main(s32 argc, char* argv[]) main(s32 argc, char* argv[])
{ {
State state; State state;
if (argc > 0 && argv[1]) if (argc > 0 && argv[1])
state.argument = argv[1]; {
if (std::string(argv[1]) == ARGUMENT_RESCALE)
{
if (argv[2] && argv[3])
{
if (_anm2_rescale(std::string(argv[2]), atof(argv[3])))
{
log_info(std::format(ARGUMENT_RESCALE_ANM2_INFO, argv[2], argv[3]));
return EXIT_SUCCESS;
}
else
log_error(ARGUMENT_RESCALE_ANM2_ERROR);
}
else
log_error(ARGUMENT_RESCALE_ARGUMENT_ERROR);
return EXIT_FAILURE;
}
else
if (argv[1])
state.argument = argv[1];
}
init(&state); init(&state);

View File

@@ -1,3 +1,8 @@
#pragma once #pragma once
#define ARGUMENT_RESCALE "--rescale"
#define ARGUMENT_RESCALE_ARGUMENT_ERROR "--rescale: specify both anm2 and scale arguments"
#define ARGUMENT_RESCALE_ANM2_ERROR "Unable to rescale anm2 {} by value {}. Make sure the file is valid."
#define ARGUMENT_RESCALE_ANM2_INFO "Scaled anm2 {} by {}"
#include "state.h" #include "state.h"

View File

@@ -83,9 +83,9 @@ void preview_draw(Preview* self)
ivec2& gridSize = self->settings->previewGridSize; ivec2& gridSize = self->settings->previewGridSize;
ivec2& gridOffset = self->settings->previewGridOffset; ivec2& gridOffset = self->settings->previewGridOffset;
vec4& gridColor = self->settings->previewGridColor; vec4& gridColor = self->settings->previewGridColor;
f32& zoom = self->settings->previewZoom;
GLuint& shaderLine = self->resources->shaders[SHADER_LINE]; GLuint& shaderLine = self->resources->shaders[SHADER_LINE];
GLuint& shaderTexture = self->resources->shaders[SHADER_TEXTURE]; GLuint& shaderTexture = self->resources->shaders[SHADER_TEXTURE];
GLuint& shaderGrid = self->resources->shaders[SHADER_GRID];
mat4 transform = canvas_transform_get(&self->canvas, self->settings->previewPan, self->settings->previewZoom, ORIGIN_CENTER); mat4 transform = canvas_transform_get(&self->canvas, self->settings->previewPan, self->settings->previewZoom, ORIGIN_CENTER);
canvas_texture_set(&self->canvas); canvas_texture_set(&self->canvas);
@@ -95,7 +95,7 @@ void preview_draw(Preview* self)
canvas_clear(self->settings->previewBackgroundColor); canvas_clear(self->settings->previewBackgroundColor);
if (self->settings->previewIsGrid) if (self->settings->previewIsGrid)
canvas_grid_draw(&self->canvas, shaderLine, transform, zoom, gridSize, gridOffset, gridColor); canvas_grid_draw(&self->canvas, shaderGrid, transform, gridSize, gridOffset, gridColor);
if (self->settings->previewIsAxes) if (self->settings->previewIsAxes)
canvas_axes_draw(&self->canvas, shaderLine, transform, self->settings->previewAxesColor); canvas_axes_draw(&self->canvas, shaderLine, transform, self->settings->previewAxesColor);
@@ -136,20 +136,20 @@ void preview_draw(Preview* self)
if (!frame.isVisible) if (!frame.isVisible)
continue; continue;
Texture* texture = map_find(self->resources->textures, self->anm2->layers[id].spritesheetID);
if (!texture || texture->isInvalid)
continue;
vec2 uvMin = frame.crop / vec2(texture->size);
vec2 uvMax = (frame.crop + frame.size) / vec2(texture->size);
f32 vertices[] = UV_VERTICES(uvMin, uvMax);
mat4 model = quad_model_get(frame.size, frame.position, frame.pivot, frame.rotation, PERCENT_TO_UNIT(frame.scale)); mat4 model = quad_model_get(frame.size, frame.position, frame.pivot, frame.rotation, PERCENT_TO_UNIT(frame.scale));
mat4 layerTransform = transform * (rootModel * model); mat4 layerTransform = transform * (rootModel * model);
canvas_texture_draw(&self->canvas, shaderTexture, texture->id, layerTransform, vertices, frame.tintRGBA, frame.offsetRGB); Texture* texture = map_find(self->resources->textures, self->anm2->layers[id].spritesheetID);
if (texture && !texture->isInvalid)
{
vec2 uvMin = frame.crop / vec2(texture->size);
vec2 uvMax = (frame.crop + frame.size) / vec2(texture->size);
f32 vertices[] = UV_VERTICES(uvMin, uvMax);
canvas_texture_draw(&self->canvas, shaderTexture, texture->id, layerTransform, vertices, frame.tintRGBA, frame.offsetRGB);
}
if (self->settings->previewIsBorder) if (self->settings->previewIsBorder)
canvas_rect_draw(&self->canvas, shaderLine, layerTransform, PREVIEW_BORDER_COLOR); canvas_rect_draw(&self->canvas, shaderLine, layerTransform, PREVIEW_BORDER_COLOR);

View File

@@ -72,6 +72,14 @@ static void _settings_setting_load(Settings* self, const std::string& line)
} }
} }
std::string settings_path_get(void)
{
char* path = SDL_GetPrefPath("", SETTINGS_FOLDER);
std::string filePath = std::string(path) + SETTINGS_PATH;
SDL_free(path);
return filePath;
}
static void _settings_setting_write(Settings* self, std::ostream& out, SettingsEntry entry) static void _settings_setting_write(Settings* self, std::ostream& out, SettingsEntry entry)
{ {
u8* selfPointer = (u8*)self; u8* selfPointer = (u8*)self;
@@ -136,59 +144,119 @@ static void _settings_setting_write(Settings* self, std::ostream& out, SettingsE
void settings_save(Settings* self) void settings_save(Settings* self)
{ {
std::ifstream input(SETTINGS_PATH); const std::string path = settings_path_get();
std::string oldContents; const std::filesystem::path filesystemPath(path);
const std::filesystem::path directory = filesystemPath.parent_path();
if (!input) if (!directory.empty())
{ {
log_error(std::format(SETTINGS_INIT_ERROR, SETTINGS_PATH)); std::error_code errorCode;
std::filesystem::create_directories(directory, errorCode);
if (errorCode)
{
log_error(std::format(SETTINGS_DIRECTORY_ERROR, directory.string(), errorCode.message()));
return;
}
}
std::string data;
if (std::filesystem::exists(filesystemPath))
{
if (std::ifstream in(path, std::ios::binary); in)
data.assign(std::istreambuf_iterator<char>(in), std::istreambuf_iterator<char>());
}
std::filesystem::path temp = filesystemPath;
temp += SETTINGS_TEMPORARY_EXTENSION;
std::ofstream out(temp, std::ios::binary | std::ios::trunc);
if (!out)
{
log_error(std::format(SETTINGS_INIT_ERROR, temp.string()));
return; return;
} }
oldContents.assign((std::istreambuf_iterator<char>(input)), std::istreambuf_iterator<char>()); out << SETTINGS_SECTION << "\n";
input.close();
std::ofstream output(SETTINGS_PATH);
if (!output)
{
log_error(std::format(SETTINGS_INIT_ERROR, SETTINGS_PATH));
return;
}
output << SETTINGS_SECTION << "\n";
for (s32 i = 0; i < SETTINGS_COUNT; i++) for (s32 i = 0; i < SETTINGS_COUNT; i++)
_settings_setting_write(self, output, SETTINGS_ENTRIES[i]); _settings_setting_write(self, out, SETTINGS_ENTRIES[i]);
output << "\n" << SETTINGS_SECTION_IMGUI << "\n"; out << "\n" << SETTINGS_SECTION_IMGUI << "\n";
output << oldContents; out << data;
output.close(); out.flush();
if (!out.good())
{
log_error(std::format(SETTINGS_SAVE_ERROR, temp.string()));
return;
}
out.close();
std::error_code errorCode;
std::filesystem::rename(temp, filesystemPath, errorCode);
if (errorCode)
{
// Windows can block rename if target exists; try remove+rename
std::filesystem::remove(filesystemPath, errorCode);
errorCode = {};
std::filesystem::rename(temp, filesystemPath, errorCode);
if (errorCode)
{
log_error(std::format(SETTINGS_SAVE_FINALIZE_ERROR, filesystemPath.string(), errorCode.message()));
std::filesystem::remove(temp);
return;
}
}
log_info(std::format(SETTINGS_SAVE_INFO, path));
} }
void settings_init(Settings* self) void settings_init(Settings* self)
{ {
std::ifstream file(SETTINGS_PATH); const std::string path = settings_path_get();
std::ifstream file(path, std::ios::binary);
std::istream* in = nullptr;
std::istringstream defaultSettings;
if (!file) if (file)
{ {
log_error(std::format(SETTINGS_INIT_ERROR, SETTINGS_PATH)); log_info(std::format(SETTINGS_INIT_INFO, path));
return; in = &file;
}
else
{
log_error(std::format(SETTINGS_INIT_ERROR, path));
log_info(SETTINGS_DEFAULT_INFO);
defaultSettings.str(SETTINGS_DEFAULT);
in = &defaultSettings;
} }
std::string line; std::string line;
bool inSettingsSection = false; bool inSettingsSection = false;
while (std::getline(file, line)) while (std::getline(*in, line))
{ {
if (line == SETTINGS_SECTION) if (line == SETTINGS_SECTION)
{ {
inSettingsSection = true; inSettingsSection = true;
continue; continue;
} }
if (line == SETTINGS_SECTION_IMGUI) break; if (line == SETTINGS_SECTION_IMGUI) break;
if (inSettingsSection) _settings_setting_load(self, line); if (inSettingsSection) _settings_setting_load(self, line);
}
// Save default settings
if (!file)
{
std::ofstream out(path, std::ios::binary | std::ios::trunc);
if (out)
{
out << SETTINGS_DEFAULT;
out.flush();
log_info(std::format(SETTINGS_SAVE_INFO, path));
}
else
log_error(std::format(SETTINGS_DEFAULT_ERROR, path));
} }
} }

View File

@@ -8,9 +8,19 @@
#define SETTINGS_BUFFER_ITEM 0xFF #define SETTINGS_BUFFER_ITEM 0xFF
#define SETTINGS_SECTION "[Settings]" #define SETTINGS_SECTION "[Settings]"
#define SETTINGS_SECTION_IMGUI "# Dear ImGui" #define SETTINGS_SECTION_IMGUI "# Dear ImGui"
#define SETTINGS_INIT_ERROR "Failed to read settings file! ({})" #define SETTINGS_INIT_ERROR "Failed to read settings file: {}"
#define SETTINGS_PATH "settings.ini" #define SETTINGS_DEFAULT_ERROR "Failed to write default settings file: {}"
#define SETTINGS_SAVE_ERROR "Failed to write settings file: {}"
#define SETTINGS_SAVE_FINALIZE_ERROR "Failed to write settings file: {} ({})"
#define SETTINGS_FLOAT_FORMAT "{:.3f}" #define SETTINGS_FLOAT_FORMAT "{:.3f}"
#define SETTINGS_INIT_INFO "Initialized settings from: {}"
#define SETTINGS_DEFAULT_INFO "Using default settings"
#define SETTINGS_DIRECTORY_ERROR "Failed to create settings directory: {} ({})"
#define SETTINGS_SAVE_INFO "Saved settings to: {}"
#define SETTINGS_FOLDER "anm2ed"
#define SETTINGS_PATH "settings.ini"
#define SETTINGS_TEMPORARY_EXTENSION ".tmp"
struct SettingsEntry struct SettingsEntry
{ {
@@ -48,6 +58,7 @@ struct Settings
bool changeIsVisible{}; bool changeIsVisible{};
bool changeIsInterpolated{}; bool changeIsInterpolated{};
s32 changeNumberFrames = 1; s32 changeNumberFrames = 1;
f32 scaleValue = 1.0f;
bool previewIsAxes = true; bool previewIsAxes = true;
bool previewIsGrid = true; bool previewIsGrid = true;
bool previewIsRootTransform = false; bool previewIsRootTransform = false;
@@ -64,11 +75,11 @@ struct Settings
vec4 previewAxesColor = {1.0, 1.0, 1.0, 0.125}; vec4 previewAxesColor = {1.0, 1.0, 1.0, 0.125};
vec4 previewBackgroundColor = {0.113, 0.184, 0.286, 1.0}; vec4 previewBackgroundColor = {0.113, 0.184, 0.286, 1.0};
ivec2 generateStartPosition = {0, 0}; ivec2 generateStartPosition = {0, 0};
ivec2 generateFrameSize = {64, 64}; ivec2 generateSize = {64, 64};
ivec2 generatePivot = {32, 32}; ivec2 generatePivot = {32, 32};
s32 generateRows = 4; s32 generateRows = 4;
s32 generateColumns = 4; s32 generateColumns = 4;
s32 generateFrameCount = 16; s32 generateCount = 16;
s32 generateDelay = 1; s32 generateDelay = 1;
bool editorIsGrid = true; bool editorIsGrid = true;
bool editorIsGridSnap = true; bool editorIsGridSnap = true;
@@ -89,7 +100,7 @@ struct Settings
s32 renderType = RENDER_PNG; s32 renderType = RENDER_PNG;
std::string renderPath = "."; std::string renderPath = ".";
std::string renderFormat = "{}.png"; std::string renderFormat = "{}.png";
std::string ffmpegPath = "/usr/bin/ffmpeg"; std::string ffmpegPath{};
}; };
const SettingsEntry SETTINGS_ENTRIES[] = const SettingsEntry SETTINGS_ENTRIES[] =
@@ -121,6 +132,7 @@ const SettingsEntry SETTINGS_ENTRIES[] =
{"changeIsVisible", TYPE_BOOL, offsetof(Settings, changeIsVisibleSet)}, {"changeIsVisible", TYPE_BOOL, offsetof(Settings, changeIsVisibleSet)},
{"changeIsInterpolated", TYPE_BOOL, offsetof(Settings, changeIsInterpolatedSet)}, {"changeIsInterpolated", TYPE_BOOL, offsetof(Settings, changeIsInterpolatedSet)},
{"changeNumberFrames", TYPE_INT, offsetof(Settings, changeNumberFrames)}, {"changeNumberFrames", TYPE_INT, offsetof(Settings, changeNumberFrames)},
{"scaleValue", TYPE_FLOAT, offsetof(Settings, scaleValue)},
{"previewIsAxes", TYPE_BOOL, offsetof(Settings, previewIsAxes)}, {"previewIsAxes", TYPE_BOOL, offsetof(Settings, previewIsAxes)},
{"previewIsGrid", TYPE_BOOL, offsetof(Settings, previewIsGrid)}, {"previewIsGrid", TYPE_BOOL, offsetof(Settings, previewIsGrid)},
{"previewIsRootTransform", TYPE_BOOL, offsetof(Settings, previewIsRootTransform)}, {"previewIsRootTransform", TYPE_BOOL, offsetof(Settings, previewIsRootTransform)},
@@ -136,12 +148,12 @@ const SettingsEntry SETTINGS_ENTRIES[] =
{"previewGridColor", TYPE_VEC4, offsetof(Settings, previewGridColor)}, {"previewGridColor", TYPE_VEC4, offsetof(Settings, previewGridColor)},
{"previewAxesColor", TYPE_VEC4, offsetof(Settings, previewAxesColor)}, {"previewAxesColor", TYPE_VEC4, offsetof(Settings, previewAxesColor)},
{"previewBackgroundColor", TYPE_VEC4, offsetof(Settings, previewBackgroundColor)}, {"previewBackgroundColor", TYPE_VEC4, offsetof(Settings, previewBackgroundColor)},
{"generateStartPosition", TYPE_VEC2, offsetof(Settings, generateStartPosition)}, {"generateStartPosition", TYPE_IVEC2, offsetof(Settings, generateStartPosition)},
{"generateFrameSize", TYPE_VEC2, offsetof(Settings, generateFrameSize)}, {"generateSize", TYPE_IVEC2, offsetof(Settings, generateSize)},
{"generatePivot", TYPE_VEC2, offsetof(Settings, generatePivot)}, {"generatePivot", TYPE_IVEC2, offsetof(Settings, generatePivot)},
{"generateRows", TYPE_INT, offsetof(Settings, generateRows)}, {"generateRows", TYPE_INT, offsetof(Settings, generateRows)},
{"generateColumns", TYPE_INT, offsetof(Settings, generateColumns)}, {"generateColumns", TYPE_INT, offsetof(Settings, generateColumns)},
{"generateFrameCount", TYPE_INT, offsetof(Settings, generateFrameCount)}, {"generateCount", TYPE_INT, offsetof(Settings, generateCount)},
{"generateDelay", TYPE_INT, offsetof(Settings, generateDelay)}, {"generateDelay", TYPE_INT, offsetof(Settings, generateDelay)},
{"editorIsGrid", TYPE_BOOL, offsetof(Settings, editorIsGrid)}, {"editorIsGrid", TYPE_BOOL, offsetof(Settings, editorIsGrid)},
{"editorIsGridSnap", TYPE_BOOL, offsetof(Settings, editorIsGridSnap)}, {"editorIsGridSnap", TYPE_BOOL, offsetof(Settings, editorIsGridSnap)},
@@ -166,5 +178,192 @@ const SettingsEntry SETTINGS_ENTRIES[] =
}; };
constexpr s32 SETTINGS_COUNT = (s32)std::size(SETTINGS_ENTRIES); constexpr s32 SETTINGS_COUNT = (s32)std::size(SETTINGS_ENTRIES);
const std::string SETTINGS_DEFAULT = R"(
[Settings]
windowX=1920
windowY=1080
playbackIsLoop=true
playbackIsClampPlayhead=false
changeIsCrop=false
changeIsSize=false
changeIsPosition=false
changeIsPivot=false
changeIsScale=false
changeIsRotation=false
changeIsDelay=false
changeIsTint=false
changeIsColorOffset=false
changeIsVisibleSet=false
changeIsInterpolatedSet=false
changeIsFromSelectedFrame=false
changeCropX=0.000
changeCropY=0.000
changeSizeX=0.000
changeSizeY=0.000
changePositionX=0.000
changePositionY=0.000
changePivotX=0.000
changePivotY=0.000
changeScaleX=0.000
changeScaleY=0.000
changeRotation=0.000
changeDelay=1
changeTintR=0.000
changeTintG=0.000
changeTintB=0.000
changeTintA=0.000
changeColorOffsetR=0.000
changeColorOffsetG=0.000
changeColorOffsetB=0.000
changeIsVisible=false
changeIsInterpolated=false
changeNumberFrames=1
scaleValue=1.000
previewIsAxes=true
previewIsGrid=false
previewIsRootTransform=true
previewIsTriggers=false
previewIsPivots=false
previewIsTargets=true
previewIsBorder=false
previewOverlayTransparency=255.000
previewZoom=400.000
previewPanX=0.000
previewPanY=0.000
previewGridSizeX=32
previewGridSizeY=32
previewGridOffsetX=0
previewGridOffsetY=0
previewGridColorR=1.000
previewGridColorG=1.000
previewGridColorB=1.000
previewGridColorA=0.125
previewAxesColorR=1.000
previewAxesColorG=1.000
previewAxesColorB=1.000
previewAxesColorA=0.125
previewBackgroundColorR=0.114
previewBackgroundColorG=0.184
previewBackgroundColorB=0.286
previewBackgroundColorA=1.000
generateStartPositionX=0.000
generateStartPositionY=0.000
generateSizeX=0.000
generateSizeY=0.000
generatePivotX=0.000
generatePivotY=0.000
generateRows=4
generateColumns=4
generateCount=16
generateDelay=1
editorIsGrid=true
editorIsGridSnap=true
editorIsBorder=true
editorZoom=400.000
editorPanX=0.000
editorPanY=0.000
editorGridSizeX=32
editorGridSizeY=32
editorGridOffsetX=0
editorGridOffsetY=0
editorGridColorR=1.000
editorGridColorG=1.000
editorGridColorB=1.000
editorGridColorA=0.125
editorBackgroundColorR=0.113
editorBackgroundColorG=0.183
editorBackgroundColorB=0.286
editorBackgroundColorA=1.000
mergeType=1
mergeIsDeleteAnimationsAfter=false
bakeInterval=1
bakeRoundScale=true
bakeRoundRotation=true
tool=0
toolColorR=0.000
toolColorG=0.000
toolColorB=0.000
toolColorA=1.000
renderType=0
renderPath=.
renderFormat={}.png
ffmpegPath=/usr/bin/ffmpeg
# Dear ImGui
[Window][## Window]
Pos=0,32
Size=1918,1032
Collapsed=0
[Window][Debug##Default]
Pos=60,60
Size=400,400
Collapsed=0
[Window][Tools]
Pos=8,40
Size=39,654
Collapsed=0
DockId=0x0000000B,0
[Window][Animations]
Pos=1452,388
Size=458,306
Collapsed=0
DockId=0x0000000A,0
[Window][Events]
Pos=1025,348
Size=425,346
Collapsed=0
DockId=0x00000008,0
[Window][Spritesheets]
Pos=1452,40
Size=458,346
Collapsed=0
DockId=0x00000009,0
[Window][Animation Preview]
Pos=49,40
Size=974,654
Collapsed=0
DockId=0x0000000C,0
[Window][Spritesheet Editor]
Pos=49,40
Size=974,654
Collapsed=0
DockId=0x0000000C,1
[Window][Timeline]
Pos=8,696
Size=1902,360
Collapsed=0
DockId=0x00000004,0
[Window][Frame Properties]
Pos=1025,40
Size=425,306
Collapsed=0
DockId=0x00000007,0
[Docking][Data]
DockSpace ID=0xFC02A410 Window=0x0E46F4F7 Pos=8,40 Size=1902,1016 Split=Y
DockNode ID=0x00000003 Parent=0xFC02A410 SizeRef=1902,654 Split=X
DockNode ID=0x00000001 Parent=0x00000003 SizeRef=1442,1016 Split=X Selected=0x024430EF
DockNode ID=0x00000005 Parent=0x00000001 SizeRef=1015,654 Split=X Selected=0x024430EF
DockNode ID=0x0000000B Parent=0x00000005 SizeRef=39,654 Selected=0x18A5FDB9
DockNode ID=0x0000000C Parent=0x00000005 SizeRef=974,654 CentralNode=1 Selected=0x024430EF
DockNode ID=0x00000006 Parent=0x00000001 SizeRef=425,654 Split=Y Selected=0x754E368F
DockNode ID=0x00000007 Parent=0x00000006 SizeRef=631,306 Selected=0x754E368F
DockNode ID=0x00000008 Parent=0x00000006 SizeRef=631,346 Selected=0x8A65D963
DockNode ID=0x00000002 Parent=0x00000003 SizeRef=458,1016 Split=Y Selected=0x4EFD0020
DockNode ID=0x00000009 Parent=0x00000002 SizeRef=634,346 Selected=0x4EFD0020
DockNode ID=0x0000000A Parent=0x00000002 SizeRef=634,306 Selected=0xC1986EE2
DockNode ID=0x00000004 Parent=0xFC02A410 SizeRef=1902,360 Selected=0x4F89F0DC
)";
void settings_save(Settings* self); void settings_save(Settings* self);
void settings_init(Settings* self); void settings_init(Settings* self);
std::string settings_path_get(void);

View File

@@ -24,9 +24,10 @@ static void _draw(State* self)
void init(State* self) void init(State* self)
{ {
settings_init(&self->settings);
log_info(STATE_INIT_INFO); log_info(STATE_INIT_INFO);
settings_init(&self->settings);
if (!SDL_Init(SDL_INIT_VIDEO)) if (!SDL_Init(SDL_INIT_VIDEO))
{ {
@@ -76,8 +77,6 @@ void init(State* self)
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
glewInit();
self->glContext = SDL_GL_CreateContext(self->window); self->glContext = SDL_GL_CreateContext(self->window);
if (!self->glContext) if (!self->glContext)
@@ -86,6 +85,8 @@ void init(State* self)
quit(self); quit(self);
} }
glewInit();
log_info(std::format(STATE_GL_CONTEXT_INIT_INFO, (const char*)glGetString(GL_VERSION))); log_info(std::format(STATE_GL_CONTEXT_INIT_INFO, (const char*)glGetString(GL_VERSION)));
glEnable(GL_BLEND); glEnable(GL_BLEND);
@@ -100,6 +101,7 @@ void init(State* self)
clipboard_init(&self->clipboard, &self->anm2); clipboard_init(&self->clipboard, &self->anm2);
snapshots_init(&self->snapshots, &self->anm2, &self->reference, &self->preview); snapshots_init(&self->snapshots, &self->anm2, &self->reference, &self->preview);
preview_init(&self->preview, &self->anm2, &self->reference, &self->resources, &self->settings); preview_init(&self->preview, &self->anm2, &self->reference, &self->resources, &self->settings);
generate_preview_init(&self->generatePreview, &self->anm2, &self->reference, &self->resources, &self->settings);
editor_init(&self->editor, &self->anm2, &self->reference, &self->resources, &self->settings); editor_init(&self->editor, &self->anm2, &self->reference, &self->resources, &self->settings);
imgui_init imgui_init
@@ -111,6 +113,7 @@ void init(State* self)
&self->reference, &self->reference,
&self->editor, &self->editor,
&self->preview, &self->preview,
&self->generatePreview,
&self->settings, &self->settings,
&self->snapshots, &self->snapshots,
&self->clipboard, &self->clipboard,
@@ -118,7 +121,7 @@ void init(State* self)
&self->glContext &self->glContext
); );
if (self->is_argument()) if (!self->argument.empty())
{ {
anm2_deserialize(&self->anm2, &self->resources, self->argument); anm2_deserialize(&self->anm2, &self->resources, self->argument);
window_title_from_path_set(self->window, self->argument); window_title_from_path_set(self->window, self->argument);
@@ -152,6 +155,7 @@ void loop(State* self)
void quit(State* self) void quit(State* self)
{ {
imgui_free(); imgui_free();
generate_preview_free(&self->generatePreview);
preview_free(&self->preview); preview_free(&self->preview);
editor_free(&self->editor); editor_free(&self->editor);
resources_free(&self->resources); resources_free(&self->resources);

View File

@@ -32,6 +32,7 @@ struct State
Dialog dialog; Dialog dialog;
Editor editor; Editor editor;
Preview preview; Preview preview;
GeneratePreview generatePreview;
Anm2 anm2; Anm2 anm2;
Anm2Reference reference; Anm2Reference reference;
Resources resources; Resources resources;
@@ -43,9 +44,6 @@ struct State
u64 lastTick{}; u64 lastTick{};
u64 tick{}; u64 tick{};
bool isRunning = true; bool isRunning = true;
bool is_last_action() const { return !lastAction.empty(); }
bool is_argument() const { return !argument.empty(); }
}; };
void init(State* state); void init(State* state);

View File

@@ -35,12 +35,16 @@ bool texture_from_path_init(Texture* self, const std::string& path)
{ {
*self = Texture{}; *self = Texture{};
u8* data = stbi_load(path.c_str(), &self->size.x, &self->size.y, &self->channels, TEXTURE_CHANNELS); u8* data = stbi_load(path.c_str(), &self->size.x, &self->size.y, &self->channels, TEXTURE_CHANNELS);
if (!data) if (!data)
{ {
log_error(std::format(TEXTURE_INIT_ERROR, path)); data = stbi_load(path_canonical_resolve(path).c_str(), &self->size.x, &self->size.y, &self->channels, TEXTURE_CHANNELS);
self->isInvalid = true; if (!data)
return false; {
log_error(std::format(TEXTURE_INIT_ERROR, path));
self->isInvalid = true;
return false;
}
} }
log_info(std::format(TEXTURE_INIT_INFO, path)); log_info(std::format(TEXTURE_INIT_INFO, path));
@@ -82,8 +86,16 @@ bool texture_from_rgba_init(Texture* self, ivec2 size, s32 channels, const u8* d
bool texture_from_rgba_write(const std::string& path, const u8* data, ivec2 size) bool texture_from_rgba_write(const std::string& path, const u8* data, ivec2 size)
{ {
bool isSuccess = stbi_write_png(path.c_str(), size.x, size.y, TEXTURE_CHANNELS, data, size.x * TEXTURE_CHANNELS);
if (!isSuccess)
{
isSuccess = stbi_write_png(path_canonical_resolve(path).c_str(), size.x, size.y, TEXTURE_CHANNELS, data, size.x * TEXTURE_CHANNELS);
if (!isSuccess) log_info(std::format(TEXTURE_SAVE_ERROR, path));
}
log_info(std::format(TEXTURE_SAVE_INFO, path)); log_info(std::format(TEXTURE_SAVE_INFO, path));
return (bool)stbi_write_png(path.c_str(), size.x, size.y, TEXTURE_CHANNELS, data, size.x * TEXTURE_CHANNELS);
return isSuccess;
} }
bool texture_from_gl_write(Texture* self, const std::string& path) bool texture_from_gl_write(Texture* self, const std::string& path)

View File

@@ -6,6 +6,7 @@
#define TEXTURE_INIT_INFO "Initialized texture from file: {}" #define TEXTURE_INIT_INFO "Initialized texture from file: {}"
#define TEXTURE_INIT_ERROR "Failed to initialize texture from file: {}" #define TEXTURE_INIT_ERROR "Failed to initialize texture from file: {}"
#define TEXTURE_SAVE_INFO "Saved texture to: {}" #define TEXTURE_SAVE_INFO "Saved texture to: {}"
#define TEXTURE_SAVE_ERROR "Failed to save texture to: {}"
struct Texture struct Texture
{ {