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
- Can output .webm or *.png sequence
- Cutting, copying and pasting
- Additional wizard options
- Robust snapshot (undo/redo) system
- 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
Download these from your package manager:

View File

@@ -25,6 +25,8 @@
#include <unordered_set>
#include <vector>
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
@@ -123,6 +125,65 @@ static inline bool string_to_bool(const std::string& string)
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)
{
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();
}
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)
{
std::error_code 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;
std::error_code ec;

View File

@@ -217,6 +217,7 @@ enum ShaderType
{
SHADER_LINE,
SHADER_TEXTURE,
SHADER_GRID,
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_TRANSFORM "u_transform"
#define SHADER_UNIFORM_TINT "u_tint"
#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"
const ShaderData SHADER_DATA[SHADER_COUNT] =
{
{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();
}
if (anm2Element == ANM2_ELEMENT_SPRITESHEET)
resources_texture_init(resources, spritesheet->path , id);
if (anm2Element == ANM2_ELEMENT_SPRITESHEET && resources)
resources_texture_init(resources, spritesheet->path, id);
xmlChild = xmlElement->FirstChildElement();
@@ -630,14 +630,6 @@ bool anm2_deserialize(Anm2* self, Resources* resources, const std::string& path)
void anm2_layer_add(Anm2* self)
{
s32 id = map_next_id_get(self->layers);
self->layers[id] = Anm2Layer{};
@@ -1127,3 +1119,52 @@ void anm2_frame_bake(Anm2* self, Anm2Reference* reference, s32 interval, bool is
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_CREATED_ON_FORMAT "%d-%B-%Y %I:%M:%S %p"
#define ANM2_EXTENSION "anm2"
#define ANM2_SPRITESHEET_EXTENSION "png"
/* Elements */
#define ANM2_ELEMENT_LIST \
X(ANIMATED_ACTOR, "AnimatedActor") \
@@ -151,7 +154,7 @@ struct Anm2Frame
{
bool isVisible = true;
bool isInterpolated = false;
f32 rotation = 1.0f;
f32 rotation{};
s32 delay = ANM2_FRAME_DELAY_MIN;
s32 atFrame = INDEX_NONE;
s32 eventID = ID_NONE;
@@ -159,7 +162,7 @@ struct Anm2Frame
vec2 pivot{};
vec2 position{};
vec2 size{};
vec2 scale{};
vec2 scale = {100, 100};
vec3 offsetRGB{};
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);
Anm2Frame* anm2_frame_from_reference(Anm2* self, Anm2Reference* reference);
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_from_time(Anm2* self, Anm2Frame* frame, Anm2Reference reference, f32 time);
void anm2_reference_clear(Anm2Reference* self);
@@ -280,3 +283,5 @@ void anm2_animation_length_set(Anm2Animation* self);
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_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);
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
glGenVertexArrays(1, &self->textureVAO);
glGenBuffers(1, &self->textureVBO);
@@ -93,7 +106,7 @@ void canvas_init(Canvas* self, const vec2& 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);
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)
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);
mat4 inverseTransform = glm::inverse(transform);
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);
glUseProgram(0);
}

View File

@@ -14,6 +14,7 @@
static const vec2 CANVAS_GRID_SIZE = {3200, 1600};
static const vec2 CANVAS_PIVOT_SIZE = {8, 8};
static const vec2 CANVAS_SCALE_DEFAULT = {1.0f, 1.0f};
const f32 CANVAS_AXIS_VERTICES[] =
{
@@ -23,6 +24,13 @@ const f32 CANVAS_AXIS_VERTICES[] =
0.0f, CANVAS_LINE_LENGTH
};
const f32 CANVAS_GRID_VERTICES[] =
{
-1.0f, -1.0f,
3.0f, -1.0f,
-1.0f, 3.0f
};
struct Canvas
{
GLuint fbo{};
@@ -41,13 +49,13 @@ struct Canvas
};
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_bind(Canvas* self);
void canvas_viewport_set(Canvas* self);
void canvas_unbind(void);
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_rect_draw(Canvas* self, const GLuint& shader, const mat4& transform, const vec4& color);
void canvas_free(Canvas* self);

View File

