The Omega Update(TM) Part 5 (Finishing)
This commit is contained in:
@@ -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:
|
||||
|
70
src/COMMON.h
70
src/COMMON.h
@@ -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;
|
||||
|
51
src/PACKED.h
51
src/PACKED.h
@@ -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}
|
||||
};
|
59
src/anm2.cpp
59
src/anm2.cpp
@@ -588,7 +588,7 @@ bool anm2_deserialize(Anm2* self, Resources* resources, const std::string& path)
|
||||
xmlAttribute = xmlAttribute->Next();
|
||||
}
|
||||
|
||||
if (anm2Element == ANM2_ELEMENT_SPRITESHEET)
|
||||
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++;
|
||||
}
|
||||
}
|
11
src/anm2.h
11
src/anm2.h
@@ -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);
|
@@ -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);
|
||||
}
|
||||
|
||||
|
12
src/canvas.h
12
src/canvas.h
@@ -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);
|
||||
|
@@ -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)
|
||||
{
|
||||
|
@@ -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);
|
@@ -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();
|
||||
}
|
||||
|
@@ -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);
|
||||
|
@@ -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)
|
||||
|
@@ -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
|
||||
|
237
src/imgui.cpp
237
src/imgui.cpp
@@ -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();
|
||||
}
|
153
src/imgui.h
153
src/imgui.h
@@ -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,
|
||||
|
30
src/main.cpp
30
src/main.cpp
@@ -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);
|
||||
|
||||
|
@@ -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"
|
@@ -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);
|
||||
|
122
src/settings.cpp
122
src/settings.cpp
@@ -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));
|
||||
}
|
||||
}
|
217
src/settings.h
217
src/settings.h
@@ -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);
|
@@ -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);
|
||||
|
@@ -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);
|
||||
|
@@ -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)
|
||||
|
@@ -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
|
||||
{
|
||||
|
Reference in New Issue
Block a user