From ea3498692a797c10906ee2d327b886918ac5037d Mon Sep 17 00:00:00 2001 From: shweet Date: Thu, 14 Aug 2025 00:25:35 -0400 Subject: [PATCH] The Omega Update(TM) Part 4 (Massive Refactor, Change All Frame Properties) --- README.md | 16 +- src/COMMON.h | 19 +- src/anm2.cpp | 134 +- src/anm2.h | 33 +- src/canvas.cpp | 36 +- src/canvas.h | 6 +- src/clipboard.cpp | 56 +- src/clipboard.h | 8 +- src/dialog.cpp | 2 - src/editor.cpp | 4 +- src/generate_preview.cpp | 101 ++ src/generate_preview.h | 22 + src/imgui.cpp | 2579 +++++++++++++++--------------- src/imgui.h | 3270 +++++++++++++++++++------------------- src/main.cpp | 2 - src/preview.cpp | 30 +- src/settings.cpp | 15 + src/settings.h | 91 +- src/snapshots.cpp | 2 +- src/snapshots.h | 5 +- src/state.cpp | 4 +- src/state.h | 2 + src/texture.cpp | 30 + src/texture.h | 3 +- src/tool.h | 21 +- 25 files changed, 3364 insertions(+), 3127 deletions(-) create mode 100644 src/generate_preview.cpp create mode 100644 src/generate_preview.h diff --git a/README.md b/README.md index 159edd4..9c42891 100644 --- a/README.md +++ b/README.md @@ -5,14 +5,14 @@ A reimplementation of *The Binding of Isaac: Rebirth*'s proprietary animation editor. Manipulates the XML-based ".anm2" format, used for in-game tweened animations. ## Features - -- Most things present in the original IsaacAnimationEditor.exe, except some stuff like drawing (why not use an art program!) +- Extended version of the original proprietary Nicalis animation editor - Smooth [Dear ImGui](https://github.com/ocornut/imgui) interface; docking, dragging and dropping, etc. -- Keybinds/keyboard control for common actions(see [src/input.h](https://github.com/ShweetsStuff/anm2ed/blob/master/src/input.h)) - -### Known Issues -- Root Transform doesn't work for scale/rotation (matrix math is hard; if you can help me fix it I will give you $100.) -- Some .anm2 files used in Rebirth might not render correctly due to the ordering of layers; just drag and drop to fix the ordering and save, they will work fine afterwards. +- New features + - Can output .webm or *.png sequence + - Cutting, copying and pasting + - Robust snapshot (undo/redo) system + - Additional hotkeys/shortcuts + - Settings that will preserve on exit ## Dependencies Download these from your package manager: @@ -20,6 +20,8 @@ Download these from your package manager: - SDL3 - GLEW +Note, to render animations, you'll need to download [FFmpeg](https://ffmpeg.org/download.html) and specify its install path in the program. + ## Build After cloning and enter the repository's directory, make sure to initialize the submodules: diff --git a/src/COMMON.h b/src/COMMON.h index db49492..6e9921f 100644 --- a/src/COMMON.h +++ b/src/COMMON.h @@ -10,20 +10,20 @@ #include #include -#include #include +#include #include #include -#include #include +#include #include #include -#include #include +#include #include #include -#include #include +#include typedef uint8_t u8; typedef uint16_t u16; @@ -123,11 +123,12 @@ static inline bool string_to_bool(const std::string& string) return lower == "true"; } -static inline void working_directory_from_file_set(const std::string& path) +static inline std::string working_directory_from_file_set(const std::string& path) { std::filesystem::path filePath = path; std::filesystem::path parentPath = filePath.parent_path(); std::filesystem::current_path(parentPath); + return parentPath; }; static inline std::string path_extension_change(const std::string& path, const std::string& extension) @@ -178,6 +179,13 @@ static inline s32 string_to_enum(const std::string& string, const char* const* a return -1; }; +template +T& dummy_value() +{ + static T value{}; + return value; +} + template static inline s32 map_next_id_get(const std::map& map) { @@ -302,6 +310,7 @@ enum DataType TYPE_STRING, TYPE_IVEC2, TYPE_VEC2, + TYPE_VEC3, TYPE_VEC4 }; diff --git a/src/anm2.cpp b/src/anm2.cpp index 6077a47..c69fa9b 100644 --- a/src/anm2.cpp +++ b/src/anm2.cpp @@ -1,5 +1,3 @@ -// Anm2 file format; serializing/deserializing - #include "anm2.h" using namespace tinyxml2; @@ -781,6 +779,7 @@ Anm2Item* anm2_item_from_reference(Anm2* self, Anm2Reference* reference) } } + Anm2Frame* anm2_frame_from_reference(Anm2* self, Anm2Reference* reference) { Anm2Item* item = anm2_item_from_reference(self, reference); @@ -903,7 +902,7 @@ s32 anm2_animation_length_get(Anm2Animation* self) accumulate_max_delay(item.frames); for (const auto& frame : self->triggers.frames) - length = std::max(length, frame.atFrame); + length = std::max(length, frame.atFrame + 1); return length; } @@ -913,7 +912,7 @@ void anm2_animation_length_set(Anm2Animation* self) self->frameNum = anm2_animation_length_get(self); } -Anm2Frame* anm2_frame_add(Anm2* self, Anm2Reference* reference, s32 time) +Anm2Frame* anm2_frame_add(Anm2* self, Anm2Frame* frame, Anm2Reference* reference, s32 time) { Anm2Animation* animation = anm2_animation_from_reference(self, reference); Anm2Item* item = anm2_item_from_reference(self, reference); @@ -923,46 +922,43 @@ Anm2Frame* anm2_frame_add(Anm2* self, Anm2Reference* reference, s32 time) if (item) { - Anm2Frame frame = Anm2Frame{}; - s32 index = INDEX_NONE; + Anm2Frame frameAdd = frame ? *frame : Anm2Frame{}; + s32 index = reference->frameIndex + 1; if (reference->itemType == ANM2_TRIGGERS) { - for (auto& frameCheck : item->frames) if (frameCheck.atFrame == time) return nullptr; + s32 index = time; + + for (auto& frameCheck : item->frames) + { + if (frameCheck.atFrame == time) + { + index++; + break; + } + } - frame.atFrame = time; + frameAdd.atFrame = index; index = item->frames.size(); + return &item->frames.emplace_back(frameAdd); } else { - s32 frameDelayCount = 0; - - for (auto& frameCheck : item->frames) - frameDelayCount += frameCheck.delay; - - if (frameDelayCount + ANM2_FRAME_DELAY_MIN > animation->frameNum) return nullptr; - - Anm2Frame* checkFrame = anm2_frame_from_reference(self, reference); - - if (checkFrame) - { - if (frameDelayCount + checkFrame->delay > animation->frameNum) - frame.delay = animation->frameNum - frameDelayCount; - - index = reference->frameIndex + 1; - } - else - index = (s32)item->frames.size(); + item->frames.insert(item->frames.begin() + index, frameAdd); + return &item->frames[index]; } - - item->frames.insert(item->frames.begin() + index, frame); - - return &item->frames[index]; } return nullptr; } +void anm2_frame_erase(Anm2* self, Anm2Reference* reference) +{ + Anm2Item* item = anm2_item_from_reference(self, reference); + if (!item) return; + item->frames.erase(item->frames.begin() + reference->frameIndex); +} + void anm2_reference_clear(Anm2Reference* self) { *self = Anm2Reference{}; @@ -970,9 +966,7 @@ void anm2_reference_clear(Anm2Reference* self) void anm2_reference_item_clear(Anm2Reference* self) { - self->itemType = ANM2_NONE; - self->itemID = ID_NONE; - self->frameIndex = INDEX_NONE; + *self = {self->animationID}; } void anm2_reference_frame_clear(Anm2Reference* self) @@ -980,20 +974,70 @@ void anm2_reference_frame_clear(Anm2Reference* self) self->frameIndex = INDEX_NONE; } +void anm2_item_frame_set(Anm2* self, Anm2Reference* reference, const Anm2FrameChange& change, Anm2ChangeType type, s32 start, s32 count) +{ + Anm2Item* item = anm2_item_from_reference(self, reference); + if (!item) return; + + if (start < 0 || count <= 0) return; + const s32 size = (s32)item->frames.size(); + if (size == 0 || start >= size) return; + + const s32 end = std::min(start + count, size); + + for (s32 i = start; i < end; ++i) + { + Anm2Frame& dest = item->frames[i]; + + // Booleans always just set if provided + if (change.isVisible) dest.isVisible = *change.isVisible; + if (change.isInterpolated) dest.isInterpolated = *change.isInterpolated; + + switch (type) + { + case ANM2_CHANGE_SET: + if (change.rotation) dest.rotation = *change.rotation; + if (change.delay) dest.delay = std::max(ANM2_FRAME_DELAY_MIN, *change.delay); + if (change.crop) dest.crop = *change.crop; + if (change.pivot) dest.pivot = *change.pivot; + if (change.position) dest.position = *change.position; + if (change.size) dest.size = *change.size; + if (change.scale) dest.scale = *change.scale; + if (change.offsetRGB) dest.offsetRGB = glm::clamp(*change.offsetRGB, 0.0f, 1.0f); + if (change.tintRGBA) dest.tintRGBA = glm::clamp(*change.tintRGBA, 0.0f, 1.0f); + break; + + case ANM2_CHANGE_ADD: + if (change.rotation) dest.rotation += *change.rotation; + if (change.delay) dest.delay = std::max(ANM2_FRAME_DELAY_MIN, dest.delay + *change.delay); + if (change.crop) dest.crop += *change.crop; + if (change.pivot) dest.pivot += *change.pivot; + if (change.position) dest.position += *change.position; + if (change.size) dest.size += *change.size; + if (change.scale) dest.scale += *change.scale; + if (change.offsetRGB) dest.offsetRGB = glm::clamp(dest.offsetRGB + *change.offsetRGB, 0.0f, 1.0f); + if (change.tintRGBA) dest.tintRGBA = glm::clamp(dest.tintRGBA + *change.tintRGBA, 0.0f, 1.0f); + break; + + case ANM2_CHANGE_SUBTRACT: + if (change.rotation) dest.rotation -= *change.rotation; + if (change.delay) dest.delay = std::max(ANM2_FRAME_DELAY_MIN, dest.delay - *change.delay); + if (change.crop) dest.crop -= *change.crop; + if (change.pivot) dest.pivot -= *change.pivot; + if (change.position) dest.position -= *change.position; + if (change.size) dest.size -= *change.size; + if (change.scale) dest.scale -= *change.scale; + if (change.offsetRGB) dest.offsetRGB = glm::clamp(dest.offsetRGB - *change.offsetRGB, 0.0f, 1.0f); + if (change.tintRGBA) dest.tintRGBA = glm::clamp(dest.tintRGBA - *change.tintRGBA, 0.0f, 1.0f); + break; + } + } +} + void anm2_animation_merge(Anm2* self, s32 animationID, const std::vector& mergeIDs, Anm2MergeType type) { Anm2Animation newAnimation = self->animations[animationID]; - newAnimation.rootAnimation.frames.clear(); - - for (auto& [id, layerAnimation] : newAnimation.layerAnimations) - layerAnimation.frames.clear(); - - for (auto& [id, nullAnimation] : newAnimation.nullAnimations) - nullAnimation.frames.clear(); - - newAnimation.triggers.frames.clear(); - auto merge_item = [&](Anm2Item& destinationItem, const Anm2Item& sourceItem) { switch (type) @@ -1017,6 +1061,8 @@ void anm2_animation_merge(Anm2* self, s32 animationID, const std::vector& m for (auto mergeID : mergeIDs) { + if (animationID == mergeID) continue; + const Anm2Animation& mergeAnimation = self->animations[mergeID]; merge_item(newAnimation.rootAnimation, mergeAnimation.rootAnimation); @@ -1047,7 +1093,7 @@ void anm2_frame_bake(Anm2* self, Anm2Reference* reference, s32 interval, bool is referenceNext.frameIndex = reference->frameIndex + 1; Anm2Frame* frameNext = anm2_frame_from_reference(self, &referenceNext); - if (!frameNext) return; + if (!frameNext) frameNext = frame; const Anm2Frame baseFrame = *frame; const Anm2Frame baseFrameNext = *frameNext; diff --git a/src/anm2.h b/src/anm2.h index 340017b..3ce65d9 100644 --- a/src/anm2.h +++ b/src/anm2.h @@ -7,6 +7,7 @@ #define ANM2_TINT_CONVERT(x) ((f32)x / 255.0f) #define ANM2_FPS_MIN 0 +#define ANM2_FPS_DEFAULT 30 #define ANM2_FPS_MAX 120 #define ANM2_FRAME_NUM_MIN 1 #define ANM2_FRAME_NUM_MAX 1000000 @@ -148,8 +149,8 @@ struct Anm2Event struct Anm2Frame { - bool isInterpolated = false; bool isVisible = true; + bool isInterpolated = false; f32 rotation = 1.0f; s32 delay = ANM2_FRAME_DELAY_MIN; s32 atFrame = INDEX_NONE; @@ -163,6 +164,21 @@ struct Anm2Frame vec4 tintRGBA = {1.0f, 1.0f, 1.0f, 1.0f}; }; +struct Anm2FrameChange +{ + std::optional isVisible; + std::optional isInterpolated; + std::optional rotation; + std::optional delay; + std::optional crop; + std::optional pivot; + std::optional position; + std::optional size; + std::optional scale; + std::optional offsetRGB; + std::optional tintRGBA; +}; + struct Anm2Item { bool isVisible = true; @@ -192,7 +208,7 @@ struct Anm2 std::map animations; std::map layerMap; // index, id s32 defaultAnimationID{}; - s32 fps = 30; + s32 fps = ANM2_FPS_DEFAULT; s32 version{}; }; @@ -231,6 +247,13 @@ enum Anm2MergeType ANM2_MERGE_IGNORE }; +enum Anm2ChangeType +{ + ANM2_CHANGE_ADD, + ANM2_CHANGE_SUBTRACT, + ANM2_CHANGE_SET +}; + void anm2_layer_add(Anm2* self); void anm2_layer_remove(Anm2* self, s32 id); void anm2_null_add(Anm2* self); @@ -246,7 +269,8 @@ 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, Anm2Reference* reference, s32 time); +Anm2Frame* anm2_frame_add(Anm2* self, Anm2Frame* frame, Anm2Reference* reference, s32 time); +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); void anm2_reference_item_clear(Anm2Reference* self); @@ -254,4 +278,5 @@ void anm2_reference_frame_clear(Anm2Reference* self); s32 anm2_animation_length_get(Anm2Animation* self); void anm2_animation_length_set(Anm2Animation* self); void anm2_animation_merge(Anm2* self, s32 animationID, const std::vector& mergeIDs, Anm2MergeType type); -void anm2_frame_bake(Anm2* self, Anm2Reference* reference, s32 interval, bool isRoundScale, bool isRoundRotation); \ No newline at end of file +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); \ No newline at end of file diff --git a/src/canvas.cpp b/src/canvas.cpp index 9d7fe2e..512e70f 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -1,5 +1,3 @@ -// Common rendering area - #include "canvas.h" static void _canvas_texture_free(Canvas* self) @@ -9,7 +7,7 @@ static void _canvas_texture_free(Canvas* self) if (self->texture != 0) glDeleteTextures(1, &self->texture); } -static void _canvas_texture_init(Canvas* self, vec2& size) +static void _canvas_texture_init(Canvas* self, const vec2& size) { _canvas_texture_free(self); @@ -35,7 +33,7 @@ static void _canvas_texture_init(Canvas* self, vec2& size) glBindFramebuffer(GL_FRAMEBUFFER, 0); } -void canvas_init(Canvas* self) +void canvas_init(Canvas* self, const vec2& size) { // Axis glGenVertexArrays(1, &self->axisVAO); @@ -91,6 +89,8 @@ void canvas_init(Canvas* self) glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(f32), (void*)(2 * sizeof(f32))); glBindVertexArray(0); + + _canvas_texture_init(self, size); } mat4 canvas_transform_get(Canvas* self, vec2& pan, f32& zoom, OriginType origin) @@ -277,30 +277,4 @@ mat4 canvas_mvp_get(mat4& transform, vec2 size, vec2 position, vec2 pivot, f32 r model = glm::scale(model, vec3(sizeScaled, 1.0f)); return transform * model; -} - -/* -mat4 canvas_mvp_get(mat4& transform, vec2 size, vec2 position, vec2 pivot, f32 rotation, vec2 scale, vec2 pivotAlt, f32 rotationAlt) -{ - vec2 scaleAbsolute = abs(scale); - vec2 signScale = glm::sign(scale); - vec2 pivotScaled = pivot * scaleAbsolute; - vec2 pivotAltScaled = pivotAlt * scaleAbsolute; - vec2 sizeScaled = size * scaleAbsolute; - - mat4 model = glm::translate(mat4(1.0f), vec3(position - pivotScaled, 0.0f)); - - model = glm::translate(model, vec3(pivotScaled, 0.0f)); - model = glm::scale(model, vec3(signScale, 1.0f)); // Flip - model = glm::rotate(model, radians(rotation), vec3(0,0,1)); - model = glm::translate(model, vec3(-pivotScaled, 0.0f)); - - model = glm::translate(model, vec3(pivotAltScaled, 0.0f)); - model = glm::rotate(model, radians(rotationAlt), vec3(0,0,1)); - model = glm::translate(model, vec3(-pivotAltScaled, 0.0f)); - - model = glm::scale(model, vec3(sizeScaled, 1.0f)); - - return transform * model; -} -*/ \ No newline at end of file +} \ No newline at end of file diff --git a/src/canvas.h b/src/canvas.h index 9e12075..effbcfb 100644 --- a/src/canvas.h +++ b/src/canvas.h @@ -4,14 +4,16 @@ #define CANVAS_ZOOM_MIN 1.0f #define CANVAS_ZOOM_MAX 2000.0f +#define CANVAS_ZOOM_DEFAULT 100.0f #define CANVAS_ZOOM_STEP 10.0f #define CANVAS_ZOOM_MOD 10.0f #define CANVAS_GRID_MIN 1 #define CANVAS_GRID_MAX 1000 +#define CANVAS_GRID_DEFAULT 32 #define CANVAS_LINE_LENGTH (FLT_MAX * 0.001f) static const vec2 CANVAS_GRID_SIZE = {3200, 1600}; -static const vec2 CANVAS_PIVOT_SIZE = {4, 4}; +static const vec2 CANVAS_PIVOT_SIZE = {8, 8}; const f32 CANVAS_AXIS_VERTICES[] = { @@ -38,7 +40,7 @@ struct Canvas vec2 size{}; }; -void canvas_init(Canvas* self); +void canvas_init(Canvas* self, const vec2& size); mat4 canvas_transform_get(Canvas* self, vec2& pan, f32& zoom, OriginType origin); void canvas_clear(vec4& color); void canvas_bind(Canvas* self); diff --git a/src/clipboard.cpp b/src/clipboard.cpp index 9021ef6..973145c 100644 --- a/src/clipboard.cpp +++ b/src/clipboard.cpp @@ -6,44 +6,26 @@ static void _clipboard_item_remove(ClipboardItem* self, Anm2* anm2) { case CLIPBOARD_FRAME: { - Anm2FrameWithReference frameWithReference = std::get(self->data); - Anm2Animation* animation = anm2_animation_from_reference(anm2, &frameWithReference.reference); - - if (!animation) break; - - std::vector* frames = nullptr; + Anm2FrameWithReference* frameWithReference = std::get_if(&self->data); - switch (frameWithReference.reference.itemType) - { - case ANM2_ROOT: - frames = &animation->rootAnimation.frames; - break; - case ANM2_LAYER: - frames = &animation->layerAnimations[frameWithReference.reference.itemID].frames; - break; - case ANM2_NULL: - frames = &animation->nullAnimations[frameWithReference.reference.itemID].frames; - break; - case ANM2_TRIGGERS: - frames = &animation->triggers.frames; - break; - default: - break; - } + if (!frameWithReference) break; - if (frames) - frames->erase(frames->begin() + frameWithReference.reference.frameIndex); - + anm2_frame_erase(anm2, &frameWithReference->reference); break; } case CLIPBOARD_ANIMATION: { - Anm2AnimationWithID animationWithID = std::get(self->data); + Anm2AnimationWithID* animationWithID = std::get_if(&self->data); + + if (!animationWithID) break; for (auto & [id, animation] : anm2->animations) { - if (id == animationWithID.id) - anm2->animations.erase(animationWithID.id); + if (id == animationWithID->id) + { + anm2->animations.erase(animationWithID->id); + break; + } } break; } @@ -69,24 +51,26 @@ static void _clipboard_item_paste(ClipboardItem* self, ClipboardLocation* locati if (!animation || !anm2Item) break; - s32 insertIndex = (reference->itemType == ANM2_TRIGGERS) ? - reference->frameIndex : std::max(reference->frameIndex, (s32)anm2Item->frames.size()); - - anm2Item->frames.insert(anm2Item->frames.begin() + insertIndex, frameWithReference->frame); - - anm2_animation_length_set(animation); + anm2_frame_add(anm2, &frameWithReference->frame, reference, reference->frameIndex); + break; } case CLIPBOARD_ANIMATION: { + Anm2AnimationWithID* animationWithID = std::get_if(&self->data); + + if (!animationWithID) break; + s32 index = 0; if (std::holds_alternative(*location)) index = std::get(*location); + else + break; index = std::clamp(index, 0, (s32)anm2->animations.size()); - map_insert_shift(anm2->animations, index, std::get(self->data).animation); + map_insert_shift(anm2->animations, index, animationWithID->animation); break; } default: diff --git a/src/clipboard.h b/src/clipboard.h index b89c852..d5eb955 100644 --- a/src/clipboard.h +++ b/src/clipboard.h @@ -15,12 +15,8 @@ struct ClipboardItem ClipboardItemType type = CLIPBOARD_NONE; ClipboardItem() = default; - - ClipboardItem(const Anm2FrameWithReference& frame) - : data(frame), type(CLIPBOARD_FRAME) {} - - ClipboardItem(const Anm2AnimationWithID& anim) - : data(anim), type(CLIPBOARD_ANIMATION) {} + ClipboardItem(const Anm2FrameWithReference& frame) : data(frame), type(CLIPBOARD_FRAME) {} + ClipboardItem(const Anm2AnimationWithID& animation) : data(animation), type(CLIPBOARD_ANIMATION) {} }; using ClipboardLocation = std::variant; diff --git a/src/dialog.cpp b/src/dialog.cpp index 7c66ad8..db1595b 100644 --- a/src/dialog.cpp +++ b/src/dialog.cpp @@ -1,5 +1,3 @@ -// File dialog; saving/writing to files - #include "dialog.h" static void _dialog_callback(void* userdata, const char* const* filelist, s32 filter) diff --git a/src/editor.cpp b/src/editor.cpp index cedd464..ffc4abf 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1,5 +1,3 @@ -// Handles the rendering of the spritesheet editor - #include "editor.h" void editor_init(Editor* self, Anm2* anm2, Anm2Reference* reference, Resources* resources, Settings* settings) @@ -9,7 +7,7 @@ void editor_init(Editor* self, Anm2* anm2, Anm2Reference* reference, Resources* self->resources = resources; self->settings = settings; - canvas_init(&self->canvas); + canvas_init(&self->canvas, vec2()); } void editor_draw(Editor* self) diff --git a/src/generate_preview.cpp b/src/generate_preview.cpp new file mode 100644 index 0000000..a4acb5e --- /dev/null +++ b/src/generate_preview.cpp @@ -0,0 +1,101 @@ +#include "generate_preview.h" + +void generate_preview_init(GeneratePreview* self, Anm2* anm2, Anm2Reference* reference, Resources* resources, Settings* settings) +{ + self->anm2 = anm2; + self->reference = reference; + self->resources = resources; + self->settings = settings; + + canvas_init(&self->canvas, GENERATE_PREVIEW_SIZE); +} + +void generate_preview_draw(GeneratePreview* self) +{ + + /* 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); + + canvas_bind(&self->canvas); + canvas_viewport_set(&self->canvas); + canvas_clear(self->settings->previewBackgroundColor); + + self->time = std::clamp(self->time, 0.0f, 1.0f); + + Anm2Animation* animation = anm2_animation_from_reference(self->anm2, self->reference); + s32& animationID = self->reference->animationID; + + if (animation) + { + Anm2Frame root; + mat4 rootModel = mat4(1.0f); + + anm2_frame_from_time(self->anm2, &root, Anm2Reference{animationID, ANM2_ROOT}, self->time); + + if (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); + f32 vertices[] = UV_VERTICES(uvMin, uvMax); + + mat4 model = quad_model_get(frame.size, frame.position, frame.pivot, frame.rotation, PERCENT_TO_UNIT(frame.scale)); + mat4 layerTransform = transform * (rootModel * model); + + canvas_texture_draw(&self->canvas, shaderTexture, texture->id, layerTransform, vertices, frame.tintRGBA, frame.offsetRGB); + + if (self->settings->previewIsBorder) + canvas_rect_draw(&self->canvas, shaderLine, layerTransform, PREVIEW_BORDER_COLOR); + + if (self->settings->previewIsPivots) + { + f32 vertices[] = ATLAS_UV_VERTICES(ATLAS_PIVOT); + mat4 pivotModel = quad_model_get(CANVAS_PIVOT_SIZE, frame.position, CANVAS_PIVOT_SIZE * 0.5f, frame.rotation, PERCENT_TO_UNIT(frame.scale)); + mat4 pivotTransform = transform * (rootModel * pivotModel); + canvas_texture_draw(&self->canvas, shaderTexture, self->resources->atlas.id, pivotTransform, vertices, PREVIEW_PIVOT_COLOR); + } + } + + + s32& animationOverlayID = self->animationOverlayID; + Anm2Animation* animationOverlay = map_find(self->anm2->animations, animationOverlayID); + + canvas_unbind(); + */ +} + +void generate_preview_free(GeneratePreview* self) +{ + canvas_free(&self->canvas); +} \ No newline at end of file diff --git a/src/generate_preview.h b/src/generate_preview.h new file mode 100644 index 0000000..b32e940 --- /dev/null +++ b/src/generate_preview.h @@ -0,0 +1,22 @@ +#pragma once + +#include "anm2.h" +#include "resources.h" +#include "settings.h" +#include "canvas.h" + +const vec2 GENERATE_PREVIEW_SIZE = {325, 215}; + +struct GeneratePreview +{ + Anm2* anm2 = nullptr; + Anm2Reference* reference = nullptr; + Resources* resources = nullptr; + Settings* settings = nullptr; + Canvas canvas; + f32 time{}; +}; + +void generate_preview_init(GeneratePreview* self, Anm2* anm2, Anm2Reference* reference, Resources* resources, Settings* settings); +void generate_preview_draw(GeneratePreview* self); +void generate_preview_free(GeneratePreview* self); \ No newline at end of file diff --git a/src/imgui.cpp b/src/imgui.cpp index e06aed6..dbaa686 100644 --- a/src/imgui.cpp +++ b/src/imgui.cpp @@ -1,30 +1,20 @@ -// Handles everything imgui - #include "imgui.h" -static bool _imgui_window_color_from_position_get(SDL_Window* self, vec2 position, vec4* color) +static bool _imgui_window_color_from_position_get(SDL_Window* self, const vec2& position, vec4& color) { - if (!self || !color) return false; - ImGuiIO& io = ImGui::GetIO(); - ivec2 framebufferPosition = {(s32)(position.x * io.DisplayFramebufferScale.x), (s32)(position.y * io.DisplayFramebufferScale.y)}; - ivec2 framebufferSize{}; - SDL_GetWindowSizeInPixels(self, &framebufferSize.x, &framebufferSize.y); + ivec2 fbPosition = {(s32)(position.x * io.DisplayFramebufferScale.x), (s32)(position.y * io.DisplayFramebufferScale.y)}; + ivec2 size{}; + SDL_GetWindowSizeInPixels(self, &size.x, &size.y); - if - ( - framebufferPosition.x < 0 || framebufferPosition.y < 0 || - framebufferPosition.x >= framebufferSize.x || - framebufferPosition.y >= framebufferSize.y - ) - return false; + if (fbPosition.x < 0 || fbPosition.y < 0 || fbPosition.x >= size.x || fbPosition.y >= size.y) return false; uint8_t rgba[4]; glPixelStorei(GL_PACK_ALIGNMENT, 1); - glReadPixels(framebufferPosition.x, framebufferSize.y - 1 - framebufferPosition.y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, rgba); + glReadPixels(fbPosition.x, size.y - 1 - fbPosition.y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, rgba); - *color = vec4(U8_TO_FLOAT(rgba[0]), U8_TO_FLOAT(rgba[1]), U8_TO_FLOAT(rgba[2]), U8_TO_FLOAT(rgba[3])); + color = vec4(U8_TO_FLOAT(rgba[0]), U8_TO_FLOAT(rgba[1]), U8_TO_FLOAT(rgba[2]), U8_TO_FLOAT(rgba[3])); return true; } @@ -35,16 +25,6 @@ static void _imgui_clipboard_hovered_item_set(Imgui* self, const T& data) self->clipboard->hoveredItem = ClipboardItem(data); } -static void _imgui_atlas_image(Imgui* self, AtlasType type) -{ - ImGui::Image(self->resources->atlas.id, ATLAS_SIZE(type), ATLAS_UV_ARGS(type)); -} - -static void _imgui_item_text(const ImguiItem& item) -{ - ImGui::Text(item.label.c_str()); -} - static bool _imgui_is_window_hovered(void) { return ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem); @@ -52,483 +32,491 @@ static bool _imgui_is_window_hovered(void) static bool _imgui_is_no_click_on_item(void) { - return ImGui::IsMouseClicked(0) && !ImGui::IsAnyItemHovered() && _imgui_is_window_hovered(); + return ImGui::IsMouseClicked(0) && !ImGui::IsAnyItemHovered(); } -static void _imgui_item(Imgui* self, const ImguiItem& item, bool* isActivated) +static bool _imgui_is_input_begin(void) { - if (item.is_mnemonic()) + return ImGui::IsItemHovered() && (ImGui::IsKeyPressed(IMGUI_INPUT_RENAME) || ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)); +} + +static bool _imgui_is_input_default(void) +{ + return ImGui::IsItemHovered() && (ImGui::IsKeyPressed(IMGUI_INPUT_DEFAULT) || ImGui::IsMouseClicked(IMGUI_MOUSE_DEFAULT)); +} + +static std::string_view imgui_window_get(void) +{ + ImGuiWindow* navWindow = ImGui::GetCurrentContext()->NavWindow; + if (!navWindow) return {}; + + std::string_view name(navWindow->Name); + size_t slash = name.find('/'); + return (slash == std::string_view::npos) ? name : name.substr(0, slash); +} + +static void _imgui_atlas(const AtlasType& self, Imgui* imgui) +{ + ImGui::Image(imgui->resources->atlas.id, ATLAS_SIZE(self), ATLAS_UV_ARGS(self)); +} + +static ImVec2 _imgui_item_size_get(const ImguiItem& self, ImguiItemType type) +{ + ImVec2 size = self.size; + + switch (type) { - ImVec2 pos = ImGui::GetItemRectMin(); + case IMGUI_ATLAS_BUTTON: + size = self.is_size() ? self.size : ImVec2(ATLAS_SIZE(self.atlas)); + break; + default: + if (self.is_row()) + size.x = (ImGui::GetWindowSize().x - (ImGui::GetStyle().ItemSpacing.x * (self.rowCount + 1))) / self.rowCount; + else if (self.isSizeToText) + size.x = (ImGui::CalcTextSize(self.label_get()).x + ImGui::GetStyle().FramePadding.x); + else if (!self.is_size()) + size.x = ImGui::CalcItemWidth(); + break; + } + + return size; +} + +static void _imgui_item_pre(const ImguiItem& self, ImguiItemType type) +{ + ImVec2 size = _imgui_item_size_get(self, type); + + switch (type) + { + case IMGUI_ITEM: + case IMGUI_TEXT: + case IMGUI_WINDOW: + case IMGUI_DOCKSPACE: + case IMGUI_CHILD: + case IMGUI_OPTION_POPUP: + break; + default: + ImGui::BeginDisabled(self.isDisabled); + } + + switch (type) + { + case IMGUI_CHILD: + if (self.color.is_normal()) ImGui::PushStyleColor(ImGuiCol_ChildBg, self.color.normal); + break; + case IMGUI_INPUT_INT: + case IMGUI_INPUT_TEXT: + case IMGUI_DRAG_FLOAT: + case IMGUI_COLOR_EDIT: + ImGui::SetNextItemWidth(size.x); + break; + case IMGUI_BUTTON: + case IMGUI_ATLAS_BUTTON: + if (self.is_border()) + { + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, self.border); + if (self.color.is_border()) ImGui::PushStyleColor(ImGuiCol_Border, self.color.border); + } + if (self.color.is_normal()) ImGui::PushStyleColor(ImGuiCol_Button, self.color.normal); + if (self.color.is_active()) ImGui::PushStyleColor(ImGuiCol_ButtonActive, self.color.active); + if (self.color.is_hovered()) ImGui::PushStyleColor(ImGuiCol_ButtonHovered, self.color.hovered); + if (self.isSelected) + { + if (self.color.is_active()) + ImGui::PushStyleColor(ImGuiCol_Button, self.color.active); + else + ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetStyle().Colors[ImGuiCol_ButtonActive]); + } + break; + default: + break; + } +} + +static void _imgui_item_post(const ImguiItem& self, Imgui* imgui, ImguiItemType type, bool& isActivated) +{ + if (self.is_mnemonic() && !self.isMnemonicDisabled) + { + ImVec2 position = ImGui::GetItemRectMin(); ImFont* font = ImGui::GetFont(); f32 fontSize = ImGui::GetFontSize(); - const char* start = item.label.c_str(); - const char* charPointer = start + item.mnemonicIndex; + const char* start = self.label.c_str(); + const char* charPointer = start + self.mnemonicIndex; - pos.x += ImGui::GetStyle().FramePadding.x; + position.x += ImGui::GetStyle().FramePadding.x; f32 offset = font->CalcTextSizeA(fontSize, FLT_MAX, 0.0f, start, charPointer).x; f32 charWidth = font->CalcTextSizeA(fontSize, FLT_MAX, 0.0f, charPointer, charPointer + 1).x; - ImVec2 lineStart = ImVec2(pos.x + offset, pos.y + fontSize + 1.0f); + ImVec2 lineStart = ImVec2(position.x + offset, position.y + fontSize + 1.0f); ImVec2 lineEnd = ImVec2(lineStart.x + charWidth, lineStart.y); ImU32 color = ImGui::GetColorU32(ImGuiCol_Text); ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, color, 1.0f); - if (isActivated && ImGui::IsKeyChordPressed(ImGuiMod_Alt | item.mnemonicKey)) + if (ImGui::IsKeyChordPressed(ImGuiMod_Alt | self.mnemonicKey)) { - *isActivated = true; - ImGui::CloseCurrentPopup(); + if (!self.isDisabled) isActivated = true; + imgui_close_current_popup(imgui); } } - if (isActivated && self->isContextualActionsEnabled && (item.is_chord() && ImGui::IsKeyChordPressed(item.chord))) - if (item.is_focus_window() && (imgui_nav_window_root_get() == item.focusWindow)) - *isActivated = true; + if (self.isUseItemActivated && !self.isDisabled) isActivated = ImGui::IsItemActivated(); + + if (imgui->isContextualActionsEnabled && (self.is_chord() && ImGui::IsKeyChordPressed(self.chord))) + if (self.is_focus_window() && (imgui_window_get() == self.focusWindow)) + if (!self.isDisabled) isActivated = true; - if (item.is_tooltip() && ImGui::IsItemHovered(ImGuiHoveredFlags_DelayNormal)) - ImGui::SetTooltip(item.tooltip.c_str()); + if (self.is_tooltip() && ImGui::IsItemHovered(ImGuiHoveredFlags_DelayNormal)) + ImGui::SetTooltip(self.tooltip.c_str()); - if (isActivated && *isActivated) + if (isActivated) { - if (item.isUndoable) imgui_undo_stack_push(self, item.action); - if (item.function) item.function(self); + if (self.is_undoable()) + imgui_undo_push(imgui, self.undoAction); - if (item.is_popup()) + if (self.function) self.function(imgui); + + if (self.is_popup()) { - self->pendingPopup = item.popup; - self->pendingPopupType = item.popupType; - self->pendingPopupPosition = ImVec2(ImGui::GetItemRectMin().x, ImGui::GetItemRectMin().y + ImGui::GetItemRectSize().y); + imgui->pendingPopup = self.popup; + imgui->pendingPopupType = self.popupType; + imgui->pendingPopupPosition = ImVec2(ImGui::GetItemRectMin().x, ImGui::GetItemRectMin().y + ImGui::GetItemRectSize().y); } } -} - -static void _imgui_pending_popup_process(Imgui* self) -{ - if (self->pendingPopup.empty()) return; - - switch (self->pendingPopupType) + + switch (type) { - case IMGUI_POPUP_CENTER_SCREEN: - ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); + case IMGUI_CHILD: + if (self.color.is_normal()) ImGui::PopStyleColor(); + break; + case IMGUI_BUTTON: + case IMGUI_ATLAS_BUTTON: + if (self.is_border()) + { + ImGui::PopStyleVar(); + if (self.color.is_border()) ImGui::PopStyleColor(); + } + if (self.color.is_normal()) ImGui::PopStyleColor(); + if (self.color.is_active()) ImGui::PopStyleColor(); + if (self.color.is_hovered()) ImGui::PopStyleColor(); + if (self.isSelected) ImGui::PopStyleColor(); break; - case IMGUI_POPUP_BY_ITEM: default: - ImGui::SetNextWindowPos(self->pendingPopupPosition); break; } - ImGui::OpenPopup(self->pendingPopup.c_str()); + switch (type) + { + case IMGUI_ITEM: + case IMGUI_TEXT: + case IMGUI_WINDOW: + case IMGUI_DOCKSPACE: + case IMGUI_CHILD: + case IMGUI_OPTION_POPUP: + break; + default: + ImGui::EndDisabled(); + } - self->pendingPopup.clear(); - self->pendingPopupType = IMGUI_POPUP_NONE; - self->pendingPopupPosition = ImVec2(); + if (self.isSameLine) ImGui::SameLine(); + if (self.isSeparator) ImGui::Separator(); } -static bool _imgui_item_combo(Imgui* self, const ImguiItem& item, s32* current) + +#define IMGUI_ITEM_FUNCTION(NAME, TYPE, FUNCTION) \ +static bool NAME(ImguiItem self, Imgui* imgui) \ +{ \ + ImguiItemType type = TYPE; \ + _imgui_item_pre(self, type); \ + bool isActivated = ([&] { return FUNCTION; })(); \ + _imgui_item_post(self, imgui, type, isActivated); \ + return isActivated; \ +} + +#define IMGUI_ITEM_VOID_FUNCTION(NAME, TYPE, FUNCTION) \ +static void NAME(const ImguiItem& self, Imgui* imgui) \ +{ \ + ImguiItemType type = TYPE; \ + bool isActivated = false; \ + _imgui_item_pre(self, type); \ + ([&] { FUNCTION; })(); \ + _imgui_item_post(self, imgui, type, isActivated); \ +} + +#define IMGUI_ITEM_VALUE_FUNCTION(NAME, TYPE, VALUE, FUNCTION) \ +static bool NAME(const ImguiItem& self, Imgui* imgui, VALUE& inValue) \ +{ \ + ImguiItemType type = TYPE; \ + VALUE value = inValue; \ + _imgui_item_pre(self, type); \ + bool isActivated = ([&](VALUE& value) { return FUNCTION; })(value); \ + if (_imgui_is_input_default()) \ + { \ + value = VALUE(self.value); \ + isActivated = true; \ + } \ + _imgui_item_post(self, imgui, type, isActivated); \ + inValue = value; \ + return isActivated; \ +} + +#define IMGUI_ITEM_VALUE_CLAMP_FUNCTION(NAME, TYPE, VALUE, FUNCTION) \ +static bool NAME(const ImguiItem& self, Imgui* imgui, VALUE& inValue) \ +{ \ + ImguiItemType type = TYPE; \ + VALUE value = inValue; \ + _imgui_item_pre(self, type); \ + bool isActivated = ([&](VALUE& value) { return FUNCTION; })(value); \ + if (_imgui_is_input_default()) \ + { \ + value = VALUE(self.value); \ + isActivated = true; \ + } \ + if (self.is_range()) value = glm::clamp(value, VALUE(self.min), VALUE(self.max)); \ + _imgui_item_post(self, imgui, type, isActivated); \ + inValue = value; \ + return isActivated; \ +} + +#define IMGUI_ITEM_CUSTOM_FUNCTION(NAME, TYPE, BODY) \ +static bool NAME(const ImguiItem& self, Imgui* imgui) \ +{ \ + ImguiItemType type = TYPE; \ + _imgui_item_pre(self, type); \ + bool isActivated = false; \ + do { BODY } while (0); \ + _imgui_item_post(self, imgui, type, isActivated); \ + return isActivated; \ +} + +#define IMGUI_ITEM_CUSTOM_FUNCTION_WITH_VALUE(NAME, TYPE, VALUE, BODY) \ +static bool NAME(const ImguiItem& self, Imgui* imgui, VALUE& inValue) \ +{ \ + ImguiItemType type = TYPE; \ + _imgui_item_pre(self, type); \ + bool isActivated = false; \ + do { BODY } while (0); \ + _imgui_item_post(self, imgui, type, isActivated); \ + return isActivated; \ +} + +#define IMGUI_ITEM_ATLAS_FUNCTION(NAME, FUNCTION) \ +static bool NAME(const ImguiItem& self, Imgui* imgui) \ +{ \ + _imgui_atlas(self.atlas, imgui); \ + ImGui::SameLine(); \ + bool isActivated = ([&] { return FUNCTION; })(); \ + return isActivated; \ +} + +#define IMGUI_ITEM_ATLAS_VALUE_FUNCTION(NAME, VALUE, FUNCTION) \ +static bool NAME(const ImguiItem& self, Imgui* imgui, VALUE& value) \ +{ \ + _imgui_atlas(self.atlas, imgui); \ + ImGui::SameLine(); \ + bool isActivated = ([&](VALUE& value) { return FUNCTION; })(value); \ + return isActivated; \ +} + +#define IMGUI_ITEM_CHECKBOX_FUNCTION(NAME, FUNCTION) \ +static bool NAME(const ImguiItem& self, Imgui* imgui, bool& boolValue) \ +{ \ + ImguiItem checkboxItem = self.copy \ + ({.label = std::format(IMGUI_INVISIBLE_FORMAT, self.label), .isMnemonicDisabled = true}); \ + checkboxItem.isDisabled = false; \ + _imgui_checkbox(checkboxItem, imgui, boolValue); \ + ImGui::SameLine(); \ + bool isActivated = ([&] { return FUNCTION; })(); \ + if (isActivated) boolValue = !boolValue; \ + return isActivated; \ +} + +#define IMGUI_ITEM_CHECKBOX_VALUE_FUNCTION(NAME, VALUE, FUNCTION) \ +static bool NAME(const ImguiItem& self, Imgui* imgui, VALUE& value, bool& boolValue) \ +{ \ + ImguiItem checkboxItem = self.copy \ + ({.label = std::format(IMGUI_INVISIBLE_FORMAT, self.label), .isMnemonicDisabled = true}); \ + checkboxItem.isDisabled = false; \ + _imgui_checkbox(checkboxItem, imgui, boolValue); \ + ImGui::SameLine(); \ + bool isActivated = ([&](VALUE& value) { return FUNCTION; })(value); \ + return isActivated; \ +} + +#define IMGUI_ITEM_DISABLED_GET(VALUE, ITEM, CONDITION) ImguiItem VALUE = ITEM; VALUE.isDisabled = CONDITION; + +IMGUI_ITEM_FUNCTION(_imgui_begin, IMGUI_WINDOW, ImGui::Begin(self.label_get(), nullptr, self.flags)); +#define IMGUI_BEGIN_OR_RETURN(item, imgui) if (!_imgui_begin(item, imgui)) { _imgui_end(); return; } +static void _imgui_end(void){ImGui::End();} +IMGUI_ITEM_VOID_FUNCTION(_imgui_dockspace, IMGUI_DOCKSPACE, ImGui::DockSpace(ImGui::GetID(self.label_get()), self.size, self.flags)); +IMGUI_ITEM_FUNCTION(_imgui_begin_child, IMGUI_CHILD, ImGui::BeginChild(self.label_get(), self.size, self.flags, self.windowFlags)); +static void _imgui_end_child(void) {ImGui::EndChild(); } +IMGUI_ITEM_VOID_FUNCTION(_imgui_text, IMGUI_TEXT, ImGui::Text(self.label_get())); +IMGUI_ITEM_FUNCTION(_imgui_button, IMGUI_BUTTON, ImGui::Button(self.label_get(), _imgui_item_size_get(self, type))); +IMGUI_ITEM_FUNCTION(_imgui_selectable, IMGUI_SELECTABLE, ImGui::Selectable(self.label_get(), self.isSelected, self.flags, _imgui_item_size_get(self, type))); +IMGUI_ITEM_VALUE_FUNCTION(_imgui_radio_button, IMGUI_RADIO_BUTTON, s32, ImGui::RadioButton(self.label_get(), &value, self.value)); +IMGUI_ITEM_VALUE_FUNCTION(_imgui_color_button, IMGUI_COLOR_BUTTON, vec4, ImGui::ColorButton(self.label_get(), ImVec4(value), self.flags)); +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_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)); +IMGUI_ITEM_VALUE_FUNCTION(_imgui_color_edit4, IMGUI_COLOR_EDIT, vec4, ImGui::ColorEdit4(self.label_get(), value_ptr(value), self.flags)); +IMGUI_ITEM_CHECKBOX_FUNCTION(_imgui_checkbox_selectable, _imgui_selectable(self, imgui)); +IMGUI_ITEM_CHECKBOX_VALUE_FUNCTION(_imgui_checkbox_checkbox, bool, _imgui_checkbox(self, imgui, value)); +IMGUI_ITEM_CHECKBOX_VALUE_FUNCTION(_imgui_checkbox_input_int, s32, _imgui_input_int(self, imgui, value)); +IMGUI_ITEM_CHECKBOX_VALUE_FUNCTION(_imgui_checkbox_drag_float, f32, _imgui_drag_float(self, imgui, value)); +IMGUI_ITEM_CHECKBOX_VALUE_FUNCTION(_imgui_checkbox_drag_float2, vec2, _imgui_drag_float2(self, imgui, value)); +IMGUI_ITEM_CHECKBOX_VALUE_FUNCTION(_imgui_checkbox_color_edit3, vec3, _imgui_color_edit3(self, imgui, value)); +IMGUI_ITEM_CHECKBOX_VALUE_FUNCTION(_imgui_checkbox_color_edit4, vec4, _imgui_color_edit4(self, imgui, value)); + +static bool _imgui_input_text(const ImguiItem& self, Imgui* imgui, std::string& value) +{ + value.resize(self.max); + _imgui_item_pre(self, IMGUI_INPUT_TEXT); + bool isActivated = ImGui::InputText(self.label_get(), value.data(), self.max, self.flags); + _imgui_item_post(self, imgui, IMGUI_INPUT_TEXT, isActivated); + return isActivated; +} + +static bool _imgui_combo(ImguiItem self, Imgui* imgui, s32* value) { std::vector cStrings; - cStrings.reserve(item.items.size()); - for (auto& string : item.items) - cStrings.push_back(string.c_str()); + cStrings.reserve(self.items.size()); + for (auto& string : self.items) + cStrings.emplace_back(string.c_str()); - bool isActivated = ImGui::Combo(item.label.c_str(), current, cStrings.data(), (s32)item.items.size()); - _imgui_item(self, item, &isActivated); - return isActivated; -} + _imgui_item_pre(self, IMGUI_COMBO); -static bool _imgui_item_selectable(Imgui* self, const ImguiItem& item) -{ - const char* label = item.label.c_str(); - s32 flags = item.flags; - - if (item.isInactive) + bool isActivated = ImGui::Combo(self.label_get(), value, cStrings.data(), (s32)self.items.size()); + if (_imgui_is_input_default()) { - ImGui::PushStyleColor(ImGuiCol_Text, IMGUI_INACTIVE_COLOR); - flags |= ImGuiSelectableFlags_Disabled; - } - else if (item.color.is_normal()) - ImGui::PushStyleColor(ImGuiCol_Text, item.color.normal); - - ImVec2 size = item.is_size() ? item.size : item.isSizeToText ? ImGui::CalcTextSize(label) : ImVec2(); - bool isActivated = ImGui::Selectable(label, item.isSelected, flags, size); - _imgui_item(self, item, &isActivated); - - if (item.isInactive || item.color.is_normal()) ImGui::PopStyleColor(); - - return isActivated; -} - -static bool _imgui_item_inputint(Imgui* self, const ImguiItem& item, s32& value) -{ - if (item.is_size()) ImGui::SetNextItemWidth(item.size.x); - - bool isActivated = ImGui::InputInt(item.label.c_str(), &value, item.step, item.stepFast, item.flags); - - if (item.min != 0 || item.max != 0) - value = std::clamp(value, (s32)item.min, (s32)item.max); - - _imgui_item(self, item, &isActivated); - - return isActivated; -} - -static bool _imgui_item_inputint2(Imgui* self, const ImguiItem& item, ivec2& value) -{ - if (item.is_size()) ImGui::SetNextItemWidth(item.size.x); - - ImGui::InputInt2(item.label.c_str(), value_ptr(value)); - bool isActivated = ImGui::IsItemActivated(); - - if (item.min > 0 || item.max > 0) - for (s32 i = 0; i < 2; i++) - value[i] = std::clamp(value[i], (s32)item.min, (s32)item.max); - - _imgui_item(self, item, &isActivated); - - return isActivated; -} - -static bool _imgui_item_inputtext(Imgui* self, const ImguiItem& item, std::string& buffer) -{ - if ((s32)buffer.size() < (s32)item.max) buffer.resize(item.max); - - ImVec2 size = item.is_size() ? item.size : ImVec2(-FLT_MIN, 0); - - if (item.isSizeToChild) - { - size.x = (ImGui::GetWindowSize().x - ImGui::GetStyle().ItemSpacing.x * (item.childRowItemCount + 1)) / item.childRowItemCount; - size.x -= ImGui::CalcTextSize(item.label.c_str()).x * 2; + *value = self.value; + isActivated = true; } - ImGui::SetNextItemWidth(size.x); - - bool isActivated = ImGui::InputText(item.label.c_str(), &buffer[0], item.max, item.flags); - _imgui_item(self, item, &isActivated); - - return isActivated; -} - -static bool _imgui_item_checkbox(Imgui* self, const ImguiItem& item, bool& value) -{ - ImGui::Checkbox(item.label.c_str(), &value); - bool isActivated = ImGui::IsItemActivated(); - - if (item.is_mnemonic() && ImGui::IsKeyChordPressed(ImGuiMod_Alt | item.mnemonicKey)) - { - value = !value; - ImGui::CloseCurrentPopup(); - } - - _imgui_item(self, item, &isActivated); - - return isActivated; -} - -static bool _imgui_item_radio_button(Imgui* self, const ImguiItem& item, s32& value) -{ - if (item.is_size()) ImGui::SetNextItemWidth(item.size.x); - - bool isActivated = ImGui::RadioButton(item.label.c_str(), &value, item.value); - _imgui_item(self, item, &isActivated); + _imgui_item_post(self, imgui, IMGUI_COMBO, isActivated); return isActivated; -} +}; -static bool _imgui_item_dragfloat(Imgui* self, const ImguiItem& item, f32& value) +IMGUI_ITEM_CUSTOM_FUNCTION(_imgui_atlas_button, IMGUI_ATLAS_BUTTON, { - ImGui::DragFloat(item.label.c_str(), &value, item.speed, item.min, item.max, item.format.c_str()); - bool isActivated = ImGui::IsItemActivated(); - _imgui_item(self, item, &isActivated); - return isActivated; -} + ImVec2 size = _imgui_item_size_get(self, type); -static bool _imgui_item_colorbutton(Imgui* self, const ImguiItem& item, vec4& color) -{ - ImGui::ColorButton(item.label.c_str(), color, item.flags); - bool isActivated = ImGui::IsItemActivated(); - _imgui_item(self, item, &isActivated); - return isActivated; -} - -static bool _imgui_item_coloredit3(Imgui* self, const ImguiItem& item, vec3& value) -{ - ImGui::ColorEdit3(item.label.c_str(), value_ptr(value), item.flags); - bool isActivated = ImGui::IsItemActivated(); - _imgui_item(self, item, &isActivated); - return isActivated; -} - -static bool _imgui_item_coloredit4(Imgui* self, const ImguiItem& item, vec4& value) -{ - ImGui::ColorEdit4(item.label.c_str(), value_ptr(value), item.flags); - bool isActivated = ImGui::IsItemActivated(); - _imgui_item(self, item, &isActivated); - return isActivated; -} - -static bool _imgui_item_dragfloat2(Imgui* self, const ImguiItem& item, vec2& value) -{ - ImGui::DragFloat2(item.label.c_str(), value_ptr(value), item.speed, item.min, item.max, item.format.c_str()); - bool isActivated = ImGui::IsItemActivated(); - _imgui_item(self, item, &isActivated); - return isActivated; -} - -static bool _imgui_item_button(Imgui* self, const ImguiItem& item) -{ - ImVec2 size = item.is_size() ? item.size : ImVec2(0, 0); - - if (item.isSizeToChild) - size.x = (ImGui::GetWindowSize().x - ImGui::GetStyle().ItemSpacing.x * (item.childRowItemCount + 1)) / item.childRowItemCount; - - bool isActivated = ImGui::Button(item.label.c_str(), size); - _imgui_item(self, item, &isActivated); - return isActivated; -} - -static bool _imgui_item_selectable_inputtext(Imgui* self, const ImguiItem& item, std::string& string, s32 id) -{ - static s32 renameID = ID_NONE; - static s32 itemID = ID_NONE; - const char* label = item.label.c_str(); - bool isActivated = false; - static std::string buffer{}; - - ImVec2 size = item.is_size() ? item.size : item.isSizeToText ? ImGui::CalcTextSize(label) :ImVec2(0, 0); - - if (renameID == id && itemID == item.id) - { - ImGui::PushID(id); - - ImguiItem itemRenamable = IMGUI_RENAMABLE; - itemRenamable.size = size; - self->isRename = true; - - isActivated = _imgui_item_inputtext(self, itemRenamable, buffer); - - if (isActivated || _imgui_is_no_click_on_item()) - { - if (itemRenamable.isUndoable) imgui_undo_stack_push(self, itemRenamable.action); - - string = buffer; - renameID = ID_NONE; - itemID = ID_NONE; - self->isRename = false; - self->isContextualActionsEnabled = true; - } - - ImGui::PopID(); - } - else - { - isActivated = ImGui::Selectable(label, item.isSelected, 0, size); - - if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) - { - buffer = string; - renameID = id; - itemID = item.id; - ImGui::SetKeyboardFocusHere(-1); - self->isContextualActionsEnabled = false; - } - } - - _imgui_item(self, item, &isActivated); - - return isActivated; -} - -static bool _imgui_item_selectable_inputint(Imgui* self, const ImguiItem& item, s32& value, s32 id) -{ - static s32 itemID = ID_NONE; - static s32 changeID = ID_NONE; - const char* label = item.label.c_str(); - bool isActivated = false; - ImVec2 size = item.is_size() ? item.size : item.isSizeToText ? ImGui::CalcTextSize(label) :ImVec2(0, 0); - - if (changeID == id && itemID == item.id) - { - ImGui::PushID(id); - - ImguiItem itemChangeable = IMGUI_CHANGEABLE; - itemChangeable.size = size; - self->isChangeValue = true; - - isActivated = _imgui_item_inputint(self, itemChangeable, value); - - if (isActivated || _imgui_is_no_click_on_item()) - { - if (itemChangeable.isUndoable) imgui_undo_stack_push(self, itemChangeable.action); - - itemID = ID_NONE; - changeID = ID_NONE; - self->isChangeValue = false; - self->isContextualActionsEnabled = true; - } - - ImGui::PopID(); - } - else - { - isActivated = ImGui::Selectable(label, item.isSelected, 0, size); - - if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) - { - itemID = item.id; - changeID = id; - ImGui::SetKeyboardFocusHere(-1); - self->isContextualActionsEnabled = false; - } - } - - _imgui_item(self, item, &isActivated); - - return isActivated; -} - -static bool _imgui_item_atlas_image_selectable(Imgui* self, const ImguiItem& item) -{ - _imgui_atlas_image(self, item.atlas); - ImGui::SameLine(); - return _imgui_item_selectable(self, item); -} - -static bool _imgui_item_text_inputtext(Imgui* self, const ImguiItem& item, std::string& string) -{ - ImguiItem copyItem = item; - copyItem.label = string; - bool isActivated = _imgui_item_selectable_inputtext(self, copyItem, string, item.id); - ImGui::SameLine(); - ImGui::Text(item.label.c_str()); - - return isActivated; -} - -static bool _imgui_item_atlas_image_selectable_inputtext(Imgui* self, ImguiItem& item, std::string& string, s32 id) -{ - _imgui_atlas_image(self, item.atlas); - ImGui::SameLine(); - return _imgui_item_selectable_inputtext(self, item, string, id); -} - -static bool _imgui_item_atlas_image_selectable_inputint(Imgui* self, ImguiItem& item, s32& value, s32 id) -{ - _imgui_atlas_image(self, item.atlas); - ImGui::SameLine(); - return _imgui_item_selectable_inputint(self, item, value, id); -} - -static bool _imgui_item_atlas_image_checkbox_selectable(Imgui* self, const ImguiItem& item, bool& value) -{ - ImguiItem checkboxItem = item; - checkboxItem.label = IMGUI_INVISIBLE_LABEL_MARKER + item.label; - - _imgui_item_checkbox(self, checkboxItem, value); - ImGui::SameLine(); - _imgui_atlas_image(self, item.atlas); - ImGui::SameLine(); - return _imgui_item_selectable(self, item); -} - -static bool _imgui_item_atlas_image_button(Imgui* self, const ImguiItem& item) -{ - bool isActivated = false; - ImVec2 imageSize = (ATLAS_SIZE(item.atlas)); - ImVec2 buttonSize = item.is_size() ? item.size : imageSize; - - if (item.color.is_normal()) ImGui::PushStyleColor(ImGuiCol_Button, item.color.normal); - if (item.color.is_active()) ImGui::PushStyleColor(ImGuiCol_ButtonActive, item.color.active); - if (item.color.is_hovered()) ImGui::PushStyleColor(ImGuiCol_ButtonHovered, item.color.hovered); - if (item.isSelected) ImGui::PushStyleColor(ImGuiCol_Button, item.color.active); - if (item.is_border()) ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, item.border); - - if (item.is_size()) + if (self.is_size()) { - isActivated = ImGui::Button(item.label.c_str(), buttonSize); + isActivated = ImGui::Button(self.label_get(), size); - ImVec2 pos = ImGui::GetItemRectMin(); - ImVec2 imageMin = pos + item.contentOffset; - ImVec2 imageMax = imageMin + imageSize; + ImVec2 start = ImGui::GetItemRectMin() + self.atlasOffset; + ImVec2 end = start + ImVec2(ATLAS_SIZE(self.atlas)); - ImGui::GetWindowDrawList()->AddImage(self->resources->atlas.id, imageMin, imageMax, ATLAS_UV_ARGS(item.atlas)); + ImGui::GetWindowDrawList()->AddImage(imgui->resources->atlas.id, start, end, ATLAS_UV_ARGS(self.atlas)); } else - isActivated = ImGui::ImageButton(item.label.c_str(), self->resources->atlas.id, buttonSize, ATLAS_UV_ARGS(item.atlas)); - _imgui_item(self, item, &isActivated); + isActivated = ImGui::ImageButton(self.label_get(), imgui->resources->atlas.id, size, ATLAS_UV_ARGS(self.atlas)); +}); + +static bool _imgui_selectable_input_int(const ImguiItem& self, Imgui* imgui, s32& value) +{ + static s32 temp; + static s32 id = ID_NONE; + ImguiItem selectable = self.copy({.label = std::format(IMGUI_SELECTABLE_INPUT_INT_FORMAT, value)}); + ImVec2 size = _imgui_item_size_get(selectable, IMGUI_SELECTABLE); + bool isActivated = false; - if (item.color.is_normal()) ImGui::PopStyleColor(); - if (item.color.is_active()) ImGui::PopStyleColor(); - if (item.color.is_hovered()) ImGui::PopStyleColor(); - if (item.isSelected) ImGui::PopStyleColor(); - if (item.is_border()) ImGui::PopStyleVar(); + if (id == self.id) + { + isActivated = _imgui_input_int(IMGUI_CHANGE_INPUT_INT.copy({.size = size}), imgui, temp); + imgui_contextual_actions_disable(imgui); + + if (isActivated || _imgui_is_no_click_on_item()) + { + value = temp; + id = ID_NONE; + imgui_contextual_actions_enable(imgui); + } + } + else + { + isActivated = _imgui_selectable(selectable, imgui); + + if (_imgui_is_input_begin()) + { + temp = value; + id = self.id; + ImGui::SetKeyboardFocusHere(-1); + } + } - return isActivated; -} + return isActivated; +}; -static void _imgui_item_begin(const ImguiItem& item) +static bool _imgui_selectable_input_text(const ImguiItem& self, Imgui* imgui, std::string& value) { - if (item.is_size()) ImGui::SetNextWindowSize(item.size); - ImGui::Begin(item.label.c_str(), nullptr, item.flags); -} + static std::string buffer{}; + static s32 id = ID_NONE; + ImguiItem selectable = self.copy({}); + ImVec2 size = _imgui_item_size_get(selectable, IMGUI_SELECTABLE); + bool isActivated = false; + + if (id == self.id) + { + isActivated = _imgui_input_text(IMGUI_CHANGE_INPUT_TEXT.copy({.size = size}), imgui, buffer); + imgui_contextual_actions_disable(imgui); + + if (isActivated || _imgui_is_no_click_on_item()) + { + value = buffer; + buffer.clear(); + id = ID_NONE; + imgui_contextual_actions_enable(imgui); + } + } + else + { + isActivated = _imgui_selectable(selectable, imgui); + + if (_imgui_is_input_begin()) + { + buffer = value; + buffer.resize(IMGUI_CHANGE_INPUT_TEXT.max); + id = self.id; + ImGui::SetKeyboardFocusHere(-1); + } + } -static void _imgui_item_end(void) -{ - ImGui::End(); -} + return isActivated; +}; -static void _imgui_item_begin_child(const ImguiItem& item) -{ - if (item.color.is_normal()) ImGui::PushStyleColor(ImGuiCol_ChildBg, item.color.normal); - ImGui::BeginChild(item.label.c_str(), item.size, item.flags, item.flagsAlt); - if (item.color.is_normal()) ImGui::PopStyleColor(); -} +IMGUI_ITEM_ATLAS_FUNCTION(_imgui_atlas_selectable, _imgui_selectable(self, imgui)); +IMGUI_ITEM_ATLAS_VALUE_FUNCTION(_imgui_atlas_selectable_input_int, s32, _imgui_selectable_input_int(self, imgui, value)); +IMGUI_ITEM_ATLAS_VALUE_FUNCTION(_imgui_atlas_selectable_input_text, std::string, _imgui_selectable_input_text(self, imgui, value)); -static void _imgui_item_end_child(void) -{ - ImGui::EndChild(); -} - -static void _imgui_item_dockspace(const ImguiItem& item) -{ - ImGui::DockSpace(ImGui::GetID(item.label.c_str()), item.size, item.flags); -} - -static void _imgui_keyboard_navigation_set(bool value) -{ - if (value) ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; - if (!value) ImGui::GetIO().ConfigFlags &= ~ImGuiConfigFlags_NavEnableKeyboard; -} - -static bool _imgui_item_option_popup(Imgui* self, const ImguiItem& item) +static bool _imgui_option_popup(ImguiItem self, Imgui* imgui) { ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); - if (ImGui::BeginPopupModal(item.popup.c_str(), nullptr, ImGuiWindowFlags_AlwaysAutoResize)) + if (imgui_begin_popup_modal(self.label_get(), imgui)) { - self->isContextualActionsEnabled = false; - - ImGui::Text(item.label.c_str()); + ImGui::Text(self.text_get()); ImGui::Separator(); - if (_imgui_item_button(self, IMGUI_POPUP_CONFIRM_BUTTON)) + if (_imgui_button(IMGUI_POPUP_OK, imgui)) { - ImGui::CloseCurrentPopup(); - ImGui::EndPopup(); - self->isContextualActionsEnabled = true; + imgui_close_current_popup(imgui); + imgui_end_popup(imgui); return true; } ImGui::SameLine(); - if (_imgui_item_button(self, IMGUI_POPUP_CANCEL_BUTTON)) - { - ImGui::CloseCurrentPopup(); - self->isContextualActionsEnabled = true; - } + if (_imgui_button(IMGUI_POPUP_CANCEL, imgui)) + imgui_close_current_popup(imgui); - ImGui::EndPopup(); + imgui_end_popup(imgui); } return false; @@ -548,49 +536,53 @@ static void _imgui_timeline(Imgui* self) static const ImU32 headerFrameInactiveColor = ImGui::GetColorU32(IMGUI_TIMELINE_HEADER_FRAME_INACTIVE_COLOR); static const ImU32 headerFrameMultipleInactiveColor = ImGui::GetColorU32(IMGUI_TIMELINE_HEADER_FRAME_MULTIPLE_INACTIVE_COLOR); static const ImU32 textColor = ImGui::GetColorU32(ImGuiCol_Text); - static ImVec2 scroll{}; - static ImVec2 pickerPos{}; - static Anm2Reference swapItemReference; - static bool isItemSwap = false; - static ImVec2 itemMin{}; - static ImVec2 mousePos{}; - static ImVec2 localMousePos{}; - static s32 frameTime = INDEX_NONE; static Anm2Reference hoverReference; - + static Anm2Reference swapItemReference; + static Anm2Type& itemType = self->reference->itemType; + static ImVec2 itemMin{}; + static ImVec2 localMousePos{}; + static ImVec2 mousePos{}; + static ImVec2 playheadPos{}; + static ImVec2 scroll{}; + static bool isItemSwap = false; static const ImVec2& frameSize = IMGUI_TIMELINE_FRAME_SIZE; - - _imgui_item_begin(IMGUI_TIMELINE); + static f32& time = self->preview->time; + static s32 frameTime{}; + static s32& itemID = self->reference->itemID; + IMGUI_BEGIN_OR_RETURN(IMGUI_TIMELINE, self); + Anm2Animation* animation = anm2_animation_from_reference(self->anm2, self->reference); if (!animation) { - ImGui::Text(IMGUI_TIMELINE_NO_ANIMATION); - _imgui_item_end(); // IMGUI_TIMELINE + ImGui::Text(IMGUI_TIMELINE_ANIMATION_NONE); + _imgui_end(); // IMGUI_TIMELINE return; } + s32 length = animation->frameNum; s32 actualLength = anm2_animation_length_get(animation); + ImVec2 actualFramesSize = {actualLength * frameSize.x, frameSize.y}; - ImVec2 framesSize = {animation->frameNum * frameSize.x, frameSize.y}; + ImVec2 framesSize = {length * frameSize.x, frameSize.y}; ImVec2 defaultItemSpacing = ImGui::GetStyle().ItemSpacing; ImVec2 defaultWindowPadding = ImGui::GetStyle().WindowPadding; ImVec2 defaultFramePadding = ImGui::GetStyle().FramePadding; - ImguiItem timelineChild = IMGUI_TIMELINE_CHILD; - timelineChild.size.y = ImGui::GetContentRegionAvail().y - IMGUI_TIMELINE_FOOTER_HEIGHT; + vec2 windowSize = ImGui::GetContentRegionAvail(); + vec2 childSize = {windowSize.x, windowSize.y - IMGUI_FOOTER_CHILD.size.y}; ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); - _imgui_item_begin_child(timelineChild); + _imgui_begin_child(IMGUI_TIMELINE_CHILD.copy({.size = childSize}), self); ImVec2 clipRectMin = ImGui::GetWindowDrawList()->GetClipRectMin(); ImVec2 clipRectMax = ImGui::GetWindowDrawList()->GetClipRectMax(); clipRectMin.x += IMGUI_TIMELINE_ITEM_SIZE.x; - ImVec2 scrollDelta = {0, 0}; + ImVec2 scrollDelta{}; if (_imgui_is_window_hovered()) { @@ -598,53 +590,60 @@ static void _imgui_timeline(Imgui* self) f32 lineHeight = ImGui::GetTextLineHeight(); scrollDelta.x -= io.MouseWheelH * lineHeight; - scrollDelta.y -= io.MouseWheel * lineHeight * 3.0f; + scrollDelta.y -= io.MouseWheel * lineHeight; } std::function timeline_header = [&]() { - static bool isHeaderClicked = false; - _imgui_item_begin_child(IMGUI_TIMELINE_HEADER); + static bool isPlayheadDrag = false; + _imgui_begin_child(IMGUI_TIMELINE_HEADER_CHILD, self); ImGui::SetScrollX(scroll.x); itemMin = ImGui::GetItemRectMin(); mousePos = ImGui::GetMousePos(); localMousePos = ImVec2(mousePos.x - itemMin.x + scroll.x, mousePos.y - itemMin.y); - frameTime = std::clamp((s32)(localMousePos.x / frameSize.x), 0, animation->frameNum - 1); + frameTime = (s32)(localMousePos.x / frameSize.x); - if (ImGui::IsMouseDown(ImGuiMouseButton_Left) && _imgui_is_window_hovered()) - isHeaderClicked = true; - - if (isHeaderClicked) + if (ImGui::IsMouseDown(0) && _imgui_is_window_hovered()) { - self->preview->time = frameTime; + if (!isPlayheadDrag) + imgui_undo_push(self, IMGUI_ACTION_MOVE_PLAYHEAD); + isPlayheadDrag = true; + } - if (ImGui::IsMouseReleased(ImGuiMouseButton_Left)) - isHeaderClicked = false; + if (isPlayheadDrag) + { + if (self->settings->playbackIsClampPlayhead) + time = std::clamp(frameTime, 0, std::max(0, length - 1)); + else + time = std::max(frameTime, 0); + + if (ImGui::IsMouseReleased(0)) + isPlayheadDrag = false; } ImVec2 cursorPos = ImGui::GetCursorScreenPos(); - pickerPos = {cursorPos.x + (self->preview->time * frameSize.x), cursorPos.y}; + playheadPos = {cursorPos.x + (time * frameSize.x), cursorPos.y}; ImDrawList* drawList = ImGui::GetWindowDrawList(); ImVec2 dummySize = actualFramesSize.x > framesSize.x ? actualFramesSize : framesSize; ImGui::Dummy(dummySize); - f32 viewWidth = ImGui::GetWindowContentRegionMax().x - ImGui::GetWindowContentRegionMin().x; + f32 viewWidth = ImGui::GetWindowContentRegionMax().x - ImGui::GetWindowContentRegionMin().x; s32 start = (s32)std::floor(scroll.x / frameSize.x) - 1; start = (start < 0) ? 0 : start; s32 end = (s32)std::ceil((scroll.x + viewWidth) / frameSize.x) + 1; end = (end > ANM2_FRAME_NUM_MAX) ? ANM2_FRAME_NUM_MAX : end; - pickerPos = ImVec2(cursorPos.x + self->preview->time * frameSize.x, cursorPos.y); + playheadPos = ImVec2(cursorPos.x + time * frameSize.x, cursorPos.y); for (s32 i = start; i < end; i++) { bool isMultiple = (i % IMGUI_TIMELINE_FRAME_MULTIPLE) == 0; - bool isInactive = i >= animation->frameNum; + bool isInactive = i >= length; f32 startX = cursorPos.x + i * frameSize.x; f32 endX = startX + frameSize.x; @@ -668,25 +667,25 @@ static void _imgui_timeline(Imgui* self) drawList->AddImage(self->resources->atlas.id, positionStart, positionEnd, ATLAS_UV_ARGS(ATLAS_FRAME_ALT)); } - _imgui_item_end_child(); // IMGUI_TIMELINE_HEADER + _imgui_end_child(); // IMGUI_TIMELINE_HEADER_CHILD ImGui::SetNextWindowPos(ImGui::GetWindowPos()); ImGui::SetNextWindowSize(ImGui::GetWindowSize()); - _imgui_item_begin(IMGUI_TIMELINE_PICKER); + _imgui_begin(IMGUI_PLAYHEAD, self); - ImVec2& pos = pickerPos; + ImVec2& pos = playheadPos; - ImVec2 lineStart = {pos.x + (frameSize.x * 0.5f) - (IMGUI_TIMELINE_PICKER_LINE_WIDTH * 0.5f), pos.y + frameSize.y}; - ImVec2 lineEnd = {lineStart.x + IMGUI_TIMELINE_PICKER_LINE_WIDTH, lineStart.y + timelineChild.size.y - frameSize.y}; + ImVec2 lineStart = {pos.x + (frameSize.x * 0.5f) - (IMGUI_PLAYHEAD_LINE_WIDTH * 0.5f), pos.y + frameSize.y}; + ImVec2 lineEnd = {lineStart.x + IMGUI_PLAYHEAD_LINE_WIDTH, lineStart.y + childSize.y - frameSize.y}; drawList = ImGui::GetWindowDrawList(); drawList->PushClipRect(clipRectMin, clipRectMax, true); drawList->AddImage(self->resources->atlas.id, pos, ImVec2(pos.x + frameSize.x, pos.y + frameSize.y), ATLAS_UV_ARGS(ATLAS_PICKER)); - drawList->AddRectFilled(lineStart, lineEnd, IMGUI_PICKER_LINE_COLOR); + drawList->AddRectFilled(lineStart, lineEnd, IMGUI_PLAYHEAD_LINE_COLOR); drawList->PopClipRect(); - _imgui_item_end(); + _imgui_end(); // IMGUI_PLAYHEAD }; std::function timeline_item_child = [&](Anm2Reference reference, s32& index) @@ -702,55 +701,72 @@ static void _imgui_timeline(Imgui* self) Anm2Null* null = nullptr; s32 buttonCount = type == ANM2_NULL ? 2 : 1; f32 buttonAreaWidth = buttonCount * buttonSize.x + (buttonCount - 1) * defaultItemSpacing.x; + bool isSelected = (self->reference->itemID == reference.itemID && self->reference->itemType == type); - ImguiItem imguiItem = *IMGUI_TIMELINE_ITEMS[type]; - ImguiItem imguiItemSelectable = *IMGUI_TIMELINE_ITEM_SELECTABLES[type]; - imguiItemSelectable.isSelected = self->reference->itemID == reference.itemID && self->reference->itemType == type; - ImGui::PushID(reference.itemID); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, defaultItemSpacing); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, defaultWindowPadding); ImVec2 childPos = ImGui::GetCursorScreenPos(); - _imgui_item_begin_child(imguiItem); + _imgui_begin_child(*IMGUI_TIMELINE_ITEM_CHILDS[type], self); ImVec2 childSize = ImGui::GetContentRegionAvail(); + std::string dragDrop = IMGUI_TIMELINE_ITEM_SELECTABLES[type]->dragDrop; + switch (type) { case ANM2_ROOT: case ANM2_TRIGGERS: - if (_imgui_item_atlas_image_selectable(self, imguiItemSelectable)) + if (_imgui_atlas_selectable(IMGUI_TIMELINE_ITEM_SELECTABLES[type]->copy({.isSelected = isSelected}), self)) *self->reference = reference; break; case ANM2_LAYER: layer = &self->anm2->layers[reference.itemID]; - imguiItemSelectable.label = std::format(IMGUI_TIMELINE_CHILD_ID_LABEL, reference.itemID, layer->name); - if (_imgui_item_atlas_image_selectable_inputtext(self, imguiItemSelectable, layer->name, index)) + if + ( + _imgui_atlas_selectable_input_text(IMGUI_TIMELINE_ITEM_SELECTABLES[type]->copy + ({ + .isSelected = isSelected, + .label = std::format(IMGUI_TIMELINE_ITEM_CHILD_FORMAT, reference.itemID, layer->name), + .id = index + }), + self, layer->name) + ) *self->reference = reference; break; case ANM2_NULL: null = &self->anm2->nulls[reference.itemID]; - imguiItemSelectable.label = std::format(IMGUI_TIMELINE_CHILD_ID_LABEL, reference.itemID, null->name); - if (_imgui_item_atlas_image_selectable_inputtext(self, imguiItemSelectable, null->name, index)) + if + ( + _imgui_atlas_selectable_input_text(IMGUI_TIMELINE_ITEM_SELECTABLES[type]->copy + ({ + .isSelected = isSelected, + .label = std::format(IMGUI_TIMELINE_ITEM_CHILD_FORMAT, reference.itemID, null->name), + .id = index + }), + self, null->name) + ) *self->reference = reference; break; default: break; } - if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2()); + if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None) && !dragDrop.empty()) { *self->reference = reference; - ImGui::SetDragDropPayload(imguiItemSelectable.dragDrop.c_str(), &reference, sizeof(Anm2Reference)); + ImGui::SetDragDropPayload(dragDrop.c_str(), &reference, sizeof(Anm2Reference)); timeline_item_child(reference, index); ImGui::EndDragDropSource(); } + ImGui::PopStyleVar(); if (ImGui::BeginDragDropTarget()) { - if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(imguiItemSelectable.dragDrop.c_str())) + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(dragDrop.c_str())) { Anm2Reference checkReference = *(Anm2Reference*)payload->Data; if (checkReference != reference) @@ -765,10 +781,9 @@ static void _imgui_timeline(Imgui* self) if (type == ANM2_LAYER) { - ImguiItem spritesheetIDItem = IMGUI_TIMELINE_SPRITESHEET_ID; - spritesheetIDItem.label = std::format(IMGUI_TIMELINE_SPRITESHEET_ID_FORMAT, layer->spritesheetID); ImGui::SameLine(); - _imgui_item_atlas_image_selectable_inputint(self, spritesheetIDItem, layer->spritesheetID, index); + _imgui_atlas_selectable_input_int(IMGUI_TIMELINE_SPRITESHEET_ID.copy + ({.label = std::format(IMGUI_SPRITESHEET_ID_FORMAT, layer->spritesheetID), .id = index}), self, layer->spritesheetID); } ImGui::SetCursorScreenPos({childPos.x + childSize.x - buttonAreaWidth, childPos.y + defaultWindowPadding.y}); @@ -776,19 +791,16 @@ static void _imgui_timeline(Imgui* self) if (type == ANM2_NULL) { const ImguiItem& rectItem = null->isShowRect ? IMGUI_TIMELINE_ITEM_SHOW_RECT : IMGUI_TIMELINE_ITEM_HIDE_RECT; - if (_imgui_item_atlas_image_button(self, rectItem)) - null->isShowRect = !null->isShowRect; - + if (_imgui_atlas_button(rectItem, self)) null->isShowRect = !null->isShowRect; ImGui::SameLine(0.0f, defaultItemSpacing.x); } const ImguiItem& visibleItem = item->isVisible ? IMGUI_TIMELINE_ITEM_VISIBLE : IMGUI_TIMELINE_ITEM_INVISIBLE; - if (_imgui_item_atlas_image_button(self, visibleItem)) - item->isVisible = !item->isVisible; + if (_imgui_atlas_button(visibleItem, self)) item->isVisible = !item->isVisible; ImGui::PopStyleVar(2); - _imgui_item_end_child(); // imguiItem + _imgui_end_child(); // itemChild ImGui::PopID(); @@ -797,14 +809,13 @@ static void _imgui_timeline(Imgui* self) std::function timeline_items_child = [&]() { + static s32& animationID = self->reference->animationID; s32 index = 0; - s32& animationID = self->reference->animationID; - - _imgui_item_begin_child(IMGUI_TIMELINE_ITEMS_CHILD); + _imgui_begin_child(IMGUI_TIMELINE_ITEMS_CHILD, self); ImGui::SetScrollY(scroll.y); - timeline_item_child({animationID, ANM2_ROOT, ID_NONE}, index); + timeline_item_child({animationID, ANM2_ROOT}, index); for (auto& [i, id] : std::ranges::reverse_view(self->anm2->layerMap)) timeline_item_child({animationID, ANM2_LAYER, id}, index); @@ -812,9 +823,9 @@ static void _imgui_timeline(Imgui* self) for (auto & [id, null] : animation->nullAnimations) timeline_item_child({animationID, ANM2_NULL, id}, index); - timeline_item_child({animationID, ANM2_TRIGGERS, ID_NONE}, index); + timeline_item_child({animationID, ANM2_TRIGGERS}, index); - _imgui_item_end_child(); // IMGUI_TIMELINE_ITEMS_CHILD + _imgui_end_child(); // IMGUI_TIMELINE_ITEMS_CHILD if (isItemSwap) { @@ -864,10 +875,7 @@ static void _imgui_timeline(Imgui* self) f32 viewWidth = ImGui::GetWindowContentRegionMax().x - ImGui::GetWindowContentRegionMin().x; - ImguiItem imguiItemFrames = IMGUI_TIMELINE_ITEM_FRAMES_CHILD; - imguiItemFrames.size = actualFramesSize.x > framesSize.x ? actualFramesSize : framesSize; - - _imgui_item_begin_child(imguiItemFrames); + _imgui_begin_child(IMGUI_TIMELINE_ITEM_FRAMES_CHILD.copy({.size = actualFramesSize.x > framesSize.x ? actualFramesSize : framesSize}), self); ImVec2 startPos = ImGui::GetCursorPos(); ImVec2 cursorPos = ImGui::GetCursorScreenPos(); @@ -875,12 +883,16 @@ static void _imgui_timeline(Imgui* self) if (_imgui_is_window_hovered()) { hoverReference = reference; - hoverReference.frameIndex = anm2_frame_index_from_time(self->anm2, reference, frameTime); + if (reference.itemType == ANM2_TRIGGERS) + hoverReference.frameIndex = frameTime; + else + hoverReference.frameIndex = anm2_frame_index_from_time(self->anm2, reference, frameTime); if (ImGui::IsMouseReleased(ImGuiMouseButton_Left)) { *self->reference = reference; self->clipboard->location = hoverReference; + _imgui_spritesheet_editor_set(self, self->anm2->layers[self->reference->itemID].spritesheetID); } } @@ -910,25 +922,22 @@ static void _imgui_timeline(Imgui* self) { ImGui::PushID(i); reference.frameIndex = i; - ImguiItem frameButton = *IMGUI_TIMELINE_FRAMES[type]; ImVec2 framePos = ImGui::GetCursorPos(); - frameButton.atlas = frame.isInterpolated ? ATLAS_CIRCLE : ATLAS_SQUARE; - frameButton.size = {frameSize.x * frame.delay, frameSize.y}; - frameButton.isSelected = reference == *self->reference; + AtlasType atlas = frame.isInterpolated ? ATLAS_CIRCLE : ATLAS_SQUARE; if (type == ANM2_TRIGGERS) { framePos.x = startPos.x + (frameSize.x * frame.atFrame); - frameButton.atlas = ATLAS_TRIGGER; + atlas = ATLAS_TRIGGER; } + ImguiItem frameButton = IMGUI_TIMELINE_FRAMES[type]->copy + ({.isSelected = reference == *self->reference, .size = {frameSize.x * frame.delay, frameSize.y}, .id = i, .atlas = atlas}); + ImGui::SetCursorPos(framePos); - if (_imgui_item_atlas_image_button(self, frameButton)) - { + if (_imgui_atlas_button(frameButton, self)) *self->reference = reference; - _imgui_spritesheet_editor_set(self, self->anm2->layers[self->reference->itemID].spritesheetID); - } if (ImGui::IsItemHovered()) { @@ -938,12 +947,20 @@ static void _imgui_timeline(Imgui* self) if (type == ANM2_TRIGGERS) { - if (ImGui::IsItemActivated()) - imgui_undo_stack_push(self, IMGUI_ACTION_TRIGGER_MOVE); + if (ImGui::IsItemActivated()) imgui_undo_push(self, IMGUI_ACTION_TRIGGER_MOVE); if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceNoPreviewTooltip)) { - frame.atFrame = frameTime; + frame.atFrame = std::max(frameTime, 0); + for (auto& frameCheck : animation->triggers.frames) + { + if (&frame == &frameCheck) continue; + if (frame.atFrame == frameCheck.atFrame) + { + frame.atFrame++; + break; + } + } ImGui::EndDragDropSource(); } } @@ -951,7 +968,7 @@ static void _imgui_timeline(Imgui* self) { if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) { - ImGui::SetDragDropPayload(frameButton.dragDrop.c_str(), &reference, sizeof(Anm2Reference)); + ImGui::SetDragDropPayload(frameButton.drag_drop_get(), &reference, sizeof(Anm2Reference)); timeline_item_frame(i, frame); ImGui::EndDragDropSource(); } @@ -959,12 +976,12 @@ static void _imgui_timeline(Imgui* self) if (ImGui::BeginDragDropTarget()) { - if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(frameButton.dragDrop.c_str())) + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(frameButton.drag_drop_get())) { Anm2Reference swapReference = *(Anm2Reference*)payload->Data; if (swapReference != reference) { - imgui_undo_stack_push(self, IMGUI_ACTION_FRAME_SWAP); + imgui_undo_push(self, IMGUI_ACTION_FRAME_SWAP); Anm2Frame* swapFrame = anm2_frame_from_reference(self->anm2, &reference); Anm2Frame* dragFrame = anm2_frame_from_reference(self->anm2, &swapReference); @@ -983,8 +1000,7 @@ static void _imgui_timeline(Imgui* self) ImGui::EndDragDropTarget(); } - if (i < (s32)item->frames.size() - 1) - ImGui::SameLine(); + if (i < (s32)item->frames.size() - 1) ImGui::SameLine(); ImGui::PopID(); }; @@ -992,7 +1008,7 @@ static void _imgui_timeline(Imgui* self) for (auto [i, frame] : std::views::enumerate(item->frames)) timeline_item_frame(i, frame); - _imgui_item_end_child(); // itemFramesChild + _imgui_end_child(); // itemFramesChild ImGui::PopID(); @@ -1004,7 +1020,7 @@ static void _imgui_timeline(Imgui* self) s32& animationID = self->reference->animationID; s32 index = 0; - _imgui_item_begin_child(IMGUI_TIMELINE_FRAMES_CHILD); + _imgui_begin_child(IMGUI_TIMELINE_FRAMES_CHILD, self); scroll.x = ImGui::GetScrollX() + scrollDelta.x; scroll.y = ImGui::GetScrollY() + scrollDelta.y; ImGui::SetScrollX(scroll.x); @@ -1020,41 +1036,45 @@ static void _imgui_timeline(Imgui* self) timeline_item_frames(Anm2Reference(animationID, ANM2_TRIGGERS), index); - _imgui_item_end_child(); // IMGUI_TIMELINE_FRAMES_CHILD + _imgui_end_child(); // IMGUI_TIMELINE_FRAMES_CHILD }; - // In order to set scroll properly, timeline_frames_child must be called first - ImGui::SetCursorPos(ImVec2(IMGUI_TIMELINE_ITEM.size)); + // In order to set scroll properly timeline_frames_child must be called first + ImGui::SetCursorPos(ImVec2(IMGUI_TIMELINE_ITEM_SIZE)); timeline_frames_child(); - ImGui::SetCursorPos(ImVec2(0, 0)); + ImGui::SetCursorPos(ImVec2()); - _imgui_item_begin_child(IMGUI_TIMELINE_ITEM); - _imgui_item_end_child(); // IMGUI_TIMELINE_ITEM + _imgui_begin_child(IMGUI_TIMELINE_ITEM_CHILD, self); + _imgui_end_child(); // IMGUI_TIMELINE_ITEM_CHILD ImGui::SameLine(); - timeline_header(); + timeline_items_child(); ImGui::PopStyleVar(2); - _imgui_item_end_child(); // IMGUI_TIMELINE_CHILD - + _imgui_end_child(); // IMGUI_TIMELINE_CHILD Anm2Frame* frame = anm2_frame_from_reference(self->anm2, self->reference); - - _imgui_item_begin_child(IMGUI_TIMELINE_FOOTER_ITEM_CHILD); - _imgui_item_button(self, IMGUI_TIMELINE_ADD_ITEM); - ImGui::SameLine(); + _imgui_begin_child(IMGUI_TIMELINE_ITEM_FOOTER_CHILD, self); + _imgui_button(IMGUI_TIMELINE_ADD_ITEM, self); - if (_imgui_item_button(self, IMGUI_TIMELINE_REMOVE_ITEM)) + if (imgui_begin_popup(IMGUI_TIMELINE_ADD_ITEM.popup, self)) { - switch (self->reference->itemType) + if (_imgui_selectable(IMGUI_TIMELINE_ADD_ITEM_LAYER, self)) anm2_layer_add(self->anm2); + if (_imgui_selectable(IMGUI_TIMELINE_ADD_ITEM_NULL, self)) anm2_null_add(self->anm2); + imgui_end_popup(self); + } + + if (_imgui_button(IMGUI_TIMELINE_REMOVE_ITEM.copy({itemType == ANM2_NONE || itemType == ANM2_ROOT || itemType == ANM2_TRIGGERS}), self)) + { + switch (itemType) { case ANM2_LAYER: - anm2_layer_remove(self->anm2, self->reference->itemID); + anm2_layer_remove(self->anm2, itemID); break; case ANM2_NULL: - anm2_null_remove(self->anm2, self->reference->itemID); + anm2_null_remove(self->anm2, itemID); break; default: break; @@ -1063,265 +1083,323 @@ static void _imgui_timeline(Imgui* self) anm2_reference_item_clear(self->reference); } - _imgui_item_end_child(); //IMGUI_TIMELINE_FOOTER_ITEM_CHILD + _imgui_end_child(); //IMGUI_TIMELINE_FOOTER_ITEM_CHILD ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); ImGui::SameLine(); ImGui::PopStyleVar(); - _imgui_item_begin_child(IMGUI_TIMELINE_FOOTER_OPTIONS_CHILD); + _imgui_begin_child(IMGUI_TIMELINE_OPTIONS_FOOTER_CHILD, self); - const ImguiItem& playPauseItem = self->preview->isPlaying ? IMGUI_TIMELINE_PAUSE : IMGUI_TIMELINE_PLAY; - if (_imgui_item_button(self, playPauseItem)) - self->preview->isPlaying = !self->preview->isPlaying; - - ImGui::SameLine(); - - if (_imgui_item_button(self, IMGUI_TIMELINE_ADD_FRAME)) - anm2_frame_add(self->anm2, self->reference, (s32)self->preview->time); - - ImGui::SameLine(); - - if(_imgui_item_button(self, IMGUI_TIMELINE_REMOVE_FRAME)) + if (_imgui_button(self->preview->isPlaying ? IMGUI_PAUSE : IMGUI_PLAY, self)) { - if (anm2_frame_from_reference(self->anm2, self->reference)) - { - Anm2Item* item = anm2_item_from_reference(self->anm2, self->reference); - item->frames.erase(item->frames.begin() + self->reference->frameIndex); - anm2_reference_frame_clear(self->reference); - } + if (!self->preview->isPlaying && time >= animation->frameNum - 1) + time = 0.0f; + self->preview->isPlaying = !self->preview->isPlaying; } - ImGui::SameLine(); + if (_imgui_button(IMGUI_ADD_FRAME, self)) + anm2_frame_add(self->anm2, nullptr, self->reference, (s32)time); - _imgui_item_button(self, IMGUI_TIMELINE_BAKE); + if(_imgui_button(IMGUI_REMOVE_FRAME.copy({!frame}), self)) + { + anm2_frame_erase(self->anm2, self->reference); + anm2_reference_frame_clear(self->reference); + } - ImGui::SameLine(); + _imgui_button(IMGUI_BAKE.copy({!frame || itemType == ANM2_TRIGGERS}), self); + + if (imgui_begin_popup_modal(IMGUI_BAKE.popup, self, IMGUI_BAKE.popupSize)) + { + static s32& interval = self->settings->bakeInterval; + static bool& isRoundScale = self->settings->bakeIsRoundScale; + static bool& isRoundRotation = self->settings->bakeIsRoundRotation; - if (_imgui_item_button(self, IMGUI_TIMELINE_FIT_ANIMATION_LENGTH)) + _imgui_begin_child(IMGUI_BAKE_CHILD, self); + + _imgui_input_int(IMGUI_BAKE_INTERVAL.copy({.max = frame->delay}), self, interval); + _imgui_checkbox(IMGUI_BAKE_ROUND_SCALE, self, isRoundScale); + _imgui_checkbox(IMGUI_BAKE_ROUND_ROTATION, self, isRoundRotation); + + if (_imgui_button(IMGUI_BAKE_CONFIRM, self)) + { + anm2_frame_bake(self->anm2, self->reference, interval, isRoundScale, isRoundRotation); + imgui_close_current_popup(self); + } + + if (_imgui_button(IMGUI_POPUP_CANCEL, self)) + imgui_close_current_popup(self); + + _imgui_end_child(); //IMGUI_BAKE_CHILD) + + imgui_end_popup(self); + } + + if (_imgui_button(IMGUI_FIT_ANIMATION_LENGTH, self)) anm2_animation_length_set(animation); - ImGui::SameLine(); - _imgui_item_inputint(self, IMGUI_TIMELINE_ANIMATION_LENGTH, animation->frameNum); - ImGui::SameLine(); - _imgui_item_inputint(self, IMGUI_TIMELINE_FPS, self->anm2->fps); - ImGui::SameLine(); - _imgui_item_checkbox(self, IMGUI_TIMELINE_LOOP, animation->isLoop); - ImGui::SameLine(); - _imgui_item_text_inputtext(self, IMGUI_TIMELINE_CREATED_BY, self->anm2->createdBy); + _imgui_input_int(IMGUI_ANIMATION_LENGTH, self, animation->frameNum); + _imgui_input_int(IMGUI_FPS, self, self->anm2->fps); + _imgui_checkbox(IMGUI_LOOP, self, animation->isLoop); + _imgui_selectable_input_text(IMGUI_CREATED_BY.copy({.label = self->anm2->createdBy}), self, self->anm2->createdBy); - _imgui_item_end_child(); //IMGUI_TIMELINE_FOOTER_OPTIONS_CHILD + _imgui_end_child(); // IMGUI_TIMELINE_FOOTER_OPTIONS_CHILD - _imgui_pending_popup_process(self); - - if (ImGui::BeginPopup(IMGUI_TIMELINE_ADD_ITEM.popup.c_str())) - { - if (_imgui_item_selectable(self, IMGUI_TIMELINE_ADD_ITEM_LAYER)) - anm2_layer_add(self->anm2); - - if (_imgui_item_selectable(self, IMGUI_TIMELINE_ADD_ITEM_NULL)) - anm2_null_add(self->anm2); - - ImGui::EndPopup(); - } - - if (ImGui::BeginPopupModal(IMGUI_TIMELINE_BAKE.popup.c_str(), nullptr, ImGuiWindowFlags_AlwaysAutoResize)) - { - static s32 interval = 1; - static bool isRoundScale = true; - static bool isRoundRotation = true; - - if (frame) - { - _imgui_item_begin_child(IMGUI_TIMELINE_BAKE_CHILD); - - ImguiItem bakeIntervalItem = IMGUI_TIMELINE_BAKE_INTERVAL; - bakeIntervalItem.max = frame->delay; - - _imgui_item_inputint(self, bakeIntervalItem, interval); - - _imgui_item_checkbox(self, IMGUI_TIMELINE_BAKE_ROUND_SCALE, isRoundScale); - _imgui_item_checkbox(self, IMGUI_TIMELINE_BAKE_ROUND_ROTATION, isRoundRotation); - - if (_imgui_item_button(self, IMGUI_TIMELINE_BAKE_CONFIRM)) - { - anm2_frame_bake(self->anm2, self->reference, interval, isRoundScale, isRoundRotation); - ImGui::CloseCurrentPopup(); - } - - ImGui::SameLine(); - - if (_imgui_item_button(self, IMGUI_TIMELINE_BAKE_CANCEL)) - ImGui::CloseCurrentPopup(); - - _imgui_item_end_child(); //IMGUI_TIMELINE_BAKE_CHILD) - - ImGui::EndPopup(); - } - else - { - ImGui::CloseCurrentPopup(); - ImGui::EndPopup(); - } - } - - _imgui_item_end(); // IMGUI_TIMELINE - - self->preview->time = std::clamp(self->preview->time, 0.0f, (f32)(animation->frameNum - 1)); + _imgui_end(); // IMGUI_TIMELINE } static void _imgui_taskbar(Imgui* self) { ImGuiViewport* viewport = ImGui::GetMainViewport(); - ImguiItem taskbarItem = IMGUI_TASKBAR; - taskbarItem.size = {viewport->Size.x, IMGUI_TASKBAR.size.y}; + ImguiItem taskbar = IMGUI_TASKBAR; + ImGui::SetNextWindowSize({viewport->Size.x, IMGUI_TASKBAR.size.y}); ImGui::SetNextWindowPos(viewport->Pos); - _imgui_item_begin(taskbarItem); + _imgui_begin(taskbar, self); Anm2Animation* animation = anm2_animation_from_reference(self->anm2, self->reference); + Anm2Item* item = anm2_item_from_reference(self->anm2, self->reference); - _imgui_item_selectable(self, IMGUI_TASKBAR_FILE); - ImGui::SameLine(); - _imgui_item_selectable(self, IMGUI_TASKBAR_WIZARD); - ImGui::SameLine(); - _imgui_item_selectable(self, IMGUI_TASKBAR_PLAYBACK); - _imgui_pending_popup_process(self); - - if (ImGui::BeginPopup(IMGUI_TASKBAR_FILE.popup.c_str())) - { - _imgui_item_selectable(self, IMGUI_FILE_NEW); // imgui_file_new - _imgui_item_selectable(self, IMGUI_FILE_OPEN); // imgui_file_open - _imgui_item_selectable(self, IMGUI_FILE_SAVE); // imgui_file_save - _imgui_item_selectable(self, IMGUI_FILE_SAVE_AS); // imgui_file_save_as - - ImGui::EndPopup(); - } - - if (ImGui::BeginPopup(IMGUI_TASKBAR_WIZARD.popup.c_str())) - { - _imgui_item_selectable(self, IMGUI_TASKBAR_WIZARD_GENERATE_ANIMATION_FROM_GRID); - ImGui::Separator(); - _imgui_item_selectable(self, IMGUI_TASKBAR_WIZARD_RENDER_ANIMATION); + _imgui_selectable(IMGUI_FILE.copy({}), self); - ImGui::EndPopup(); - } - - _imgui_pending_popup_process(self); - - if (ImGui::BeginPopupModal(IMGUI_TASKBAR_WIZARD_RENDER_ANIMATION.popup.c_str(), nullptr, ImGuiWindowFlags_AlwaysAutoResize)) + if (imgui_begin_popup(IMGUI_FILE.popup, self)) { - if (animation) - { - RenderType& type = self->settings->renderType; - std::string& path = self->settings->renderPath; - std::string& format = self->settings->renderFormat; - std::string& ffmpegPath = self->settings->ffmpegPath; + _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 - self->isContextualActionsEnabled = false; - - _imgui_item_begin_child(IMGUI_RENDER_ANIMATION_CHILD); - - if (_imgui_item_atlas_image_button(self, IMGUI_RENDER_ANIMATION_BROWSE)) - { - switch (self->settings->renderType) - { - case RENDER_PNG: - dialog_render_directory_set(self->dialog); - break; - default: - dialog_render_path_set(self->dialog); - break; - } - } - - if (self->dialog->isSelected && self->dialog->type == DIALOG_RENDER_PATH_SET) - { - path = self->dialog->path; - dialog_reset(self->dialog); - } - - ImGui::SameLine(); - _imgui_item_inputtext(self, IMGUI_RENDER_ANIMATION_LOCATION, path); - - if (_imgui_item_atlas_image_button(self, IMGUI_RENDER_ANIMATION_FFMPEG_BROWSE)) - dialog_ffmpeg_path_set(self->dialog); - - if (self->dialog->isSelected && self->dialog->type == DIALOG_FFMPEG_PATH_SET) - { - ffmpegPath = self->dialog->path; - dialog_reset(self->dialog); - } - - ImGui::SameLine(); - _imgui_item_inputtext(self, IMGUI_RENDER_ANIMATION_FFMPEG_PATH, ffmpegPath); - - _imgui_item_combo(self, IMGUI_RENDER_ANIMATION_OUTPUT, (s32*)&type); - - _imgui_item_inputtext(self, IMGUI_RENDER_ANIMATION_FORMAT, format); - - ImGui::Separator(); - - if (_imgui_item_button(self, IMGUI_RENDER_ANIMATION_CONFIRM)) - { - bool isRenderStart = true; - - switch (type) - { - case RENDER_PNG: - if (!std::filesystem::is_directory(path)) - { - imgui_message_queue_push(self, IMGUI_MESSAGE_RENDER_ANIMATION_DIRECTORY_ERROR); - isRenderStart = false; - } - break; - case RENDER_GIF: - case RENDER_WEBM: - if (!path_valid(path)) - { - imgui_message_queue_push(self, IMGUI_MESSAGE_RENDER_ANIMATION_PATH_ERROR); - isRenderStart = false; - } - default: - break; - } - - if (isRenderStart) - preview_render_start(self->preview); - else - self->preview->isRenderCancelled = true; - - ImGui::CloseCurrentPopup(); - } - - ImGui::SameLine(); - - if (_imgui_item_button(self, IMGUI_RENDER_ANIMATION_CANCEL)) - ImGui::CloseCurrentPopup(); - - _imgui_item_end_child(); // IMGUI_RENDER_ANIMATION_CHILD - ImGui::EndPopup(); - } - else - { - ImGui::CloseCurrentPopup(); - ImGui::EndPopup(); - imgui_message_queue_push(self, IMGUI_MESSAGE_RENDER_ANIMATION_NO_SELECTED_ANIMATION_ERROR); - } + imgui_end_popup(self); } - _imgui_pending_popup_process(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)); + dialog_reset(self->dialog); + } - if (ImGui::BeginPopupModal(IMGUI_RENDER_ANIMATION_CONFIRM.popup.c_str(), nullptr, ImGuiWindowFlags_AlwaysAutoResize)) + if (self->dialog->isSelected && self->dialog->type == DIALOG_ANM2_SAVE) + { + anm2_serialize(self->anm2, self->dialog->path); + window_title_from_path_set(self->window, self->dialog->path); + imgui_log_push(self, std::format(IMGUI_LOG_FILE_SAVE_FORMAT, self->dialog->path)); + dialog_reset(self->dialog); + } + + _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_RENDER_ANIMATION.copy({!animation}), self); + + imgui_end_popup(self); + } + + if (imgui_begin_popup_modal(IMGUI_GENERATE_ANIMATION_FROM_GRID.popup, self, IMGUI_GENERATE_ANIMATION_FROM_GRID.popupSize)) + { + _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_end_child(); //IMGUI_GENERATE_ANIMATION_FROM_GRID_OPTIONS_CHILD + + ImGui::SameLine(); + + _imgui_begin_child(IMGUI_GENERATE_ANIMATION_FROM_GRID_PREVIEW_CHILD, self); + _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_POPUP_CANCEL, self)) imgui_close_current_popup(self); + _imgui_end_child(); // IMGUI_FOOTER_CHILD + + imgui_end_popup(self); + } + + if (imgui_begin_popup_modal(IMGUI_CHANGE_ALL_FRAME_PROPERTIES.popup, self, IMGUI_CHANGE_ALL_FRAME_PROPERTIES.popupSize)) + { + static auto& isCrop = self->settings->changeIsCrop; + static auto& isSize = self->settings->changeIsSize; + static auto& isPosition = self->settings->changeIsPosition; + static auto& isPivot = self->settings->changeIsPivot; + static auto& isScale = self->settings->changeIsScale; + static auto& isRotation = self->settings->changeIsRotation; + static auto& isColorOffset = self->settings->changeIsColorOffset; + static auto& isTint = self->settings->changeIsTint; + static auto& isVisibleSet = self->settings->changeIsVisibleSet; + static auto& isInterpolatedSet = self->settings->changeIsInterpolatedSet; + static auto& crop = self->settings->changeCrop; + static auto& size = self->settings->changeSize; + static auto& position = self->settings->changePosition; + static auto& pivot = self->settings->changePivot; + static auto& scale = self->settings->changeScale; + static auto& rotation = self->settings->changeRotation; + static auto& isDelay = self->settings->changeIsDelay; + static auto& delay = self->settings->changeDelay; + static auto& tint = self->settings->changeTint; + static auto& colorOffset = self->settings->changeColorOffset; + static auto& isVisible = self->settings->changeIsVisible; + static auto& isInterpolated = self->settings->changeIsInterpolated; + static auto& isFromSelectedFrame = self->settings->changeIsFromSelectedFrame; + static auto& numberFrames = self->settings->changeNumberFrames; + s32 start = std::max(self->reference->frameIndex, 0); + s32 max = isFromSelectedFrame ? + std::max(ANM2_FRAME_NUM_MIN, (s32)item->frames.size() - start) : (s32)item->frames.size(); + + auto change_frames = [&](Anm2ChangeType type) + { + anm2_item_frame_set + ( + self->anm2, self->reference, + Anm2FrameChange + { + isVisibleSet ? std::optional{isVisible} : std::nullopt, + isInterpolatedSet ? std::optional{isInterpolated} : std::nullopt, + isRotation ? std::optional{rotation} : std::nullopt, + isDelay ? std::optional{delay} : std::nullopt, + isCrop ? std::optional{crop} : std::nullopt, + isPivot ? std::optional{pivot} : std::nullopt, + isPosition ? std::optional{position} : std::nullopt, + isSize ? std::optional{size} : std::nullopt, + isScale ? std::optional{scale} : std::nullopt, + isColorOffset ? std::optional{colorOffset} : std::nullopt, + isTint ? std::optional{tint} : std::nullopt + }, + type, start, numberFrames + ); + + imgui_close_current_popup(self); + }; + + _imgui_begin_child(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_CHILD, self); + _imgui_checkbox_drag_float2(IMGUI_FRAME_PROPERTIES_CROP.copy({!isCrop}), self, crop, isCrop); + _imgui_checkbox_drag_float2(IMGUI_FRAME_PROPERTIES_SIZE.copy({!isSize}), self, size, isSize); + _imgui_checkbox_drag_float2(IMGUI_FRAME_PROPERTIES_POSITION.copy({!isPosition}), self, position, isPosition); + _imgui_checkbox_drag_float2(IMGUI_FRAME_PROPERTIES_PIVOT.copy({!isPivot}), self, pivot, isPivot); + _imgui_checkbox_drag_float2(IMGUI_FRAME_PROPERTIES_SCALE.copy({!isScale}), self, scale, isScale); + _imgui_checkbox_drag_float(IMGUI_FRAME_PROPERTIES_ROTATION.copy({!isRotation}), self, rotation, isRotation); + _imgui_checkbox_input_int(IMGUI_FRAME_PROPERTIES_DELAY.copy({!isDelay}), self, delay, isDelay); + _imgui_checkbox_color_edit4(IMGUI_FRAME_PROPERTIES_TINT.copy({!isTint}), self, tint, isTint); + _imgui_checkbox_color_edit3(IMGUI_FRAME_PROPERTIES_COLOR_OFFSET.copy({!isColorOffset}), self, colorOffset, isColorOffset); + _imgui_checkbox_checkbox(IMGUI_FRAME_PROPERTIES_VISIBLE.copy({!isVisibleSet}), self, isVisible, isVisibleSet); + _imgui_checkbox_checkbox(IMGUI_FRAME_PROPERTIES_INTERPOLATED.copy({!isInterpolatedSet}), self, isInterpolated, isInterpolatedSet); + _imgui_end_child(); // IMGUI_FOOTER_CHILD + + _imgui_begin_child(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_SETTINGS_CHILD, self); + _imgui_text(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_SETTINGS, self); + _imgui_checkbox(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_FROM_SELECTED_FRAME, self, isFromSelectedFrame); + _imgui_input_int(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_NUMBER_FRAMES.copy({.max = max, .value = max}), self, numberFrames); + _imgui_end_child(); // IMGUI_CHANGE_ALL_FRAME_PROPERTIES_SETTINGS_CHILD + + _imgui_begin_child(IMGUI_FOOTER_CHILD, self); + if (_imgui_button(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_ADD, self)) change_frames(ANM2_CHANGE_ADD); + if (_imgui_button(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_SUBTRACT, self)) change_frames(ANM2_CHANGE_SUBTRACT); + if (_imgui_button(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_SET, self)) change_frames(ANM2_CHANGE_SET); + if (_imgui_button(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_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); + + if (_imgui_atlas_button(IMGUI_RENDER_ANIMATION_LOCATION_BROWSE, self)) + { + switch (self->settings->renderType) + { + case RENDER_PNG: dialog_render_directory_set(self->dialog); break; + default: + dialog_render_path_set(self->dialog); + break; + } + } + + if (self->dialog->isSelected && self->dialog->type == DIALOG_RENDER_PATH_SET) + { + self->settings->renderPath = self->dialog->path; + dialog_reset(self->dialog); + } + + _imgui_input_text(IMGUI_RENDER_ANIMATION_LOCATION, self, self->settings->renderPath); + + if (_imgui_atlas_button(IMGUI_RENDER_ANIMATION_FFMPEG_BROWSE, self)) + dialog_ffmpeg_path_set(self->dialog); + + if (self->dialog->isSelected && self->dialog->type == DIALOG_FFMPEG_PATH_SET) + { + self->settings->ffmpegPath = self->dialog->path; + dialog_reset(self->dialog); + } + + _imgui_input_text(IMGUI_RENDER_ANIMATION_FFMPEG_PATH, self, self->settings->ffmpegPath); + _imgui_input_text(IMGUI_RENDER_ANIMATION_FORMAT, self, self->settings->renderFormat); + _imgui_combo(IMGUI_RENDER_ANIMATION_OUTPUT, self, &self->settings->renderType); + + if (_imgui_button(IMGUI_RENDER_ANIMATION_CONFIRM, self)) + { + bool isRenderStart = true; + + switch (self->settings->renderType) + { + case RENDER_PNG: + if (!std::filesystem::is_directory(self->settings->renderPath)) + { + imgui_log_push(self, IMGUI_LOG_RENDER_ANIMATION_DIRECTORY_ERROR); + isRenderStart = false; + } + break; + case RENDER_GIF: + case RENDER_WEBM: + if (!path_valid(self->settings->renderPath)) + { + imgui_log_push(self, IMGUI_LOG_RENDER_ANIMATION_PATH_ERROR); + isRenderStart = false; + } + default: + break; + } + + if (isRenderStart) + preview_render_start(self->preview); + else + self->preview->isRenderCancelled = true; + + imgui_close_current_popup(self); + } + + if (_imgui_button(IMGUI_POPUP_CANCEL, self)) + imgui_close_current_popup(self); + + _imgui_end_child(); //IMGUI_RENDER_ANIMATION_CHILD + + imgui_end_popup(self); + } + + if (imgui_begin_popup_modal(IMGUI_RENDER_ANIMATION_CONFIRM.popup, self, IMGUI_RENDER_ANIMATION_CONFIRM.popupSize)) { auto rendering_end = [&]() { preview_render_end(self->preview); - self->isContextualActionsEnabled = true; - ImGui::CloseCurrentPopup(); + imgui_close_current_popup(self); }; - RenderType& type = self->settings->renderType; - std::string& format = self->settings->renderFormat; std::vector& frames = self->preview->renderFrames; std::string path = std::string(self->settings->renderPath.c_str()); @@ -1333,29 +1411,25 @@ static void _imgui_taskbar(Imgui* self) if (!animation) { - imgui_message_queue_push(self, IMGUI_MESSAGE_RENDER_ANIMATION_NO_ANIMATION_ERROR); + imgui_log_push(self, IMGUI_LOG_RENDER_ANIMATION_NO_ANIMATION_ERROR); rendering_end(); } - _imgui_item_begin_child(IMGUI_RENDERING_ANIMATION_CHILD); - f32 progress = self->preview->time / (animation->frameNum - 1); ImGui::ProgressBar(progress); - if (_imgui_item_button(self, IMGUI_RENDERING_ANIMATION_CANCEL)) + if (_imgui_button(IMGUI_RENDERING_ANIMATION_CANCEL, self)) self->preview->isRenderCancelled = true; - _imgui_item_end_child(); // IMGUI_RENDERING_ANIMATION_CHILD - if (self->preview->isRenderFinished && frames.empty()) { - imgui_message_queue_push(self, IMGUI_MESSAGE_RENDER_ANIMATION_NO_FRAMES_ERROR); + imgui_log_push(self, IMGUI_LOG_RENDER_ANIMATION_NO_FRAMES_ERROR); rendering_end(); } if (self->preview->isRenderFinished) { - switch (type) + switch (self->settings->renderType) { case RENDER_PNG: { @@ -1364,34 +1438,26 @@ static void _imgui_taskbar(Imgui* self) for (auto [i, frame] : std::views::enumerate(frames)) { - std::string framePath = std::vformat(format, std::make_format_args(i)); - framePath = path_extension_change(framePath, RENDER_EXTENSIONS[type]); + std::string framePath = std::vformat(self->settings->renderFormat, std::make_format_args(i)); + framePath = path_extension_change(framePath, RENDER_EXTENSIONS[self->settings->renderType]); if (!frame.isInvalid) texture_from_gl_write(&frame, framePath); } std::filesystem::current_path(workingPath); - imgui_message_queue_push(self, std::format(IMGUI_MESSAGE_RENDER_ANIMATION_FRAMES_SAVE_FORMAT, path)); + imgui_log_push(self, std::format(IMGUI_LOG_RENDER_ANIMATION_FRAMES_SAVE_FORMAT, path)); break; } case RENDER_GIF: case RENDER_WEBM: { std::string ffmpegPath = std::string(self->settings->ffmpegPath.c_str()); - path = path_extension_change(path, RENDER_EXTENSIONS[type]); + path = path_extension_change(path, RENDER_EXTENSIONS[self->settings->renderType]); - if (ffmpeg_render(ffmpegPath, path, frames, self->preview->canvas.size, self->anm2->fps, type)) - { - std::string message = std::format(IMGUI_MESSAGE_RENDER_ANIMATION_SAVE_FORMAT, path); - imgui_message_queue_push(self, message); - log_info(message); - } + if (ffmpeg_render(ffmpegPath, path, frames, self->preview->canvas.size, self->anm2->fps, (RenderType)self->settings->renderType)) + imgui_log_push(self, std::format(IMGUI_LOG_RENDER_ANIMATION_SAVE_FORMAT, path)); else - { - std::string message = std::format(IMGUI_MESSAGE_RENDER_ANIMATION_FFMPEG_ERROR, path); - imgui_message_queue_push(self, message); - log_error(message); - } + imgui_log_push(self, std::format(IMGUI_LOG_RENDER_ANIMATION_FFMPEG_ERROR, path)); break; } default: @@ -1401,129 +1467,81 @@ static void _imgui_taskbar(Imgui* self) rendering_end(); } - ImGui::EndPopup(); + imgui_end_popup(self); } - if (ImGui::BeginPopupModal(IMGUI_TASKBAR_WIZARD_GENERATE_ANIMATION_FROM_GRID.popup.c_str(), nullptr, ImGuiWindowFlags_AlwaysAutoResize)) + _imgui_selectable(IMGUI_PLAYBACK.copy({}), self); + + if (imgui_begin_popup(IMGUI_PLAYBACK.popup, self, IMGUI_PLAYBACK.popupSize)) { - ImGui::EndPopup(); - } - - if (ImGui::BeginPopup(IMGUI_TASKBAR_PLAYBACK.popup.c_str())) - { - _imgui_item_checkbox(self, IMGUI_PLAYBACK_ALWAYS_LOOP, self->settings->playbackIsLoop); - ImGui::EndPopup(); - } - - _imgui_item_end(); - - if (self->dialog->isSelected) - { - switch (self->dialog->type) - { - case 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_message_queue_push(self, std::format(IMGUI_MESSAGE_FILE_OPEN_FORMAT, self->dialog->path)); - break; - case DIALOG_ANM2_SAVE: - anm2_serialize(self->anm2, self->dialog->path); - window_title_from_path_set(self->window, self->dialog->path); - imgui_message_queue_push(self, std::format(IMGUI_MESSAGE_FILE_SAVE_FORMAT, self->dialog->path)); - break; - default: - break; - } - - dialog_reset(self->dialog); + _imgui_checkbox_selectable(IMGUI_ALWAYS_LOOP, self, self->settings->playbackIsLoop); + _imgui_checkbox_selectable(IMGUI_CLAMP_PLAYHEAD, self, self->settings->playbackIsClampPlayhead); + imgui_end_popup(self); } + + _imgui_end(); } static void _imgui_tools(Imgui* self) { - _imgui_item_begin(IMGUI_TOOLS); + ImGuiStyle style = ImGui::GetStyle(); + + IMGUI_BEGIN_OR_RETURN(IMGUI_TOOLS, self); f32 availableWidth = ImGui::GetContentRegionAvail().x; - f32 usedWidth = ImGui::GetStyle().FramePadding.x; + f32 usedWidth = style.FramePadding.x; for (s32 i = 0; i < TOOL_COUNT; i++) { - const ImguiItem item = *IMGUI_TOOL_ITEMS[i]; + ImguiItem item = *IMGUI_TOOL_ITEMS[i]; - if (i > 0 && usedWidth < availableWidth) + if (i > 0 && usedWidth + ImGui::GetItemRectSize().x < availableWidth) ImGui::SameLine(); else usedWidth = 0; + item.isSelected = self->settings->tool == (ToolType)i; + + switch ((ToolType)i) + { + case TOOL_UNDO: item.isDisabled = self->snapshots->undoStack.is_empty(); break; + case TOOL_REDO: item.isDisabled = self->snapshots->redoStack.is_empty(); break; + default: break; + } + if (i != TOOL_COLOR) - { - vec4 buttonColor = self->settings->tool == (ToolType)i ? ImGui::GetStyle().Colors[ImGuiCol_ButtonHovered] : ImGui::GetStyle().Colors[ImGuiCol_Button]; - ImGui::PushStyleColor(ImGuiCol_Button, buttonColor); - - _imgui_item_atlas_image_button(self, item); - - ImGui::PopStyleColor(); - } + _imgui_atlas_button(item, self); else - _imgui_item_coloredit4(self, IMGUI_TOOL_COLOR, self->settings->toolColor); - - usedWidth += ImGui::GetItemRectSize().x + ImGui::GetStyle().ItemSpacing.x; - } - - _imgui_item_end(); // IMGUI_TOOLS + _imgui_color_edit4(item, self, self->settings->toolColor); - if (self->settings->tool == TOOL_COLOR_PICKER) - { - if (ImGui::IsMouseDown(ImGuiMouseButton_Left)) - { - vec2 mousePos{}; - SDL_GetMouseState(&mousePos.x, &mousePos.y); - - vec4 color{}; - if (_imgui_window_color_from_position_get(self->window, mousePos, &color)) - { - self->settings->toolColor = color; - - ImGui::BeginTooltip(); - _imgui_item_colorbutton(self, IMGUI_TOOL_COLOR_PICKER_TOOLTIP_COLOR, color); - ImGui::EndTooltip(); - } - } + usedWidth += ImGui::GetItemRectSize().x + style.ItemSpacing.x; } - if (TOOL_MOUSE_CURSOR_IS_CONSTANT[self->settings->tool]) - SDL_SetCursor(SDL_CreateSystemCursor(TOOL_MOUSE_CURSORS[self->settings->tool])); + _imgui_end(); // IMGUI_TOOLS } static void _imgui_animations(Imgui* self) { - _imgui_item_begin(IMGUI_ANIMATIONS); + IMGUI_BEGIN_OR_RETURN(IMGUI_ANIMATIONS, self); + ImVec2 size = ImGui::GetContentRegionAvail(); - ImguiItem animationsChild = IMGUI_ANIMATIONS_CHILD; - animationsChild.size.y = ImGui::GetContentRegionAvail().y - IMGUI_ANIMATIONS_FOOTER_HEIGHT; - _imgui_item_begin_child(animationsChild); + _imgui_begin_child(IMGUI_ANIMATIONS_CHILD.copy({.size = {size.x, size.y - IMGUI_FOOTER_CHILD.size.y}}), self); - std::function animation_item = [&](s32 id, Anm2Animation& animation) + for (auto & [id, animation] : self->anm2->animations) { ImGui::PushID(id); + + ImguiItem animationItem = IMGUI_ANIMATION.copy + ({ + .isSelected = self->reference->animationID == id, + .label = self->anm2->defaultAnimationID == id ? std::format(IMGUI_ANIMATION_DEFAULT_FORMAT, animation.name) : animation.name, + .id = id + }); - ImguiItem animationItem = IMGUI_ANIMATION; - animationItem.isSelected = self->reference->animationID == id; - - if (id == self->anm2->defaultAnimationID) - animationItem.label = std::format(IMGUI_ANIMATION_DEFAULT_FORMAT, animation.name); - else - animationItem.label = animation.name; - - if (_imgui_item_atlas_image_selectable_inputtext(self, animationItem, animation.name, id)) + if (_imgui_atlas_selectable_input_text(animationItem, self, animation.name)) { self->reference->animationID = id; anm2_reference_item_clear(self->reference); - self->preview->isPlaying = false; - self->preview->time = 0.0f; } if (ImGui::IsItemHovered()) @@ -1535,19 +1553,20 @@ static void _imgui_animations(Imgui* self) if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) { - ImGui::SetDragDropPayload(animationItem.dragDrop.c_str(), &id, sizeof(s32)); - animation_item(id, animation); + if (ImGui::IsDragDropActive()) ImGui::SetNextItemWidth(_imgui_item_size_get(animationItem, IMGUI_SELECTABLE).x); + ImGui::SetDragDropPayload(animationItem.drag_drop_get(), &id, sizeof(s32)); + _imgui_atlas_selectable(animationItem, self); ImGui::EndDragDropSource(); } if (ImGui::BeginDragDropTarget()) { - if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(animationItem.dragDrop.c_str())) + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(animationItem.drag_drop_get())) { s32 sourceID = *(s32*)payload->Data; if (sourceID != id) { - imgui_undo_stack_push(self, IMGUI_ACTION_ANIMATION_SWAP); + imgui_undo_push(self, IMGUI_ACTION_ANIMATION_SWAP); map_swap(self->anm2->animations, sourceID, id); } } @@ -1558,64 +1577,41 @@ static void _imgui_animations(Imgui* self) ImGui::PopID(); }; - for (auto & [id, animation] : self->anm2->animations) - animation_item(id, animation); - - _imgui_item_end_child(); // animationsChild + _imgui_end_child(); // animationsChild Anm2Animation* animation = anm2_animation_from_reference(self->anm2, self->reference); - _imgui_item_begin_child(IMGUI_ANIMATIONS_OPTIONS_CHILD); + _imgui_begin_child(IMGUI_FOOTER_CHILD, self); - if (_imgui_item_button(self, IMGUI_ANIMATION_ADD)) + if (_imgui_button(IMGUI_ANIMATION_ADD, self)) { - bool isDefault = (s32)self->anm2->animations.size() == 0; s32 id = anm2_animation_add(self->anm2); - self->reference->animationID = id; - if (isDefault) + if (self->anm2->animations.size() == 0) self->anm2->defaultAnimationID = id; } - - ImGui::SameLine(); - if (_imgui_item_button(self, IMGUI_ANIMATION_DUPLICATE) && animation) + if (_imgui_button(IMGUI_ANIMATION_DUPLICATE.copy({!animation}), self)) { s32 id = map_next_id_get(self->anm2->animations); self->anm2->animations.insert({id, *animation}); self->reference->animationID = id; } - ImGui::SameLine(); - _imgui_item_button(self, IMGUI_ANIMATIONS_MERGE); - ImGui::SameLine(); + _imgui_button(IMGUI_ANIMATION_MERGE.copy({!animation}), self); - if (_imgui_item_button(self, IMGUI_ANIMATION_REMOVE) && animation) + if (imgui_begin_popup_modal(IMGUI_ANIMATION_MERGE.popup, self, IMGUI_ANIMATION_MERGE.popupSize)) { - anm2_animation_remove(self->anm2, self->reference->animationID); - anm2_reference_clear(self->reference); - } - - ImGui::SameLine(); - - if (_imgui_item_button(self, IMGUI_ANIMATION_DEFAULT) && animation) - self->anm2->defaultAnimationID = self->reference->animationID; - - _imgui_pending_popup_process(self); - - if (ImGui::BeginPopupModal(IMGUI_ANIMATIONS_MERGE.popup.c_str(), nullptr, ImGuiWindowFlags_AlwaysAutoResize)) - { - static s32 mergeType = (s32)ANM2_MERGE_APPEND_FRAMES; - static bool isDeleteAnimationsAfter = false; - static std::vector animationIDs; - static s32 lastClickedID = ID_NONE; - const bool isModCtrl = ImGui::IsKeyDown(IMGUI_INPUT_CTRL); const bool isModShift = ImGui::IsKeyDown(IMGUI_INPUT_SHIFT); - - static std::vector sortedIDs; + static bool& isDeleteAnimationsAfter = self->settings->mergeIsDeleteAnimationsAfter; + static s32 lastClickedID = ID_NONE; + static s32& mergeType = self->settings->mergeType; static size_t lastAnimationCount = 0; + static std::vector animationIDs; + static std::vector sortedIDs; + if (self->anm2->animations.size() != lastAnimationCount) { sortedIDs.clear(); @@ -1625,17 +1621,24 @@ static void _imgui_animations(Imgui* self) lastAnimationCount = self->anm2->animations.size(); } - _imgui_item_begin_child(IMGUI_ANIMATIONS_MERGE_CHILD); + _imgui_begin_child(IMGUI_MERGE_ANIMATIONS_CHILD, self); for (const auto& [id, animation] : self->anm2->animations) { ImGui::PushID(id); - ImguiItem animationItem = IMGUI_ANIMATION; - animationItem.label = animation.name; - animationItem.isSelected = std::find(animationIDs.begin(), animationIDs.end(), id) != animationIDs.end(); - - if (_imgui_item_atlas_image_selectable(self, animationItem)) + if + ( + _imgui_atlas_selectable + ( + IMGUI_ANIMATION.copy + ({ + .isSelected = std::find(animationIDs.begin(), animationIDs.end(), id) != animationIDs.end(), + .label = animation.name + }), + self + ) + ) { if (isModCtrl) { @@ -1673,28 +1676,23 @@ static void _imgui_animations(Imgui* self) ImGui::PopID(); } - _imgui_item_end_child(); //IMGUI_ANIMATIONS_MERGE_CHILD + _imgui_end_child(); //IMGUI_MERGE_ANIMATIONS_CHILD - _imgui_item_begin_child(IMGUI_ANIMATIONS_MERGE_ON_CONFLICT_CHILD); + _imgui_begin_child(IMGUI_MERGE_ON_CONFLICT_CHILD, self); + _imgui_text(IMGUI_MERGE_ON_CONFLICT, self); + _imgui_radio_button(IMGUI_MERGE_APPEND_FRAMES, self, mergeType); + _imgui_radio_button(IMGUI_MERGE_REPLACE_FRAMES, self, mergeType); + _imgui_radio_button(IMGUI_MERGE_PREPEND_FRAMES, self, mergeType); + _imgui_radio_button(IMGUI_MERGE_IGNORE, self, mergeType); + _imgui_end_child(); //IMGUI_MERGE_ON_CONFLICT_CHILD - _imgui_item_text(IMGUI_ANIMATIONS_MERGE_ON_CONFLICT); + _imgui_begin_child(IMGUI_MERGE_OPTIONS_CHILD, self); - _imgui_item_radio_button(self, IMGUI_ANIMATIONS_MERGE_APPEND_FRAMES, mergeType); - ImGui::SameLine(); - _imgui_item_radio_button(self, IMGUI_ANIMATIONS_MERGE_REPLACE_FRAMES, mergeType); - _imgui_item_radio_button(self, IMGUI_ANIMATIONS_MERGE_PREPEND_FRAMES, mergeType); - ImGui::SameLine(); - _imgui_item_radio_button(self, IMGUI_ANIMATIONS_MERGE_IGNORE, mergeType); + _imgui_checkbox(IMGUI_MERGE_DELETE_ANIMATIONS_AFTER, self, isDeleteAnimationsAfter); - _imgui_item_end_child(); //IMGUI_ANIMATIONS_MERGE_ON_CONFLICT_CHILD + _imgui_end_child(); //IMGUI_MERGE_OPTIONS_CHILD - _imgui_item_begin_child(IMGUI_ANIMATIONS_MERGE_OPTIONS_CHILD); - - _imgui_item_checkbox(self, IMGUI_ANIMATIONS_MERGE_DELETE_ANIMATIONS_AFTER, isDeleteAnimationsAfter); - - _imgui_item_end_child(); //IMGUI_ANIMATIONS_MERGE_OPTIONS_CHILD - - if (_imgui_item_button(self, IMGUI_ANIMATIONS_MERGE_CONFIRM)) + if (_imgui_button(IMGUI_MERGE_CONFIRM.copy({animationIDs.empty()}), self)) { anm2_animation_merge(self->anm2, self->reference->animationID, animationIDs, (Anm2MergeType)mergeType); @@ -1704,47 +1702,45 @@ static void _imgui_animations(Imgui* self) self->anm2->animations.erase(id); animationIDs.clear(); - ImGui::CloseCurrentPopup(); + imgui_close_current_popup(self); } - ImGui::SameLine(); - - if (_imgui_item_button(self, IMGUI_ANIMATIONS_MERGE_CANCEL)) + if (_imgui_button(IMGUI_POPUP_CANCEL, self)) { animationIDs.clear(); - ImGui::CloseCurrentPopup(); + imgui_close_current_popup(self); } - - ImGui::EndPopup(); + + imgui_end_popup(self); } + if (_imgui_button(IMGUI_ANIMATION_REMOVE.copy({!animation}), self)) + { + anm2_animation_remove(self->anm2, self->reference->animationID); + anm2_reference_clear(self->reference); + } + + if (_imgui_button(IMGUI_ANIMATION_DEFAULT.copy({!animation}), self)) + self->anm2->defaultAnimationID = self->reference->animationID; - - - - _imgui_item_end_child(); // IMGUI_ANIMATIONS_OPTIONS_CHILD) - _imgui_item_end(); + _imgui_end_child(); // IMGUI_FOOTER_CHILD + _imgui_end(); // IMGUI_ANIMATIONS } static void _imgui_events(Imgui* self) { static s32 selectedID = ID_NONE; - _imgui_item_begin(IMGUI_EVENTS); - - ImguiItem eventsChild = IMGUI_EVENTS_CHILD; - eventsChild.size.y = ImGui::GetContentRegionAvail().y - IMGUI_EVENTS_FOOTER_HEIGHT; - _imgui_item_begin_child(eventsChild); + IMGUI_BEGIN_OR_RETURN(IMGUI_EVENTS, self); + ImVec2 windowSize = ImGui::GetContentRegionAvail(); + + _imgui_begin_child(IMGUI_EVENTS_CHILD.copy({.size = {windowSize.x, windowSize.y - IMGUI_FOOTER_CHILD.size.y}}), self); std::function event_item = [&](s32 id, Anm2Event& event) { ImGui::PushID(id); - - ImguiItem eventItem = IMGUI_EVENT; - eventItem.label = event.name; - eventItem.isSelected = id == selectedID; - if (_imgui_item_atlas_image_selectable_inputtext(self, eventItem, event.name, id)) + if (_imgui_atlas_selectable_input_text(IMGUI_EVENT.copy({.isSelected = id == selectedID, .label = event.name, .id = id}), self, event.name)) selectedID = id; ImGui::PopID(); @@ -1753,20 +1749,18 @@ static void _imgui_events(Imgui* self) for (auto& [id, event] : self->anm2->events) event_item(id, event); - _imgui_item_end_child(); // eventsChild + _imgui_end_child(); // eventsChild - _imgui_item_begin_child(IMGUI_EVENTS_OPTIONS_CHILD); + _imgui_begin_child(IMGUI_FOOTER_CHILD, self); - if (_imgui_item_button(self, IMGUI_EVENT_ADD)) + if (_imgui_button(IMGUI_EVENTS_ADD, self)) { s32 id = map_next_id_get(self->anm2->events); self->anm2->events[id] = Anm2Event{}; selectedID = id; } - ImGui::SameLine(); - - if (_imgui_item_button(self, IMGUI_EVENT_REMOVE_UNUSED)) + if (_imgui_button(IMGUI_EVENTS_REMOVE_UNUSED.copy({self->anm2->events.empty()}), self)) { std::unordered_set usedEventIDs; @@ -1784,47 +1778,51 @@ static void _imgui_events(Imgui* self) } } - _imgui_item_end_child(); // IMGUI_ANIMATIONS_OPTIONS_CHILD) - _imgui_item_end(); + _imgui_end_child(); // IMGUI_ANIMATIONS_OPTIONS_CHILD) + _imgui_end(); // IMGUI_EVENTS } static void _imgui_spritesheets(Imgui* self) { - static std::unordered_map isSelectedIDs; + static std::unordered_set selectedIDs; static s32 highlightedID = ID_NONE; - _imgui_item_begin(IMGUI_SPRITESHEETS); + IMGUI_BEGIN_OR_RETURN(IMGUI_SPRITESHEETS, self); - ImguiItem spritesheetsChild = IMGUI_SPRITESHEETS_CHILD; - spritesheetsChild.size.y = ImGui::GetContentRegionAvail().y - IMGUI_SPRITESHEETS_FOOTER_HEIGHT; + ImVec2 windowSize = ImGui::GetContentRegionAvail(); - _imgui_item_begin_child(spritesheetsChild); + _imgui_begin_child(IMGUI_SPRITESHEETS_CHILD.copy({.size = {windowSize.x, windowSize.y - IMGUI_SPRITESHEETS_FOOTER_CHILD.size.y}}), self); std::function spritesheet_item = [&](s32 id, Anm2Spritesheet& spritesheet) { ImGui::PushID(id); - ImguiItem spritesheetItem = IMGUI_SPRITESHEET; 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); - spritesheetItem.label = std::format(IMGUI_SPRITESHEET_FORMAT, id, spritesheet.path); - spritesheetItem.isSelected = id == highlightedID; + if (_imgui_checkbox(IMGUI_SPRITESHEET_SELECTED, self, isContains)) + { + if (isContains) + selectedIDs.insert(id); + else + selectedIDs.erase(id); + } - _imgui_item_begin_child(IMGUI_SPRITESHEET_CHILD); - - if (_imgui_item_atlas_image_checkbox_selectable(self, spritesheetItem, isSelectedIDs[id])) + if (_imgui_atlas_selectable(IMGUI_SPRITESHEET.copy({.isSelected = id == highlightedID, .label = std::format(IMGUI_SPRITESHEET_FORMAT, id, spritesheet.path)}), self)) highlightedID = id; if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) { - ImGui::SetDragDropPayload(spritesheetItem.dragDrop.c_str(), &id, sizeof(s32)); + ImGui::SetDragDropPayload(IMGUI_SPRITESHEET.drag_drop_get(), &id, sizeof(s32)); spritesheet_item(id, spritesheet); ImGui::EndDragDropSource(); } if (ImGui::BeginDragDropTarget()) { - if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(spritesheetItem.dragDrop.c_str())) + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(IMGUI_SPRITESHEET.drag_drop_get())) { s32 sourceID = *(s32*)payload->Data; if (sourceID != id) @@ -1845,72 +1843,68 @@ static void _imgui_spritesheets(Imgui* self) spritesheetPreviewSize.y = IMGUI_SPRITESHEET_PREVIEW_SIZE.x / spritesheetAspect; if (texture->isInvalid) - _imgui_atlas_image(self, ATLAS_NONE); + _imgui_atlas(ATLAS_NONE, self); else ImGui::Image(texture->id, spritesheetPreviewSize); - _imgui_item_end_child(); // IMGUI_SPRITESHEET_CHILD + _imgui_end_child(); // IMGUI_SPRITESHEET_CHILD ImGui::PopID(); }; - for (auto [id, spritesheet] : self->anm2->spritesheets) + for (auto& [id, spritesheet] : self->anm2->spritesheets) spritesheet_item(id, spritesheet); - _imgui_item_end_child(); // spritesheetsChild + _imgui_end_child(); // spritesheetsChild - _imgui_item_begin_child(IMGUI_SPRITESHEETS_OPTIONS_CHILD); + _imgui_begin_child(IMGUI_SPRITESHEETS_FOOTER_CHILD, self); - if (_imgui_item_button(self, IMGUI_SPRITESHEETS_ADD)) + if (_imgui_button(IMGUI_SPRITESHEET_ADD, self)) dialog_spritesheet_add(self->dialog); - ImGui::SameLine(); - - if (_imgui_item_button(self, IMGUI_SPRITESHEETS_RELOAD)) + if (self->dialog->isSelected && self->dialog->type == DIALOG_SPRITESHEET_ADD) { - for (auto& [id, isSelected] : isSelectedIDs) - { - if (isSelected) - { - std::filesystem::path workingPath = std::filesystem::current_path(); - working_directory_from_file_set(self->anm2->path); - resources_texture_init(self->resources, self->anm2->spritesheets[id].path, id); - std::filesystem::current_path(workingPath); - } - } - } - - ImGui::SameLine(); + 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); - if (_imgui_item_button(self, IMGUI_SPRITESHEETS_REPLACE) && highlightedID != ID_NONE) - dialog_spritesheet_replace(self->dialog, highlightedID); - - if (self->dialog->isSelected) - { - switch (self->dialog->type) - { - case DIALOG_SPRITESHEET_ADD: - { - s32 id = map_next_id_get(self->resources->textures); - self->anm2->spritesheets[id] = Anm2Spritesheet{}; - self->anm2->spritesheets[id].path = self->dialog->path; - resources_texture_init(self->resources, self->dialog->path, id); - break; - } - case DIALOG_SPRITESHEET_REPLACE: - self->anm2->spritesheets[self->dialog->replaceID].path = self->dialog->path; - resources_texture_init(self->resources, self->dialog->path, self->dialog->replaceID); - break; - default: - break; - } - + s32 id = map_next_id_get(self->resources->textures); + self->anm2->spritesheets[id] = Anm2Spritesheet{}; + self->anm2->spritesheets[id].path = spritesheetPath; + resources_texture_init(self->resources, spritesheetPath, id); dialog_reset(self->dialog); + + std::filesystem::current_path(workingPath); + } + + if (_imgui_button(IMGUI_SPRITESHEETS_RELOAD.copy({selectedIDs.empty()}), self)) + { + for (auto& id : selectedIDs) + { + std::filesystem::path workingPath = std::filesystem::current_path(); + working_directory_from_file_set(self->anm2->path); + resources_texture_init(self->resources, self->anm2->spritesheets[id].path, id); + std::filesystem::current_path(workingPath); + } } - ImGui::SameLine(); + if (_imgui_button(IMGUI_SPRITESHEETS_REPLACE.copy({highlightedID == ID_NONE}), self)) + dialog_spritesheet_replace(self->dialog, highlightedID); - if (_imgui_item_button(self, IMGUI_SPRITESHEETS_REMOVE_UNUSED)) + if (self->dialog->isSelected && self->dialog->type == DIALOG_SPRITESHEET_REPLACE) + { + 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); + + self->anm2->spritesheets[self->dialog->replaceID].path = spritesheetPath; + resources_texture_init(self->resources, spritesheetPath, self->dialog->replaceID); + dialog_reset(self->dialog); + + std::filesystem::current_path(workingPath); + } + + if (_imgui_button(IMGUI_SPRITESHEETS_REMOVE_UNUSED.copy({self->anm2->spritesheets.empty()}), self)) { std::unordered_set usedSpritesheetIDs; @@ -1930,156 +1924,158 @@ static void _imgui_spritesheets(Imgui* self) } } - if (_imgui_item_button(self, IMGUI_SPRITESHEETS_SELECT_ALL)) - for (auto& [id, _] : self->anm2->spritesheets) - isSelectedIDs[id] = true; + if (_imgui_button(IMGUI_SPRITESHEETS_SELECT_ALL.copy({selectedIDs.size() == self->anm2->spritesheets.size()}), self)) + for (auto [id, _] : self->anm2->spritesheets) + selectedIDs.insert(id); - ImGui::SameLine(); + if (_imgui_button(IMGUI_SPRITESHEETS_SELECT_NONE.copy({selectedIDs.empty()}), self)) + selectedIDs.clear(); - if (_imgui_item_button(self, IMGUI_SPRITESHEETS_SELECT_NONE)) - for (auto& [id, _] : self->anm2->spritesheets) - isSelectedIDs[id] = false; - - ImGui::SameLine(); - - if (_imgui_item_button(self, IMGUI_SPRITESHEETS_SAVE)) + if (_imgui_button(IMGUI_SPRITESHEET_SAVE.copy({selectedIDs.empty()}), self)) { - for (auto& [id, isSelected] : isSelectedIDs) + for (auto& id : selectedIDs) { - if (isSelected) - { - 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]; - texture_from_gl_write(texture, spritesheet->path); - imgui_message_queue_push(self, std::format(IMGUI_MESSAGE_SPRITESHEET_SAVE_FORMAT, id, spritesheet->path)); - std::filesystem::current_path(workingPath); - } + 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]; + 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); } } - if (_imgui_is_no_click_on_item()) - highlightedID = ID_NONE; + if (_imgui_is_no_click_on_item()) highlightedID = ID_NONE; - _imgui_item_end_child(); //IMGUI_SPRITESHEETS_OPTIONS_CHILD - _imgui_item_end(); + _imgui_end_child(); //IMGUI_SPRITESHEETS_FOOTER_CHILD + _imgui_end(); // IMGUI_SPRITESHEETS } static void _imgui_animation_preview(Imgui* self) { - static bool isPreviewHover = false; + static s32& tool = self->settings->tool; + static f32& zoom = self->settings->previewZoom; + static vec2& pan = self->settings->previewPan; + static vec2& size = self->preview->canvas.size; static vec2 mousePos{}; static vec2 previewPos{}; static ImVec2 previewScreenPos{}; - static vec2& pan = self->settings->previewPan; - static f32& zoom = self->settings->previewZoom; - static vec2& size = self->preview->canvas.size; std::string mousePositionString = std::format(IMGUI_POSITION_FORMAT, (s32)mousePos.x, (s32)mousePos.y); - _imgui_item_begin(IMGUI_ANIMATION_PREVIEW); - - _imgui_item_begin_child(IMGUI_ANIMATION_PREVIEW_GRID_SETTINGS); - _imgui_item_checkbox(self, IMGUI_ANIMATION_PREVIEW_GRID, self->settings->previewIsGrid); + _imgui_begin(IMGUI_ANIMATION_PREVIEW, self); + _imgui_begin_child(IMGUI_CANVAS_GRID_CHILD, self); + _imgui_checkbox(IMGUI_CANVAS_GRID, self, self->settings->previewIsGrid); ImGui::SameLine(); - _imgui_item_coloredit4(self, IMGUI_ANIMATION_PREVIEW_GRID_COLOR, self->settings->previewGridColor); - _imgui_item_inputint2(self, IMGUI_ANIMATION_PREVIEW_GRID_SIZE, self->settings->previewGridSize); - _imgui_item_inputint2(self, IMGUI_ANIMATION_PREVIEW_GRID_OFFSET, self->settings->previewGridOffset); - _imgui_item_end_child(); + _imgui_color_edit4(IMGUI_CANVAS_GRID_COLOR, self, self->settings->previewGridColor); + _imgui_input_int2(IMGUI_CANVAS_GRID_SIZE, self, self->settings->previewGridSize); + _imgui_input_int2(IMGUI_CANVAS_GRID_OFFSET, self, self->settings->previewGridOffset); + _imgui_end_child(); // IMGUI_CANVAS_GRID_CHILD ImGui::SameLine(); - _imgui_item_begin_child(IMGUI_ANIMATION_PREVIEW_VIEW_SETTINGS); - _imgui_item_dragfloat(self, IMGUI_ANIMATION_PREVIEW_ZOOM, zoom); - if (_imgui_item_button(self, IMGUI_ANIMATION_PREVIEW_CENTER_VIEW)) pan = vec2(); + _imgui_begin_child(IMGUI_CANVAS_VIEW_CHILD, self); + _imgui_drag_float(IMGUI_CANVAS_ZOOM, self, zoom); + if (_imgui_button(IMGUI_CANVAS_CENTER_VIEW.copy({pan == vec2()}), self)) pan = vec2(); ImGui::Text(mousePositionString.c_str()); - _imgui_item_end_child(); + _imgui_end_child(); //IMGUI_CANVAS_VIEW_CHILD ImGui::SameLine(); - _imgui_item_begin_child(IMGUI_ANIMATION_PREVIEW_BACKGROUND_SETTINGS); - _imgui_item_coloredit4(self, IMGUI_ANIMATION_PREVIEW_BACKGROUND_COLOR, self->settings->previewBackgroundColor); + _imgui_begin_child(IMGUI_CANVAS_VISUAL_CHILD, self); + _imgui_color_edit4(IMGUI_CANVAS_BACKGROUND_COLOR, self, self->settings->previewBackgroundColor); std::vector animationIDs; - ImguiItem animationOverlayItem = IMGUI_ANIMATION_PREVIEW_OVERLAY; - s32 animationOverlayCount = self->anm2->animations.size() + 1; + ImguiItem animationOverlayItem = IMGUI_CANVAS_ANIMATION_OVERLAY; - animationIDs.reserve(animationOverlayCount); - animationOverlayItem.items.reserve(animationOverlayCount); - animationIDs.push_back(ID_NONE); - animationOverlayItem.items.push_back(IMGUI_ANIMATION_NONE); + animationIDs.emplace_back(ID_NONE); + animationOverlayItem.items.emplace_back(IMGUI_NONE); - for (auto & [id, animation] : self->anm2->animations) + for (auto& [id, animation] : self->anm2->animations) { - animationIDs.push_back(id); - animationOverlayItem.items.push_back(animation.name); + animationIDs.emplace_back(id); + animationOverlayItem.items.emplace_back(animation.name); } - s32 animationIndex = std::find(animationIDs.begin(), animationIDs.end(), self->preview->animationOverlayID) - animationIDs.begin(); + s32 animationIndex = 0; - if (_imgui_item_combo(self, animationOverlayItem, &animationIndex)) + if (self->preview->animationOverlayID != ID_NONE) + animationIndex = std::find(animationIDs.begin(), animationIDs.end(), self->preview->animationOverlayID) - animationIDs.begin(); + + if (_imgui_combo(animationOverlayItem, self, &animationIndex)) self->preview->animationOverlayID = animationIDs[animationIndex]; - _imgui_item_dragfloat(self, IMGUI_ANIMATION_PREVIEW_OVERLAY_TRANSPARENCY, self->settings->previewOverlayTransparency); - _imgui_item_end_child(); + _imgui_drag_float(IMGUI_CANVAS_ANIMATION_OVERLAY_TRANSPARENCY, self, self->settings->previewOverlayTransparency); + _imgui_end_child(); //IMGUI_CANVAS_VISUAL_CHILD ImGui::SameLine(); - _imgui_item_begin_child(IMGUI_ANIMATION_PREVIEW_HELPER_SETTINGS); - _imgui_item_checkbox(self, IMGUI_ANIMATION_PREVIEW_AXIS, self->settings->previewIsAxis); + _imgui_begin_child(IMGUI_CANVAS_HELPER_CHILD, self); + _imgui_checkbox(IMGUI_CANVAS_AXES, self, self->settings->previewIsAxes); ImGui::SameLine(); - _imgui_item_coloredit4(self, IMGUI_ANIMATION_PREVIEW_AXIS_COLOR, self->settings->previewAxisColor); - _imgui_item_checkbox(self, IMGUI_ANIMATION_PREVIEW_ROOT_TRANSFORM, self->settings->previewIsRootTransform); - _imgui_item_checkbox(self, IMGUI_ANIMATION_PREVIEW_PIVOTS, self->settings->previewIsPivots); + _imgui_color_edit4(IMGUI_CANVAS_AXES_COLOR, self, self->settings->previewAxesColor); + _imgui_checkbox(IMGUI_CANVAS_ROOT_TRANSFORM, self, self->settings->previewIsRootTransform); ImGui::SameLine(); - _imgui_item_checkbox(self, IMGUI_ANIMATION_PREVIEW_TARGETS, self->settings->previewIsTargets); + _imgui_checkbox(IMGUI_CANVAS_TRIGGERS, self, self->settings->previewIsTriggers); + _imgui_checkbox(IMGUI_CANVAS_PIVOTS, self, self->settings->previewIsPivots); ImGui::SameLine(); - _imgui_item_checkbox(self, IMGUI_ANIMATION_PREVIEW_BORDER, self->settings->previewIsBorder); - _imgui_item_end_child(); + _imgui_checkbox(IMGUI_CANVAS_TARGETS, self, self->settings->previewIsTargets); + ImGui::SameLine(); + _imgui_checkbox(IMGUI_CANVAS_BORDER, self, self->settings->previewIsBorder); + _imgui_end_child(); // IMGUI_CANVAS_HELPER_CHILD previewPos = vec2(ImGui::GetCursorPos()); + previewScreenPos = vec2(ImGui::GetCursorScreenPos()); size = ImGui::GetContentRegionAvail(); + preview_draw(self->preview); ImGui::Image(self->preview->canvas.texture, size); - if (!ImGui::IsItemHovered()) + if (self->settings->previewIsTriggers) { - if (isPreviewHover) + Anm2Frame trigger; + anm2_frame_from_time(self->anm2, &trigger, {self->reference->animationID, ANM2_TRIGGERS}, self->preview->time); + + if (trigger.eventID != ID_NONE) { - SDL_SetCursor(SDL_CreateSystemCursor(MOUSE_CURSOR_DEFAULT)); - _imgui_keyboard_navigation_set(true); - isPreviewHover = false; + f32 textScale = ImGui::GetCurrentWindow()->FontWindowScale; + ImVec2 textPos = previewScreenPos + ImGui::GetStyle().ItemSpacing; + ImGui::SetWindowFontScale(IMGUI_TRIGGERS_FONT_SCALE); + ImGui::GetWindowDrawList()->AddText(textPos, IMGUI_TRIGGERS_EVENT_COLOR, self->anm2->events[trigger.eventID].name.c_str()); + ImGui::SetWindowFontScale(textScale); } - _imgui_item_end(); + } + + if (ImGui::IsItemHovered()) + self->pendingCursor = TOOL_CURSORS[tool]; + else + { + _imgui_end(); // IMGUI_ANIMATION_EDITOR return; } - isPreviewHover = true; + _imgui_end(); // IMGUI_ANIMATION_PREVIEW - _imgui_keyboard_navigation_set(false); - mousePos = (vec2((ImGui::GetMousePos()) - (ImGui::GetWindowPos() + previewPos)) - (size * 0.5f) - pan) / PERCENT_TO_UNIT(zoom); - - ToolType tool = self->settings->tool; - bool isLeft = ImGui::IsKeyPressed(IMGUI_INPUT_LEFT); - bool isRight = ImGui::IsKeyPressed(IMGUI_INPUT_RIGHT); - bool isUp = ImGui::IsKeyPressed(IMGUI_INPUT_UP); - bool isDown = ImGui::IsKeyPressed(IMGUI_INPUT_DOWN); - bool isMod = ImGui::IsKeyDown(IMGUI_INPUT_SHIFT); - bool isMouseClick = ImGui::IsMouseClicked(ImGuiMouseButton_Left); - bool isMouseDown = ImGui::IsMouseDown(ImGuiMouseButton_Left); - bool isMouseMiddleDown = ImGui::IsMouseDown(ImGuiMouseButton_Middle); - ImVec2 mouseDelta = ImGui::GetIO().MouseDelta; - f32 mouseWheel = ImGui::GetIO().MouseWheel; - - SDL_SetCursor(SDL_CreateSystemCursor(TOOL_MOUSE_CURSORS[tool])); - - if (self->settings->tool == TOOL_MOVE || self->settings->tool == TOOL_SCALE || self->settings->tool == TOOL_ROTATE) - if (isMouseClick || isLeft || isRight || isUp || isDown) - imgui_undo_stack_push(self, IMGUI_ACTION_FRAME_TRANSFORM); - if ((self->settings->tool == TOOL_PAN && isMouseDown) || isMouseMiddleDown) + const bool isLeft = ImGui::IsKeyPressed(IMGUI_INPUT_LEFT); + const bool isRight = ImGui::IsKeyPressed(IMGUI_INPUT_RIGHT); + const bool isUp = ImGui::IsKeyPressed(IMGUI_INPUT_UP); + 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 isMouseClick = ImGui::IsMouseClicked(ImGuiMouseButton_Left); + const bool isMouseDown = ImGui::IsMouseDown(ImGuiMouseButton_Left); + const bool isMouseMiddleDown = ImGui::IsMouseDown(ImGuiMouseButton_Middle); + const ImVec2 mouseDelta = ImGui::GetIO().MouseDelta; + const f32 mouseWheel = ImGui::GetIO().MouseWheel; + + if (tool == TOOL_MOVE || tool == TOOL_SCALE || tool == TOOL_ROTATE) + if (isMouseClick || isLeft || isRight || isUp || isDown) + imgui_undo_push(self, IMGUI_ACTION_FRAME_TRANSFORM); + + if ((tool == TOOL_PAN && isMouseDown) || isMouseMiddleDown) pan += vec2(mouseDelta.x, mouseDelta.y); Anm2Frame* frame = nullptr; @@ -2129,86 +2125,81 @@ static void _imgui_animation_preview(Imgui* self) } } - if (mouseWheel != 0 || ImGui::IsKeyPressed(IMGUI_INPUT_ZOOM_IN) || ImGui::IsKeyPressed(IMGUI_INPUT_ZOOM_OUT)) + if (mouseWheel != 0 || isZoomIn || isZoomOut) { - f32 delta = (mouseWheel > 0 || ImGui::IsKeyPressed(IMGUI_INPUT_ZOOM_IN)) ? CANVAS_ZOOM_STEP : -CANVAS_ZOOM_STEP; - delta = ImGui::IsKeyDown(IMGUI_INPUT_SHIFT) ? delta * CANVAS_ZOOM_MOD : delta; - zoom = ROUND_NEAREST_MULTIPLE(zoom + delta, CANVAS_ZOOM_STEP); - zoom = std::clamp(zoom, CANVAS_ZOOM_MIN, CANVAS_ZOOM_MAX); + f32 delta = (mouseWheel > 0 || isZoomIn) ? CANVAS_ZOOM_STEP : -CANVAS_ZOOM_STEP; + delta = isMod ? delta * CANVAS_ZOOM_MOD : delta; + zoom = std::clamp(ROUND_NEAREST_MULTIPLE(zoom + delta, CANVAS_ZOOM_STEP), CANVAS_ZOOM_MIN, CANVAS_ZOOM_MAX); } - - _imgui_item_end(); } static void _imgui_spritesheet_editor(Imgui* self) { - static bool isEditorHover = false; static vec2 mousePos = {0, 0}; - vec2& pan = self->settings->editorPan; - f32& zoom = self->settings->editorZoom; - vec2& size = self->editor->canvas.size; - ivec2& gridSize = self->settings->editorGridSize; + static s32& tool = self->settings->tool; + static vec4& toolColor = self->settings->toolColor; + static ivec2& gridSize = self->settings->editorGridSize; + static ivec2& gridOffset = self->settings->editorGridOffset; + static vec2& pan = self->settings->editorPan; + static f32& zoom = self->settings->editorZoom; + static vec2& size = self->editor->canvas.size; std::string mousePositionString = std::format(IMGUI_POSITION_FORMAT, (s32)mousePos.x, (s32)mousePos.y); - _imgui_item_begin(IMGUI_SPRITESHEET_EDITOR); + _imgui_begin(IMGUI_SPRITESHEET_EDITOR, self); - _imgui_item_begin_child(IMGUI_SPRITESHEET_EDITOR_GRID_SETTINGS); - _imgui_item_checkbox(self, IMGUI_SPRITESHEET_EDITOR_GRID, self->settings->editorIsGrid); + _imgui_begin_child(IMGUI_CANVAS_GRID_CHILD, self); + _imgui_checkbox(IMGUI_CANVAS_GRID, self, self->settings->editorIsGrid); ImGui::SameLine(); - _imgui_item_checkbox(self, IMGUI_SPRITESHEET_EDITOR_GRID_SNAP, self->settings->editorIsGridSnap); + _imgui_checkbox(IMGUI_CANVAS_GRID_SNAP, self, self->settings->editorIsGridSnap); ImGui::SameLine(); - _imgui_item_coloredit4(self, IMGUI_SPRITESHEET_EDITOR_GRID_COLOR, self->settings->editorGridColor); - _imgui_item_inputint2(self, IMGUI_SPRITESHEET_EDITOR_GRID_SIZE, self->settings->editorGridSize); - _imgui_item_inputint2(self, IMGUI_SPRITESHEET_EDITOR_GRID_OFFSET, self->settings->editorGridOffset); - _imgui_item_end_child(); + _imgui_color_edit4(IMGUI_CANVAS_GRID_COLOR, self, self->settings->editorGridColor); + _imgui_input_int2(IMGUI_CANVAS_GRID_SIZE, self, gridSize); + _imgui_input_int2(IMGUI_CANVAS_GRID_OFFSET, self, gridOffset); + _imgui_end_child(); ImGui::SameLine(); - _imgui_item_begin_child(IMGUI_SPRITESHEET_EDITOR_VIEW_SETTINGS); - _imgui_item_dragfloat(self, IMGUI_SPRITESHEET_EDITOR_ZOOM, self->settings->editorZoom); - if (_imgui_item_button(self, IMGUI_SPRITESHEET_EDITOR_CENTER_VIEW)) pan = vec2(); + _imgui_begin_child(IMGUI_CANVAS_VIEW_CHILD, self); + _imgui_drag_float(IMGUI_CANVAS_ZOOM, self, zoom); + if (_imgui_button(IMGUI_CANVAS_CENTER_VIEW.copy({pan == vec2()}), self)) pan = vec2(); ImGui::Text(mousePositionString.c_str()); - _imgui_item_end_child(); + _imgui_end_child(); // IMGUI_CANVAS_VIEW_CHILD ImGui::SameLine(); - _imgui_item_begin_child(IMGUI_SPRITESHEET_EDITOR_BACKGROUND_SETTINGS); - _imgui_item_coloredit4(self, IMGUI_SPRITESHEET_EDITOR_BACKGROUND_COLOR, self->settings->editorBackgroundColor); - _imgui_item_checkbox(self, IMGUI_SPRITESHEET_EDITOR_BORDER, self->settings->editorIsBorder); - _imgui_item_end_child(); + _imgui_begin_child(IMGUI_CANVAS_VISUAL_CHILD, self); + _imgui_color_edit4(IMGUI_CANVAS_BACKGROUND_COLOR, self, self->settings->editorBackgroundColor); + _imgui_checkbox(IMGUI_CANVAS_BORDER, self, self->settings->editorIsBorder); + _imgui_end_child(); // IMGUI_CANVAS_VISUAL_CHILD ImVec2 editorPos = ImGui::GetCursorPos(); size = ImGui::GetContentRegionAvail(); + editor_draw(self->editor); ImGui::Image(self->editor->canvas.texture, size); - if (!ImGui::IsItemHovered()) + if (ImGui::IsItemHovered()) + self->pendingCursor = TOOL_CURSORS[tool]; + else { - if (isEditorHover) - { - SDL_SetCursor(SDL_CreateSystemCursor(MOUSE_CURSOR_DEFAULT)); - _imgui_keyboard_navigation_set(true); - isEditorHover = false; - } - _imgui_item_end(); + _imgui_end(); // IMGUI_SPRITESHEET_EDITOR return; } - isEditorHover = true; + _imgui_end(); // IMGUI_SPRITESHEET_EDITOR - _imgui_keyboard_navigation_set(false); - mousePos = (vec2((ImGui::GetMousePos()) - (ImGui::GetWindowPos() + editorPos)) - pan) / PERCENT_TO_UNIT(zoom); - bool isMouseClick = ImGui::IsMouseClicked(ImGuiMouseButton_Left); - bool isMouseDown = ImGui::IsMouseDown(ImGuiMouseButton_Left); - bool isMouseMiddleDown = ImGui::IsMouseDown(ImGuiMouseButton_Middle); - f32 mouseWheel = ImGui::GetIO().MouseWheel; - ImVec2 mouseDelta = ImGui::GetIO().MouseDelta; + const bool isMouseClick = ImGui::IsMouseClicked(ImGuiMouseButton_Left); + 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 isMod = ImGui::IsKeyDown(IMGUI_INPUT_SHIFT); + const f32 mouseWheel = ImGui::GetIO().MouseWheel; + const ImVec2 mouseDelta = ImGui::GetIO().MouseDelta; - SDL_SetCursor(SDL_CreateSystemCursor(TOOL_MOUSE_CURSORS[self->settings->tool])); - - if ((self->settings->tool == TOOL_PAN && isMouseDown) || isMouseMiddleDown) + if ((tool == TOOL_PAN && isMouseDown) || isMouseMiddleDown) pan += vec2(mouseDelta.x, mouseDelta.y); Anm2Frame* frame = nullptr; @@ -2217,157 +2208,137 @@ static void _imgui_spritesheet_editor(Imgui* self) Texture* texture = map_find(self->resources->textures, self->editor->spritesheetID); - if (texture) + vec2 position = mousePos; + + switch (tool) { - vec2 position = mousePos; - vec4 color = self->settings->tool == TOOL_ERASE ? COLOR_TRANSPARENT : self->settings->toolColor; + case TOOL_CROP: + if (!frame || !texture) break; - switch (self->settings->tool) - { - case TOOL_CROP: - if (!frame) break; - - if (self->settings->editorIsGridSnap) - position = {(s32)(position.x / gridSize.x) * gridSize.x, (s32)(position.y / gridSize.y) * gridSize.y}; - - if (isMouseClick) + if (self->settings->editorIsGridSnap) + { + position = { - imgui_undo_stack_push(self, IMGUI_ACTION_FRAME_CROP); - frame->crop = position; - frame->size = ivec2(0,0); - } - else if (isMouseDown) - frame->size = position - frame->crop; - break; - case TOOL_DRAW: - case TOOL_ERASE: - if (isMouseDown) - texture_pixel_set(texture, mousePos, color); - break; - default: - break; + (s32)((position.x - gridOffset.x) / gridSize.x) * gridSize.x + gridOffset.x, + (s32)((position.y - gridOffset.y) / gridSize.y) * gridSize.y + gridOffset.y + }; + } + + if (isMouseClick) + { + imgui_undo_push(self, IMGUI_ACTION_FRAME_CROP); + frame->crop = position; + frame->size = ivec2(0,0); + } + else if (isMouseDown) + frame->size = position - frame->crop; + break; + case TOOL_DRAW: + case TOOL_ERASE: + { + if (!frame || !texture) break; + vec4 color = tool == TOOL_ERASE ? COLOR_TRANSPARENT : toolColor; + + if (isMouseDown) + texture_pixel_set(texture, position, color); + break; } + case TOOL_COLOR_PICKER: + if (isMouseDown) + { + SDL_GetMouseState(&mousePos.x, &mousePos.y); + _imgui_window_color_from_position_get(self->window, mousePos, toolColor); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2()); + ImGui::BeginTooltip(); + _imgui_color_button(IMGUI_COLOR_PICKER_BUTTON, self, toolColor); + ImGui::EndTooltip(); + ImGui::PopStyleVar(); + } + break; + default: + break; } - if (mouseWheel != 0 || ImGui::IsKeyPressed(IMGUI_INPUT_ZOOM_IN) || ImGui::IsKeyPressed(IMGUI_INPUT_ZOOM_OUT)) + if (mouseWheel != 0 || isZoomIn || isZoomOut) { - f32 delta = (mouseWheel > 0 || ImGui::IsKeyPressed(IMGUI_INPUT_ZOOM_IN)) ? CANVAS_ZOOM_STEP : -CANVAS_ZOOM_STEP; - delta = ImGui::IsKeyDown(IMGUI_INPUT_SHIFT) ? delta * CANVAS_ZOOM_MOD : delta; - self->settings->editorZoom = ROUND_NEAREST_MULTIPLE(self->settings->editorZoom + delta, CANVAS_ZOOM_STEP); - self->settings->editorZoom = std::clamp(self->settings->editorZoom, CANVAS_ZOOM_MIN, CANVAS_ZOOM_MAX); + f32 delta = (mouseWheel > 0 || isZoomIn) ? CANVAS_ZOOM_STEP : -CANVAS_ZOOM_STEP; + delta = isMod ? delta * CANVAS_ZOOM_MOD : delta; + zoom = std::clamp(ROUND_NEAREST_MULTIPLE(zoom + delta, CANVAS_ZOOM_STEP), CANVAS_ZOOM_MIN, CANVAS_ZOOM_MAX); } - - _imgui_item_end(); - - } static void _imgui_frame_properties(Imgui* self) { - _imgui_item_begin(IMGUI_FRAME_PROPERTIES); + static Anm2Type& type = self->reference->itemType; + + IMGUI_BEGIN_OR_RETURN(IMGUI_FRAME_PROPERTIES, self); Anm2Frame* frame = anm2_frame_from_reference(self->anm2, self->reference); - - if (!frame) - { - ImGui::Text(IMGUI_FRAME_PROPERTIES_NO_FRAME); - _imgui_item_end(); - return; - } - Anm2Type type = self->reference->itemType; - Anm2Animation* animation = anm2_animation_from_reference(self->anm2, self->reference); - - ImGui::Text(IMGUI_FRAME_PROPERTIES_TITLE[type].c_str()); + bool isLayerFrame = frame && type == ANM2_LAYER; - if (type == ANM2_ROOT || type == ANM2_NULL || type == ANM2_LAYER) + if (type != ANM2_TRIGGERS || !frame) { - _imgui_item_dragfloat2(self, IMGUI_FRAME_PROPERTIES_POSITION, frame->position); - - if (type == ANM2_LAYER) - { - _imgui_item_dragfloat2(self, IMGUI_FRAME_PROPERTIES_CROP, frame->crop); - _imgui_item_dragfloat2(self, IMGUI_FRAME_PROPERTIES_SIZE, frame->size); - _imgui_item_dragfloat2(self, IMGUI_FRAME_PROPERTIES_PIVOT, frame->pivot); - } - - _imgui_item_dragfloat2(self, IMGUI_FRAME_PROPERTIES_SCALE, frame->scale); - _imgui_item_dragfloat(self, IMGUI_FRAME_PROPERTIES_ROTATION, frame->rotation); - _imgui_item_inputint(self, IMGUI_FRAME_PROPERTIES_DURATION, frame->delay); - _imgui_item_coloredit4(self, IMGUI_FRAME_PROPERTIES_TINT, frame->tintRGBA); - _imgui_item_coloredit3(self, IMGUI_FRAME_PROPERTIES_COLOR_OFFSET, frame->offsetRGB); - - if (_imgui_item_button(self, IMGUI_FRAME_PROPERTIES_FLIP_X)) - frame->scale.x = -frame->scale.x; - ImGui::SameLine(); - if (_imgui_item_button(self, IMGUI_FRAME_PROPERTIES_FLIP_Y)) - frame->scale.y = -frame->scale.y; - - _imgui_item_checkbox(self, IMGUI_FRAME_PROPERTIES_VISIBLE, frame->isVisible); - ImGui::SameLine(); - _imgui_item_checkbox(self, IMGUI_FRAME_PROPERTIES_INTERPOLATED, frame->isInterpolated); - + _imgui_drag_float2(IMGUI_FRAME_PROPERTIES_CROP.copy({!isLayerFrame}), self, !isLayerFrame ? dummy_value() : frame->crop); + _imgui_drag_float2(IMGUI_FRAME_PROPERTIES_SIZE.copy({!isLayerFrame}), self, !isLayerFrame ? dummy_value() : frame->size); + _imgui_drag_float2(IMGUI_FRAME_PROPERTIES_POSITION.copy({!frame}), self, !frame ? dummy_value() : frame->position); + _imgui_drag_float2(IMGUI_FRAME_PROPERTIES_PIVOT.copy({!isLayerFrame}), self, !isLayerFrame ? dummy_value() : frame->pivot); + _imgui_drag_float2(IMGUI_FRAME_PROPERTIES_SCALE.copy({!frame}), self, !frame ? dummy_value() : frame->scale); + _imgui_drag_float(IMGUI_FRAME_PROPERTIES_ROTATION.copy({!frame}), self, !frame ? dummy_value() : frame->rotation); + _imgui_input_int(IMGUI_FRAME_PROPERTIES_DELAY.copy({!frame}), self, !frame ? dummy_value() : frame->delay); + _imgui_color_edit4(IMGUI_FRAME_PROPERTIES_TINT.copy({!frame}), self, !frame ? dummy_value() : frame->tintRGBA); + _imgui_color_edit3(IMGUI_FRAME_PROPERTIES_COLOR_OFFSET.copy({!frame}), self, !frame ? dummy_value() : frame->offsetRGB); + _imgui_checkbox(IMGUI_FRAME_PROPERTIES_VISIBLE.copy({!frame}), self, !frame ? dummy_value() : frame->isVisible); + _imgui_checkbox(IMGUI_FRAME_PROPERTIES_INTERPOLATED.copy({!frame}), self, !frame ? dummy_value() : frame->isInterpolated); + if (_imgui_button(IMGUI_FRAME_PROPERTIES_FLIP_X.copy({!frame}), self)) frame->scale.x = -frame->scale.x; + if (_imgui_button(IMGUI_FRAME_PROPERTIES_FLIP_Y.copy({!frame}), self)) frame->scale.y = -frame->scale.y; } - else if (type == ANM2_TRIGGERS) + else { std::vector eventIDs; - ImguiItem framePropertiesEventItem = IMGUI_FRAME_PROPERTIES_EVENT; - s32 eventComboCount = self->anm2->events.size() + 1; + ImguiItem eventItem = IMGUI_FRAME_PROPERTIES_EVENT.copy({!frame}); - framePropertiesEventItem.items.reserve(eventComboCount); - eventIDs.reserve(eventComboCount); - framePropertiesEventItem.items.push_back(IMGUI_EVENT_NONE); - eventIDs.push_back(ID_NONE); + eventIDs.emplace_back(ID_NONE); + eventItem.items.emplace_back(IMGUI_NONE); for (auto & [id, event] : self->anm2->events) { - eventIDs.push_back(id); - framePropertiesEventItem.items.push_back(event.name); + eventIDs.emplace_back(id); + eventItem.items.emplace_back(event.name); } - + s32 eventIndex = std::find(eventIDs.begin(), eventIDs.end(), frame->eventID) - eventIDs.begin(); - if (_imgui_item_combo(self, framePropertiesEventItem, &eventIndex)) + if (_imgui_combo(eventItem, self, &eventIndex)) frame->eventID = eventIDs[eventIndex]; - - _imgui_item_inputint(self, IMGUI_FRAME_PROPERTIES_AT_FRAME, frame->atFrame); - frame->atFrame = std::clamp(frame->atFrame, 0, animation->frameNum - 1); + + _imgui_input_int(IMGUI_FRAME_PROPERTIES_AT_FRAME.copy({!frame}), self, frame->atFrame); } - _imgui_item_end(); + _imgui_end(); // IMGUI_FRAME_PROPERTIES } -static void _imgui_messages(Imgui* self) +static void _imgui_log(Imgui* self) { ImGuiIO& io = ImGui::GetIO(); ImGuiStyle& style = ImGui::GetStyle(); ImVec4 borderColor = style.Colors[ImGuiCol_Border]; ImVec4 textColor = style.Colors[ImGuiCol_Text]; - ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar | - ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoMove | - ImGuiWindowFlags_NoScrollbar | - ImGuiWindowFlags_NoSavedSettings | - ImGuiWindowFlags_AlwaysAutoResize | - ImGuiWindowFlags_NoFocusOnAppearing | - ImGuiWindowFlags_NoNav | - ImGuiWindowFlags_NoInputs; - - ImVec2 position = {io.DisplaySize.x - IMGUI_MESSAGE_PADDING, io.DisplaySize.y - IMGUI_MESSAGE_PADDING}; + ImVec2 position = {io.DisplaySize.x - IMGUI_LOG_PADDING, io.DisplaySize.y - IMGUI_LOG_PADDING}; - for (s32 i = (s32)self->messageQueue.size() - 1; i >= 0; --i) + for (s32 i = (s32)self->log.size() - 1; i >= 0; --i) { - ImguiMessage& message = self->messageQueue[i]; - f32 lifetime = message.timeRemaining / IMGUI_MESSAGE_DURATION; + ImguiLogItem& item = self->log[i]; + f32 lifetime = item.timeRemaining / IMGUI_LOG_DURATION; borderColor.w = lifetime; textColor.w = lifetime; - message.timeRemaining -= io.DeltaTime; + item.timeRemaining -= io.DeltaTime; - if (message.timeRemaining <= 0.0f) + if (item.timeRemaining <= 0.0f) { - self->messageQueue.erase(self->messageQueue.begin() + i); + self->log.erase(self->log.begin() + i); continue; } @@ -2376,15 +2347,15 @@ static void _imgui_messages(Imgui* self) ImGui::PushStyleColor(ImGuiCol_Text, textColor); ImGui::SetNextWindowBgAlpha(lifetime); - ImGui::Begin(std::format(IMGUI_MESSAGE_FORMAT, i).c_str(), nullptr, flags); - ImGui::TextUnformatted(message.text.c_str()); + _imgui_begin(IMGUI_LOG_WINDOW.copy({.label = std::format(IMGUI_LOG_FORMAT, i)}), self); + ImGui::TextUnformatted(item.text.c_str()); ImVec2 windowSize = ImGui::GetWindowSize(); - ImGui::End(); + _imgui_end(); // IMGUI_LOG_WINDOW ImGui::PopStyleColor(2); - position.y -= windowSize.y + IMGUI_MESSAGE_PADDING; - } + position.y -= windowSize.y + IMGUI_LOG_PADDING; + } } static void _imgui_persistent(Imgui* self) @@ -2392,32 +2363,29 @@ static void _imgui_persistent(Imgui* self) if (!self->isContextualActionsEnabled) return; if (ImGui::IsMouseClicked(ImGuiMouseButton_Right)) - ImGui::OpenPopup(IMGUI_CONTEXT_MENU.popup.c_str()); + imgui_open_popup(IMGUI_CONTEXT_MENU.label_get()); - if (ImGui::BeginPopup(IMGUI_CONTEXT_MENU.popup.c_str())) + if (imgui_begin_popup(IMGUI_CONTEXT_MENU.label_get(), self)) { - ImguiItem pasteItem = IMGUI_PASTE; - pasteItem.isInactive = self->clipboard->item.type == CLIPBOARD_NONE; + _imgui_selectable(IMGUI_CUT, self); + _imgui_selectable(IMGUI_COPY, self); + _imgui_selectable(IMGUI_PASTE.copy({self->clipboard->item.type == CLIPBOARD_NONE}), self); - _imgui_item_selectable(self, IMGUI_CUT); - _imgui_item_selectable(self, IMGUI_COPY); - _imgui_item_selectable(self, pasteItem); - - ImGui::EndPopup(); + imgui_end_popup(self); } } static void _imgui_dock(Imgui* self) { + ImguiItem window = IMGUI_WINDOW_MAIN; ImGuiViewport* viewport = ImGui::GetMainViewport(); ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x, viewport->Pos.y + IMGUI_TASKBAR.size.y)); ImGui::SetNextWindowSize(ImVec2(viewport->Size.x, viewport->Size.y - IMGUI_TASKBAR.size.y)); ImGui::SetNextWindowViewport(viewport->ID); - - _imgui_item_begin(IMGUI_WINDOW); - - _imgui_item_dockspace(IMGUI_DOCKSPACE); + + _imgui_begin(window, self); + _imgui_dockspace(IMGUI_DOCKSPACE_MAIN, self); _imgui_tools(self); _imgui_animations(self); @@ -2427,11 +2395,11 @@ static void _imgui_dock(Imgui* self) _imgui_spritesheet_editor(self); _imgui_timeline(self); _imgui_frame_properties(self); - - ImGui::End(); + + _imgui_end(); // IMGUI_WINDOW_MAIN } -void imgui_init +void imgui_init ( Imgui* self, Dialog* dialog, @@ -2484,7 +2452,7 @@ void imgui_update(Imgui* self) _imgui_taskbar(self); _imgui_dock(self); - _imgui_messages(self); + _imgui_log(self); _imgui_persistent(self); if (self->isContextualActionsEnabled) @@ -2498,7 +2466,15 @@ void imgui_update(Imgui* self) } } } - + + if (self->pendingCursor != self->cursor) + { + SDL_SetCursor(SDL_CreateSystemCursor(self->pendingCursor)); + self->cursor = self->pendingCursor; + } + + self->pendingCursor = CURSOR_DEFAULT; + SDL_Event event; while(SDL_PollEvent(&event)) @@ -2508,19 +2484,22 @@ void imgui_update(Imgui* self) switch (event.type) { case SDL_EVENT_QUIT: - if (ImGui::IsPopupOpen(IMGUI_EXIT_CONFIRMATION.popup.c_str())) + if (!self->snapshots->undoStack.is_empty()) { - self->isQuit = true; - break; + if (imgui_is_popup_open(IMGUI_EXIT_CONFIRMATION.label_get())) + self->isQuit = true; + else + imgui_open_popup(IMGUI_EXIT_CONFIRMATION.label_get()); } - ImGui::OpenPopup(IMGUI_EXIT_CONFIRMATION.popup.c_str()); + else + self->isQuit = true; break; default: break; } } - if (_imgui_item_option_popup(self, IMGUI_EXIT_CONFIRMATION)) + if (_imgui_option_popup(IMGUI_EXIT_CONFIRMATION, self)) self->isQuit = true; } diff --git a/src/imgui.h b/src/imgui.h index 332b073..933808c 100644 --- a/src/imgui.h +++ b/src/imgui.h @@ -3,6 +3,7 @@ #include "clipboard.h" #include "dialog.h" #include "editor.h" +#include "ffmpeg.h" #include "preview.h" #include "resources.h" #include "settings.h" @@ -10,26 +11,34 @@ #include "tool.h" #include "window.h" -#include "ffmpeg.h" - #define IMGUI_IMPL_OPENGL_LOADER_CUSTOM #define IMGUI_ENABLE_DOCKING -#define IM_VEC2_CLASS_EXTRA \ - inline bool operator==(const ImVec2& rhs) const { return x == rhs.x && y == rhs.y; } \ - inline bool operator!=(const ImVec2& rhs) const { return !(*this == rhs); } \ - inline ImVec2 operator+(const ImVec2& rhs) const { return ImVec2(x + rhs.x, y + rhs.y); } \ - inline ImVec2 operator-(const ImVec2& rhs) const { return ImVec2(x - rhs.x, y - rhs.y); } \ - inline ImVec2 operator*(const ImVec2& rhs) const { return ImVec2(x * rhs.x, y * rhs.y); } \ - inline ImVec2(const vec2& v) : x(v.x), y(v.y) {} \ +#define IM_VEC2_CLASS_EXTRA \ + inline bool operator==(const ImVec2& rhs) const { return x == rhs.x && y == rhs.y; } \ + inline bool operator!=(const ImVec2& rhs) const { return !(*this == rhs); } \ + inline ImVec2 operator+(const ImVec2& rhs) const { return ImVec2(x + rhs.x, y + rhs.y); } \ + inline ImVec2 operator-(const ImVec2& rhs) const { return ImVec2(x - rhs.x, y - rhs.y); } \ + inline ImVec2 operator*(const ImVec2& rhs) const { return ImVec2(x * rhs.x, y * rhs.y); } \ + inline ImVec2 operator*(float s) const { return ImVec2(x * s, y * s); } \ + friend inline ImVec2 operator*(float s, const ImVec2& v) { return ImVec2(v.x * s, v.y * s); } \ + inline ImVec2& operator+=(const ImVec2& rhs) { x += rhs.x; y += rhs.y; return *this; } \ + inline ImVec2& operator-=(const ImVec2& rhs) { x -= rhs.x; y -= rhs.y; return *this; } \ + inline ImVec2& operator*=(float s) { x *= s; y *= s; return *this; } \ + inline ImVec2(const vec2& v) : x(v.x), y(v.y) {} \ inline operator vec2() const { return vec2(x, y); } - -#define IM_VEC4_CLASS_EXTRA \ - inline bool operator==(const ImVec4& rhs) const { return x == rhs.x && y == rhs.y && z == rhs.z && w == rhs.w; } \ - inline bool operator!=(const ImVec4& rhs) const { return !(*this == rhs); } \ - inline ImVec4 operator+(const ImVec4& rhs) const { return ImVec4(x + rhs.x, y + rhs.y, z + rhs.z, w + rhs.w); } \ - inline ImVec4 operator-(const ImVec4& rhs) const { return ImVec4(x - rhs.x, y - rhs.y, z - rhs.z, w - rhs.w); } \ - inline ImVec4 operator*(const ImVec4& rhs) const { return ImVec4(x * rhs.x, y * rhs.y, z * rhs.z, w * rhs.w); } \ - inline ImVec4(const vec4& v) : x(v.x), y(v.y), z(v.z), w(v.w) {} \ + +#define IM_VEC4_CLASS_EXTRA \ + inline bool operator==(const ImVec4& rhs) const { return x == rhs.x && y == rhs.y && z == rhs.z && w == rhs.w; } \ + inline bool operator!=(const ImVec4& rhs) const { return !(*this == rhs); } \ + inline ImVec4 operator+(const ImVec4& rhs) const { return ImVec4(x + rhs.x, y + rhs.y, z + rhs.z, w + rhs.w); } \ + inline ImVec4 operator-(const ImVec4& rhs) const { return ImVec4(x - rhs.x, y - rhs.y, z - rhs.z, w - rhs.w); } \ + inline ImVec4 operator*(const ImVec4& rhs) const { return ImVec4(x * rhs.x, y * rhs.y, z * rhs.z, w * rhs.w); } \ + inline ImVec4 operator*(float s) const { return ImVec4(x * s, y * s, z * s, w * s); } \ + friend inline ImVec4 operator*(float s, const ImVec4& v) { return ImVec4(v.x * s, v.y * s, v.z * s, v.w * s); } \ + inline ImVec4& operator+=(const ImVec4& rhs) { x += rhs.x; y += rhs.y; z += rhs.z; w += rhs.w; return *this; } \ + inline ImVec4& operator-=(const ImVec4& rhs) { x -= rhs.x; y -= rhs.y; z -= rhs.z; w -= rhs.w; return *this; } \ + inline ImVec4& operator*=(float s) { x *= s; y *= s; z *= s; w *= s; return *this; } \ + inline ImVec4(const vec4& v) : x(v.x), y(v.y), z(v.z), w(v.w) {} \ inline operator vec4() const { return vec4(x, y, z, w); } #include @@ -37,71 +46,66 @@ #include #include -#define IMGUI_ANIMATIONS_FOOTER_HEIGHT 40 -#define IMGUI_ANIMATIONS_OPTIONS_ROW_ITEM_COUNT 5 #define IMGUI_CHORD_NONE (ImGuiMod_None) #define IMGUI_EVENTS_FOOTER_HEIGHT 40 -#define IMGUI_EVENTS_OPTIONS_ROW_ITEM_COUNT 2 #define IMGUI_FRAME_BORDER 2.0f -#define IMGUI_MESSAGE_DURATION 3.0f -#define IMGUI_MESSAGE_PADDING 10.0f -#define IMGUI_PICKER_LINE_COLOR IM_COL32(255, 255, 255, 255) -#define IMGUI_POPUP_OPTION_CHILD_ROW_ITEM_COUNT 2 -#define IMGUI_RENDER_ANIMATION_OPTIONS_ROW_ITEM_COUNT 2 +#define IMGUI_LOG_DURATION 3.0f +#define IMGUI_LOG_PADDING 10.0f +#define IMGUI_PLAYHEAD_LINE_COLOR IM_COL32(255, 255, 255, 255) +#define IMGUI_TRIGGERS_EVENT_COLOR IM_COL32(255, 255, 255, 128) +#define IMGUI_PLAYHEAD_LINE_WIDTH 2.0f #define IMGUI_SPRITESHEETS_FOOTER_HEIGHT 65 -#define IMGUI_SPRITESHEETS_OPTIONS_FIRST_ROW_ITEM_COUNT 4 -#define IMGUI_SPRITESHEETS_OPTIONS_SECOND_ROW_ITEM_COUNT 3 -#define IMGUI_TIMELINE_BAKE_OPTIONS_CHILD_ROW_ITEM_COUNT 2 -#define IMGUI_TIMELINE_FOOTER_HEIGHT 40 -#define IMGUI_TIMELINE_FOOTER_ITEM_CHILD_ITEM_COUNT 2 -#define IMGUI_TIMELINE_FRAME_BORDER 2 #define IMGUI_TIMELINE_FRAME_MULTIPLE 5 #define IMGUI_TIMELINE_MERGE -#define IMGUI_TIMELINE_MERGE_OPTIONS_ROW_ITEM_COUNT 2 -#define IMGUI_TIMELINE_PICKER_LINE_WIDTH 2.0f +#define IMGUI_TOOL_COLOR_PICKER_DURATION 0.25f +#define IMGUI_OPTION_POPUP_ROW_COUNT 2 #define IMGUI_ACTION_FRAME_CROP "Frame Crop" #define IMGUI_ACTION_FRAME_SWAP "Frame Swap" #define IMGUI_ACTION_FRAME_TRANSFORM "Frame Transform" #define IMGUI_ACTION_ANIMATION_SWAP "Animation Swap" #define IMGUI_ACTION_TRIGGER_MOVE "Trigger AtFrame" +#define IMGUI_ACTION_MOVE_PLAYHEAD "Move Playhead" -#define IMGUI_MESSAGE_FILE_OPEN_FORMAT "Opened anm2: {}" -#define IMGUI_MESSAGE_FILE_SAVE_FORMAT "Saved anm2 to: {}" -#define IMGUI_MESSAGE_RENDER_ANIMATION_FRAMES_SAVE_FORMAT "Saved rendered frames to: {}" -#define IMGUI_MESSAGE_RENDER_ANIMATION_SAVE_FORMAT "Saved rendered animation to: {}" -#define IMGUI_MESSAGE_RENDER_ANIMATION_NO_SELECTED_ANIMATION_ERROR "Select an animation first to render!" -#define IMGUI_MESSAGE_RENDER_ANIMATION_NO_ANIMATION_ERROR "No animation selected; rendering cancelled." -#define IMGUI_MESSAGE_RENDER_ANIMATION_NO_FRAMES_ERROR "No frames to render; rendering cancelled." -#define IMGUI_MESSAGE_RENDER_ANIMATION_DIRECTORY_ERROR "Invalid directory! Make sure it's valid and you have write permissions." -#define IMGUI_MESSAGE_RENDER_ANIMATION_PATH_ERROR "Invalid path! Make sure it's valid and you have write permissions." -#define IMGUI_MESSAGE_RENDER_ANIMATION_FFMPEG_PATH_ERROR "Invalid FFmpeg path! Make sure you have it installed and the path is correct." -#define IMGUI_MESSAGE_RENDER_ANIMATION_FFMPEG_ERROR "FFmpeg could not render animation! Check paths or your FFmpeg installation." -#define IMGUI_MESSAGE_SPRITESHEET_SAVE_FORMAT "Saved spritesheet #{} to: {}" +#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_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." +#define IMGUI_LOG_SPRITESHEET_SAVE_FORMAT "Saved spritesheet #{} to: {}" + +#define IMGUI_NONE "None" #define IMGUI_ANIMATION_DEFAULT_FORMAT "(*) {}" -#define IMGUI_ANIMATION_NONE "None" #define IMGUI_BUFFER_MAX 255 -#define IMGUI_EVENT_NONE "None" -#define IMGUI_FRAME_PROPERTIES_NO_FRAME "Select a frame to show properties..." #define IMGUI_INVISIBLE_LABEL_MARKER "##" #define IMGUI_ITEM_SELECTABLE_EDITABLE_LABEL "## Editing" -#define IMGUI_MESSAGE_FORMAT "## Message {}" -#define IMGUI_MESSAGE_REDO_FORMAT "Redo: {}" -#define IMGUI_MESSAGE_UNDO_FORMAT "Undo: {}" +#define IMGUI_LOG_FORMAT "## Log {}" +#define IMGUI_LOG_REDO_FORMAT "Redo: {}" +#define IMGUI_LOG_UNDO_FORMAT "Undo: {}" #define IMGUI_OPENGL_VERSION "#version 330" #define IMGUI_POSITION_FORMAT "Position: ({:8}, {:8})" #define IMGUI_SPRITESHEET_FORMAT "#{} {}" -#define IMGUI_TIMELINE_CHILD_ID_LABEL "#{} {}" +#define IMGUI_SPRITESHEET_ID_FORMAT "#{}" +#define IMGUI_TIMELINE_ITEM_CHILD_FORMAT "#{} {}" #define IMGUI_TIMELINE_FRAME_LABEL_FORMAT "## {}" -#define IMGUI_TIMELINE_NO_ANIMATION "Select an animation to show timeline..." -#define IMGUI_TIMELINE_SPRITESHEET_ID_FORMAT "#{}" +#define IMGUI_SELECTABLE_INPUT_INT_FORMAT "#{}" +#define IMGUI_TIMELINE_ANIMATION_NONE "Select an animation to show timeline..." -#define IMGUI_SPACING 4 +#define IMGUI_LABEL_SHORTCUT_FORMAT "({})" +#define IMGUI_TOOLTIP_SHORTCUT_FORMAT "\n(Shortcut: {})" +#define IMGUI_INVISIBLE_FORMAT "## {}" -const ImVec2 IMGUI_TIMELINE_FRAME_SIZE = {16, 40}; -const ImVec2 IMGUI_TIMELINE_FRAME_CONTENT_OFFSET = {ATLAS_SIZE_SMALL.x * 0.25f, (IMGUI_TIMELINE_FRAME_SIZE.y * 0.5f) - (ATLAS_SIZE_SMALL.y * 0.5f)}; +#define IMGUI_TRIGGERS_FONT_SCALE 2.0 + +const ImVec2 IMGUI_TIMELINE_FRAME_SIZE = {12, 36}; +const ImVec2 IMGUI_TIMELINE_FRAME_ATLAS_OFFSET = {ATLAS_SIZE_SMALL.x * 0.25f, (IMGUI_TIMELINE_FRAME_SIZE.y * 0.5f) - (ATLAS_SIZE_SMALL.y * 0.5f)}; const ImVec2 IMGUI_TIMELINE_ITEM_SELECTABLE_SIZE = {150, 0}; -const ImVec2 IMGUI_TIMELINE_ITEM_SIZE = {300, 40}; +const ImVec2 IMGUI_TIMELINE_ITEM_SIZE = {300, IMGUI_TIMELINE_FRAME_SIZE.y}; const ImVec4 IMGUI_TIMELINE_FRAME_COLOR = {0.0f, 0.0f, 0.0f, 0.125}; const ImVec4 IMGUI_TIMELINE_FRAME_MULTIPLE_COLOR = {0.113, 0.184, 0.286, 0.125}; @@ -109,15 +113,12 @@ const ImVec4 IMGUI_TIMELINE_HEADER_FRAME_COLOR = {0.113, 0.184, 0.286, 0.5}; const ImVec4 IMGUI_TIMELINE_HEADER_FRAME_MULTIPLE_COLOR = {0.113, 0.184, 0.286, 1.0}; const ImVec4 IMGUI_TIMELINE_HEADER_FRAME_INACTIVE_COLOR = {0.113, 0.184, 0.286, 0.125}; const ImVec4 IMGUI_TIMELINE_HEADER_FRAME_MULTIPLE_INACTIVE_COLOR = {0.113, 0.184, 0.286, 0.25}; -const ImVec4 IMGUI_INACTIVE_COLOR = {0.5, 0.5, 0.5, 1.0}; const ImVec4 IMGUI_ACTIVE_COLOR = {1.0, 1.0, 1.0, 1.0}; - -const ImVec2 IMGUI_FRAME_PROPERTIES_FLIP_BUTTON_SIZE = {100, 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_TOOLTIP_OFFSET = {16, 8}; const vec2 IMGUI_SPRITESHEET_EDITOR_CROP_FORGIVENESS = {1, 1}; -const ImVec2 IMGUI_CANVAS_CHILD_SIZE = {230, 85}; const ImGuiKey IMGUI_INPUT_DELETE = ImGuiKey_Delete; const ImGuiKey IMGUI_INPUT_LEFT = ImGuiKey_LeftArrow; @@ -128,34 +129,35 @@ const ImGuiKey IMGUI_INPUT_SHIFT = ImGuiMod_Shift; const ImGuiKey IMGUI_INPUT_CTRL = ImGuiMod_Ctrl; const ImGuiKey IMGUI_INPUT_ZOOM_IN = ImGuiKey_1; const ImGuiKey IMGUI_INPUT_ZOOM_OUT = ImGuiKey_2; - -inline const std::string IMGUI_FRAME_PROPERTIES_TITLE[ANM2_COUNT] = -{ - "", - "-- Root --", - "-- Layer --", - "-- Null --", - "-- Trigger --", -}; - -static inline ImGuiKey imgui_key_get(char c) -{ - if (c >= 'a' && c <= 'z') c -= 'a' - 'A'; - if (c >= 'A' && c <= 'Z') return static_cast(ImGuiKey_A + (c - 'A')); - return ImGuiKey_None; -} - -struct ImguiMessage -{ - std::string text; - f32 timeRemaining; -}; +const ImGuiKey IMGUI_INPUT_ENTER = ImGuiKey_Enter; +const ImGuiKey IMGUI_INPUT_RENAME = ImGuiKey_F2; +const ImGuiKey IMGUI_INPUT_DEFAULT = ImGuiKey_Home; +const ImGuiMouseButton IMGUI_MOUSE_DEFAULT = ImGuiMouseButton_Middle; enum ImguiPopupType { IMGUI_POPUP_NONE, IMGUI_POPUP_BY_ITEM, - IMGUI_POPUP_CENTER_SCREEN + IMGUI_POPUP_CENTER_WINDOW +}; + +struct ImguiColorSet +{ + ImVec4 normal{}; + ImVec4 active{}; + ImVec4 hovered{}; + ImVec4 border{}; + + bool is_normal() const { return normal != ImVec4(); } + bool is_active() const { return active != ImVec4(); } + bool is_hovered() const { return hovered != ImVec4(); } + bool is_border() const { return border != ImVec4(); } +}; + +struct ImguiLogItem +{ + std::string text; + f32 timeRemaining; }; struct Imgui @@ -174,10 +176,11 @@ struct Imgui std::string pendingPopup{}; ImguiPopupType pendingPopupType = IMGUI_POPUP_NONE; ImVec2 pendingPopupPosition{}; - std::vector messageQueue; + std::vector log; + SDL_SystemCursor cursor; + SDL_SystemCursor pendingCursor; + bool isCursorSet = false; bool isContextualActionsEnabled = true; - bool isRename = false; - bool isChangeValue = false; bool isQuit = false; }; @@ -192,180 +195,18 @@ struct ImguiHotkey bool is_focus_window() const { return !focusWindow.empty(); } }; +static void imgui_log_push(Imgui* self, const std::string& text) +{ + self->log.push_back({text, IMGUI_LOG_DURATION}); + std::println("[IMGUI] {}", text); +} + static std::vector& imgui_hotkey_registry() { static std::vector registry; return registry; } -struct ImguiColorSet -{ - ImVec4 normal{}; - ImVec4 active{}; - ImVec4 hovered{}; - - bool is_normal() const { return normal != ImVec4(); } - bool is_active() const { return active != ImVec4(); } - bool is_hovered() const { return hovered != ImVec4(); } -}; - -struct ImguiItemBuilder -{ - std::string label{}; - std::string tooltip{}; - std::string action = SNAPSHOT_ACTION; - std::string popup{}; - std::string dragDrop{}; - std::string format = "%.1f"; - std::string focusWindow{}; - std::vector items{}; - ImguiFunction function = nullptr; - ImGuiKeyChord chord = IMGUI_CHORD_NONE; - AtlasType atlas = ATLAS_NONE; - ImguiPopupType popupType = IMGUI_POPUP_BY_ITEM; - bool isUndoable = false; - bool isSizeToText = true; - bool isSizeToChild = false; - ImguiColorSet color{}; - ImVec2 size{}; - ImVec2 contentOffset{}; - s32 childRowItemCount = 1; - f64 speed{}; - f64 min{}; - f64 max = IMGUI_BUFFER_MAX; - s32 border{}; - s32 step = 1; - s32 stepFast = 1; - s32 value = ID_NONE; - s32 id = ID_NONE; - s32 flags{}; - s32 flagsAlt{}; -}; - -struct ImguiItem -{ - std::string label{}; - std::string tooltip{}; - std::string action = SNAPSHOT_ACTION; - std::string popup{}; - std::string dragDrop{}; - std::string format = "%.1f"; - std::string focusWindow{}; - std::vector items{}; - ImguiFunction function = nullptr; - ImGuiKeyChord chord = IMGUI_CHORD_NONE; - AtlasType atlas = ATLAS_NONE; - ImguiPopupType popupType = IMGUI_POPUP_BY_ITEM; - bool isUndoable = false; - bool isInactive = false; - bool isSizeToText = true; - bool isSizeToChild = true; - bool isSelected = false; - f64 speed{}; - f64 min{}; - f64 max = IMGUI_BUFFER_MAX; - s32 border{}; - s32 childRowItemCount = 1; - s32 step = 1; - s32 stepFast = 1; - s32 value = ID_NONE; - s32 id{}; - s32 flags{}; - s32 flagsAlt{}; - ImguiColorSet color{}; - ImVec2 size{}; - ImVec2 contentOffset{}; - ImGuiKey mnemonicKey = ImGuiKey_None; - s32 mnemonicIndex = -1; - - bool is_tooltip() const { return !tooltip.empty(); } - bool is_popup() const { return !popup.empty(); } - bool is_action() const { return !action.empty(); } - bool is_size() const { return size != ImVec2(); } - bool is_border() const { return border != 0; } - bool is_mnemonic() const { return mnemonicIndex != -1 && mnemonicKey != ImGuiKey_None; } - bool is_chord() const { return chord != IMGUI_CHORD_NONE;} - bool is_focus_window() const { return !focusWindow.empty(); } - - ImguiItem(const ImguiItemBuilder& builder) - { - static s32 newID = 0; - - tooltip = builder.tooltip; - popup = builder.popup; - action = builder.action; - dragDrop = builder.dragDrop; - format = builder.format; - focusWindow = builder.focusWindow; - items = builder.items; - function = builder.function; - chord = builder.chord; - popupType = builder.popupType; - atlas = builder.atlas; - isUndoable = builder.isUndoable; - isSizeToText = builder.isSizeToText; - isSizeToChild = builder.isSizeToChild; - color = builder.color; - size = builder.size; - contentOffset = builder.contentOffset; - speed = builder.speed; - min = builder.min; - max = builder.max; - border = builder.border; - childRowItemCount = builder.childRowItemCount; - step = builder.step; - stepFast = builder.stepFast; - value = builder.value; - flags = builder.flags; - flagsAlt = builder.flagsAlt; - - id = newID; - newID++; - - if (is_chord() && function) - imgui_hotkey_registry().push_back({chord, function, focusWindow}); - - mnemonicIndex = -1; - - for (s32 i = 0; i < (s32)builder.label.size(); i++) - { - if (builder.label[i] == '&') - { - if (builder.label[i + 1] == '&') - { - label += '&'; - i++; - } - else if (builder.label[i + 1] != '\0') - { - mnemonicKey = imgui_key_get(builder.label[i + 1]); - mnemonicIndex = (s32)label.size(); - label += builder.label[i + 1]; - i++; - } - } - else - label += builder.label[i]; - } - } -}; - -static void imgui_message_queue_push(Imgui* self, const std::string& text) -{ - self->messageQueue.push_back({text, IMGUI_MESSAGE_DURATION}); -} - -static inline std::string_view imgui_nav_window_root_get() -{ - ImGuiWindow* navWindow = ImGui::GetCurrentContext()->NavWindow; - if (!navWindow) - return {}; - - std::string_view name(navWindow->Name); - size_t slash = name.find('/'); - return (slash == std::string_view::npos) ? name : name.substr(0, slash); -} - static inline void imgui_file_new(Imgui* self) { anm2_reference_clear(self->reference); @@ -385,7 +226,7 @@ static inline void imgui_file_save(Imgui* self) else { anm2_serialize(self->anm2, self->anm2->path); - imgui_message_queue_push(self, std::format(IMGUI_MESSAGE_FILE_SAVE_FORMAT, self->anm2->path)); + imgui_log_push(self, std::format(IMGUI_LOG_FILE_SAVE_FORMAT, self->anm2->path)); } } @@ -394,10 +235,10 @@ static inline void imgui_file_save_as(Imgui* self) dialog_anm2_save(self->dialog); } -static inline void imgui_undo_stack_push(Imgui* self, const std::string& action = SNAPSHOT_ACTION) +static inline void imgui_undo_push(Imgui* self, const std::string& action = SNAPSHOT_ACTION) { Snapshot snapshot = {*self->anm2, *self->reference, self->preview->time, action}; - snapshots_undo_stack_push(self->snapshots, &snapshot); + snapshots_undo_push(self->snapshots, &snapshot); } static inline void imgui_tool_pan_set(Imgui* self) @@ -445,7 +286,7 @@ static inline void imgui_undo(Imgui* self) if (self->snapshots->undoStack.top == 0) return; snapshots_undo(self->snapshots); - imgui_message_queue_push(self, std::format(IMGUI_MESSAGE_UNDO_FORMAT, self->snapshots->action)); + imgui_log_push(self, std::format(IMGUI_LOG_UNDO_FORMAT, self->snapshots->action)); } static inline void imgui_redo(Imgui* self) @@ -454,7 +295,7 @@ static inline void imgui_redo(Imgui* self) std::string action = self->snapshots->action; snapshots_redo(self->snapshots); - imgui_message_queue_push(self, std::format(IMGUI_MESSAGE_REDO_FORMAT, action)); + imgui_log_push(self, std::format(IMGUI_LOG_REDO_FORMAT, action)); } static inline void imgui_cut(Imgui* self) @@ -472,946 +313,1138 @@ static inline void imgui_paste(Imgui* self) clipboard_paste(self->clipboard); } -const inline ImguiItem IMGUI_WINDOW = ImguiItem -({ - .label = "## Window", - .flags = ImGuiWindowFlags_NoTitleBar | - ImGuiWindowFlags_NoCollapse | - ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoMove | - ImGuiWindowFlags_NoBringToFrontOnFocus | - ImGuiWindowFlags_NoNavFocus -}); - -const inline ImguiItem IMGUI_DOCKSPACE = ImguiItem -({ - .label = "## Dockspace", - .flags = ImGuiDockNodeFlags_PassthruCentralNode -}); - -const inline ImguiItem IMGUI_TASKBAR = ImguiItem -({ - .label = "## Taskbar", - .size = {0, 32}, - .flags = ImGuiWindowFlags_NoTitleBar | - ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoMove | - ImGuiWindowFlags_NoScrollbar | - ImGuiWindowFlags_NoScrollWithMouse | - ImGuiWindowFlags_NoSavedSettings -}); - -const inline ImguiItem IMGUI_TASKBAR_FILE = ImguiItem -({ - .label = "&File", - .tooltip = "Opens the file menu, for reading/writing anm2 files.", - .popup = "## File Popup", - .isSizeToText = true -}); - -const inline ImguiItem IMGUI_FILE_NEW = ImguiItem -({ - .label = "&New", - .tooltip = "Load a blank .anm2 file to edit.", - .function = imgui_file_new, - .chord = ImGuiMod_Ctrl | ImGuiKey_N -}); - -const inline ImguiItem IMGUI_FILE_OPEN = ImguiItem -({ - .label = "&Open", - .tooltip = "Open an existing .anm2 file to edit.", - .function = imgui_file_open, - .chord = ImGuiMod_Ctrl | ImGuiKey_O -}); - -const inline ImguiItem IMGUI_FILE_SAVE = ImguiItem -({ - .label = "&Save", - .tooltip = "Saves the current .anm2 file to its path.\nIf no path exists, one can be chosen.", - .function = imgui_file_save, - .chord = ImGuiMod_Ctrl | ImGuiKey_S -}); - -const inline ImguiItem IMGUI_FILE_SAVE_AS = ImguiItem -({ - .label = "S&ave As", - .tooltip = "Saves the current .anm2 file to a chosen path.", - .function = imgui_file_save_as, - .chord = ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiKey_S -}); - -const inline ImguiItem IMGUI_TASKBAR_WIZARD = ImguiItem -({ - .label = "&Wizard", - .tooltip = "Opens the wizard menu, for neat functions related to the .anm2.", - .popup = "## Wizard Popup", - .isSizeToText = true -}); - -const inline ImguiItem IMGUI_TASKBAR_WIZARD_GENERATE_ANIMATION_FROM_GRID = ImguiItem -({ - .label = "&Generate Animation from Grid", - .tooltip = "Generate a new animation from grid values.", - .popup = "Generate Animation from Grid", - .popupType = IMGUI_POPUP_CENTER_SCREEN -}); - -const inline ImguiItem IMGUI_TASKBAR_WIZARD_RENDER_ANIMATION = ImguiItem -({ - .label = "&Render Animation", - .tooltip = "Renders the current animation preview; output options can be customized.", - .popup = "Render Animation", - .popupType = IMGUI_POPUP_CENTER_SCREEN -}); - -const inline ImguiItem IMGUI_RENDER_ANIMATION_CHILD = ImguiItem -({ - .label = "## Render Animation Child", - .size = {600, 125} -}); - -const inline ImguiItem IMGUI_RENDER_ANIMATION_LOCATION = ImguiItem -({ - .label = "Location", - .tooltip = "Set the rendered animation's output location.", - .isSizeToChild = true -}); - -const inline ImguiItem IMGUI_RENDER_ANIMATION_BROWSE = ImguiItem -({ - .label = "## Location Browse", - .atlas = ATLAS_FOLDER -}); - -const inline ImguiItem IMGUI_RENDER_ANIMATION_OUTPUT = ImguiItem -({ - .label = "Output", - .tooltip = "Select the rendered animation output.\nIt can either be one animated image or a sequence of frames.", - .items = {std::begin(RENDER_TYPE_STRINGS), std::end(RENDER_TYPE_STRINGS)}, - .isSizeToChild = true -}); - -const inline ImguiItem IMGUI_RENDER_ANIMATION_FORMAT = ImguiItem -({ - .label = "Format", - .tooltip = "(PNG images only).\nSet the format of each output frame; i.e., its filename.\nThe format will only take one argument; that being the frame's index.\nFor example, a format like \"{}.png\" will export a frame of index 0 as \"0.png\".", - .isSizeToChild = true -}); - -const inline ImguiItem IMGUI_RENDER_ANIMATION_FFMPEG_PATH = ImguiItem -({ - .label = "FFmpeg Path", - .tooltip = "Sets the path FFmpeg currently resides in.\nFFmpeg is required for rendering animations.\nDownload it from https://ffmpeg.org/, your package manager, or wherever else.", - .isSizeToChild = true -}); - -const inline ImguiItem IMGUI_RENDER_ANIMATION_FFMPEG_BROWSE = ImguiItem -({ - .label = "## FFMpeg Browse", - .atlas = ATLAS_FOLDER -}); - -const inline ImguiItem IMGUI_RENDER_ANIMATION_CONFIRM = ImguiItem -({ - .label = "Render", - .tooltip = "Render the animation with the chosen options.", - .popup = "Rendering Animation...", - .popupType = IMGUI_POPUP_CENTER_SCREEN, - .isSizeToChild = true, - .childRowItemCount = IMGUI_RENDER_ANIMATION_OPTIONS_ROW_ITEM_COUNT -}); - -const inline ImguiItem IMGUI_RENDER_ANIMATION_CANCEL = ImguiItem -({ - .label = "Cancel", - .tooltip = "Cancel rendering animation.", - .isSizeToChild = true, - .childRowItemCount = IMGUI_RENDER_ANIMATION_OPTIONS_ROW_ITEM_COUNT -}); - -const inline ImguiItem IMGUI_RENDERING_ANIMATION_CHILD = ImguiItem -({ - .label = "## Render Animation Child", - .size = {300, 60}, - .flags = true -}); - -const inline ImguiItem IMGUI_RENDERING_ANIMATION_CANCEL = ImguiItem -({ - .label = "Cancel", - .isSizeToChild = true -}); - -const inline ImguiItem IMGUI_TASKBAR_PLAYBACK = ImguiItem -({ - .label = "&Playback", - .tooltip = "Opens the playback menu, for configuring playback settings.", - .popup = "## Playback Popup", - .isSizeToText = true -}); - -const inline ImguiItem IMGUI_PLAYBACK_ALWAYS_LOOP = ImguiItem -({ - .label = "&Always Loop", - .tooltip = "Sets the animation playback to always loop, regardless of the animation's loop setting." -}); - -const inline ImguiItem IMGUI_ANIMATIONS = ImguiItem({"Animations"}); - -const inline ImguiItem IMGUI_ANIMATIONS_CHILD = ImguiItem -({ - .label = "## Animations Child", - .flags = true, -}); - -const inline ImguiItem IMGUI_ANIMATION = ImguiItem -({ - .label = "## Animation Item", - .action = "Select Animation", - .dragDrop = "## Animation Drag Drop", - .atlas = ATLAS_ANIMATION, - .isUndoable = true, - .isSizeToText = false -}); - -const inline ImguiItem IMGUI_ANIMATIONS_OPTIONS_CHILD = ImguiItem -({ - .label = "## Animations Options Child", - .flags = true, -}); - -const inline ImguiItem IMGUI_ANIMATION_ADD = ImguiItem -({ - .label = "Add", - .tooltip = "Adds a new animation.", - .action = "Add Animation", - .isUndoable = true, - .isSizeToChild = true, - .childRowItemCount = IMGUI_ANIMATIONS_OPTIONS_ROW_ITEM_COUNT -}); - -const inline ImguiItem IMGUI_ANIMATION_DUPLICATE = ImguiItem -({ - .label = "Duplicate", - .tooltip = "Duplicates the selected animation, placing it after.", - .action = "Duplicate Animation", - .isUndoable = true, - .isSizeToChild = true, - .childRowItemCount = IMGUI_ANIMATIONS_OPTIONS_ROW_ITEM_COUNT -}); - -const inline ImguiItem IMGUI_ANIMATIONS_MERGE = ImguiItem -({ - .label = "Merge", - .tooltip = "Open the animation merge popup, to merge animations together.", - .popup = "Merge Animations", - .popupType = IMGUI_POPUP_CENTER_SCREEN, - .isSizeToChild = true, - .childRowItemCount = IMGUI_ANIMATIONS_OPTIONS_ROW_ITEM_COUNT -}); - -const inline ImguiItem IMGUI_ANIMATIONS_MERGE_CHILD = ImguiItem -({ - .label = "## Merge Animations", - .size = {300, 250}, - .flags = true -}); - -const inline ImguiItem IMGUI_ANIMATIONS_MERGE_ON_CONFLICT_CHILD = ImguiItem -({ - .label = "## Merge On Conflict Child", - .size = {300, 75}, - .flags = true -}); - -const inline ImguiItem IMGUI_ANIMATIONS_MERGE_ON_CONFLICT = ImguiItem({"On Conflict"}); - -const inline ImguiItem IMGUI_ANIMATIONS_MERGE_APPEND_FRAMES = ImguiItem -({ - .label = "Append Frames ", - .tooltip = "On frame conflict, the merged animation will have the selected animations' frames appended, in top-to-bottom order.", - .value = ANM2_MERGE_APPEND_FRAMES -}); - -const inline ImguiItem IMGUI_ANIMATIONS_MERGE_REPLACE_FRAMES = ImguiItem -({ - .label = "Replace Frames", - .tooltip = "On frame conflict, the merged animation will have the latest selected animations' frames.", - .value = ANM2_MERGE_REPLACE_FRAMES -}); - -const inline ImguiItem IMGUI_ANIMATIONS_MERGE_PREPEND_FRAMES = ImguiItem -({ - .label = "Prepend Frames", - .tooltip = "On frame conflict, the merged animation will have the selected animations' frames prepend, in top-to-bottom order.", - .value = ANM2_MERGE_PREPEND_FRAMES -}); - -const inline ImguiItem IMGUI_ANIMATIONS_MERGE_IGNORE = ImguiItem -({ - .label = "Ignore ", - .tooltip = "On frame conflict, the merged animation will ignore the other selected animations' frames.", - .value = ANM2_MERGE_IGNORE -}); - -const inline ImguiItem IMGUI_ANIMATIONS_MERGE_OPTIONS_CHILD = ImguiItem -({ - .label = "## Merge Options Child", - .size = {300, 35}, - .flags = true -}); - -const inline ImguiItem IMGUI_ANIMATIONS_MERGE_DELETE_ANIMATIONS_AFTER = ImguiItem -({ - .label = "Delete Animations After Merging", - .tooltip = "After merging, the selected animations (besides the original) will be deleted." -}); - -const inline ImguiItem IMGUI_ANIMATIONS_MERGE_CONFIRM = ImguiItem -({ - .label = "Merge", - .tooltip = "Merge the selected animations with the options set.", - .action = "Merge Animations", - .isUndoable = true, - .isSizeToChild = true, - .childRowItemCount = IMGUI_TIMELINE_MERGE_OPTIONS_ROW_ITEM_COUNT -}); - -const inline ImguiItem IMGUI_ANIMATIONS_MERGE_CANCEL = ImguiItem -({ - .label = "Cancel", - .tooltip = "Cancel merging.", - .isSizeToChild = true, - .childRowItemCount = IMGUI_TIMELINE_MERGE_OPTIONS_ROW_ITEM_COUNT -}); - -const inline ImguiItem IMGUI_ANIMATION_DEFAULT = ImguiItem -({ - .label = "Default", - .tooltip = "Sets the selected animation to be the default.", - .isUndoable = true, - .isSizeToChild = true, - .childRowItemCount = IMGUI_ANIMATIONS_OPTIONS_ROW_ITEM_COUNT -}); - -const inline ImguiItem IMGUI_ANIMATION_REMOVE = ImguiItem -({ - .label = "Remove", - .tooltip = "Removes the selected animation.", - .action = "Remove Animation", - .focusWindow = IMGUI_ANIMATIONS.label, - .chord = ImGuiKey_Delete, - .isUndoable = true, - .isSizeToChild = true, - .childRowItemCount = IMGUI_ANIMATIONS_OPTIONS_ROW_ITEM_COUNT -}); - -const inline ImguiItem IMGUI_EVENTS = ImguiItem({"Events"}); - -const inline ImguiItem IMGUI_EVENTS_CHILD = ImguiItem -({ - .label = "## Events Child", - .flags = true -}); - -const inline ImguiItem IMGUI_EVENT = ImguiItem -({ - .label = "## Event Item", - .atlas = ATLAS_EVENT, - .isUndoable = true, - .isSizeToText = false -}); - -const inline ImguiItem IMGUI_EVENTS_OPTIONS_CHILD = ImguiItem -({ - .label = "## Events Options Child", - .flags = true, -}); - -const inline ImguiItem IMGUI_EVENT_ADD = ImguiItem -({ - .label = "Add", - .tooltip = "Adds a new event.", - .action = "Add Event", - .isUndoable = true, - .isSizeToChild = true, - .childRowItemCount = IMGUI_EVENTS_OPTIONS_ROW_ITEM_COUNT -}); - -const inline ImguiItem IMGUI_EVENT_REMOVE_UNUSED = ImguiItem -({ - .label = "Remove Unused", - .tooltip = "Removes all unused events (i.e., not being used in any triggers in any animation).", - .action = "Remove Unused Events", - .isUndoable = true, - .isSizeToChild = true, - .childRowItemCount = IMGUI_EVENTS_OPTIONS_ROW_ITEM_COUNT -}); - -const inline ImguiItem IMGUI_SPRITESHEETS = ImguiItem({"Spritesheets"}); - -const inline ImguiItem IMGUI_SPRITESHEETS_CHILD = ImguiItem -({ - .label = "## Spritesheets Child", - .flags = true -}); - -const inline ImguiItem IMGUI_SPRITESHEET_CHILD = ImguiItem -({ - .label = "## Spritesheet Child", - .size = {0, 175}, - .flags = true -}); - -const inline ImguiItem IMGUI_SPRITESHEET = ImguiItem -({ - .label = "## Spritesheet", - .dragDrop = "## Spritesheet Drag Drop", - .atlas = ATLAS_SPRITESHEET, - .flags = true -}); - -const inline ImguiItem IMGUI_SPRITESHEETS_OPTIONS_CHILD = ImguiItem -({ - .label = "## Spritesheets Options Child", - .flags = true, -}); - -const inline ImguiItem IMGUI_SPRITESHEETS_ADD = ImguiItem -({ - .label = "Add", - .tooltip = "Select an image to add as a spritesheet.", - .isSizeToChild = true, - .childRowItemCount = IMGUI_SPRITESHEETS_OPTIONS_FIRST_ROW_ITEM_COUNT -}); - -const inline ImguiItem IMGUI_SPRITESHEETS_RELOAD = ImguiItem -({ - .label = "Reload", - .tooltip = "Reload the selected spritesheet.", - .isSizeToChild = true, - .childRowItemCount = IMGUI_SPRITESHEETS_OPTIONS_FIRST_ROW_ITEM_COUNT -}); - -const inline ImguiItem IMGUI_SPRITESHEETS_REPLACE = ImguiItem -({ - .label = "Replace", - .tooltip = "Replace the highlighted spritesheet with another.", - .isSizeToChild = true, - .childRowItemCount = IMGUI_SPRITESHEETS_OPTIONS_FIRST_ROW_ITEM_COUNT -}); - -const inline ImguiItem IMGUI_SPRITESHEETS_REMOVE_UNUSED = ImguiItem -({ - .label = "Remove Unused", - .tooltip = "Remove all unused spritesheets in the anm2 (i.e., no layer in any animation uses the spritesheet).", - .isSizeToChild = true, - .childRowItemCount = IMGUI_SPRITESHEETS_OPTIONS_FIRST_ROW_ITEM_COUNT -}); - -const inline ImguiItem IMGUI_SPRITESHEETS_SELECT_ALL = ImguiItem -({ - .label = "Select All", - .tooltip = "Select all spritesheets.", - .isSizeToChild = true, - .childRowItemCount = IMGUI_SPRITESHEETS_OPTIONS_SECOND_ROW_ITEM_COUNT -}); - -const inline ImguiItem IMGUI_SPRITESHEETS_SELECT_NONE = ImguiItem -({ - .label = "Select None", - .tooltip = "Unselect all spritesheets.", - .isSizeToChild = true, - .childRowItemCount = IMGUI_SPRITESHEETS_OPTIONS_SECOND_ROW_ITEM_COUNT -}); - -const inline ImguiItem IMGUI_SPRITESHEETS_SAVE = ImguiItem -({ - .label = "Save", - .tooltip = "Save the selected spritesheet(s) to their original path.", - .isSizeToChild = true, - .childRowItemCount = IMGUI_SPRITESHEETS_OPTIONS_SECOND_ROW_ITEM_COUNT -}); - -const inline ImguiItem IMGUI_ANIMATION_PREVIEW = ImguiItem -({ - .label = "Animation Preview", - .flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse -}); - -const inline ImguiItem IMGUI_ANIMATION_PREVIEW_GRID_SETTINGS = ImguiItem -({ - .label = "## Grid Settings", - .size = IMGUI_CANVAS_CHILD_SIZE, - .flags = true -}); - -const inline ImguiItem IMGUI_ANIMATION_PREVIEW_GRID = ImguiItem -({ - .label = "Grid", - .tooltip = "Toggles the visiblity of a grid over the animation preview." -}); - -const inline ImguiItem IMGUI_ANIMATION_PREVIEW_GRID_COLOR = ImguiItem -({ - .label = "Color", - .tooltip = "Change the color of the animation preview grid.", - .flags = ImGuiColorEditFlags_NoInputs -}); - -const inline ImguiItem IMGUI_ANIMATION_PREVIEW_GRID_SIZE = ImguiItem -({ - .label = "Size", - .tooltip = "Change the size of the animation preview grid, in pixels.", - .min = CANVAS_GRID_MIN, - .max = CANVAS_GRID_MAX -}); - -const inline ImguiItem IMGUI_ANIMATION_PREVIEW_GRID_OFFSET = ImguiItem -({ - .label = "Offset", - .tooltip = "Change the offset of the animation preview grid, in pixels." -}); - -const inline ImguiItem IMGUI_ANIMATION_PREVIEW_VIEW_SETTINGS = ImguiItem -({ - .label = "## View Settings", - .size = IMGUI_CANVAS_CHILD_SIZE, - .flags = true -}); - -const inline ImguiItem IMGUI_ANIMATION_PREVIEW_ZOOM = ImguiItem -({ - .label = "Zoom", - .tooltip = "Change the animation preview's zoom.", - .format = "%.0f", - .min = CANVAS_ZOOM_MIN, - .max = CANVAS_ZOOM_MAX -}); - -const inline ImguiItem IMGUI_ANIMATION_PREVIEW_CENTER_VIEW = ImguiItem -({ - .label = "Center View", - .tooltip = "Centers the current view.", - .size = {-FLT_MIN, 0} -}); - -const inline ImguiItem IMGUI_ANIMATION_PREVIEW_BACKGROUND_SETTINGS = ImguiItem -({ - .label = "## Background Settings", - .size = IMGUI_CANVAS_CHILD_SIZE, - .flags = true -}); - -const inline ImguiItem IMGUI_ANIMATION_PREVIEW_BACKGROUND_COLOR = ImguiItem -({ - .label = "Background Color", - .tooltip = "Change the background color of the animation preview.", - .flags = ImGuiColorEditFlags_NoInputs -}); - -const inline ImguiItem IMGUI_ANIMATION_PREVIEW_OVERLAY = ImguiItem -({ - .label = "Overlay", - .tooltip = "Choose an animation to overlay over the previewed animation, for reference." -}); - -const inline ImguiItem IMGUI_ANIMATION_PREVIEW_OVERLAY_TRANSPARENCY = ImguiItem -({ - .label = "Alpha", - .tooltip = "Set the transparency of the animation overlay.", - .format = "%.0f", - .isSizeToChild = true, - .min = 0, - .max = 255 -}); - -const inline ImguiItem IMGUI_ANIMATION_PREVIEW_HELPER_SETTINGS = ImguiItem -({ - .label = "## Helper Settings", - .size = IMGUI_CANVAS_CHILD_SIZE, - .flags = true -}); - -const inline ImguiItem IMGUI_ANIMATION_PREVIEW_AXIS = ImguiItem -({ - .label = "Axis", - .tooltip = "Toggle the display of the X/Y axes on the animation preview." -}); - -const inline ImguiItem IMGUI_ANIMATION_PREVIEW_AXIS_COLOR = ImguiItem -({ - .label = "Color", - .tooltip = "Change the color of the animation preview's axes.", - .flags = ImGuiColorEditFlags_NoInputs -}); - -const inline ImguiItem IMGUI_ANIMATION_PREVIEW_ROOT_TRANSFORM = ImguiItem -({ - .label = "Root Transform", - .tooltip = "Toggles the root frames's attributes transforming the other items in an animation." -}); - -const inline ImguiItem IMGUI_ANIMATION_PREVIEW_PIVOTS = ImguiItem -({ - .label = "Pivots", - .tooltip = "Toggles drawing each layer's pivot." -}); - -const inline ImguiItem IMGUI_ANIMATION_PREVIEW_TARGETS = ImguiItem -({ - .label = "Targets", - .tooltip = "Toggles drawing the targets (i.e., the colored root/null icons)." -}); - -const inline ImguiItem IMGUI_ANIMATION_PREVIEW_BORDER = ImguiItem -({ - .label = "Border", - .tooltip = "Toggles the appearance of a border around each layer." -}); - -const inline ImguiItem IMGUI_SPRITESHEET_EDITOR = ImguiItem -({ - .label = "Spritesheet Editor", - .flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse -}); - -const inline ImguiItem IMGUI_SPRITESHEET_EDITOR_GRID_SETTINGS = ImguiItem -({ - .label = "## Grid Settings", - .size = IMGUI_CANVAS_CHILD_SIZE, - .flags = true -}); - -const inline ImguiItem IMGUI_SPRITESHEET_EDITOR_GRID = ImguiItem -({ - .label = "Grid", - .tooltip = "Toggles the visiblity of a grid over the spritesheet editor." -}); - -const inline ImguiItem IMGUI_SPRITESHEET_EDITOR_GRID_SNAP = ImguiItem -({ - .label = "Snap", - .tooltip = "Using the crop tool will snap the points to the nearest grid point." -}); - -const inline ImguiItem IMGUI_SPRITESHEET_EDITOR_GRID_COLOR = ImguiItem -({ - .label = "Color", - .tooltip = "Change the color of the spritesheet editor grid.", - .flags = ImGuiColorEditFlags_NoInputs -}); - -const inline ImguiItem IMGUI_SPRITESHEET_EDITOR_GRID_SIZE = ImguiItem -({ - .label = "Size", - .tooltip = "Change the size of the spritesheet editor grid, in pixels.", - .min = CANVAS_GRID_MIN, - .max = CANVAS_GRID_MAX -}); - -const inline ImguiItem IMGUI_SPRITESHEET_EDITOR_GRID_OFFSET = ImguiItem -({ - .label = "Offset", - .tooltip = "Change the offset of the spritesheet editor grid, in pixels." -}); - -const inline ImguiItem IMGUI_SPRITESHEET_EDITOR_VIEW_SETTINGS = ImguiItem -({ - .label = "## View Settings", - .size = IMGUI_CANVAS_CHILD_SIZE, - .flags = true -}); - -const inline ImguiItem IMGUI_SPRITESHEET_EDITOR_ZOOM = ImguiItem -({ - .label = "Zoom", - .tooltip = "Change the spritesheet editor's zoom.", - .format = "%.0f", - .min = CANVAS_ZOOM_MIN, - .max = CANVAS_ZOOM_MAX -}); - -const inline ImguiItem IMGUI_SPRITESHEET_EDITOR_CENTER_VIEW = ImguiItem -({ - .label = "Center View", - .tooltip = "Centers the current view.", - .size = {-FLT_MIN, 0}, -}); - -const inline ImguiItem IMGUI_SPRITESHEET_EDITOR_BACKGROUND_SETTINGS = ImguiItem -({ - .label = "## Background Settings", - .size = IMGUI_CANVAS_CHILD_SIZE, - .flags = true -}); - -const inline ImguiItem IMGUI_SPRITESHEET_EDITOR_BACKGROUND_COLOR = ImguiItem -({ - .label = "Background Color", - .tooltip = "Change the background color of the spritesheet editor.", - .flags = ImGuiColorEditFlags_NoInputs -}); - -const inline ImguiItem IMGUI_SPRITESHEET_EDITOR_BORDER = ImguiItem -({ - .label = "Border", - .tooltip = "Toggle a border appearing around the selected spritesheet." -}); - -const inline ImguiItem IMGUI_FRAME_PROPERTIES = ImguiItem({"Frame Properties"}); - -const inline ImguiItem IMGUI_FRAME_PROPERTIES_POSITION = ImguiItem -({ - .label = "Position", - .tooltip = "Change the position of the selected frame.", - .action = "Frame Position", - .format = "%.0f", - .isUndoable = true, - .speed = 0.25f, - .min = 0, - .max = 0 -}); - -const inline ImguiItem IMGUI_FRAME_PROPERTIES_CROP = ImguiItem -({ - .label = "Crop", - .tooltip = "Change the crop position of the selected frame.", - .action = "Frame Crop", - .format = "%.0f", - .isUndoable = true, - .speed = 0.25f, - .min = 0, - .max = 0 -}); - -const inline ImguiItem IMGUI_FRAME_PROPERTIES_SIZE = ImguiItem -({ - .label = "Size", - .tooltip = "Change the size of the crop of the selected frame.", - .action = "Frame Size", - .format = "%.0f", - .isUndoable = true, - .speed = 0.25f, - .min = 0, - .max = 0 -}); - -const inline ImguiItem IMGUI_FRAME_PROPERTIES_PIVOT = ImguiItem -({ - .label = "Pivot", - .tooltip = "Change the pivot of the selected frame.", - .action = "Frame Pivot", - .format = "%.0f", - .isUndoable = true, - .speed = 0.25f, - .min = 0, - .max = 0 -}); - -const inline ImguiItem IMGUI_FRAME_PROPERTIES_SCALE = ImguiItem -({ - .label = "Scale", - .tooltip = "Change the scale of the selected frame.", - .action = "Frame Scale", - .isUndoable = true, - .speed = 0.25f, - .min = 0, - .max = 0 -}); - -const inline ImguiItem IMGUI_FRAME_PROPERTIES_ROTATION = ImguiItem -({ - .label = "Rotation", - .tooltip = "Change the rotation of the selected frame.", - .action = "Frame Rotation", - .isUndoable = true, - .speed = 0.25f, - .min = 0, - .max = 0 -}); - -const inline ImguiItem IMGUI_FRAME_PROPERTIES_DURATION = ImguiItem -({ - .label = "Duration", - .tooltip = "Change the duration of the selected frame.", - .action = "Frame Duration", - .isUndoable = true, - .min = ANM2_FRAME_NUM_MIN, - .max = ANM2_FRAME_NUM_MAX -}); - -const inline ImguiItem IMGUI_FRAME_PROPERTIES_TINT = ImguiItem -({ - .label = "Tint", - .tooltip = "Change the tint of the selected frame.", - .action = "Frame Tint", - .isUndoable = true, -}); - -const inline ImguiItem IMGUI_FRAME_PROPERTIES_COLOR_OFFSET = ImguiItem -({ - .label = "Color Offset", - .tooltip = "Change the color offset of the selected frame.", - .action = "Frame Color Offset", - .isUndoable = true, -}); - -const inline ImguiItem IMGUI_FRAME_PROPERTIES_VISIBLE = ImguiItem -({ - .label = "Visible", - .tooltip = "Toggles the visibility of the selected frame.", - .action = "Frame Visibility", - .isUndoable = true, -}); - -const inline ImguiItem IMGUI_FRAME_PROPERTIES_INTERPOLATED = ImguiItem -({ - .label = "Interpolation", - .tooltip = "Toggles the interpolation of the selected frame.", - .action = "Frame Interpolation", - .isUndoable = true, -}); - -const inline ImguiItem IMGUI_FRAME_PROPERTIES_FLIP_X = ImguiItem -({ - .label = "Flip X", - .tooltip = "Change the sign of the X scale, to cheat flipping the layer horizontally.\n(Anm2 doesn't support flipping directly)", - .action = "Frame Flip X", - .isUndoable = true, - .size = IMGUI_FRAME_PROPERTIES_FLIP_BUTTON_SIZE -}); - -const inline ImguiItem IMGUI_FRAME_PROPERTIES_FLIP_Y = ImguiItem -({ - .label = "Flip Y", - .tooltip = "Change the sign of the Y scale, to cheat flipping the layer vertically.\n(Anm2 doesn't support flipping directly)", - .action = "Frame Flip Y", - .isUndoable = true, - .size = IMGUI_FRAME_PROPERTIES_FLIP_BUTTON_SIZE -}); - -const inline ImguiItem IMGUI_FRAME_PROPERTIES_EVENT = ImguiItem -({ - .label = "Event", - .tooltip = "Change the event the trigger uses.\n", - .action = "Trigger Event", - .isUndoable = true -}); - -const inline ImguiItem IMGUI_FRAME_PROPERTIES_AT_FRAME = ImguiItem -({ - .label = "At Frame", - .tooltip = "Change the frame where the trigger occurs.", - .action = "Trigger At Frame", - .isUndoable = true -}); - -const inline ImguiItem IMGUI_TOOLS = ImguiItem({"Tools"}); - -const inline ImguiItem IMGUI_TOOL_PAN = ImguiItem -({ - .label = "## Pan", - .tooltip = "Use the pan tool.\nWill shift the view as the cursor is dragged.\nYou can also use the middle mouse button to pan at any time.", - .function = imgui_tool_pan_set, - .chord = ImGuiKey_P, - .atlas = ATLAS_PAN, -}); - -const inline ImguiItem IMGUI_TOOL_MOVE = ImguiItem -({ - .label = "## Move", - .tooltip = "Use the move tool.\nWill move the selected item as the cursor is dragged, or directional keys are pressed.\n(Animation Preview only.)", - .function = imgui_tool_move_set, - .chord = ImGuiKey_M, - .atlas = ATLAS_MOVE, -}); - -const inline ImguiItem IMGUI_TOOL_ROTATE = ImguiItem -({ - .label = "## Rotate", - .tooltip = "Use the rotate tool.\nWill rotate the selected item as the cursor is dragged, or directional keys are pressed.\n(Animation Preview only.)", - .function = imgui_tool_rotate_set, - .chord = ImGuiKey_R, - .atlas = ATLAS_ROTATE, -}); - -const inline ImguiItem IMGUI_TOOL_SCALE = ImguiItem -({ - .label = "## Scale", - .tooltip = "Use the scale tool.\nWill scale the selected item as the cursor is dragged, or directional keys are pressed.\n(Animation Preview only.)", - .function = imgui_tool_scale_set, - .chord = ImGuiKey_S, - .atlas = ATLAS_SCALE, -}); - -const inline ImguiItem IMGUI_TOOL_CROP = ImguiItem -({ - .label = "## Crop", - .tooltip = "Use the crop tool.\nWill produce a crop rectangle based on how the cursor is dragged.\n(Spritesheet Editor only.)", - .function = imgui_tool_crop_set, - .chord = ImGuiKey_C, - .atlas = ATLAS_CROP, -}); - -const inline ImguiItem IMGUI_TOOL_DRAW = ImguiItem -({ - .label = "## Draw", - .tooltip = "Draws pixels onto the selected spritesheet, with the current color.\n(Spritesheet Editor only.)", - .function = imgui_tool_draw_set, - .chord = ImGuiKey_B, - .atlas = ATLAS_DRAW, -}); - -const inline ImguiItem IMGUI_TOOL_ERASE = ImguiItem -({ - .label = "## Erase", - .tooltip = "Erases pixels from the selected spritesheet.\n(Spritesheet Editor only.)", - .function = imgui_tool_erase_set, - .chord = ImGuiKey_E, - .atlas = ATLAS_ERASE, -}); - -const inline ImguiItem IMGUI_TOOL_COLOR_PICKER = ImguiItem -({ - .label = "## Color Picker", - .tooltip = "Selects a color from anywhere on the screen, to be used for drawing.", - .function = imgui_tool_color_picker_set, - .chord = ImGuiKey_W, - .atlas = ATLAS_COLOR_PICKER, -}); - -const inline ImguiItem IMGUI_TOOL_UNDO = ImguiItem -({ - .label = "## Undo", - .tooltip = "Undoes the last action.", - .function = imgui_undo, - .chord = ImGuiKey_Z, - .atlas = ATLAS_UNDO -}); - -const inline ImguiItem IMGUI_TOOL_REDO = ImguiItem -({ - .label = "## Redo", - .tooltip = "Redoes the last action.", - .function = imgui_redo, - .chord = ImGuiMod_Shift + ImGuiKey_Z, - .atlas = ATLAS_REDO -}); - -const inline ImguiItem IMGUI_TOOL_COLOR = ImguiItem -({ - .label = "## Color", - .tooltip = "Set the color, to be used by the draw tool.", - .flags = ImGuiColorEditFlags_NoInputs -}); - -const inline ImguiItem IMGUI_TOOL_COLOR_PICKER_TOOLTIP_COLOR = ImguiItem -({ - .label = "## Color Picker Tooltip Color", - .flags = ImGuiColorEditFlags_NoTooltip -}); +static inline ImGuiKey imgui_key_from_char_get(char c) +{ + if (c >= 'a' && c <= 'z') c -= 'a' - 'A'; + if (c >= 'A' && c <= 'Z') return (ImGuiKey)(ImGuiKey_A + (c - 'A')); + return ImGuiKey_None; +} + +static inline std::string imgui_string_from_chord_get(ImGuiKeyChord chord) +{ + std::string result; + + if (chord & ImGuiMod_Ctrl) result += "Ctrl+"; + if (chord & ImGuiMod_Shift) result += "Shift+"; + if (chord & ImGuiMod_Alt) result += "Alt+"; + + ImGuiKey key = (ImGuiKey)(chord & ~ImGuiMod_Mask_); + + if (key >= ImGuiKey_A && key <= ImGuiKey_Z) + result.push_back('A' + (key - ImGuiKey_A)); + else if (key >= ImGuiKey_0 && key <= ImGuiKey_9) + result.push_back('0' + (key - ImGuiKey_0)); + else + { + // Fallback to ImGui's built-in name for non-alphanumerics + const char* name = ImGui::GetKeyName(key); + if (name && *name) + result += name; + else + result += "Unknown"; + } + + return result; +} + +static void imgui_contextual_actions_enable(Imgui* self) { self->isContextualActionsEnabled = true; } +static void imgui_contextual_actions_disable(Imgui* self){ self->isContextualActionsEnabled = false; } +static inline bool imgui_is_popup_open(const std::string& label) { return ImGui::IsPopupOpen(label.c_str()); } +static inline void imgui_open_popup(const std::string& label) { ImGui::OpenPopup(label.c_str()); } +static inline void imgui_pending_popup_process(Imgui* self) +{ + if (self->pendingPopup.empty()) return; + + switch (self->pendingPopupType) + { + case IMGUI_POPUP_CENTER_WINDOW: + ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); + break; + case IMGUI_POPUP_BY_ITEM: + default: + ImGui::SetNextWindowPos(self->pendingPopupPosition); + break; + } + + imgui_open_popup(self->pendingPopup.c_str()); + + self->pendingPopup.clear(); + self->pendingPopupType = IMGUI_POPUP_NONE; + self->pendingPopupPosition = ImVec2(); +} + +static inline bool imgui_begin_popup(const std::string& label, Imgui* imgui, ImVec2 size = ImVec2()) +{ + imgui_pending_popup_process(imgui); + if (size != ImVec2()) ImGui::SetNextWindowSizeConstraints(size, ImVec2(FLT_MAX, FLT_MAX)); + bool isActivated = ImGui::BeginPopup(label.c_str()); + return isActivated; +} + +static inline bool imgui_begin_popup_modal(const std::string& label, Imgui* imgui, ImVec2 size = ImVec2()) +{ + 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); + if (isActivated) imgui_contextual_actions_disable(imgui); + return isActivated; +} + +static inline void imgui_close_current_popup(Imgui* imgui) +{ + imgui_contextual_actions_enable(imgui); + ImGui::CloseCurrentPopup(); +} + +static inline void imgui_end_popup(Imgui* imgui) +{ + ImGui::EndPopup(); + imgui_pending_popup_process(imgui); +} + +enum ImguiItemType +{ + IMGUI_ITEM, + IMGUI_TEXT, + IMGUI_WINDOW, + IMGUI_DOCKSPACE, + IMGUI_CHILD, + IMGUI_OPTION_POPUP, + IMGUI_SELECTABLE, + IMGUI_BUTTON, + IMGUI_RADIO_BUTTON, + IMGUI_COLOR_BUTTON, + IMGUI_CHECKBOX, + IMGUI_INPUT_INT, + IMGUI_INPUT_TEXT, + IMGUI_DRAG_FLOAT, + IMGUI_COLOR_EDIT, + IMGUI_COMBO, + IMGUI_ATLAS_BUTTON +}; + +struct ImguiItemOverride +{ + bool isDisabled{}; + bool isSelected{}; + std::string label{}; + ImVec2 size{}; + s32 id{}; + s32 max{}; + AtlasType atlas = ATLAS_NONE; + bool isMnemonicDisabled{}; + s32 value{}; +}; + +// Item +struct ImguiItem +{ + std::string label{}; + std::string tooltip{}; + std::string format{}; + std::string& text = tooltip; + std::string undoAction{}; + std::string popup{}; + std::string dragDrop{}; + std::string focusWindow{}; + std::vector items{}; + AtlasType atlas; + ImGuiKeyChord chord = IMGUI_CHORD_NONE; + ImGuiKey mnemonicKey = ImGuiKey_None; + s32 mnemonicIndex = INDEX_NONE; + ImVec2 size{}; + ImVec2 popupSize{}; + ImguiColorSet color{}; + ImguiFunction function = nullptr; + ImguiPopupType popupType = IMGUI_POPUP_CENTER_WINDOW; + bool isDisabled = false; + bool isMnemonicDisabled = false; + bool isSelected = false; + bool isUseItemActivated = false; + bool isSizeToText = false; + bool isShortcutInLabel = false; + bool isSameLine = false; + bool isSeparator = false; + s32 id = 0; + s32 idOffset{}; + f32 speed = 0.25f; + s32 step = 1; + s32 stepFast = 10; + s32 border{}; + s32 max{}; + s32 min{}; + s32 value{}; + s32 flags{}; + s32 windowFlags{}; + s32 rowCount = 0; + vec2 atlasOffset; + + void construct() + { + static s32 idNew = 0; + id = idNew++; + + if (is_chord()) + { + std::string chordString = imgui_string_from_chord_get(chord); + if (isShortcutInLabel) + label += std::format(IMGUI_LABEL_SHORTCUT_FORMAT, chordString); + tooltip += std::format(IMGUI_TOOLTIP_SHORTCUT_FORMAT, chordString); + if (function) + imgui_hotkey_registry().push_back({chord, function, focusWindow}); + } + + std::string labelNew{}; + + for (s32 i = 0; i < (s32)label.size(); i++) + { + if (label[i] == '&') + { + if (label[i + 1] == '&') + { + labelNew += '&'; + i++; + } + else if (label[i + 1] != '\0') + { + mnemonicKey = imgui_key_from_char_get(label[i + 1]); + mnemonicIndex = (s32)labelNew.size(); + labelNew += label[i + 1]; + i++; + } + } + else + labelNew += label[i]; + } + + label = labelNew; + } + + ImguiItem copy(const ImguiItemOverride& override) const + { + ImguiItem out = *this; + if (override.isDisabled) + { + out.isDisabled = override.isDisabled; + out.format.clear(); + } + if(override.isSelected) out.isSelected = override.isSelected; + if (is_popup() && imgui_is_popup_open(popup)) out.isSelected = true; + if (id != ID_NONE) out.id = id + idOffset + override.id; + if (!override.label.empty()) out.label = override.label; + if (override.size != ImVec2{}) out.size = override.size; + if (override.max != 0) out.max = override.max; + if (override.value != 0) out.value = override.value; + if (override.atlas != ATLAS_NONE) out.atlas = override.atlas; + if (override.isMnemonicDisabled) out.isMnemonicDisabled = override.isMnemonicDisabled; + return out; + } + + bool is_border() const { return border != 0; } + bool is_row() const { return rowCount != 0; } + bool is_chord() const { return chord != IMGUI_CHORD_NONE; } + bool is_drag_drop() const { return !dragDrop.empty(); } + bool is_focus_window() const { return !focusWindow.empty(); } + bool is_popup() const { return !popup.empty(); } + bool is_size() const { return size != ImVec2(); } + bool is_popup_size() const { return popupSize != ImVec2(); } + bool is_tooltip() const { return !tooltip.empty(); } + bool is_undoable() const { return !undoAction.empty(); } + bool is_mnemonic() const { return mnemonicKey != ImGuiKey_None; } + bool is_range() const { return min != 0 || max != 0; } + const char* label_get() const { return label.c_str(); } + const char* drag_drop_get() const { return dragDrop.c_str(); } + const char* tooltip_get() const { return tooltip.c_str(); } + const char* text_get() const { return text.c_str(); } + const char* format_get() const { return format.c_str(); } +}; + +#define IMGUI_ITEM(NAME, ...) const inline ImguiItem NAME = []{ ImguiItem self; __VA_ARGS__; self.construct(); return self; }() + +IMGUI_ITEM(IMGUI_WINDOW_MAIN, + self.label = "## Window", + self.flags = ImGuiWindowFlags_NoTitleBar | + ImGuiWindowFlags_NoCollapse | + ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoBringToFrontOnFocus | + ImGuiWindowFlags_NoNavFocus +); + +IMGUI_ITEM(IMGUI_DOCKSPACE_MAIN, + self.label = "## Dockspace", + self.flags = ImGuiDockNodeFlags_PassthruCentralNode +); + +IMGUI_ITEM(IMGUI_FOOTER_CHILD, + self.label = "## Footer Child", + self.size = {0, 36}, + self.flags = true +); + +IMGUI_ITEM(IMGUI_TASKBAR, + self.label = "## Taskbar", + self.size = {0, 32}, + self.flags = ImGuiWindowFlags_NoTitleBar | + ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoScrollbar | + ImGuiWindowFlags_NoScrollWithMouse | + ImGuiWindowFlags_NoSavedSettings +); + +IMGUI_ITEM(IMGUI_FILE, + self.label = "&File", + self.tooltip = "Opens the file menu, for reading/writing anm2 files.", + self.popup = "## File Popup", + self.popupType = IMGUI_POPUP_BY_ITEM, + self.isSizeToText = true, + self.isSameLine = true +); + +IMGUI_ITEM(IMGUI_NEW, + self.label = "&New ", + self.tooltip = "Load a blank .anm2 file to edit.", + self.function = imgui_file_new, + self.chord = ImGuiMod_Ctrl | ImGuiKey_N, + self.isSizeToText = true, + self.isShortcutInLabel = true +); + +IMGUI_ITEM(IMGUI_OPEN, + self.label = "&Open ", + self.tooltip = "Open an existing .anm2 file to edit.", + self.function = imgui_file_open, + self.chord = ImGuiMod_Ctrl | ImGuiKey_O, + self.isSizeToText = true, + self.isShortcutInLabel = true +); + +IMGUI_ITEM(IMGUI_SAVE, + self.label = "&Save ", + self.tooltip = "Saves the current .anm2 file to its path.\nIf no path exists, one can be chosen.", + self.function = imgui_file_save, + self.chord = ImGuiMod_Ctrl | ImGuiKey_S, + self.isSizeToText = true, + self.isShortcutInLabel = true +); + +IMGUI_ITEM(IMGUI_SAVE_AS, + self.label = "S&ave As ", + self.tooltip = "Saves the current .anm2 file to a chosen path.", + self.function = imgui_file_save_as, + self.chord = ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiKey_S, + self.isSizeToText = true, + self.isShortcutInLabel = true +); + +IMGUI_ITEM(IMGUI_WIZARD, + self.label = "&Wizard", + self.tooltip = "Opens the wizard menu, for neat functions related to the .anm2.", + self.popup = "## Wizard Popup", + self.popupType = IMGUI_POPUP_BY_ITEM, + self.isSizeToText = true, + self.isSameLine = true +); + +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} +); + +IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_OPTIONS_CHILD, + self.label = "## Generate Animation From Grid Options Child", + self.size = + { + IMGUI_GENERATE_ANIMATION_FROM_GRID.popupSize.x / 2, + IMGUI_GENERATE_ANIMATION_FROM_GRID.popupSize.y - IMGUI_FOOTER_CHILD.size.y + }, + self.flags = true +); + +IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_START_POSITION, + self.label = "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", + self.tooltip = "Set the size of each frame in the generated animation." +); + +IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_PIVOT, + self.label = "Pivot", + self.tooltip = "Set the pivot of each frame in the generated animation." +); + +IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_ROWS, + self.label = "Rows", + self.tooltip = "Set how many rows will be used in the generated animation.", + self.max = 1000 +); + +IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_COLUMNS, + self.label = "Columns", + self.tooltip = "Set how many columns will be used in the generated animation.", + self.max = 1000 +); + +IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_FRAME_COUNT, + self.label = "Count", + self.tooltip = "Set how many frames will be made for the generated animation." +); + +IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_DELAY, + self.label = "Delay", + self.tooltip = "Set the delay of each frame in the generated animation.", + self.max = 1000 +); + +IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_PREVIEW_CHILD, + self.label = "## Generate Animation From Grid Preview Child", + self.size = + { + IMGUI_GENERATE_ANIMATION_FROM_GRID.popupSize.x / 2, + IMGUI_GENERATE_ANIMATION_FROM_GRID.popupSize.y - IMGUI_FOOTER_CHILD.size.y + }, + self.flags = true +); + +IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_GENERATE, + self.label = "Generate", + self.tooltip = "Generate an animation with the used settings.", + self.rowCount = IMGUI_OPTION_POPUP_ROW_COUNT, + self.isSameLine = true +); + +IMGUI_ITEM(IMGUI_CHANGE_ALL_FRAME_PROPERTIES, + self.label = "&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 +); + +IMGUI_ITEM(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_CHILD, + self.label = "## Change All Frame Properties Child", + self.size = + { + IMGUI_CHANGE_ALL_FRAME_PROPERTIES.popupSize.x, + 250 + }, + self.flags = true +); + +IMGUI_ITEM(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_SETTINGS_CHILD, + self.label = "## Change All Frame Properties Settings Child", + self.size = + { + IMGUI_CHANGE_ALL_FRAME_PROPERTIES.popupSize.x, + 55 + }, + self.flags = true +); + +IMGUI_ITEM(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_SETTINGS, self.label = "Settings"); + +IMGUI_ITEM(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_FROM_SELECTED_FRAME, + self.label = "From Selected Frames", + self.tooltip = "The set frame properties will start from the selected frame.", + self.isSameLine = true +); + +IMGUI_ITEM(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_NUMBER_FRAMES, + self.label = "# of Frames", + self.tooltip = "Set the amount of frames that the set frame properties will apply to.", + self.size = {200, 0}, + self.value = ANM2_FRAME_NUM_MIN, + self.max = 1000 +); + +#define IMGUI_CHANGE_ALL_FRAME_PROPERTIES_OPTIONS_ROW_COUNT 4 +IMGUI_ITEM(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_ADD, + self.label = "Add", + self.tooltip = "The specified values will be added to all specified frames.", + self.undoAction = "Add Frame Properties", + self.rowCount = IMGUI_CHANGE_ALL_FRAME_PROPERTIES_OPTIONS_ROW_COUNT, + self.isSameLine = true +); + +IMGUI_ITEM(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_SUBTRACT, + self.label = "Subtract", + self.tooltip = "The specified values will be added to all selected frames.", + self.undoAction = "Subtract Frame Properties", + self.rowCount = IMGUI_CHANGE_ALL_FRAME_PROPERTIES_OPTIONS_ROW_COUNT, + self.isSameLine = true +); + +IMGUI_ITEM(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_SET, + self.label = "Set", + self.tooltip = "The specified values will be set to the specified value in selected frames.", + self.undoAction = "Set Frame Properties", + self.rowCount = IMGUI_CHANGE_ALL_FRAME_PROPERTIES_OPTIONS_ROW_COUNT, + self.isSameLine = true +); + +IMGUI_ITEM(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_CANCEL, + self.label = "Cancel", + self.tooltip = "Cancel changing all frame properties.", + self.rowCount = IMGUI_CHANGE_ALL_FRAME_PROPERTIES_OPTIONS_ROW_COUNT +); + +IMGUI_ITEM(IMGUI_RENDER_ANIMATION, + self.label = "&Render Animation", + self.tooltip = "Renders the current animation preview; output options can be customized.", + self.popup = "Render Animation", + self.popupSize = {600, 125} +); + +IMGUI_ITEM(IMGUI_RENDER_ANIMATION_CHILD, + self.label = "## Render Animation Child", + self.size = {600, 125} +); + +IMGUI_ITEM(IMGUI_RENDER_ANIMATION_LOCATION_BROWSE, + self.label = "## Location Browse", + self.tooltip = "Open file explorer to pick rendered animation location.", + self.atlas = ATLAS_FOLDER, + self.isSameLine = true +); + +IMGUI_ITEM(IMGUI_RENDER_ANIMATION_LOCATION, + self.label = "Location", + self.tooltip = "Select the location of the rendered animation.", + self.max = 255 +); + +IMGUI_ITEM(IMGUI_RENDER_ANIMATION_FFMPEG_BROWSE, + self.label = "## FFMpeg Browse", + self.tooltip = "Open file explorer to pick the path of FFmpeg", + self.atlas = ATLAS_FOLDER, + self.isSameLine = true +); + +IMGUI_ITEM(IMGUI_RENDER_ANIMATION_FFMPEG_PATH, + self.label = "FFmpeg Path", + self.tooltip = "Sets the path FFmpeg currently resides in.\nFFmpeg is required for rendering animations.\nDownload it from https://ffmpeg.org/, your package manager, or wherever else.", + self.max = 255 +); + +IMGUI_ITEM(IMGUI_RENDER_ANIMATION_OUTPUT, + self.label = "Output", + self.tooltip = "Select the rendered animation output.\nIt can either be one animated image or a sequence of frames.", + self.items = {std::begin(RENDER_TYPE_STRINGS), std::end(RENDER_TYPE_STRINGS)}, + self.value = RENDER_PNG, + self.isSeparator = true +); + +IMGUI_ITEM(IMGUI_RENDER_ANIMATION_FORMAT, + self.label = "Format", + self.tooltip = "(PNG images only).\nSet the format of each output frame; i.e., its filename.\nThe format will only take one argument; that being the frame's index.\nFor example, a format like \"{}.png\" will export a frame of index 0 as \"0.png\".", + self.max = 255 +); + +IMGUI_ITEM(IMGUI_RENDER_ANIMATION_CONFIRM, + self.label = "Render", + self.tooltip = "Render the animation, with the used settings.", + self.popup = "Rendering Animation...", + self.popupType = IMGUI_POPUP_CENTER_WINDOW, + self.popupSize = {300, 60}, + self.isSameLine = true, + self.rowCount = IMGUI_OPTION_POPUP_ROW_COUNT +); + +IMGUI_ITEM(IMGUI_RENDERING_ANIMATION_CANCEL, + self.label = "Cancel", + self.tooltip = "Cancel rendering the animation.", + self.rowCount = 1 +); + +IMGUI_ITEM(IMGUI_PLAYBACK, + self.label = "&Playback", + self.tooltip = "Opens the playback menu, for configuring playback settings.", + self.popup = "## Playback Popup", + self.popupType = IMGUI_POPUP_BY_ITEM, + self.popupSize = {150, 0}, + self.isSizeToText = true +); + +IMGUI_ITEM(IMGUI_ALWAYS_LOOP, + self.label = "&Always Loop", + self.tooltip = "Sets the animation playback to always loop, regardless of the animation's loop setting.", + self.isSizeToText = true +); + +IMGUI_ITEM(IMGUI_CLAMP_PLAYHEAD, + self.label = "&Clamp Playhead", + self.tooltip = "The playhead (draggable icon on timeline) won't be able to exceed the animation length.", + self.isSizeToText = true +); + +IMGUI_ITEM(IMGUI_ANIMATIONS, + self.label = "Animations", + self.flags = ImGuiWindowFlags_NoScrollbar | + ImGuiWindowFlags_NoScrollWithMouse +); +IMGUI_ITEM(IMGUI_ANIMATIONS_CHILD, self.label = "## Animations Child", self.flags = true); + +IMGUI_ITEM(IMGUI_ANIMATION, + self.label = "## Animation Item", + self.undoAction = "Select Animation", + self.dragDrop = "## Animation Drag Drop", + self.atlas = ATLAS_ANIMATION, + self.idOffset = 2000 +); + +#define IMGUI_ANIMATIONS_OPTIONS_ROW_COUNT 5 +IMGUI_ITEM(IMGUI_ANIMATION_ADD, + self.label = "Add", + self.tooltip = "Adds a new animation.", + self.undoAction = "Add Animation", + self.rowCount = IMGUI_ANIMATIONS_OPTIONS_ROW_COUNT, + self.isSameLine = true +); + +IMGUI_ITEM(IMGUI_ANIMATION_DUPLICATE, + self.label = "Duplicate", + self.tooltip = "Duplicates the selected animation, placing it after.", + self.undoAction = "Duplicate Animation", + self.rowCount = IMGUI_ANIMATIONS_OPTIONS_ROW_COUNT, + self.isSameLine = true +); + +IMGUI_ITEM(IMGUI_ANIMATION_MERGE, + self.label = "Merge", + self.tooltip = "Open the animation merge popup, to merge animations together.", + self.popup = "Merge Animations", + self.popupSize = {300, 400}, + self.rowCount = IMGUI_ANIMATIONS_OPTIONS_ROW_COUNT, + self.isSameLine = true +); + +IMGUI_ITEM(IMGUI_MERGE_ANIMATIONS_CHILD, + self.label = "## Merge Animations", + self.size = {IMGUI_ANIMATION_MERGE.popupSize.x, 250}, + self.flags = true +); + +IMGUI_ITEM(IMGUI_MERGE_ON_CONFLICT_CHILD, + self.label = "## Merge On Conflict Child", + self.size = {IMGUI_ANIMATION_MERGE.popupSize.x, 75}, + self.flags = true +); + +IMGUI_ITEM(IMGUI_MERGE_ON_CONFLICT, self.label = "On Conflict"); + +IMGUI_ITEM(IMGUI_MERGE_APPEND_FRAMES, + self.label = "Append Frames ", + self.tooltip = "On frame conflict, the merged animation will have the selected animations' frames appended.", + self.value = ANM2_MERGE_APPEND_FRAMES, + self.isSameLine = true +); + +IMGUI_ITEM(IMGUI_MERGE_REPLACE_FRAMES, + self.label = "Replace Frames", + self.tooltip = "On frame conflict, the merged animation will have the latest selected animations' frames.", + self.value = ANM2_MERGE_REPLACE_FRAMES +); + +IMGUI_ITEM(IMGUI_MERGE_PREPEND_FRAMES, + self.label = "Prepend Frames", + self.tooltip = "On frame conflict, the merged animation will have the selected animations' frames prepended.", + self.value = ANM2_MERGE_PREPEND_FRAMES, + self.isSameLine = true +); + +IMGUI_ITEM(IMGUI_MERGE_IGNORE, + self.label = "Ignore ", + self.tooltip = "On frame conflict, the merged animation will ignore the other selected animations' frames.", + self.value = ANM2_MERGE_IGNORE +); + +IMGUI_ITEM(IMGUI_MERGE_OPTIONS_CHILD, + self.label = "## Merge Options Child", + self.size = {IMGUI_ANIMATION_MERGE.popupSize.x, 35}, + self.flags = true +); + +IMGUI_ITEM(IMGUI_MERGE_DELETE_ANIMATIONS_AFTER, + self.label = "Delete Animations After Merging", + self.tooltip = "After merging, the selected animations (besides the original) will be deleted." +); + +IMGUI_ITEM(IMGUI_MERGE_CONFIRM, + self.label = "Merge", + self.tooltip = "Merge the selected animations with the options set.", + self.undoAction = "Merge Animations", + self.rowCount = IMGUI_OPTION_POPUP_ROW_COUNT, + self.isSameLine = true +); + +IMGUI_ITEM(IMGUI_ANIMATION_REMOVE, + self.label = "Remove", + self.tooltip = "Remove the selected animation.", + self.undoAction = "Remove Animation", + self.rowCount = IMGUI_ANIMATIONS_OPTIONS_ROW_COUNT, + self.chord = ImGuiKey_Delete, + self.focusWindow = IMGUI_ANIMATIONS.label, + self.isSameLine = true +); + +IMGUI_ITEM(IMGUI_ANIMATION_DEFAULT, + self.label = "Default", + self.tooltip = "Set the selected animation as the default one.", + self.undoAction = "Default Animation", + self.rowCount = IMGUI_ANIMATIONS_OPTIONS_ROW_COUNT, + self.isSameLine = true +); + +IMGUI_ITEM(IMGUI_EVENTS, + self.label = "Events", + self.flags = ImGuiWindowFlags_NoScrollbar | + ImGuiWindowFlags_NoScrollWithMouse +); + +IMGUI_ITEM(IMGUI_EVENTS_CHILD, self.label = "## Events Child", self.flags = true); + +IMGUI_ITEM(IMGUI_EVENT, + self.label = "## Event", + self.atlas = ATLAS_EVENT, + self.idOffset = 1000 +); + +#define IMGUI_EVENTS_OPTIONS_ROW_COUNT 2 +IMGUI_ITEM(IMGUI_EVENTS_ADD, + self.label = "Add", + self.tooltip = "Adds a new event.", + self.undoAction = "Add Event", + self.rowCount = IMGUI_EVENTS_OPTIONS_ROW_COUNT, + self.isSameLine = true +); + +IMGUI_ITEM(IMGUI_EVENTS_REMOVE_UNUSED, + self.label = "Remove Unused", + self.tooltip = "Removes all unused events (i.e., not being used in any triggers in any animation).", + self.undoAction = "Remove Unused Events", + self.rowCount = IMGUI_EVENTS_OPTIONS_ROW_COUNT +); + +IMGUI_ITEM(IMGUI_SPRITESHEETS, + self.label = "Spritesheets", + self.flags = ImGuiWindowFlags_NoScrollbar | + ImGuiWindowFlags_NoScrollWithMouse +); + +IMGUI_ITEM(IMGUI_SPRITESHEETS_CHILD, self.label = "## Spritesheets Child", self.flags = true); + +IMGUI_ITEM(IMGUI_SPRITESHEET_CHILD, + self.label = "## Spritesheet Child", + self.size = {0, 175}, + self.flags = true +); + +IMGUI_ITEM(IMGUI_SPRITESHEET_SELECTED, self.label = "## Spritesheet Selected", self.isSameLine = true); + +IMGUI_ITEM(IMGUI_SPRITESHEET, + self.label = "## Spritesheet", + self.dragDrop = "## Spritesheet Drag Drop", + self.atlas = ATLAS_SPRITESHEET, + self.isSizeToText = true +); + +IMGUI_ITEM(IMGUI_SPRITESHEETS_FOOTER_CHILD, + self.label = "## Spritesheets Footer Child", + self.size = {0, 60}, + self.flags = true +); + +#define IMGUI_SPRITESHEETS_OPTIONS_FIRST_ROW_COUNT 4 +#define IMGUI_SPRITESHEETS_OPTIONS_SECOND_ROW_COUNT 3 +IMGUI_ITEM(IMGUI_SPRITESHEET_ADD, + self.label = "Add", + self.tooltip = "Select an image to add as a spritesheet.", + self.rowCount = IMGUI_SPRITESHEETS_OPTIONS_FIRST_ROW_COUNT, + self.isSameLine = true +); + +IMGUI_ITEM(IMGUI_SPRITESHEETS_RELOAD, + self.label = "Reload", + self.tooltip = "Reload the selected spritesheet.", + self.rowCount = IMGUI_SPRITESHEETS_OPTIONS_FIRST_ROW_COUNT, + self.isSameLine = true +); + +IMGUI_ITEM(IMGUI_SPRITESHEETS_REPLACE, + self.label = "Replace", + self.tooltip = "Replace the highlighted spritesheet with another.", + self.rowCount = IMGUI_SPRITESHEETS_OPTIONS_FIRST_ROW_COUNT, + self.isSameLine = true +); + +IMGUI_ITEM(IMGUI_SPRITESHEETS_REMOVE_UNUSED, + self.label = "Remove Unused", + self.tooltip = "Remove all unused spritesheets in the anm2 (i.e., no layer in any animation uses the spritesheet).", + self.rowCount = IMGUI_SPRITESHEETS_OPTIONS_FIRST_ROW_COUNT +); + +IMGUI_ITEM(IMGUI_SPRITESHEETS_SELECT_ALL, + self.label = "Select All", + self.tooltip = "Select all spritesheets.", + self.rowCount = IMGUI_SPRITESHEETS_OPTIONS_SECOND_ROW_COUNT, + self.isSameLine = true +); + +IMGUI_ITEM(IMGUI_SPRITESHEETS_SELECT_NONE, + self.label = "Select None", + self.tooltip = "Unselect all spritesheets.", + self.rowCount = IMGUI_SPRITESHEETS_OPTIONS_SECOND_ROW_COUNT, + self.isSameLine = true +); + +IMGUI_ITEM(IMGUI_SPRITESHEET_SAVE, + self.label = "Save", + self.tooltip = "Save the selected spritesheets to their original locations.", + self.rowCount = IMGUI_SPRITESHEETS_OPTIONS_SECOND_ROW_COUNT +); + +const ImVec2 IMGUI_CANVAS_CHILD_SIZE = {230, 85}; +IMGUI_ITEM(IMGUI_CANVAS_GRID_CHILD, + self.label = "## Canvas Grid Child", + self.size = IMGUI_CANVAS_CHILD_SIZE, + self.flags = true +); + +IMGUI_ITEM(IMGUI_CANVAS_GRID, + self.label = "Grid", + self.tooltip = "Toggles the visiblity of the canvas' grid." +); + +IMGUI_ITEM(IMGUI_CANVAS_GRID_SNAP, + self.label = "Snap", + self.tooltip = "Using the crop tool will snap the points to the nearest grid point." +); + +IMGUI_ITEM(IMGUI_CANVAS_GRID_COLOR, + self.label = "Color", + self.tooltip = "Change the color of the canvas' grid.", + self.flags = ImGuiColorEditFlags_NoInputs +); + +IMGUI_ITEM(IMGUI_CANVAS_GRID_SIZE, + self.label = "Size", + self.tooltip = "Change the size of the canvas' grid.", + self.min = CANVAS_GRID_MIN, + self.max = CANVAS_GRID_MAX, + self.value = CANVAS_GRID_DEFAULT +); + +IMGUI_ITEM(IMGUI_CANVAS_GRID_OFFSET, + self.label = "Offset", + self.tooltip = "Change the offset of the canvas' grid, in pixels." +); + +IMGUI_ITEM(IMGUI_CANVAS_VIEW_CHILD, + self.label = "## View Child", + self.size = IMGUI_CANVAS_CHILD_SIZE, + self.flags = true +); + +IMGUI_ITEM(IMGUI_CANVAS_ZOOM, + self.label = "Zoom", + self.tooltip = "Change the zoom of the canvas.", + self.format = "%.0f", + self.min = CANVAS_ZOOM_MIN, + self.max = CANVAS_ZOOM_MAX, + self.value = CANVAS_ZOOM_DEFAULT +); + +IMGUI_ITEM(IMGUI_CANVAS_CENTER_VIEW, + self.label = "Center View", + self.tooltip = "Centers the current view on the canvas.", + self.size = {-FLT_MIN, 0} +); + +IMGUI_ITEM(IMGUI_CANVAS_VISUAL_CHILD, + self.label = "## Animation Preview Visual Child", + self.size = IMGUI_CANVAS_CHILD_SIZE, + self.flags = true +); + +IMGUI_ITEM(IMGUI_CANVAS_BACKGROUND_COLOR, + self.label = "Background Color", + self.tooltip = "Change the background color of the canvas.", + self.flags = ImGuiColorEditFlags_NoInputs +); + +IMGUI_ITEM(IMGUI_CANVAS_ANIMATION_OVERLAY, + self.label = "Overlay", + self.tooltip = "Choose an animation to overlay over the previewed animation, for reference." +); + +IMGUI_ITEM(IMGUI_CANVAS_ANIMATION_OVERLAY_TRANSPARENCY, + self.label = "Alpha", + self.tooltip = "Set the transparency of the animation overlay.", + self.format = "%.0f", + self.value = 255, + self.max = 255 +); + +IMGUI_ITEM(IMGUI_CANVAS_HELPER_CHILD, + self.label = "## Animation Preview Helper Child", + self.size = IMGUI_CANVAS_CHILD_SIZE, + self.flags = true +); + +IMGUI_ITEM(IMGUI_CANVAS_AXES, + self.label = "Axes", + self.tooltip = "Toggle the display of the X/Y axes." +); + +IMGUI_ITEM(IMGUI_CANVAS_AXES_COLOR, + self.label = "Color", + self.tooltip = "Change the color of the axes.", + self.flags = ImGuiColorEditFlags_NoInputs +); + +IMGUI_ITEM(IMGUI_CANVAS_ROOT_TRANSFORM, + self.label = "Root Transform", + self.tooltip = "Toggles the root frames's attributes transforming the other items in an animation.", + self.value = true +); + +IMGUI_ITEM(IMGUI_CANVAS_TRIGGERS, + self.label = "Triggers", + self.tooltip = "Toggles activated triggers drawing their event name.", + self.value = true +); + +IMGUI_ITEM(IMGUI_CANVAS_PIVOTS, + self.label = "Pivots", + self.tooltip = "Toggles drawing each layer's pivot." +); + +IMGUI_ITEM(IMGUI_CANVAS_TARGETS, + self.label = "Targets", + self.tooltip = "Toggles drawing the targets (i.e., the colored root/null icons)." +); + +IMGUI_ITEM(IMGUI_CANVAS_BORDER, + self.label = "Border", + self.tooltip = "Toggles the appearance of a border around the items." +); + +IMGUI_ITEM(IMGUI_ANIMATION_PREVIEW, + self.label = "Animation Preview", + self.flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse +); + +IMGUI_ITEM(IMGUI_SPRITESHEET_EDITOR, + self.label = "Spritesheet Editor", + self.flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse +); + +IMGUI_ITEM(IMGUI_FRAME_PROPERTIES, self.label = "Frame Properties"); + +IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_POSITION, + self.label = "Position", + self.tooltip = "Change the position of the selected frame.", + self.undoAction = "Frame Position", + self.format = "%.0f" +); + +IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_CROP, + self.label = "Crop", + self.tooltip = "Change the crop position of the selected frame.", + self.undoAction = "Frame Crop", + self.format = "%.0f" +); + +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.format = "%.0f" +); + +IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_PIVOT, + self.label = "Pivot", + self.tooltip = "Change the pivot of the selected frame.", + self.undoAction = "Frame Pivot", + self.format = "%.0f" +); + +IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_SCALE, + self.label = "Scale", + self.tooltip = "Change the scale of the selected frame.", + self.undoAction = "Frame Scale", + self.format = "%.0f", + self.value = 100 +); + +IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_ROTATION, + self.label = "Rotation", + self.tooltip = "Change the rotation of the selected frame.", + self.undoAction = "Frame Rotation", + self.format = "%.0f" +); + +IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_DELAY, + self.label = "Duration", + self.tooltip = "Change the duration of the selected frame.", + self.undoAction = "Frame Duration", + self.min = ANM2_FRAME_NUM_MIN, + self.max = ANM2_FRAME_NUM_MAX, + self.value = ANM2_FRAME_NUM_MIN +); + +IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_TINT, + self.label = "Tint", + self.tooltip = "Change the tint of the selected frame.", + self.undoAction = "Frame Tint", + self.value = 1 +); + +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.value = 0 +); + +const ImVec2 IMGUI_FRAME_PROPERTIES_FLIP_BUTTON_SIZE = {75, 0}; +IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_FLIP_X, + self.label = "Flip X", + self.tooltip = "Change the sign of the X scale, to cheat flipping the layer horizontally.\n(Anm2 doesn't support flipping directly.)", + self.undoAction = "Frame Flip X", + self.size = IMGUI_FRAME_PROPERTIES_FLIP_BUTTON_SIZE, + self.isSameLine = true +); + +IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_FLIP_Y, + self.label = "Flip Y", + self.tooltip = "Change the sign of the Y scale, to cheat flipping the layer vertically.\n(Anm2 doesn't support flipping directly.)", + self.undoAction = "Frame Flip Y", + self.size = IMGUI_FRAME_PROPERTIES_FLIP_BUTTON_SIZE +); + +IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_VISIBLE, + self.label = "Visible", + self.tooltip = "Toggles the visibility of the selected frame.", + self.undoAction = "Frame Visibility", + self.isSameLine = true, + self.value = true +); + +IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_INTERPOLATED, + self.label = "Interpolation", + self.tooltip = "Toggles the interpolation of the selected frame.", + self.undoAction = "Frame Interpolation", + self.value = true +); + +IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_EVENT, + self.label = "Event", + self.tooltip = "Change the event the trigger uses.", + self.undoAction = "Trigger Event" +); + +IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_AT_FRAME, + self.label = "At Frame", + self.tooltip = "Change the frame where the trigger occurs.", + self.undoAction = "Trigger At Frame" +); + +IMGUI_ITEM(IMGUI_TOOLS, self.label = "Tools"); + +IMGUI_ITEM(IMGUI_TOOL_PAN, + self.label = "## Pan", + self.tooltip = "Use the pan tool.\nWill shift the view as the cursor is dragged.\nYou can also use the middle mouse button to pan at any time.", + self.function = imgui_tool_pan_set, + self.chord = ImGuiKey_P, + self.atlas = ATLAS_PAN +); + +IMGUI_ITEM(IMGUI_TOOL_MOVE, + self.label = "## Move", + self.tooltip = "Use the move tool.\nWill move the selected item as the cursor is dragged, or directional keys are pressed.\n(Animation Preview only.)", + self.function = imgui_tool_move_set, + self.chord = ImGuiKey_M, + self.atlas = ATLAS_MOVE +); + +IMGUI_ITEM(IMGUI_TOOL_ROTATE, + self.label = "## Rotate", + self.tooltip = "Use the rotate tool.\nWill rotate the selected item as the cursor is dragged, or directional keys are pressed.\n(Animation Preview only.)", + self.function = imgui_tool_rotate_set, + self.chord = ImGuiKey_R, + self.atlas = ATLAS_ROTATE +); + +IMGUI_ITEM(IMGUI_TOOL_SCALE, + self.label = "## Scale", + self.tooltip = "Use the scale tool.\nWill scale the selected item as the cursor is dragged, or directional keys are pressed.\n(Animation Preview only.)", + self.function = imgui_tool_scale_set, + self.chord = ImGuiKey_S, + self.atlas = ATLAS_SCALE +); + +IMGUI_ITEM(IMGUI_TOOL_CROP, + self.label = "## Crop", + self.tooltip = "Use the crop tool.\nWill produce a crop rectangle based on how the cursor is dragged.\n(Spritesheet Editor only.)", + self.function = imgui_tool_crop_set, + self.chord = ImGuiKey_C, + self.atlas = ATLAS_CROP +); + +IMGUI_ITEM(IMGUI_TOOL_DRAW, + self.label = "## Draw", + self.tooltip = "Draws pixels onto the selected spritesheet, with the current color.\n(Spritesheet Editor only.)", + self.function = imgui_tool_draw_set, + self.chord = ImGuiKey_B, + self.atlas = ATLAS_DRAW +); + +IMGUI_ITEM(IMGUI_TOOL_ERASE, + self.label = "## Erase", + self.tooltip = "Erases pixels from the selected spritesheet.\n(Spritesheet Editor only.)", + self.function = imgui_tool_erase_set, + self.chord = ImGuiKey_E, + self.atlas = ATLAS_ERASE +); + +IMGUI_ITEM(IMGUI_TOOL_COLOR_PICKER, + self.label = "## Color Picker", + self.tooltip = "Selects a color from the canvas, to be used for drawing.\n(Spritesheet Editor only).", + self.function = imgui_tool_color_picker_set, + self.chord = ImGuiKey_W, + self.atlas = ATLAS_COLOR_PICKER +); + +IMGUI_ITEM(IMGUI_TOOL_UNDO, + self.label = "## Undo", + self.tooltip = "Undoes the last action.", + self.function = imgui_undo, + self.chord = ImGuiKey_Z, + self.atlas = ATLAS_UNDO +); + +IMGUI_ITEM(IMGUI_TOOL_REDO, + self.label = "## Redo", + self.tooltip = "Redoes the last action.", + self.function = imgui_redo, + self.chord = ImGuiMod_Shift + ImGuiKey_Z, + self.atlas = ATLAS_REDO +); + +IMGUI_ITEM(IMGUI_TOOL_COLOR, + self.label = "## Color", + self.tooltip = "Set the color, to be used by the draw tool.", + self.flags = ImGuiColorEditFlags_NoInputs +); const inline ImguiItem* IMGUI_TOOL_ITEMS[TOOL_COUNT] = { @@ -1425,144 +1458,128 @@ const inline ImguiItem* IMGUI_TOOL_ITEMS[TOOL_COUNT] = &IMGUI_TOOL_COLOR_PICKER, &IMGUI_TOOL_UNDO, &IMGUI_TOOL_REDO, - &IMGUI_TOOL_COLOR, + &IMGUI_TOOL_COLOR }; -const inline ImguiItem IMGUI_TIMELINE = ImguiItem -({ - .label = "Timeline", - .flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse -}); +IMGUI_ITEM(IMGUI_COLOR_PICKER_BUTTON, self.label = "## Color Picker Button"); -const inline ImguiItem IMGUI_TIMELINE_CHILD = ImguiItem -({ - .label = "## Timeline Child", - .flags = true -}); +IMGUI_ITEM(IMGUI_TIMELINE, + self.label = "Timeline", + self.flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse +); -const inline ImguiItem IMGUI_TIMELINE_HEADER = ImguiItem -({ - .label = "## Timeline Header", - .size = {0, IMGUI_TIMELINE_FRAME_SIZE.y}, - .flagsAlt = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse -}); +IMGUI_ITEM(IMGUI_TIMELINE_CHILD, + self.label = "## Timeline Child", + self.flags = true +); -const inline ImguiItem IMGUI_TIMELINE_ITEMS_CHILD = ImguiItem -({ - .label = "## Timeline Items", - .size = {IMGUI_TIMELINE_ITEM_SIZE.x, 0}, - .flagsAlt = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse -}); +IMGUI_ITEM(IMGUI_TIMELINE_HEADER_CHILD, + self.label = "## Timeline Header Child", + self.size = {0, IMGUI_TIMELINE_FRAME_SIZE.y}, + self.windowFlags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse +); -const inline ImguiItem IMGUI_TIMELINE_ITEM_FRAMES_CHILD = ImguiItem -({ - .label = "## Timeline Item Frames", - .size = {0, IMGUI_TIMELINE_FRAME_SIZE.y}, - .flags = true -}); +IMGUI_ITEM(IMGUI_PLAYHEAD, + self.label = "## Playhead", + self.flags = ImGuiWindowFlags_NoTitleBar | + ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoBackground | + ImGuiWindowFlags_NoInputs +); -const inline ImguiItem IMGUI_TIMELINE_FRAMES_CHILD = ImguiItem -({ - .label = "## Timeline Frames", - .size = {-1, -1}, - .flagsAlt = ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_NoScrollWithMouse -}); +IMGUI_ITEM(IMGUI_TIMELINE_ITEMS_CHILD, + self.label = "## Timeline Items", + self.size = {IMGUI_TIMELINE_ITEM_SIZE.x, 0}, + self.windowFlags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse +); -const inline ImguiItem IMGUI_TIMELINE_ITEM = ImguiItem -({ - .label = "## Timeline Item", - .size = IMGUI_TIMELINE_ITEM_SIZE, - .flags = true -}); +IMGUI_ITEM(IMGUI_TIMELINE_ITEM_CHILD, + self.label = "## Timeline Item Child", + self.size = IMGUI_TIMELINE_ITEM_SIZE, + self.flags = true, + self.windowFlags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse +); -const inline ImguiItem IMGUI_TIMELINE_ITEM_ROOT = ImguiItem -({ - .label = "## Root Item", - .color = {{0.010, 0.049, 0.078, 1.0f}}, - .size = IMGUI_TIMELINE_ITEM_SIZE, - .flags = true -}); +IMGUI_ITEM(IMGUI_TIMELINE_ITEM_ROOT_CHILD, + self.label = "## Root Item Child", + self.color = {{0.045f, 0.08f, 0.11f, 1.0f}}, + self.size = IMGUI_TIMELINE_ITEM_SIZE, + self.flags = true, + self.windowFlags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse +); -const inline ImguiItem IMGUI_TIMELINE_ITEM_LAYER = ImguiItem -({ - .label = "## Layer Item", - .color = {{0.098, 0.039, 0.020, 1.0f}}, - .size = IMGUI_TIMELINE_ITEM_SIZE, - .flags = true -}); +IMGUI_ITEM(IMGUI_TIMELINE_ITEM_LAYER_CHILD, + self.label = "## Layer Item Child", + self.color = {{0.0875f, 0.05f, 0.015f, 1.0f}}, + self.size = IMGUI_TIMELINE_ITEM_SIZE, + self.flags = true, + self.windowFlags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse +); -const inline ImguiItem IMGUI_TIMELINE_ITEM_NULL = ImguiItem -({ - .label = "## Null Item", - .color = {{0.020, 0.049, 0.000, 1.0f}}, - .size = IMGUI_TIMELINE_ITEM_SIZE, - .flags = true -}); +IMGUI_ITEM(IMGUI_TIMELINE_ITEM_NULL_CHILD, + self.label = "## Null Item Child", + self.color = {{0.055f, 0.10f, 0.055f, 1.0f}}, + self.size = IMGUI_TIMELINE_ITEM_SIZE, + self.flags = true, + self.windowFlags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse +); -const inline ImguiItem IMGUI_TIMELINE_ITEM_TRIGGERS = ImguiItem -({ - .label = "## Triggers Item", - .color = {{0.078, 0.020, 0.029, 1.0f}}, - .size = IMGUI_TIMELINE_ITEM_SIZE, - .flags = true -}); +IMGUI_ITEM(IMGUI_TIMELINE_ITEM_TRIGGERS_CHILD, + self.label = "## Triggers Item Child", + self.color = {{0.10f, 0.0375f, 0.07f, 1.0f}}, + self.size = IMGUI_TIMELINE_ITEM_SIZE, + self.flags = true, + self.windowFlags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse +); -const inline ImguiItem* IMGUI_TIMELINE_ITEMS[ANM2_COUNT] +const inline ImguiItem* IMGUI_TIMELINE_ITEM_CHILDS[ANM2_COUNT] { - &IMGUI_TIMELINE_ITEM, - &IMGUI_TIMELINE_ITEM_ROOT, - &IMGUI_TIMELINE_ITEM_LAYER, - &IMGUI_TIMELINE_ITEM_NULL, - &IMGUI_TIMELINE_ITEM_TRIGGERS + &IMGUI_TIMELINE_ITEM_CHILD, + &IMGUI_TIMELINE_ITEM_ROOT_CHILD, + &IMGUI_TIMELINE_ITEM_LAYER_CHILD, + &IMGUI_TIMELINE_ITEM_NULL_CHILD, + &IMGUI_TIMELINE_ITEM_TRIGGERS_CHILD }; -const inline ImguiItem IMGUI_TIMELINE_ITEM_SELECTABLE = ImguiItem -({ - .label = "## Selectable", - .size = IMGUI_TIMELINE_ITEM_SELECTABLE_SIZE -}); +IMGUI_ITEM(IMGUI_TIMELINE_ITEM_SELECTABLE, + self.label = "## Selectable", + self.size = IMGUI_TIMELINE_ITEM_SELECTABLE_SIZE +); -const inline ImguiItem IMGUI_TIMELINE_ITEM_ROOT_SELECTABLE = ImguiItem -({ - .label = "Root", - .tooltip = "The root item of an animation.\nChanging its properties will transform the rest of the animation.", - .action = "Root Item Select", - .atlas = ATLAS_ROOT, - .isUndoable = true, - .size = IMGUI_TIMELINE_ITEM_SELECTABLE_SIZE -}); +IMGUI_ITEM(IMGUI_TIMELINE_ITEM_ROOT_SELECTABLE, + self.label = "Root", + self.tooltip = "The root item of an animation.\nChanging its properties will transform the rest of the animation.", + self.undoAction = "Root Item Select", + self.atlas = ATLAS_ROOT, + self.size = IMGUI_TIMELINE_ITEM_SELECTABLE_SIZE +); -const inline ImguiItem IMGUI_TIMELINE_ITEM_LAYER_SELECTABLE = ImguiItem -({ - .label = "## Layer Selectable", - .tooltip = "A layer item.\nA graphical item within the animation.", - .action = "Layer Item Select", - .dragDrop = "## Layer Drag Drop", - .atlas = ATLAS_LAYER, - .isUndoable = true, - .size = IMGUI_TIMELINE_ITEM_SELECTABLE_SIZE -}); +IMGUI_ITEM(IMGUI_TIMELINE_ITEM_LAYER_SELECTABLE, + self.label = "## Layer Selectable", + self.tooltip = "A layer item.\nA graphical item within the animation.", + self.undoAction = "Layer Item Select", + self.dragDrop = "## Layer Drag Drop", + self.atlas = ATLAS_LAYER, + self.size = IMGUI_TIMELINE_ITEM_SELECTABLE_SIZE +); -const inline ImguiItem IMGUI_TIMELINE_ITEM_NULL_SELECTABLE = ImguiItem -({ - .label = "## Null Selectable", - .tooltip = "A null item.\nAn invisible item within the animation that is accessible via a game engine.", - .action = "Null Item Select", - .dragDrop = "## Null Drag Drop", - .atlas = ATLAS_NULL, - .isUndoable = true, - .size = IMGUI_TIMELINE_ITEM_SELECTABLE_SIZE -}); +IMGUI_ITEM(IMGUI_TIMELINE_ITEM_NULL_SELECTABLE, + self.label = "## Null Selectable", + self.tooltip = "A null item.\nAn invisible item within the animation that is accessible via a game engine.", + self.undoAction = "Null Item Select", + self.dragDrop = "## Null Drag Drop", + self.atlas = ATLAS_NULL, + self.size = IMGUI_TIMELINE_ITEM_SELECTABLE_SIZE +); -const inline ImguiItem IMGUI_TIMELINE_ITEM_TRIGGERS_SELECTABLE = ImguiItem -({ - .label = "Triggers", - .tooltip = "The animation's triggers.\nWill fire based on an event.", - .action = "Triggers Item Select", - .atlas = ATLAS_TRIGGERS, - .isUndoable = true, - .size = IMGUI_TIMELINE_ITEM_SELECTABLE_SIZE -}); +IMGUI_ITEM(IMGUI_TIMELINE_ITEM_TRIGGERS_SELECTABLE, + self.label = "Triggers", + self.tooltip = "The animation's triggers.\nWill fire based on an event.", + self.undoAction = "Triggers Item Select", + self.atlas = ATLAS_TRIGGERS, + self.size = IMGUI_TIMELINE_ITEM_SELECTABLE_SIZE +); const inline ImguiItem* IMGUI_TIMELINE_ITEM_SELECTABLES[ANM2_COUNT] { @@ -1573,94 +1590,92 @@ const inline ImguiItem* IMGUI_TIMELINE_ITEM_SELECTABLES[ANM2_COUNT] &IMGUI_TIMELINE_ITEM_TRIGGERS_SELECTABLE }; -const inline ImguiItem IMGUI_TIMELINE_ITEM_VISIBLE = ImguiItem -({ - .label = "## Visible", - .tooltip = "The item is visible.\nPress to set to invisible.", - .atlas = ATLAS_VISIBLE, - .isUndoable = true -}); +IMGUI_ITEM(IMGUI_TIMELINE_ITEM_VISIBLE, + self.label = "## Visible", + self.tooltip = "The item is visible.\nPress to set to invisible.", + self.undoAction = "Item Invisible", + self.atlas = ATLAS_VISIBLE +); -const inline ImguiItem IMGUI_TIMELINE_ITEM_INVISIBLE = ImguiItem -({ - .label = "## Invisible", - .tooltip = "The item is invisible.\nPress to set to visible.", - .atlas = ATLAS_INVISIBLE, - .isUndoable = true -}); +IMGUI_ITEM(IMGUI_TIMELINE_ITEM_INVISIBLE, + self.label = "## Invisible", + self.tooltip = "The item is invisible.\nPress to set to visible.", + self.undoAction = "Item Visible", + self.atlas = ATLAS_INVISIBLE +); -const inline ImguiItem IMGUI_TIMELINE_ITEM_SHOW_RECT = ImguiItem -({ - .label = "## Show Rect", - .tooltip = "The rect is shown.\nPress to hide rect.", - .atlas = ATLAS_SHOW_RECT, - .isUndoable = true -}); +IMGUI_ITEM(IMGUI_TIMELINE_ITEM_SHOW_RECT, + self.label = "## Show Rect", + self.tooltip = "The rect is shown.\nPress to hide rect.", + self.undoAction = "Hide Rect", + self.atlas = ATLAS_SHOW_RECT +); -const inline ImguiItem IMGUI_TIMELINE_ITEM_HIDE_RECT = ImguiItem -({ - .label = "## Hide Rect", - .tooltip = "The rect is hidden.\nPress to show rect.", - .atlas = ATLAS_HIDE_RECT, - .isUndoable = true -}); +IMGUI_ITEM(IMGUI_TIMELINE_ITEM_HIDE_RECT, + self.label = "## Hide Rect", + self.tooltip = "The rect is hidden.\nPress to show rect.", + self.undoAction = "Show Rect", + self.atlas = ATLAS_HIDE_RECT +); -const inline ImguiItem IMGUI_TIMELINE_SPRITESHEET_ID = ImguiItem -({ - .label = "## Spritesheet ID", - .tooltip = "Change the spritesheet ID this item uses.", - .atlas = ATLAS_SPRITESHEET, - .isUndoable = true, - .size = {32, 0}, -}); +IMGUI_ITEM(IMGUI_TIMELINE_SPRITESHEET_ID, + self.label = "## Spritesheet ID", + self.tooltip = "Change the spritesheet ID this item uses.", + self.atlas = ATLAS_SPRITESHEET, + self.size = {32, 0} +); -const inline ImguiItem IMGUI_TIMELINE_FRAME = ImguiItem({"## Frame"}); +IMGUI_ITEM(IMGUI_TIMELINE_FRAMES_CHILD, + self.label = "## Timeline Frames Child", + self.windowFlags = ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_HorizontalScrollbar +); -const inline ImguiItem IMGUI_TIMELINE_ROOT_FRAME = ImguiItem -({ - .label = "## Root Frame", - .action = "Root Frame Select", - .isUndoable = true, - .color = {{0.020, 0.294, 0.569, 0.5}, {0.471, 0.882, 1.000, 0.75}, {0.314, 0.588, 0.843, 0.75}}, - .size = IMGUI_TIMELINE_FRAME_SIZE, - .contentOffset = IMGUI_TIMELINE_FRAME_CONTENT_OFFSET, - .border = IMGUI_TIMELINE_FRAME_BORDER, -}); +IMGUI_ITEM(IMGUI_TIMELINE_ITEM_FRAMES_CHILD, + self.label = "## Timeline Item Frames Child", + self.size = {0, IMGUI_TIMELINE_FRAME_SIZE.y} +); -const inline ImguiItem IMGUI_TIMELINE_LAYER_FRAME = ImguiItem -({ - .label = "## Layer Frame", - .action = "Layer Frame Select", - .dragDrop = "## Layer Frame Drag Drop", - .isUndoable = true, - .color = {{0.529, 0.157, 0.000, 0.5}, {1.000, 0.618, 0.324, 0.75}, {0.882, 0.412, 0.216, 0.75}}, - .size = IMGUI_TIMELINE_FRAME_SIZE, - .contentOffset = IMGUI_TIMELINE_FRAME_CONTENT_OFFSET, - .border = IMGUI_TIMELINE_FRAME_BORDER, -}); +IMGUI_ITEM(IMGUI_TIMELINE_FRAME, self.label = "## Frame"); -const inline ImguiItem IMGUI_TIMELINE_NULL_FRAME = ImguiItem -({ - .label = "## Null Frame", - .action = "Null Frame Select", - .dragDrop = "## Null Frame Drag Drop", - .isUndoable = true, - .color = {{0.137, 0.353, 0.000, 0.5}, {0.646, 0.971, 0.441, 0.75}, {0.431, 0.647, 0.294, 0.75}}, - .size = IMGUI_TIMELINE_FRAME_SIZE, - .contentOffset = IMGUI_TIMELINE_FRAME_CONTENT_OFFSET, - .border = IMGUI_TIMELINE_FRAME_BORDER, -}); +#define IMGUI_TIMELINE_FRAME_BORDER 5 +static const vec4 IMGUI_FRAME_BORDER_COLOR = {1.0f, 1.0f, 1.0f, 0.25f}; +IMGUI_ITEM(IMGUI_TIMELINE_ROOT_FRAME, + self.label = "## Root Frame", + self.undoAction = "Root Frame Select", + self.color = {{0.14f, 0.27f, 0.39f, 1.0f}, {0.28f, 0.54f, 0.78f, 1.0f}, {0.36f, 0.70f, 0.95f, 1.0f}, IMGUI_FRAME_BORDER_COLOR}, + self.size = IMGUI_TIMELINE_FRAME_SIZE, + self.atlasOffset = IMGUI_TIMELINE_FRAME_ATLAS_OFFSET, + self.border = IMGUI_FRAME_BORDER +); -const inline ImguiItem IMGUI_TIMELINE_TRIGGERS_FRAME = ImguiItem -({ - .label = "## Triggers Frame", - .action = "Trigger Select", - .isUndoable = true, - .color = {{0.529, 0.118, 0.196, 0.5}, {1.000, 0.618, 0.735, 0.75}, {0.804, 0.412, 0.490, 0.75}}, - .size = IMGUI_TIMELINE_FRAME_SIZE, - .contentOffset = IMGUI_TIMELINE_FRAME_CONTENT_OFFSET, - .border = IMGUI_TIMELINE_FRAME_BORDER, -}); +IMGUI_ITEM(IMGUI_TIMELINE_LAYER_FRAME, + self.label = "## Layer Frame", + self.undoAction = "Layer Frame Select", + self.dragDrop = "## Layer Frame Drag Drop", + self.color = {{0.45f, 0.18f, 0.07f, 1.0f}, {0.78f, 0.32f, 0.12f, 1.0f}, {0.95f, 0.40f, 0.15f, 1.0f}, IMGUI_FRAME_BORDER_COLOR}, + self.size = IMGUI_TIMELINE_FRAME_SIZE, + self.atlasOffset = IMGUI_TIMELINE_FRAME_ATLAS_OFFSET, + self.border = IMGUI_FRAME_BORDER +); + +IMGUI_ITEM(IMGUI_TIMELINE_NULL_FRAME, + self.label = "## Null Frame", + self.undoAction = "Null Frame Select", + self.dragDrop = "## Null Frame Drag Drop", + self.color = {{0.17f, 0.33f, 0.17f, 1.0f}, {0.34f, 0.68f, 0.34f, 1.0f}, {0.44f, 0.88f, 0.44f, 1.0f}, IMGUI_FRAME_BORDER_COLOR}, + self.size = IMGUI_TIMELINE_FRAME_SIZE, + self.atlasOffset = IMGUI_TIMELINE_FRAME_ATLAS_OFFSET, + self.border = IMGUI_FRAME_BORDER +); + +IMGUI_ITEM(IMGUI_TIMELINE_TRIGGERS_FRAME, + self.label = "## Triggers Frame", + self.undoAction = "Trigger Select", + self.color = {{0.36f, 0.14f, 0.24f, 1.0f}, {0.72f, 0.28f, 0.48f, 1.0f}, {0.92f, 0.36f, 0.60f, 1.0f}, IMGUI_FRAME_BORDER_COLOR}, + self.size = IMGUI_TIMELINE_FRAME_SIZE, + self.atlasOffset = IMGUI_TIMELINE_FRAME_ATLAS_OFFSET, + self.border = IMGUI_FRAME_BORDER +); const inline ImguiItem* IMGUI_TIMELINE_FRAMES[ANM2_COUNT] { @@ -1671,281 +1686,248 @@ const inline ImguiItem* IMGUI_TIMELINE_FRAMES[ANM2_COUNT] &IMGUI_TIMELINE_TRIGGERS_FRAME }; -const inline ImguiItem IMGUI_TIMELINE_PICKER = ImguiItem -({ - .label = "## Timeline Picker", - .flags = ImGuiWindowFlags_NoTitleBar | - ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoMove | - ImGuiWindowFlags_NoBackground | - ImGuiWindowFlags_NoInputs -}); +IMGUI_ITEM(IMGUI_TIMELINE_ITEM_FOOTER_CHILD, + self.label = "## Item Footer Child", + self.size = {IMGUI_TIMELINE_ITEM_CHILD.size.x, IMGUI_FOOTER_CHILD.size.y}, + self.flags = true +); +IMGUI_ITEM(IMGUI_TIMELINE_OPTIONS_FOOTER_CHILD, + self.label = "## Options Footer Child", + self.size = {0, IMGUI_FOOTER_CHILD.size.y}, + self.flags = true +); -const inline ImguiItem IMGUI_TIMELINE_FOOTER_ITEM_CHILD = ImguiItem -({ - .label = "## Timeline Footer Item Child", - .size = {IMGUI_TIMELINE_ITEM_SIZE.x, 0}, - .flags = true -}); +#define IMGUI_TIMELINE_FOOTER_ITEM_CHILD_ITEM_COUNT 2 +IMGUI_ITEM(IMGUI_TIMELINE_ADD_ITEM, + self.label = "Add", + self.tooltip = "Adds an item (layer or null) to the animation.", + self.popup = "## Add Item Popup", + self.popupType = IMGUI_POPUP_BY_ITEM, + self.rowCount = IMGUI_TIMELINE_FOOTER_ITEM_CHILD_ITEM_COUNT, + self.isSameLine = true +); -const inline ImguiItem IMGUI_TIMELINE_ADD_ITEM = ImguiItem -({ - .label = "Add", - .tooltip = "Adds an item (layer or null) to the animation.", - .popup = "##Add Item Popup", - .isSizeToChild = true, - .childRowItemCount = IMGUI_TIMELINE_FOOTER_ITEM_CHILD_ITEM_COUNT -}); +IMGUI_ITEM(IMGUI_TIMELINE_ADD_ITEM_LAYER, + self.label = "Layer", + self.tooltip = "Adds a layer item.\nA layer item is a primary graphical item, using a spritesheet.", + self.undoAction = "Add Layer", + self.isSizeToText = true +); -const inline ImguiItem IMGUI_TIMELINE_ADD_ITEM_LAYER = ImguiItem -({ - .label = "Layer", - .tooltip = "Adds a layer item.\nA layer item is a primary graphical item, using a spritesheet.", - .action = "Add Layer", - .isUndoable = true, +IMGUI_ITEM(IMGUI_TIMELINE_ADD_ITEM_NULL, + self.label = "Null", + self.tooltip = "Adds a null item.\nA null item is an invisible item, often accessed by the game engine.", + self.undoAction = "Add Null", + self.isSizeToText = true +); -}); +IMGUI_ITEM(IMGUI_TIMELINE_REMOVE_ITEM, + self.label = "Remove", + self.tooltip = "Removes the selected item (layer or null) from the animation.", + self.undoAction = "Remove Item", + self.focusWindow = IMGUI_TIMELINE.label, + self.rowCount = IMGUI_TIMELINE_FOOTER_ITEM_CHILD_ITEM_COUNT +); -const inline ImguiItem IMGUI_TIMELINE_ADD_ITEM_NULL = ImguiItem -({ - .label = "Null", - .tooltip = "Adds a null item.\nA null item is an invisible item, often accessed by the game engine.", - .action = "Add Null", - .isUndoable = true -}); +#define IMGUI_TIMELINE_OPTIONS_ROW_COUNT 10 +IMGUI_ITEM(IMGUI_PLAY, + self.label = "|> Play", + self.tooltip = "Play the current animation, if paused.", + self.focusWindow = IMGUI_TIMELINE.label, + self.chord = ImGuiKey_Space, + self.rowCount = IMGUI_TIMELINE_OPTIONS_ROW_COUNT, + self.isSameLine = true +); -const inline ImguiItem IMGUI_TIMELINE_REMOVE_ITEM = ImguiItem -({ - .label = "Remove", - .tooltip = "Removes the selected item (layer or null) from the animation.", - .action = "Remove Item", - .focusWindow = IMGUI_TIMELINE.label, - .isUndoable = true, - .isSizeToChild = true, - .childRowItemCount = IMGUI_TIMELINE_FOOTER_ITEM_CHILD_ITEM_COUNT -}); +IMGUI_ITEM(IMGUI_PAUSE, + self.label = "|| Pause", + self.tooltip = "Pause the current animation, if playing.", + self.focusWindow = IMGUI_TIMELINE.label, + self.chord = ImGuiKey_Space, + self.rowCount = IMGUI_TIMELINE_OPTIONS_ROW_COUNT, + self.isSameLine = true +); -const inline ImguiItem IMGUI_TIMELINE_FOOTER_OPTIONS_CHILD = ImguiItem -({ - .label = "## Timeline Footer Options Child", - .flags = true -}); +IMGUI_ITEM(IMGUI_ADD_FRAME, + self.label = "+ Insert Frame", + self.tooltip = "Inserts a frame in the selected animation item, based on the preview time.", + self.undoAction = "Insert Frame", + self.rowCount = IMGUI_TIMELINE_OPTIONS_ROW_COUNT, + self.isSameLine = true +); -const inline ImguiItem IMGUI_TIMELINE_PLAY = ImguiItem -({ - .label = "|> Play", - .tooltip = "Play the current animation, if paused.", - .focusWindow = IMGUI_TIMELINE.label, - .chord = ImGuiKey_Space -}); +IMGUI_ITEM(IMGUI_REMOVE_FRAME, + self.label = "- Delete Frame", + self.tooltip = "Removes the selected frame from the selected animation item.", + self.undoAction = "Delete Frame", + self.focusWindow = IMGUI_TIMELINE.label, + self.chord = ImGuiKey_Delete, + self.rowCount = IMGUI_TIMELINE_OPTIONS_ROW_COUNT, + self.isSameLine = true +); -const inline ImguiItem IMGUI_TIMELINE_PAUSE = ImguiItem -({ - .label = "|| Pause", - .tooltip = "Pause the current animation, if playing.", - .focusWindow = IMGUI_TIMELINE.label, - .chord = ImGuiKey_Space -}); +IMGUI_ITEM(IMGUI_BAKE, + self.label = "Bake", + self.tooltip = "Opens the bake popup menu, if a frame is selected.\nBaking a frame takes the currently interpolated values at the time between it and the next frame and separates them based on the interval.", + self.popup = "Bake Frames", + self.rowCount = IMGUI_TIMELINE_OPTIONS_ROW_COUNT, + self.popupSize = {260, 145}, + self.isSameLine = true +); -const inline ImguiItem IMGUI_TIMELINE_ADD_FRAME = ImguiItem -({ - .label = "+ Insert Frame", - .tooltip = "Inserts a frame in the selected animation item, based on the preview time.", - .action = "Insert Frame", - .isUndoable = true -}); +IMGUI_ITEM(IMGUI_BAKE_CHILD, + self.label = "## Bake Child", + self.flags = true +); -const inline ImguiItem IMGUI_TIMELINE_REMOVE_FRAME = ImguiItem -({ - .label = "- Delete Frame", - .tooltip = "Removes the selected frame from the selected animation item.", - .action = "Delete Frame", - .focusWindow = IMGUI_TIMELINE.label, - .chord = ImGuiKey_Delete, - .isUndoable = true, -}); +IMGUI_ITEM(IMGUI_BAKE_INTERVAL, + self.label = "Interval", + self.tooltip = "Sets the delay of the baked frames the selected frame will be separated out into.", + self.min = ANM2_FRAME_DELAY_MIN, + self.value = ANM2_FRAME_DELAY_MIN +); -const inline ImguiItem IMGUI_TIMELINE_BAKE = ImguiItem -({ - .label = "Bake", - .tooltip = "Opens the bake popup menu, if a frame is selected.\nBaking a frame takes the currently interpolated values at the time between it and the next frame and separates them based on the interval.", - .popup = "Bake Frames", - .popupType = IMGUI_POPUP_CENTER_SCREEN -}); +IMGUI_ITEM(IMGUI_BAKE_ROUND_SCALE, + self.label = "Round Scale", + self.tooltip = "The scale of the baked frames will be rounded to the nearest integer.", + self.value = true +); -const inline ImguiItem IMGUI_TIMELINE_BAKE_CHILD = ImguiItem -({ - .label = "## Bake Child", - .size = {200, 110}, - .flags = true -}); +IMGUI_ITEM(IMGUI_BAKE_ROUND_ROTATION, + self.label = "Round Rotation", + self.tooltip = "The rotation of the baked frames will be rounded to the nearest integer.", + self.value = true, + self.isSeparator = true +); -const inline ImguiItem IMGUI_TIMELINE_BAKE_INTERVAL = ImguiItem -({ - .label = "Interval", - .tooltip = "Sets the delay of the baked frames the selected frame will be separated out into.", - .min = ANM2_FRAME_DELAY_MIN -}); +IMGUI_ITEM(IMGUI_BAKE_CONFIRM, + self.label = "Bake", + self.tooltip = "Bake the selected frame with the options selected.", + self.undoAction = "Bake Frames", + self.rowCount = IMGUI_OPTION_POPUP_ROW_COUNT, + self.isSameLine = true +); -const inline ImguiItem IMGUI_TIMELINE_BAKE_ROUND_SCALE = ImguiItem -({ - .label = "Round Scale", - .tooltip = "The scale of the baked frames will be rounded to the nearest integer." -}); +IMGUI_ITEM(IMGUI_FIT_ANIMATION_LENGTH, + self.label = "Fit Animation Length", + self.tooltip = "Sets the animation's length to the latest frame.", + self.undoAction = "Fit Animation Length", + self.rowCount = IMGUI_TIMELINE_OPTIONS_ROW_COUNT, + self.isSameLine = true +); -const inline ImguiItem IMGUI_TIMELINE_BAKE_ROUND_ROTATION = ImguiItem -({ - .label = "Round Rotation", - .tooltip = "The rotation of the baked frames will be rounded to the nearest integer." -}); +IMGUI_ITEM(IMGUI_ANIMATION_LENGTH, + self.label = "Length", + self.tooltip = "Sets the animation length.\n(Will not change frames.)", + self.undoAction = "Set Animation Length", + self.rowCount = IMGUI_TIMELINE_OPTIONS_ROW_COUNT, + self.min = ANM2_FRAME_NUM_MIN, + self.max = ANM2_FRAME_NUM_MAX, + self.value = ANM2_FRAME_NUM_MIN, + self.isSameLine = true +); -const inline ImguiItem IMGUI_TIMELINE_BAKE_CONFIRM = ImguiItem -({ - .label = "Bake", - .tooltip = "Bake the selected frame with the options selected.", - .action = "Baking Frames", - .isUndoable = true, - .isSizeToChild = true, - .childRowItemCount = IMGUI_TIMELINE_BAKE_OPTIONS_CHILD_ROW_ITEM_COUNT -}); +IMGUI_ITEM(IMGUI_FPS, + self.label = "FPS", + self.tooltip = "Sets the animation's frames per second (its speed).", + self.undoAction = "Set FPS", + self.rowCount = IMGUI_TIMELINE_OPTIONS_ROW_COUNT, + self.min = ANM2_FPS_MIN, + self.max = ANM2_FPS_MAX, + self.value = ANM2_FPS_DEFAULT, + self.isSameLine = true +); -const inline ImguiItem IMGUI_TIMELINE_BAKE_CANCEL = ImguiItem -({ - .label = "Cancel", - .tooltip = "Cancel baking the selected frame.", - .isSizeToChild = true, - .childRowItemCount = IMGUI_TIMELINE_BAKE_OPTIONS_CHILD_ROW_ITEM_COUNT -}); +IMGUI_ITEM(IMGUI_LOOP, + self.label = "Loop", + self.tooltip = "Toggles the animation looping.", + self.undoAction = "Set Loop", + self.rowCount = IMGUI_TIMELINE_OPTIONS_ROW_COUNT, + self.value = true, + self.isSameLine = true +); -const inline ImguiItem IMGUI_TIMELINE_FIT_ANIMATION_LENGTH = ImguiItem -({ - .label = "Fit Animation Length", - .tooltip = "Sets the animation's length to the latest frame.", - .action = "Fit Animation Length", - .isUndoable = true -}); +IMGUI_ITEM(IMGUI_CREATED_BY, + self.label = "Author", + self.tooltip = "Sets the author of the animation.", + self.rowCount = IMGUI_TIMELINE_OPTIONS_ROW_COUNT, + self.max = 255 +); -const inline ImguiItem IMGUI_TIMELINE_ANIMATION_LENGTH = ImguiItem -({ - .label = "Animation Length", - .tooltip = "Sets the animation length.\n(Will not change frames.)", - .action = "Set Animation Length", - .isUndoable = true, - .size = {100, 0}, - .min = ANM2_FRAME_NUM_MIN, - .max = ANM2_FRAME_NUM_MAX -}); +IMGUI_ITEM(IMGUI_CONTEXT_MENU, self.label = "## Context Menu"); -const inline ImguiItem IMGUI_TIMELINE_FPS = ImguiItem -({ - .label = "FPS", - .tooltip = "Sets the animation's frames per second (its speed).", - .action = "Set FPS", - .isUndoable = true, - .size = {100, 0}, - .min = ANM2_FPS_MIN, - .max = ANM2_FPS_MAX -}); +IMGUI_ITEM(IMGUI_CUT, + self.label = "Cut", + self.tooltip = "Cuts the currently selected contextual element; removing it and putting it to the clipboard.", + self.undoAction = "Cut", + self.function = imgui_cut, + self.chord = ImGuiMod_Ctrl | ImGuiKey_X, + self.isSizeToText = true +); -const inline ImguiItem IMGUI_TIMELINE_LOOP = ImguiItem -({ - .label = "Loop", - .tooltip = "Toggles the animation looping.", - .action = "Set Loop", - .isUndoable = true -}); +IMGUI_ITEM(IMGUI_COPY, + self.label = "Copy", + self.tooltip = "Copies the currently selected contextual element to the clipboard.", + self.undoAction = "Copy", + self.function = imgui_copy, + self.chord = ImGuiMod_Ctrl | ImGuiKey_C, + self.isSizeToText = true +); -const inline ImguiItem IMGUI_TIMELINE_CREATED_BY = ImguiItem -({ - .label = "Author", - .tooltip = "Sets the author of the animation.", - .size = {150, 0}, - .max = ANM2_STRING_MAX -}); +IMGUI_ITEM(IMGUI_PASTE, + self.label = "Paste", + self.tooltip = "Pastes the currently selection contextual element from the clipboard.", + self.undoAction = "Paste", + self.function = imgui_paste, + self.chord = ImGuiMod_Ctrl | ImGuiKey_V, + self.isSizeToText = true +); -const inline ImguiItem IMGUI_TIMELINE_CREATED_ON = ImguiItem({"Created on: "}); -const inline ImguiItem IMGUI_TIMELINE_VERSION = ImguiItem({"Version: "}); +IMGUI_ITEM(IMGUI_CHANGE_INPUT_TEXT, + self.label = "## Input Text", + self.tooltip = "Rename the selected item.", + self.undoAction = "Rename Item", + self.flags = ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_EnterReturnsTrue, + self.max = 255 +); -const inline ImguiItem IMGUI_CONTEXT_MENU = ImguiItem -({ - .label = "## Context Menu", - .popup = "## Context Menu Popup" -}); +IMGUI_ITEM(IMGUI_CHANGE_INPUT_INT, + self.label = "## Input Int", + self.tooltip = "Change the selected item's value.", + self.undoAction = "Change Value", + self.step = 0 +); -const inline ImguiItem IMGUI_CUT = ImguiItem -({ - .label = "Cut", - .tooltip = "Cuts the currently selected contextual element; removing it and putting it to the clipboard.", - .action = "Cut", - .function = imgui_cut, - .chord = ImGuiMod_Ctrl | ImGuiKey_X, - .isUndoable = true, -}); +IMGUI_ITEM(IMGUI_EXIT_CONFIRMATION, + self.label = "Exit Confirmation", + self.text = "Unsaved changes will be lost!\nAre you sure you want to exit?" +); -const inline ImguiItem IMGUI_COPY = ImguiItem -({ - .label = "Copy", - .tooltip = "Copies the currently selected contextual element to the clipboard.", - .action = "Copy", - .function = imgui_copy, - .chord = ImGuiMod_Ctrl | ImGuiKey_C, - .isUndoable = true, -}); +#define IMGUI_OPTION_POPUP_ROW_COUNT 2 +IMGUI_ITEM(IMGUI_POPUP_OK, + self.label = "OK", + self.tooltip = "Confirm the action.", + self.rowCount = IMGUI_OPTION_POPUP_ROW_COUNT +); -const inline ImguiItem IMGUI_PASTE = ImguiItem -({ - .label = "Paste", - .tooltip = "Pastes the currently selection contextual element from the clipboard.", - .action = "Paste", - .function = imgui_paste, - .chord = ImGuiMod_Ctrl | ImGuiKey_V, - .isUndoable = true, -}); +IMGUI_ITEM(IMGUI_POPUP_CANCEL, + self.label = "Cancel", + self.tooltip = "Cancel the action.", + self.rowCount = IMGUI_OPTION_POPUP_ROW_COUNT +); -const inline ImguiItem IMGUI_RENAMABLE = ImguiItem -({ - .label = "## Renaming", - .tooltip = "Rename the selected item.", - .action = "Rename", - .isUndoable = true, - .max = IMGUI_BUFFER_MAX, - .flags = ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_EnterReturnsTrue -}); - -const inline ImguiItem IMGUI_CHANGEABLE = ImguiItem -({ - .label = "## Changing", - .tooltip = "Change the selected item's value.", - .action = "Change Value", - .isUndoable = true, - .step = 0 -}); - -const inline ImguiItem IMGUI_EXIT_CONFIRMATION = ImguiItem -({ - .label = "Unsaved changes will be lost!\nAre you sure you want to exit?", - .popup = "Exit Confirmation" -}); - -const inline ImguiItem IMGUI_POPUP_OK_BUTTON = ImguiItem -({ - .label = "OK", - .isSizeToChild = true -}); - -const inline ImguiItem IMGUI_POPUP_CONFIRM_BUTTON = ImguiItem -({ - .label = "OK", - .isSizeToChild = true, - .childRowItemCount = IMGUI_POPUP_OPTION_CHILD_ROW_ITEM_COUNT -}); - -const inline ImguiItem IMGUI_POPUP_CANCEL_BUTTON = ImguiItem -({ - .label = "Cancel", - .isSizeToChild = true, - .childRowItemCount = IMGUI_POPUP_OPTION_CHILD_ROW_ITEM_COUNT -}); +IMGUI_ITEM(IMGUI_LOG_WINDOW, + self.label = "## Log Window", + self.flags = ImGuiWindowFlags_NoTitleBar | + ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoScrollbar | + ImGuiWindowFlags_NoSavedSettings | + ImGuiWindowFlags_AlwaysAutoResize | + ImGuiWindowFlags_NoFocusOnAppearing | + ImGuiWindowFlags_NoNav | + ImGuiWindowFlags_NoInputs +); void imgui_init ( diff --git a/src/main.cpp b/src/main.cpp index 0ca3856..2c26fcc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5 +1,3 @@ -// Main function - #include "main.h" s32 diff --git a/src/preview.cpp b/src/preview.cpp index 575fcee..6873eeb 100644 --- a/src/preview.cpp +++ b/src/preview.cpp @@ -1,5 +1,3 @@ -// Handles the render of the animation preview - #include "preview.h" static void _preview_render_textures_free(Preview* self) @@ -17,11 +15,12 @@ void preview_init(Preview* self, Anm2* anm2, Anm2Reference* reference, Resources self->resources = resources; self->settings = settings; - canvas_init(&self->canvas); + canvas_init(&self->canvas, vec2()); } void preview_tick(Preview* self) { + f32& time = self->time; Anm2Animation* animation = anm2_animation_from_reference(self->anm2, self->reference); if (animation) @@ -45,29 +44,38 @@ void preview_tick(Preview* self) self->renderFrames.push_back(frameTexture); } - self->time += (f32)self->anm2->fps / TICK_RATE; + time += (f32)self->anm2->fps / TICK_RATE; - if (self->time >= (f32)animation->frameNum - 1) + if (time >= (f32)animation->frameNum - 1) { if (self->isRender) { self->isRender = false; self->isRenderFinished = true; - self->time = 0.0f; + time = 0.0f; self->isPlaying = false; } else { if (self->settings->playbackIsLoop) - self->time = 0.0f; + time = 0.0f; else + { + time = std::clamp(time, 0.0f, std::max(0.0f, (f32)animation->frameNum - 1)); self->isPlaying = false; + } } } } - - self->time = std::clamp(self->time, 0.0f, (f32)animation->frameNum - 1); + + if (self->settings->playbackIsClampPlayhead) + time = std::clamp(time, 0.0f, std::max(0.0f, (f32)animation->frameNum - 1)); + else + time = std::max(time, 0.0f); + } + + } void preview_draw(Preview* self) @@ -89,8 +97,8 @@ void preview_draw(Preview* self) if (self->settings->previewIsGrid) canvas_grid_draw(&self->canvas, shaderLine, transform, zoom, gridSize, gridOffset, gridColor); - if (self->settings->previewIsAxis) - canvas_axes_draw(&self->canvas, shaderLine, transform, self->settings->previewAxisColor); + if (self->settings->previewIsAxes) + canvas_axes_draw(&self->canvas, shaderLine, transform, self->settings->previewAxesColor); Anm2Animation* animation = anm2_animation_from_reference(self->anm2, self->reference); s32& animationID = self->reference->animationID; diff --git a/src/settings.cpp b/src/settings.cpp index f6250c5..cde70e2 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -54,6 +54,13 @@ static void _settings_setting_load(Settings* self, const std::string& line) if ((value = match_key(key + "X"))) { v->x = std::atoi(value); return; } if ((value = match_key(key + "Y"))) { v->y = std::atoi(value); return; } } + else if (entry.type == TYPE_VEC3) + { + vec3* v = (vec3*)target; + if ((value = match_key(key + "R"))) { v->x = std::atof(value); return; } + if ((value = match_key(key + "G"))) { v->y = std::atof(value); return; } + if ((value = match_key(key + "B"))) { v->z = std::atof(value); return; } + } else if (entry.type == TYPE_VEC4) { vec4* v = (vec4*)target; @@ -105,6 +112,14 @@ static void _settings_setting_write(Settings* self, std::ostream& out, SettingsE out << entry.key << "Y=" << std::format(SETTINGS_FLOAT_FORMAT, data->y) << "\n"; break; } + case TYPE_VEC3: + { + vec3* data = (vec3*)(selfPointer + entry.offset); + out << entry.key << "R=" << std::format(SETTINGS_FLOAT_FORMAT, data->r) << "\n"; + out << entry.key << "G=" << std::format(SETTINGS_FLOAT_FORMAT, data->g) << "\n"; + out << entry.key << "B=" << std::format(SETTINGS_FLOAT_FORMAT, data->b) << "\n"; + break; + } case TYPE_VEC4: { vec4* data = (vec4*)(selfPointer + entry.offset); diff --git a/src/settings.h b/src/settings.h index d6cd6ec..1ec1664 100644 --- a/src/settings.h +++ b/src/settings.h @@ -1,5 +1,6 @@ #pragma once +#include "anm2.h" #include "render.h" #include "tool.h" @@ -22,20 +23,53 @@ struct Settings { ivec2 windowSize = {1080, 720}; bool playbackIsLoop = true; - bool previewIsAxis = true; + bool playbackIsClampPlayhead = true; + bool changeIsCrop = false; + bool changeIsSize = false; + bool changeIsPosition = false; + bool changeIsPivot = false; + bool changeIsScale = false; + bool changeIsRotation = false; + bool changeIsDelay = false; + bool changeIsTint = false; + bool changeIsColorOffset = false; + bool changeIsVisibleSet = false; + bool changeIsInterpolatedSet = false; + bool changeIsFromSelectedFrame = false; + vec2 changeCrop{}; + vec2 changeSize{}; + vec2 changePosition{}; + vec2 changePivot{}; + vec2 changeScale{}; + f32 changeRotation{}; + s32 changeDelay{}; + vec4 changeTint{}; + vec3 changeColorOffset{}; + bool changeIsVisible{}; + bool changeIsInterpolated{}; + s32 changeNumberFrames = 1; + bool previewIsAxes = true; bool previewIsGrid = true; bool previewIsRootTransform = false; + bool previewIsTriggers = true; bool previewIsPivots = false; bool previewIsTargets = true; bool previewIsBorder = false; f32 previewOverlayTransparency = 255.0f; f32 previewZoom = 200.0; vec2 previewPan = {0.0, 0.0}; - ivec2 previewGridSize = {32, 32}; + ivec2 previewGridSize = {32, 3}; ivec2 previewGridOffset{}; vec4 previewGridColor = {1.0, 1.0, 1.0, 0.125}; - vec4 previewAxisColor = {1.0, 1.0, 1.0, 0.125}; + vec4 previewAxesColor = {1.0, 1.0, 1.0, 0.125}; vec4 previewBackgroundColor = {0.113, 0.184, 0.286, 1.0}; + ivec2 generateStartPosition = {0, 0}; + ivec2 generateFrameSize = {64, 64}; + ivec2 generatePivot = {32, 32}; + s32 generateRows = 4; + s32 generateColumns = 4; + s32 generateFrameCount = 16; + s32 generateDelay = 1; bool editorIsGrid = true; bool editorIsGridSnap = true; bool editorIsBorder = true; @@ -45,9 +79,14 @@ struct Settings ivec2 editorGridOffset = {32, 32}; vec4 editorGridColor = {1.0, 1.0, 1.0, 0.125}; vec4 editorBackgroundColor = {0.113, 0.184, 0.286, 1.0}; - ToolType tool = TOOL_PAN; + s32 mergeType = ANM2_MERGE_APPEND_FRAMES; + bool mergeIsDeleteAnimationsAfter = false; + s32 bakeInterval = 1; + bool bakeIsRoundScale = true; + bool bakeIsRoundRotation = true; + s32 tool = TOOL_PAN; vec4 toolColor = {1.0, 1.0, 1.0, 1.0}; - RenderType renderType = RENDER_PNG; + s32 renderType = RENDER_PNG; std::string renderPath = "."; std::string renderFormat = "{}.png"; std::string ffmpegPath = "/usr/bin/ffmpeg"; @@ -57,9 +96,35 @@ const SettingsEntry SETTINGS_ENTRIES[] = { {"window", TYPE_IVEC2, offsetof(Settings, windowSize)}, {"playbackIsLoop", TYPE_BOOL, offsetof(Settings, playbackIsLoop)}, - {"previewIsAxis", TYPE_BOOL, offsetof(Settings, previewIsAxis)}, + {"playbackIsClampPlayhead", TYPE_BOOL, offsetof(Settings, playbackIsClampPlayhead)}, + {"changeIsCrop", TYPE_BOOL, offsetof(Settings, changeIsCrop)}, + {"changeIsSize", TYPE_BOOL, offsetof(Settings, changeIsSize)}, + {"changeIsPosition", TYPE_BOOL, offsetof(Settings, changeIsPosition)}, + {"changeIsPivot", TYPE_BOOL, offsetof(Settings, changeIsPivot)}, + {"changeIsScale", TYPE_BOOL, offsetof(Settings, changeIsScale)}, + {"changeIsRotation", TYPE_BOOL, offsetof(Settings, changeIsRotation)}, + {"changeIsDelay", TYPE_BOOL, offsetof(Settings, changeIsDelay)}, + {"changeIsTint", TYPE_BOOL, offsetof(Settings, changeIsTint)}, + {"changeIsColorOffset", TYPE_BOOL, offsetof(Settings, changeIsColorOffset)}, + {"changeIsVisibleSet", TYPE_BOOL, offsetof(Settings, changeIsVisibleSet)}, + {"changeIsInterpolatedSet", TYPE_BOOL, offsetof(Settings, changeIsInterpolatedSet)}, + {"changeIsFromSelectedFrame", TYPE_BOOL, offsetof(Settings, changeIsFromSelectedFrame)}, + {"changeCrop", TYPE_VEC2, offsetof(Settings, changeCrop)}, + {"changeSize", TYPE_VEC2, offsetof(Settings, changeSize)}, + {"changePosition", TYPE_VEC2, offsetof(Settings, changePosition)}, + {"changePivot", TYPE_VEC2, offsetof(Settings, changePivot)}, + {"changeScale", TYPE_VEC2, offsetof(Settings, changeScale)}, + {"changeRotation", TYPE_FLOAT, offsetof(Settings, changeRotation)}, + {"changeDelay", TYPE_INT, offsetof(Settings, changeDelay)}, + {"changeTint", TYPE_VEC4, offsetof(Settings, changeTint)}, + {"changeColorOffset", TYPE_VEC3, offsetof(Settings, changeColorOffset)}, + {"changeIsVisible", TYPE_BOOL, offsetof(Settings, changeIsVisibleSet)}, + {"changeIsInterpolated", TYPE_BOOL, offsetof(Settings, changeIsInterpolatedSet)}, + {"changeNumberFrames", TYPE_INT, offsetof(Settings, changeNumberFrames)}, + {"previewIsAxes", TYPE_BOOL, offsetof(Settings, previewIsAxes)}, {"previewIsGrid", TYPE_BOOL, offsetof(Settings, previewIsGrid)}, {"previewIsRootTransform", TYPE_BOOL, offsetof(Settings, previewIsRootTransform)}, + {"previewIsTriggers", TYPE_BOOL, offsetof(Settings, previewIsTriggers)}, {"previewIsPivots", TYPE_BOOL, offsetof(Settings, previewIsPivots)}, {"previewIsTargets", TYPE_BOOL, offsetof(Settings, previewIsTargets)}, {"previewIsBorder", TYPE_BOOL, offsetof(Settings, previewIsBorder)}, @@ -69,8 +134,15 @@ const SettingsEntry SETTINGS_ENTRIES[] = {"previewGridSize", TYPE_IVEC2, offsetof(Settings, previewGridSize)}, {"previewGridOffset", TYPE_IVEC2, offsetof(Settings, previewGridOffset)}, {"previewGridColor", TYPE_VEC4, offsetof(Settings, previewGridColor)}, - {"previewAxisColor", TYPE_VEC4, offsetof(Settings, previewAxisColor)}, + {"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)}, + {"generateRows", TYPE_INT, offsetof(Settings, generateRows)}, + {"generateColumns", TYPE_INT, offsetof(Settings, generateColumns)}, + {"generateFrameCount", TYPE_INT, offsetof(Settings, generateFrameCount)}, + {"generateDelay", TYPE_INT, offsetof(Settings, generateDelay)}, {"editorIsGrid", TYPE_BOOL, offsetof(Settings, editorIsGrid)}, {"editorIsGridSnap", TYPE_BOOL, offsetof(Settings, editorIsGridSnap)}, {"editorIsBorder", TYPE_BOOL, offsetof(Settings, editorIsBorder)}, @@ -80,6 +152,11 @@ const SettingsEntry SETTINGS_ENTRIES[] = {"editorGridOffset", TYPE_IVEC2, offsetof(Settings, editorGridOffset)}, {"editorGridColor", TYPE_VEC4, offsetof(Settings, editorGridColor)}, {"editorBackgroundColor", TYPE_VEC4, offsetof(Settings, editorBackgroundColor)}, + {"mergeType", TYPE_INT, offsetof(Settings, mergeType)}, + {"mergeIsDeleteAnimationsAfter", TYPE_BOOL, offsetof(Settings, mergeIsDeleteAnimationsAfter)}, + {"bakeInterval", TYPE_INT, offsetof(Settings, bakeInterval)}, + {"bakeRoundScale", TYPE_BOOL, offsetof(Settings, bakeIsRoundScale)}, + {"bakeRoundRotation", TYPE_BOOL, offsetof(Settings, bakeIsRoundRotation)}, {"tool", TYPE_INT, offsetof(Settings, tool)}, {"toolColor", TYPE_VEC4, offsetof(Settings, toolColor)}, {"renderType", TYPE_INT, offsetof(Settings, renderType)}, diff --git a/src/snapshots.cpp b/src/snapshots.cpp index 679daaf..97335cf 100644 --- a/src/snapshots.cpp +++ b/src/snapshots.cpp @@ -41,7 +41,7 @@ void snapshots_reset(Snapshots* self) self->action.clear(); } -void snapshots_undo_stack_push(Snapshots* self, const Snapshot* snapshot) +void snapshots_undo_push(Snapshots* self, const Snapshot* snapshot) { _snapshot_stack_push(&self->undoStack, snapshot); self->redoStack.top = 0; diff --git a/src/snapshots.h b/src/snapshots.h index 390dda6..f1cfd93 100644 --- a/src/snapshots.h +++ b/src/snapshots.h @@ -2,6 +2,7 @@ #include "anm2.h" #include "preview.h" +#include "texture.h" #define SNAPSHOT_STACK_MAX 1000 #define SNAPSHOT_ACTION "Action" @@ -18,6 +19,8 @@ struct SnapshotStack { Snapshot snapshots[SNAPSHOT_STACK_MAX]; s32 top = 0; + + bool is_empty() const { return top == 0; } }; struct Snapshots @@ -30,7 +33,7 @@ struct Snapshots SnapshotStack redoStack; }; -void snapshots_undo_stack_push(Snapshots* self, const Snapshot* snapshot); +void snapshots_undo_push(Snapshots* self, const Snapshot* snapshot); void snapshots_init(Snapshots* self, Anm2* anm2, Anm2Reference* reference, Preview* preview); void snapshots_undo(Snapshots* self); void snapshots_redo(Snapshots* self); diff --git a/src/state.cpp b/src/state.cpp index 6f17f87..deb60b5 100644 --- a/src/state.cpp +++ b/src/state.cpp @@ -17,8 +17,6 @@ static void _update(State* self) static void _draw(State* self) { - editor_draw(&self->editor); - preview_draw(&self->preview); imgui_draw(); SDL_GL_SwapWindow(self->window); @@ -147,6 +145,8 @@ void loop(State* self) _update(self); _draw(self); + + SDL_Delay(STATE_DELAY_MIN); } void quit(State* self) diff --git a/src/state.h b/src/state.h index 521376e..7e213fd 100644 --- a/src/state.h +++ b/src/state.h @@ -13,6 +13,8 @@ #define STATE_QUIT_INFO "Exiting..." #define STATE_GL_LINE_WIDTH 2.0f +#define STATE_DELAY_MIN 1 + #define STATE_MIX_FLAGS (MIX_INIT_MP3 | MIX_INIT_OGG | MIX_INIT_WAV) #define STATE_MIX_SAMPLE_RATE 44100 #define STATE_MIX_FORMAT MIX_DEFAULT_FORMAT diff --git a/src/texture.cpp b/src/texture.cpp index 1c08084..89bd924 100644 --- a/src/texture.cpp +++ b/src/texture.cpp @@ -114,3 +114,33 @@ bool texture_pixel_set(Texture* self, ivec2 position, vec4 color) return true; } + +Texture texture_copy(Texture* self) +{ + Texture copy = *self; + _texture_gl_set(©, nullptr); + + GLuint fboSource; + GLuint fboDestination; + glGenFramebuffers(1, &fboSource); + glGenFramebuffers(1, &fboDestination); + + glBindFramebuffer(GL_READ_FRAMEBUFFER, fboSource); + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, self->id, 0); + + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fboDestination); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, copy.id, 0); + + glBlitFramebuffer + ( + 0, 0, self->size.x, self->size.y, + 0, 0, self->size.x, self->size.y, + GL_COLOR_BUFFER_BIT, GL_NEAREST + ); + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glDeleteFramebuffers(1, &fboSource); + glDeleteFramebuffers(1, &fboDestination); + + return copy; +} \ No newline at end of file diff --git a/src/texture.h b/src/texture.h index 9eeb0fe..eb671ec 100644 --- a/src/texture.h +++ b/src/texture.h @@ -22,4 +22,5 @@ bool texture_from_rgba_init(Texture* self, ivec2 size, s32 channels, const u8* d bool texture_from_rgba_write(const std::string& path, const u8* data, ivec2 size); bool texture_pixel_set(Texture* self, ivec2 position, vec4 color); void texture_free(Texture* self); -std::vector texture_download(const Texture* self); \ No newline at end of file +std::vector texture_download(const Texture* self); +Texture texture_copy(Texture* self); \ No newline at end of file diff --git a/src/tool.h b/src/tool.h index f4359e7..132e806 100644 --- a/src/tool.h +++ b/src/tool.h @@ -21,8 +21,8 @@ enum ToolType TOOL_COUNT, }; -const SDL_SystemCursor MOUSE_CURSOR_DEFAULT = SDL_SYSTEM_CURSOR_DEFAULT; -const SDL_SystemCursor TOOL_MOUSE_CURSORS[TOOL_COUNT] = +const SDL_SystemCursor CURSOR_DEFAULT = SDL_SYSTEM_CURSOR_DEFAULT; +const SDL_SystemCursor TOOL_CURSORS[TOOL_COUNT] = { SDL_SYSTEM_CURSOR_POINTER, SDL_SYSTEM_CURSOR_MOVE, @@ -34,19 +34,4 @@ const SDL_SystemCursor TOOL_MOUSE_CURSORS[TOOL_COUNT] = SDL_SYSTEM_CURSOR_CROSSHAIR, SDL_SYSTEM_CURSOR_DEFAULT, SDL_SYSTEM_CURSOR_DEFAULT -}; - -const bool TOOL_MOUSE_CURSOR_IS_CONSTANT[TOOL_COUNT] = -{ - false, - false, - false, - false, - false, - false, - false, - true, - false, - false, - false -}; +}; \ No newline at end of file