@@ -1,5 +1,9 @@
#include "dialog.h"
#ifdef _WIN32
#include <windows.h>
#endif
static void _dialog_callback(void* userdata, const char* const* filelist, s32 filter)
{
Dialog* self;
@@ -67,6 +71,17 @@ void dialog_ffmpeg_path_set(Dialog* self)
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
dialog_reset(Dialog* self)
{

View File

@@ -54,3 +54,4 @@ void dialog_render_path_set(Dialog* self);
void dialog_render_directory_set(Dialog* self);
void dialog_ffmpeg_path_set(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;
GLuint& shaderLine = self->resources->shaders[SHADER_LINE];
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);
canvas_texture_set(&self->canvas);
@@ -48,7 +49,7 @@ void editor_draw(Editor* self)
}
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();
}

View File

@@ -34,7 +34,7 @@ struct Editor
GLuint textureVBO;
GLuint borderVAO;
GLuint borderVBO;
s32 spritesheetID = -1;
s32 spritesheetID = ID_NONE;
};
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)
{
static auto& columns = self->settings->generateColumns;
static auto& count = self->settings->generateCount;
static GLuint& shaderTexture = self->resources->shaders[SHADER_TEXTURE];
const mat4 transform = canvas_transform_get(&self->canvas, {}, CANVAS_ZOOM_DEFAULT, ORIGIN_CENTER);
/* TODO
f32& zoom = self->settings->previewZoom;
GLuint& shaderLine = self->resources->shaders[SHADER_LINE];
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_viewport_set(&self->canvas);
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)
if (item && texture && !texture->isInvalid)
{
Anm2Frame root;
mat4 rootModel = mat4(1.0f);
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);
anm2_frame_from_time(self->anm2, &root, Anm2Reference{animationID, ANM2_ROOT}, self->time);
if (self->settings->previewIsRootTransform)
rootModel = quad_parent_model_get(root.position, vec2(0.0f), root.rotation, PERCENT_TO_UNIT(root.scale));
// Root
if (self->settings->previewIsTargets && animation->rootAnimation.isVisible && root.isVisible)
{
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);
vec2 uvMin = crop / vec2(texture->size);
vec2 uvMax = (crop + 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);
mat4 model = quad_model_get(size, {}, pivot, {}, CANVAS_SCALE_DEFAULT);
mat4 generateTransform = transform * 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);
canvas_texture_draw(&self->canvas, shaderTexture, texture->id, generateTransform, vertices, COLOR_OPAQUE, COLOR_OFFSET_NONE);
}
}
s32& animationOverlayID = self->animationOverlayID;
Anm2Animation* animationOverlay = map_find(self->anm2->animations, animationOverlayID);
canvas_unbind();
*/
}
void generate_preview_free(GeneratePreview* self)

View File

@@ -5,6 +5,9 @@
#include "settings.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};
struct GeneratePreview

View File

@@ -19,6 +19,34 @@ static bool _imgui_window_color_from_position_get(SDL_Window* self, const vec2&
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>
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_TEXT:
case IMGUI_DRAG_FLOAT:
case IMGUI_SLIDER_FLOAT:
case IMGUI_COLOR_EDIT:
ImGui::SetNextItemWidth(size.x);
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_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_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_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));
@@ -522,6 +553,24 @@ static bool _imgui_option_popup(ImguiItem self, Imgui* imgui)
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)
{
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);
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, defaultItemSpacing);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, defaultWindowPadding);
_imgui_context_menu(self);
ImGui::PopStyleVar(2);
_imgui_end_child(); // IMGUI_TIMELINE_FRAMES_CHILD
};
@@ -1056,6 +1110,7 @@ static void _imgui_timeline(Imgui* self)
_imgui_end_child(); // IMGUI_TIMELINE_CHILD
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_button(IMGUI_TIMELINE_ADD_ITEM, self);
@@ -1066,7 +1121,7 @@ static void _imgui_timeline(Imgui* 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)
{
@@ -1098,8 +1153,13 @@ static void _imgui_timeline(Imgui* self)
self->preview->isPlaying = !self->preview->isPlaying;
}
if (_imgui_button(IMGUI_ADD_FRAME, self))
anm2_frame_add(self->anm2, nullptr, self->reference, (s32)time);
if (_imgui_button(IMGUI_ADD_FRAME.copy({!item}), self))
{
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))
{
@@ -1163,23 +1223,18 @@ static void _imgui_taskbar(Imgui* self)
if (imgui_begin_popup(IMGUI_FILE.popup, self))
{
_imgui_selectable(IMGUI_NEW, self); // imgui_file_new
_imgui_selectable(IMGUI_OPEN, self); // imgui_file_open
_imgui_selectable(IMGUI_SAVE, self); // imgui_file_save
_imgui_selectable(IMGUI_SAVE_AS, self); // imgui_file_save_as
_imgui_selectable(IMGUI_NEW, self);
_imgui_selectable(IMGUI_OPEN, self);
_imgui_selectable(IMGUI_SAVE, self);
_imgui_selectable(IMGUI_SAVE_AS, self);
_imgui_selectable(IMGUI_EXPLORE_ANM2_LOCATION, self);
_imgui_selectable(IMGUI_EXIT, self);
imgui_end_popup(self);
}
if (self->dialog->isSelected && self->dialog->type == DIALOG_ANM2_OPEN)
{
*self->reference = Anm2Reference{};
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));
_imgui_anm2_new(self, self->dialog->path);
dialog_reset(self->dialog);
}
@@ -1191,12 +1246,20 @@ static void _imgui_taskbar(Imgui* self)
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);
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_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_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))
{
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_input_int2(IMGUI_GENERATE_ANIMATION_FROM_GRID_START_POSITION, self, self->settings->generateStartPosition);
_imgui_input_int2(IMGUI_GENERATE_ANIMATION_FROM_GRID_FRAME_SIZE, self, self->settings->generateFrameSize);
_imgui_input_int2(IMGUI_GENERATE_ANIMATION_FROM_GRID_PIVOT, self, self->settings->generatePivot);
_imgui_input_int(IMGUI_GENERATE_ANIMATION_FROM_GRID_ROWS, self, self->settings->generateRows);
_imgui_input_int(IMGUI_GENERATE_ANIMATION_FROM_GRID_COLUMNS, self, self->settings->generateColumns);
_imgui_input_int
(
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_input_int2(IMGUI_GENERATE_ANIMATION_FROM_GRID_START_POSITION, self, startPosition);
_imgui_input_int2(IMGUI_GENERATE_ANIMATION_FROM_GRID_SIZE, self, size);
_imgui_input_int2(IMGUI_GENERATE_ANIMATION_FROM_GRID_PIVOT, self, pivot);
_imgui_input_int(IMGUI_GENERATE_ANIMATION_FROM_GRID_ROWS, self, rows);
_imgui_input_int(IMGUI_GENERATE_ANIMATION_FROM_GRID_COLUMNS, self, columns);
_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_end_child(); //IMGUI_GENERATE_ANIMATION_FROM_GRID_OPTIONS_CHILD
ImGui::SameLine();
_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_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);
_imgui_end_child(); // IMGUI_FOOTER_CHILD
@@ -1316,6 +1396,24 @@ static void _imgui_taskbar(Imgui* 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))
{
_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;
dialog_reset(self->dialog);
@@ -1356,6 +1458,14 @@ static void _imgui_taskbar(Imgui* self)
{
bool isRenderStart = true;
if (!std::filesystem::exists(self->settings->ffmpegPath))
{
imgui_log_push(self, IMGUI_LOG_RENDER_ANIMATION_FFMPEG_PATH_ERROR);
isRenderStart = false;
}
if (isRenderStart)
{
switch (self->settings->renderType)
{
case RENDER_PNG:
@@ -1367,7 +1477,7 @@ static void _imgui_taskbar(Imgui* self)
break;
case RENDER_GIF:
case RENDER_WEBM:
if (!path_valid(self->settings->renderPath))
if (!path_is_valid(self->settings->renderPath))
{
imgui_log_push(self, IMGUI_LOG_RENDER_ANIMATION_PATH_ERROR);
isRenderStart = false;
@@ -1375,6 +1485,7 @@ static void _imgui_taskbar(Imgui* self)
default:
break;
}
}
if (isRenderStart)
preview_render_start(self->preview);
@@ -1577,6 +1688,8 @@ static void _imgui_animations(Imgui* self)
ImGui::PopID();
};
_imgui_context_menu(self);
_imgui_end_child(); // animationsChild
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];
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))
{
@@ -1864,17 +1977,8 @@ static void _imgui_spritesheets(Imgui* self)
if (self->dialog->isSelected && self->dialog->type == DIALOG_SPRITESHEET_ADD)
{
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(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);
_imgui_spritesheet_add(self, self->dialog->path);
dialog_reset(self->dialog);
std::filesystem::current_path(workingPath);
}
if (_imgui_button(IMGUI_SPRITESHEETS_RELOAD.copy({selectedIDs.empty()}), self))
@@ -1935,10 +2039,10 @@ static void _imgui_spritesheets(Imgui* self)
{
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];
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);
imgui_log_push(self, std::format(IMGUI_LOG_SPRITESHEET_SAVE_FORMAT, id, spritesheet->path));
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 isMod = ImGui::IsKeyDown(IMGUI_INPUT_SHIFT);
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 isMouseDown = ImGui::IsMouseDown(ImGuiMouseButton_Left);
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 isMouseMiddleDown = ImGui::IsMouseDown(ImGuiMouseButton_Middle);
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 f32 mouseWheel = ImGui::GetIO().MouseWheel;
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)
{
@@ -2408,6 +2496,7 @@ void imgui_init
Anm2Reference* reference,
Editor* editor,
Preview* preview,
GeneratePreview* generatePreview,
Settings* settings,
Snapshots* snapshots,
Clipboard* clipboard,
@@ -2423,6 +2512,7 @@ void imgui_init
self->reference = reference;
self->editor = editor;
self->preview = preview;
self->generatePreview = generatePreview;
self->settings = settings;
self->snapshots = snapshots;
self->clipboard = clipboard;
@@ -2441,7 +2531,7 @@ void imgui_init
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
io.ConfigWindowsMoveFromTitleBarOnly = true;
ImGui::LoadIniSettingsFromDisk(SETTINGS_PATH);
ImGui::LoadIniSettingsFromDisk(settings_path_get().c_str());
}
void imgui_update(Imgui* self)
@@ -2453,7 +2543,6 @@ void imgui_update(Imgui* self)
_imgui_taskbar(self);
_imgui_dock(self);
_imgui_log(self);
_imgui_persistent(self);
if (self->isContextualActionsEnabled)
{
@@ -2483,15 +2572,20 @@ void imgui_update(Imgui* self)
switch (event.type)
{
case SDL_EVENT_QUIT:
if (!self->snapshots->undoStack.is_empty())
case SDL_EVENT_DROP_FILE:
{
if (imgui_is_popup_open(IMGUI_EXIT_CONFIRMATION.label_get()))
self->isQuit = true;
else
imgui_open_popup(IMGUI_EXIT_CONFIRMATION.label_get());
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;
}
else
case SDL_EVENT_QUIT:
imgui_quit(self);
if (imgui_is_popup_open(IMGUI_EXIT_CONFIRMATION.popup))
self->isQuit = true;
break;
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)
@@ -2513,7 +2605,6 @@ void imgui_free(void)
{
ImGui_ImplSDL3_Shutdown();
ImGui_ImplOpenGL3_Shutdown();
ImGui::SaveIniSettingsToDisk(SETTINGS_PATH);
ImGui::SaveIniSettingsToDisk(settings_path_get().c_str());
ImGui::DestroyContext();
}

View File

@@ -5,6 +5,7 @@
#include "editor.h"
#include "ffmpeg.h"
#include "preview.h"
#include "generate_preview.h"
#include "resources.h"
#include "settings.h"
#include "snapshots.h"
@@ -67,13 +68,16 @@
#define IMGUI_ACTION_TRIGGER_MOVE "Trigger AtFrame"
#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_SAVE_FORMAT "Saved anm2 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_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_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_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."
@@ -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_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 vec2 IMGUI_SPRITESHEET_EDITOR_CROP_FORGIVENESS = {1, 1};
@@ -168,6 +172,7 @@ struct Imgui
Anm2Reference* reference = nullptr;
Editor* editor = nullptr;
Preview* preview = nullptr;
GeneratePreview* generatePreview = nullptr;
Settings* settings = nullptr;
Snapshots* snapshots = nullptr;
Clipboard* clipboard = nullptr;
@@ -182,6 +187,7 @@ struct Imgui
bool isCursorSet = false;
bool isContextualActionsEnabled = true;
bool isQuit = false;
bool isTryQuit = false;
};
typedef void(*ImguiFunction)(Imgui*);
@@ -235,6 +241,21 @@ static inline void imgui_file_save_as(Imgui* self)
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)
{
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);
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;
}
@@ -385,7 +406,7 @@ static inline bool imgui_begin_popup_modal(const std::string& label, Imgui* imgu
{
imgui_pending_popup_process(imgui);
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);
return isActivated;
}
@@ -402,6 +423,8 @@ static inline void imgui_end_popup(Imgui* imgui)
imgui_pending_popup_process(imgui);
}
enum ImguiItemType
{
IMGUI_ITEM,
@@ -417,6 +440,8 @@ enum ImguiItemType
IMGUI_CHECKBOX,
IMGUI_INPUT_INT,
IMGUI_INPUT_TEXT,
IMGUI_INPUT_FLOAT,
IMGUI_SLIDER_FLOAT,
IMGUI_DRAG_FLOAT,
IMGUI_COLOR_EDIT,
IMGUI_COMBO,
@@ -470,14 +495,15 @@ struct ImguiItem
f32 speed = 0.25f;
s32 step = 1;
s32 stepFast = 10;
s32 border{};
s32 max{};
s32 min{};
s32 max{};
s32 value{};
vec2 atlasOffset;
s32 border{};
s32 flags{};
s32 windowFlags{};
s32 rowCount = 0;
vec2 atlasOffset;
void construct()
{
@@ -638,6 +664,28 @@ IMGUI_ITEM(IMGUI_SAVE_AS,
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,
self.label = "&Wizard",
self.tooltip = "Opens the wizard menu, for neat functions related to the .anm2.",
@@ -647,12 +695,17 @@ IMGUI_ITEM(IMGUI_WIZARD,
self.isSameLine = true
);
#define IMGUI_GENERATE_ANIMATION_FROM_GRID_PADDING 40
IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID,
self.label = "&Generate Animation from Grid",
self.tooltip = "Generate a new animation from grid values.",
self.popup = "Generate Animation from Grid",
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,
@@ -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."
);
IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_FRAME_SIZE,
self.label = "Frame Size",
IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_SIZE,
self.label = "Size",
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
);
IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_FRAME_COUNT,
IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_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,
@@ -713,9 +767,30 @@ IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_PREVIEW_CHILD,
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,
self.label = "Generate",
self.tooltip = "Generate an animation with the used settings.",
self.undoAction = "Generate Animation from Grid",
self.rowCount = IMGUI_OPTION_POPUP_ROW_COUNT,
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.popup = "Change All Frame Properties",
self.popupType = IMGUI_POPUP_CENTER_WINDOW,
self.popupSize = {500, 380},
self.isSeparator = true
self.popupSize = {500, 380}
);
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
);
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,
self.label = "&Render Animation",
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,
self.label = "## Spritesheet Child",
self.size = {0, 175},
self.rowCount = 1,
self.size = {0, IMGUI_SPRITESHEET_PREVIEW_SIZE.y + 40},
self.flags = true
);
@@ -1253,6 +1361,7 @@ IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_POSITION,
self.label = "Position",
self.tooltip = "Change the position of the selected frame.",
self.undoAction = "Frame Position",
self.isUseItemActivated = true,
self.format = "%.0f"
);
@@ -1260,6 +1369,7 @@ IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_CROP,
self.label = "Crop",
self.tooltip = "Change the crop position of the selected frame.",
self.undoAction = "Frame Crop",
self.isUseItemActivated = true,
self.format = "%.0f"
);
@@ -1267,6 +1377,7 @@ IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_SIZE,
self.label = "Size",
self.tooltip = "Change the size of the crop of the selected frame.",
self.undoAction = "Frame Size",
self.isUseItemActivated = true,
self.format = "%.0f"
);
@@ -1274,6 +1385,7 @@ IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_PIVOT,
self.label = "Pivot",
self.tooltip = "Change the pivot of the selected frame.",
self.undoAction = "Frame Pivot",
self.isUseItemActivated = true,
self.format = "%.0f"
);
@@ -1282,6 +1394,7 @@ IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_SCALE,
self.tooltip = "Change the scale of the selected frame.",
self.undoAction = "Frame Scale",
self.format = "%.0f",
self.isUseItemActivated = true,
self.value = 100
);
@@ -1289,6 +1402,7 @@ IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_ROTATION,
self.label = "Rotation",
self.tooltip = "Change the rotation of the selected frame.",
self.undoAction = "Frame Rotation",
self.isUseItemActivated = true,
self.format = "%.0f"
);
@@ -1296,6 +1410,7 @@ IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_DELAY,
self.label = "Duration",
self.tooltip = "Change the duration of the selected frame.",
self.undoAction = "Frame Duration",
self.isUseItemActivated = true,
self.min = ANM2_FRAME_NUM_MIN,
self.max = ANM2_FRAME_NUM_MAX,
self.value = ANM2_FRAME_NUM_MIN
@@ -1305,6 +1420,7 @@ IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_TINT,
self.label = "Tint",
self.tooltip = "Change the tint of the selected frame.",
self.undoAction = "Frame Tint",
self.isUseItemActivated = true,
self.value = 1
);
@@ -1312,6 +1428,7 @@ IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_COLOR_OFFSET,
self.label = "Color Offset",
self.tooltip = "Change the color offset of the selected frame.",
self.undoAction = "Frame Color Offset",
self.isUseItemActivated = true,
self.value = 0
);
@@ -1898,10 +2015,7 @@ IMGUI_ITEM(IMGUI_CHANGE_INPUT_INT,
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
IMGUI_ITEM(IMGUI_POPUP_OK,
@@ -1938,6 +2052,7 @@ void imgui_init
Anm2Reference* reference,
Editor* editor,
Preview* preview,
GeneratePreview* generatePreview,
Settings* settings,
Snapshots* snapshots,
Clipboard* clipboard,

View File

@@ -1,12 +1,42 @@
#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
main(s32 argc, char* argv[])
{
State state;
if (argc > 0 && 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);

View File

@@ -1,3 +1,8 @@
#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"

View File

@@ -83,9 +83,9 @@ void preview_draw(Preview* self)
ivec2& gridSize = self->settings->previewGridSize;
ivec2& gridOffset = self->settings->previewGridOffset;
vec4& gridColor = self->settings->previewGridColor;
f32& zoom = self->settings->previewZoom;
GLuint& shaderLine = self->resources->shaders[SHADER_LINE];
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);
canvas_texture_set(&self->canvas);
@@ -95,7 +95,7 @@ void preview_draw(Preview* self)
canvas_clear(self->settings->previewBackgroundColor);
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)
canvas_axes_draw(&self->canvas, shaderLine, transform, self->settings->previewAxesColor);
@@ -136,19 +136,19 @@ void preview_draw(Preview* self)
if (!frame.isVisible)
continue;
mat4 model = quad_model_get(frame.size, frame.position, frame.pivot, frame.rotation, PERCENT_TO_UNIT(frame.scale));
mat4 layerTransform = transform * (rootModel * model);
Texture* texture = map_find(self->resources->textures, self->anm2->layers[id].spritesheetID);
if (!texture || texture->isInvalid)
continue;
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);
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);

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)
{
u8* selfPointer = (u8*)self;
@@ -136,59 +144,119 @@ static void _settings_setting_write(Settings* self, std::ostream& out, SettingsE
void settings_save(Settings* self)
{
std::ifstream input(SETTINGS_PATH);
std::string oldContents;
const std::string path = settings_path_get();
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;
}
oldContents.assign((std::istreambuf_iterator<char>(input)), std::istreambuf_iterator<char>());
input.close();
std::ofstream output(SETTINGS_PATH);
if (!output)
{
log_error(std::format(SETTINGS_INIT_ERROR, SETTINGS_PATH));
return;
}
output << SETTINGS_SECTION << "\n";
out << SETTINGS_SECTION << "\n";
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";
output << oldContents;
out << "\n" << SETTINGS_SECTION_IMGUI << "\n";
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)
{
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));
return;
log_info(std::format(SETTINGS_INIT_INFO, path));
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;
bool inSettingsSection = false;
while (std::getline(file, line))
while (std::getline(*in, line))
{
if (line == SETTINGS_SECTION)
{
inSettingsSection = true;
continue;
}
if (line == SETTINGS_SECTION_IMGUI) break;
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_SECTION "[Settings]"
#define SETTINGS_SECTION_IMGUI "# Dear ImGui"
#define SETTINGS_INIT_ERROR "Failed to read settings file! ({})"
#define SETTINGS_PATH "settings.ini"
#define SETTINGS_INIT_ERROR "Failed to read settings file: {}"
#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_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
{
@@ -48,6 +58,7 @@ struct Settings
bool changeIsVisible{};
bool changeIsInterpolated{};
s32 changeNumberFrames = 1;
f32 scaleValue = 1.0f;
bool previewIsAxes = true;
bool previewIsGrid = true;
bool previewIsRootTransform = false;
@@ -64,11 +75,11 @@ struct Settings
vec4 previewAxesColor = {1.0, 1.0, 1.0, 0.125};
vec4 previewBackgroundColor = {0.113, 0.184, 0.286, 1.0};
ivec2 generateStartPosition = {0, 0};
ivec2 generateFrameSize = {64, 64};
ivec2 generateSize = {64, 64};
ivec2 generatePivot = {32, 32};
s32 generateRows = 4;
s32 generateColumns = 4;
s32 generateFrameCount = 16;
s32 generateCount = 16;
s32 generateDelay = 1;
bool editorIsGrid = true;
bool editorIsGridSnap = true;
@@ -89,7 +100,7 @@ struct Settings
s32 renderType = RENDER_PNG;
std::string renderPath = ".";
std::string renderFormat = "{}.png";
std::string ffmpegPath = "/usr/bin/ffmpeg";
std::string ffmpegPath{};
};
const SettingsEntry SETTINGS_ENTRIES[] =
@@ -121,6 +132,7 @@ const SettingsEntry SETTINGS_ENTRIES[] =
{"changeIsVisible", TYPE_BOOL, offsetof(Settings, changeIsVisibleSet)},
{"changeIsInterpolated", TYPE_BOOL, offsetof(Settings, changeIsInterpolatedSet)},
{"changeNumberFrames", TYPE_INT, offsetof(Settings, changeNumberFrames)},
{"scaleValue", TYPE_FLOAT, offsetof(Settings, scaleValue)},
{"previewIsAxes", TYPE_BOOL, offsetof(Settings, previewIsAxes)},
{"previewIsGrid", TYPE_BOOL, offsetof(Settings, previewIsGrid)},
{"previewIsRootTransform", TYPE_BOOL, offsetof(Settings, previewIsRootTransform)},
@@ -136,12 +148,12 @@ const SettingsEntry SETTINGS_ENTRIES[] =
{"previewGridColor", TYPE_VEC4, offsetof(Settings, previewGridColor)},
{"previewAxesColor", TYPE_VEC4, offsetof(Settings, previewAxesColor)},
{"previewBackgroundColor", TYPE_VEC4, offsetof(Settings, previewBackgroundColor)},
{"generateStartPosition", TYPE_VEC2, offsetof(Settings, generateStartPosition)},
{"generateFrameSize", TYPE_VEC2, offsetof(Settings, generateFrameSize)},
{"generatePivot", TYPE_VEC2, offsetof(Settings, generatePivot)},
{"generateStartPosition", TYPE_IVEC2, offsetof(Settings, generateStartPosition)},
{"generateSize", TYPE_IVEC2, offsetof(Settings, generateSize)},
{"generatePivot", TYPE_IVEC2, offsetof(Settings, generatePivot)},
{"generateRows", TYPE_INT, offsetof(Settings, generateRows)},
{"generateColumns", TYPE_INT, offsetof(Settings, generateColumns)},
{"generateFrameCount", TYPE_INT, offsetof(Settings, generateFrameCount)},
{"generateCount", TYPE_INT, offsetof(Settings, generateCount)},
{"generateDelay", TYPE_INT, offsetof(Settings, generateDelay)},
{"editorIsGrid", TYPE_BOOL, offsetof(Settings, editorIsGrid)},
{"editorIsGridSnap", TYPE_BOOL, offsetof(Settings, editorIsGridSnap)},
@@ -166,5 +178,192 @@ const SettingsEntry 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_init(Settings* self);
std::string settings_path_get(void);

View File

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

View File

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

View File

@@ -36,12 +36,16 @@ bool texture_from_path_init(Texture* self, const std::string& path)
*self = Texture{};
u8* data = stbi_load(path.c_str(), &self->size.x, &self->size.y, &self->channels, TEXTURE_CHANNELS);
if (!data)
{
data = stbi_load(path_canonical_resolve(path).c_str(), &self->size.x, &self->size.y, &self->channels, TEXTURE_CHANNELS);
if (!data)
{
log_error(std::format(TEXTURE_INIT_ERROR, path));
self->isInvalid = true;
return false;
}
}
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 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));
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)

View File

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