Snapshots, refactoring

This commit is contained in:
2025-10-26 16:06:34 -04:00
parent fe9366f9ef
commit dd3aeae6d2
16 changed files with 443 additions and 106 deletions

View File

@@ -19,7 +19,6 @@ namespace anm2ed::animation_preview
{ {
constexpr auto NULL_COLOR = vec4(0.0f, 0.0f, 1.0f, 0.90f); constexpr auto NULL_COLOR = vec4(0.0f, 0.0f, 1.0f, 0.90f);
constexpr auto TARGET_SIZE = vec2(32, 32); constexpr auto TARGET_SIZE = vec2(32, 32);
constexpr auto PIVOT_SIZE = vec2(8, 8);
constexpr auto POINT_SIZE = vec2(4, 4); constexpr auto POINT_SIZE = vec2(4, 4);
constexpr auto NULL_RECT_SIZE = vec2(100); constexpr auto NULL_RECT_SIZE = vec2(100);
constexpr auto TRIGGER_TEXT_COLOR = ImVec4(1.0f, 1.0f, 1.0f, 0.5f); constexpr auto TRIGGER_TEXT_COLOR = ImVec4(1.0f, 1.0f, 1.0f, 0.5f);
@@ -83,9 +82,17 @@ namespace anm2ed::animation_preview
auto widgetSize = imgui::widget_size_with_row_get(2); auto widgetSize = imgui::widget_size_with_row_get(2);
imgui::shortcut(settings.shortcutCenterView);
if (ImGui::Button("Center View", widgetSize)) pan = vec2(); if (ImGui::Button("Center View", widgetSize)) pan = vec2();
imgui::set_item_tooltip_shortcut("Centers the view.", settings.shortcutCenterView);
ImGui::SameLine(); ImGui::SameLine();
ImGui::Button("Fit", widgetSize);
imgui::shortcut(settings.shortcutFit);
if (ImGui::Button("Fit", widgetSize))
if (animation) set_to_rect(zoom, pan, animation->rect(isRootTransform));
imgui::set_item_tooltip_shortcut("Set the view to match the extent of the animation.", settings.shortcutFit);
ImGui::TextUnformatted(std::format(POSITION_FORMAT, (int)mousePos.x, (int)mousePos.y).c_str()); ImGui::TextUnformatted(std::format(POSITION_FORMAT, (int)mousePos.x, (int)mousePos.y).c_str());
} }
ImGui::EndChild(); ImGui::EndChild();
@@ -181,9 +188,8 @@ namespace anm2ed::animation_preview
auto layerTransform = transform * math::quad_model_get(frame.size, frame.position, frame.pivot, auto layerTransform = transform * math::quad_model_get(frame.size, frame.position, frame.pivot,
math::percent_to_unit(frame.scale), frame.rotation); math::percent_to_unit(frame.scale), frame.rotation);
auto inset = 0.5f / vec2(texture.size); // needed to avoid bleed auto uvMin = frame.crop / vec2(texture.size);
auto uvMin = frame.crop / vec2(texture.size) + inset; auto uvMax = (frame.crop + frame.size) / vec2(texture.size);
auto uvMax = (frame.crop + frame.size) / vec2(texture.size) - inset;
auto vertices = math::uv_vertices_get(uvMin, uvMax); auto vertices = math::uv_vertices_get(uvMin, uvMax);
vec3 frameColorOffset = frame.offset + colorOffset; vec3 frameColorOffset = frame.offset + colorOffset;
vec4 frameTint = frame.tint; vec4 frameTint = frame.tint;
@@ -298,15 +304,24 @@ namespace anm2ed::animation_preview
mousePos = position_translate(zoom, pan, to_vec2(ImGui::GetMousePos()) - to_vec2(cursorScreenPos)); mousePos = position_translate(zoom, pan, to_vec2(ImGui::GetMousePos()) - to_vec2(cursorScreenPos));
auto isRound = settings.propertiesIsRound; auto isMouseClick = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
auto isMouseReleased = ImGui::IsMouseReleased(ImGuiMouseButton_Left);
auto isMouseDown = ImGui::IsMouseDown(ImGuiMouseButton_Left); auto isMouseDown = ImGui::IsMouseDown(ImGuiMouseButton_Left);
auto isMouseMiddleDown = ImGui::IsMouseDown(ImGuiMouseButton_Middle); auto isMouseMiddleDown = ImGui::IsMouseDown(ImGuiMouseButton_Middle);
auto isLeftPressed = ImGui::IsKeyPressed(ImGuiKey_LeftArrow, false);
auto isRightPressed = ImGui::IsKeyPressed(ImGuiKey_RightArrow, false);
auto isUpPressed = ImGui::IsKeyPressed(ImGuiKey_UpArrow, false);
auto isDownPressed = ImGui::IsKeyPressed(ImGuiKey_DownArrow, false);
auto isLeftReleased = ImGui::IsKeyReleased(ImGuiKey_LeftArrow);
auto isRightReleased = ImGui::IsKeyReleased(ImGuiKey_RightArrow);
auto isUpReleased = ImGui::IsKeyReleased(ImGuiKey_UpArrow);
auto isDownReleased = ImGui::IsKeyReleased(ImGuiKey_DownArrow);
auto isLeft = imgui::chord_repeating(ImGuiKey_LeftArrow); auto isLeft = imgui::chord_repeating(ImGuiKey_LeftArrow);
auto isRight = imgui::chord_repeating(ImGuiKey_RightArrow); auto isRight = imgui::chord_repeating(ImGuiKey_RightArrow);
auto isUp = imgui::chord_repeating(ImGuiKey_UpArrow); auto isUp = imgui::chord_repeating(ImGuiKey_UpArrow);
auto isDown = imgui::chord_repeating(ImGuiKey_DownArrow); auto isDown = imgui::chord_repeating(ImGuiKey_DownArrow);
auto isMouseRightDown = ImGui::IsMouseDown(ImGuiMouseButton_Right); auto isMouseRightDown = ImGui::IsMouseDown(ImGuiMouseButton_Right);
auto mouseDelta = to_vec2(ImGui::GetIO().MouseDelta); auto mouseDelta = to_ivec2(ImGui::GetIO().MouseDelta);
auto mouseWheel = ImGui::GetIO().MouseWheel; auto mouseWheel = ImGui::GetIO().MouseWheel;
auto isZoomIn = imgui::chord_repeating(imgui::string_to_chord(settings.shortcutZoomIn)); auto isZoomIn = imgui::chord_repeating(imgui::string_to_chord(settings.shortcutZoomIn));
auto isZoomOut = imgui::chord_repeating(imgui::string_to_chord(settings.shortcutZoomOut)); auto isZoomOut = imgui::chord_repeating(imgui::string_to_chord(settings.shortcutZoomOut));
@@ -314,7 +329,10 @@ namespace anm2ed::animation_preview
auto frame = document.frame_get(); auto frame = document.frame_get();
auto useTool = tool; auto useTool = tool;
auto step = isMod ? step::FAST : step::NORMAL; auto step = isMod ? step::FAST : step::NORMAL;
auto isClick = isMouseDown; auto isKeyPressed = isLeftPressed || isRightPressed || isUpPressed || isDownPressed;
auto isKeyReleased = isLeftReleased || isRightReleased || isUpReleased || isDownReleased;
auto isBegin = isMouseClick || isKeyPressed;
auto isEnd = isMouseReleased || isKeyReleased;
if (isMouseMiddleDown) useTool = tool::PAN; if (isMouseMiddleDown) useTool = tool::PAN;
if (tool == tool::MOVE && isMouseRightDown) useTool = tool::SCALE; if (tool == tool::MOVE && isMouseRightDown) useTool = tool::SCALE;
@@ -323,30 +341,42 @@ namespace anm2ed::animation_preview
switch (useTool) switch (useTool)
{ {
case tool::PAN: case tool::PAN:
if (isClick || isMouseMiddleDown) pan += isRound ? vec2(ivec2(mouseDelta)) : mouseDelta; if (isMouseDown || isMouseMiddleDown) pan += mouseDelta;
break; break;
case tool::MOVE: case tool::MOVE:
if (!frame) break; if (!frame) break;
if (isClick) frame->position = isRound ? vec2(ivec2(mousePos)) : mousePos; if (isBegin) document.snapshot("Frame Position");
if (isMouseDown) frame->position = mousePos;
if (isLeft) frame->position.x -= step; if (isLeft) frame->position.x -= step;
if (isRight) frame->position.x += step; if (isRight) frame->position.x += step;
if (isUp) frame->position.y -= step; if (isUp) frame->position.y -= step;
if (isDown) frame->position.y += step; if (isDown) frame->position.y += step;
if (isEnd) document.change(change::FRAMES);
break; break;
case tool::SCALE: case tool::SCALE:
if (!frame) break; if (!frame) break;
if (isClick) frame->scale += isRound ? vec2(ivec2(mouseDelta)) : mouseDelta; if (isBegin) document.snapshot("Frame Scale");
if (isMouseDown) frame->scale += mouseDelta;
if (isLeft) frame->scale.x -= step;
if (isRight) frame->scale.x += step;
if (isUp) frame->scale.y -= step;
if (isDown) frame->scale.y += step;
if (isEnd) document.change(change::FRAMES);
break; break;
case tool::ROTATE: case tool::ROTATE:
if (!frame) break; if (!frame) break;
if (isClick) frame->rotation += isRound ? (int)mouseDelta.y : mouseDelta.y; if (isBegin) document.snapshot("Frame Rotation");
if (isMouseDown) frame->rotation += mouseDelta.y;
if (isLeft || isDown) frame->rotation -= step;
if (isUp || isRight) frame->rotation += step;
if (isEnd) document.change(change::FRAMES);
break; break;
default: default:
break; break;
} }
if (mouseWheel != 0 || isZoomIn || isZoomOut) if (mouseWheel != 0 || isZoomIn || isZoomOut)
zoom_set(zoom, pan, mousePos, (mouseWheel > 0 || isZoomIn) ? zoomStep : -zoomStep); zoom_set(zoom, pan, vec2(mousePos), (mouseWheel > 0 || isZoomIn) ? zoomStep : -zoomStep);
} }
} }
ImGui::End(); ImGui::End();

View File

@@ -10,7 +10,7 @@ namespace anm2ed::animation_preview
class AnimationPreview : public canvas::Canvas class AnimationPreview : public canvas::Canvas
{ {
bool isPreviewHovered{}; bool isPreviewHovered{};
glm::vec2 mousePos{}; glm::ivec2 mousePos{};
public: public:
AnimationPreview(); AnimationPreview();

View File

@@ -797,6 +797,50 @@ namespace anm2ed::anm2
return std::string(printer.CStr()); return std::string(printer.CStr());
} }
vec4 Animation::rect(bool isRootTransform)
{
f32 minX = std::numeric_limits<f32>::infinity();
f32 minY = std::numeric_limits<f32>::infinity();
f32 maxX = -std::numeric_limits<f32>::infinity();
f32 maxY = -std::numeric_limits<f32>::infinity();
bool any = false;
constexpr ivec2 CORNERS[4] = {{0, 0}, {1, 0}, {1, 1}, {0, 1}};
for (float t = 0.0f; t < (float)frameNum; t += 1.0f)
{
mat4 transform(1.0f);
if (isRootTransform)
{
auto root = rootAnimation.frame_generate(t, anm2::ROOT);
transform *= math::quad_model_parent_get(root.position, {}, math::percent_to_unit(root.scale), root.rotation);
}
for (auto& [id, layerAnimation] : layerAnimations)
{
auto frame = layerAnimation.frame_generate(t, anm2::LAYER);
if (frame.size == vec2() || !frame.isVisible) continue;
auto layerTransform = transform * math::quad_model_get(frame.size, frame.position, frame.pivot,
math::percent_to_unit(frame.scale), frame.rotation);
for (auto& corner : CORNERS)
{
vec4 world = layerTransform * vec4(corner, 0.0f, 1.0f);
minX = std::min(minX, world.x);
minY = std::min(minY, world.y);
maxX = std::max(maxX, world.x);
maxY = std::max(maxY, world.y);
any = true;
}
}
}
if (!any) return vec4(-1.0f);
return {minX, minY, maxX - minX, maxY - minY};
}
Animations::Animations() = default; Animations::Animations() = default;
Animations::Animations(XMLElement* element) Animations::Animations(XMLElement* element)

View File

@@ -208,6 +208,7 @@ namespace anm2ed::anm2
void item_remove(Type, int = -1); void item_remove(Type, int = -1);
void serialize(tinyxml2::XMLDocument&, tinyxml2::XMLElement*); void serialize(tinyxml2::XMLDocument&, tinyxml2::XMLElement*);
int length(); int length();
glm::vec4 rect(bool);
std::string to_string(); std::string to_string();
}; };

View File

@@ -3,6 +3,7 @@
#include "math.h" #include "math.h"
#include <glm/ext/matrix_clip_space.hpp> #include <glm/ext/matrix_clip_space.hpp>
#include <glm/ext/matrix_transform.hpp> #include <glm/ext/matrix_transform.hpp>
#include <glm/gtc/matrix_inverse.hpp>
#include <glm/gtc/type_ptr.hpp> #include <glm/gtc/type_ptr.hpp>
using namespace glm; using namespace glm;
@@ -183,13 +184,11 @@ namespace anm2ed::canvas
void Canvas::grid_render(Shader& shader, float zoom, vec2 pan, ivec2 size, ivec2 offset, vec4 color) void Canvas::grid_render(Shader& shader, float zoom, vec2 pan, ivec2 size, ivec2 offset, vec4 color)
{ {
auto zoomFactor = math::percent_to_unit(zoom); auto transform = glm::inverse(transform_get(zoom, pan));
glUseProgram(shader.id); glUseProgram(shader.id);
glUniform2f(glGetUniformLocation(shader.id, shader::UNIFORM_VIEW_SIZE), this->size.x, this->size.y); glUniformMatrix4fv(glGetUniformLocation(shader.id, shader::UNIFORM_TRANSFORM), 1, GL_FALSE, value_ptr(transform));
glUniform2f(glGetUniformLocation(shader.id, shader::UNIFORM_PAN), pan.x, pan.y);
glUniform1f(glGetUniformLocation(shader.id, shader::UNIFORM_ZOOM), zoomFactor);
glUniform2f(glGetUniformLocation(shader.id, shader::UNIFORM_SIZE), (float)size.x, (float)size.y); glUniform2f(glGetUniformLocation(shader.id, shader::UNIFORM_SIZE), (float)size.x, (float)size.y);
glUniform2f(glGetUniformLocation(shader.id, shader::UNIFORM_OFFSET), (float)offset.x, (float)offset.y); glUniform2f(glGetUniformLocation(shader.id, shader::UNIFORM_OFFSET), (float)offset.x, (float)offset.y);
glUniform4f(glGetUniformLocation(shader.id, shader::UNIFORM_COLOR), color.r, color.g, color.b, color.a); glUniform4f(glGetUniformLocation(shader.id, shader::UNIFORM_COLOR), color.r, color.g, color.b, color.a);
@@ -262,7 +261,7 @@ namespace anm2ed::canvas
glBindFramebuffer(GL_FRAMEBUFFER, 0); glBindFramebuffer(GL_FRAMEBUFFER, 0);
} }
void Canvas::zoom_set(float& zoom, vec2& pan, vec2& focus, float step) void Canvas::zoom_set(float& zoom, vec2& pan, vec2 focus, float step)
{ {
auto zoomFactor = math::percent_to_unit(zoom); auto zoomFactor = math::percent_to_unit(zoom);
float newZoom = glm::clamp(math::round_nearest_multiple(zoom + step, step), canvas::ZOOM_MIN, canvas::ZOOM_MAX); float newZoom = glm::clamp(math::round_nearest_multiple(zoom + step, step), canvas::ZOOM_MIN, canvas::ZOOM_MAX);
@@ -279,4 +278,19 @@ namespace anm2ed::canvas
auto zoomFactor = math::percent_to_unit(zoom); auto zoomFactor = math::percent_to_unit(zoom);
return (position - pan - (size * 0.5f)) / zoomFactor; return (position - pan - (size * 0.5f)) / zoomFactor;
} }
void Canvas::set_to_rect(float& zoom, vec2& pan, vec4 rect)
{
if (rect != vec4(-1.0f) && (rect.z > 0 && rect.w > 0))
{
f32 scaleX = size.x / rect.z;
f32 scaleY = size.y / rect.w;
f32 fitScale = std::min(scaleX, scaleY);
zoom = math::unit_to_percent(fitScale);
vec2 rectCenter = {rect.x + rect.z * 0.5f, rect.y + rect.w * 0.5f};
pan = -rectCenter * fitScale;
}
}
} }

View File

@@ -9,6 +9,7 @@ namespace anm2ed::canvas
{ {
constexpr float TEXTURE_VERTICES[] = {0, 0, 0.0f, 0.0f, 1, 0, 1.0f, 0.0f, 1, 1, 1.0f, 1.0f, 0, 1, 0.0f, 1.0f}; constexpr float TEXTURE_VERTICES[] = {0, 0, 0.0f, 0.0f, 1, 0, 1.0f, 0.0f, 1, 1, 1.0f, 1.0f, 0, 1, 0.0f, 1.0f};
constexpr auto PIVOT_SIZE = glm::vec2(8, 8);
constexpr auto ZOOM_MIN = 1.0f; constexpr auto ZOOM_MIN = 1.0f;
constexpr auto ZOOM_MAX = 2000.0f; constexpr auto ZOOM_MAX = 2000.0f;
constexpr auto POSITION_FORMAT = "Position: ({:8} {:8})"; constexpr auto POSITION_FORMAT = "Position: ({:8} {:8})";
@@ -49,7 +50,8 @@ namespace anm2ed::canvas
void clear(glm::vec4&); void clear(glm::vec4&);
void bind(); void bind();
void unbind(); void unbind();
void zoom_set(float&, glm::vec2&, glm::vec2&, float); void zoom_set(float&, glm::vec2&, glm::vec2, float);
glm::vec2 position_translate(float&, glm::vec2&, glm::vec2); glm::vec2 position_translate(float&, glm::vec2&, glm::vec2);
void set_to_rect(float& zoom, glm::vec2& pan, glm::vec4 rect);
}; };
} }

View File

@@ -13,6 +13,8 @@ using namespace anm2ed::toast;
using namespace anm2ed::types; using namespace anm2ed::types;
using namespace anm2ed::util; using namespace anm2ed::util;
using namespace glm;
namespace anm2ed::document namespace anm2ed::document
{ {
Document::Document(const std::string& path, bool isNew, std::string* errorString) Document::Document(const std::string& path, bool isNew, std::string* errorString)
@@ -158,6 +160,110 @@ namespace anm2ed::document
change(change::FRAMES); change(change::FRAMES);
} }
void Document::frame_crop_set(anm2::Frame* frame, vec2 crop)
{
if (!frame) return;
snapshot("Frame Crop");
frame->crop = crop;
change(change::FRAMES);
}
void Document::frame_size_set(anm2::Frame* frame, vec2 size)
{
if (!frame) return;
snapshot("Frame Size");
frame->size = size;
change(change::FRAMES);
}
void Document::frame_position_set(anm2::Frame* frame, vec2 position)
{
if (!frame) return;
snapshot("Frame Position");
frame->position = position;
change(change::FRAMES);
}
void Document::frame_pivot_set(anm2::Frame* frame, vec2 pivot)
{
if (!frame) return;
snapshot("Frame Pivot");
frame->pivot = pivot;
change(change::FRAMES);
}
void Document::frame_scale_set(anm2::Frame* frame, vec2 scale)
{
if (!frame) return;
snapshot("Frame Scale");
frame->scale = scale;
change(change::FRAMES);
}
void Document::frame_rotation_set(anm2::Frame* frame, float rotation)
{
if (!frame) return;
snapshot("Frame Rotation");
frame->rotation = rotation;
change(change::FRAMES);
}
void Document::frame_delay_set(anm2::Frame* frame, int delay)
{
if (!frame) return;
snapshot("Frame Delay");
frame->delay = delay;
change(change::FRAMES);
}
void Document::frame_tint_set(anm2::Frame* frame, vec4 tint)
{
if (!frame) return;
snapshot("Frame Tint");
frame->tint = tint;
change(change::FRAMES);
}
void Document::frame_offset_set(anm2::Frame* frame, vec3 offset)
{
if (!frame) return;
snapshot("Frame Color Offset");
frame->offset = offset;
change(change::FRAMES);
}
void Document::frame_is_visible_set(anm2::Frame* frame, bool isVisible)
{
if (!frame) return;
snapshot("Frame Visibility");
frame->isVisible = isVisible;
change(change::FRAMES);
}
void Document::frame_is_interpolated_set(anm2::Frame* frame, bool isInterpolated)
{
if (!frame) return;
snapshot("Frame Interpolation");
frame->isInterpolated = isInterpolated;
change(change::FRAMES);
}
void Document::frame_flip_x(anm2::Frame* frame)
{
if (!frame) return;
snapshot("Frame Flip X");
frame->scale.x = -frame->scale.x;
change(change::FRAMES);
}
void Document::frame_flip_y(anm2::Frame* frame)
{
if (!frame) return;
snapshot("Frame Flip Y");
frame->scale.y = -frame->scale.y;
change(change::FRAMES);
}
anm2::Item* Document::item_get() anm2::Item* Document::item_get()
{ {
return anm2.item_get(reference); return anm2.item_get(reference);
@@ -301,16 +407,6 @@ namespace anm2ed::document
toasts.error(std::format("Failed to deserialize event(s): {}", errorString)); toasts.error(std::format("Failed to deserialize event(s): {}", errorString));
} }
void Document::item_visible_toggle(anm2::Item* item)
{
if (!item) return;
snapshot("Item Visible");
item->isVisible = !item->isVisible;
change(change::ITEMS);
}
void Document::item_add(anm2::Type type, int id, std::string& name, locale::Type locale, int spritesheetID) void Document::item_add(anm2::Type type, int id, std::string& name, locale::Type locale, int spritesheetID)
{ {
snapshot("Add Item"); snapshot("Add Item");
@@ -330,15 +426,21 @@ namespace anm2ed::document
void Document::item_remove(anm2::Animation* animation) void Document::item_remove(anm2::Animation* animation)
{ {
snapshot("Remove Item");
if (!animation) return; if (!animation) return;
snapshot("Remove Item");
animation->item_remove(reference.itemType, reference.itemID); animation->item_remove(reference.itemType, reference.itemID);
reference = {reference.animationIndex}; reference = {reference.animationIndex};
change(change::ITEMS); change(change::ITEMS);
} }
void Document::item_visible_toggle(anm2::Item* item)
{
if (!item) return;
snapshot("Item Visibility");
item->isVisible = !item->isVisible;
change(change::ITEMS);
}
anm2::Animation* Document::animation_get() anm2::Animation* Document::animation_get()
{ {
return anm2.animation_get(reference); return anm2.animation_get(reference);

View File

@@ -73,12 +73,27 @@ namespace anm2ed::document
anm2::Frame* frame_get(); anm2::Frame* frame_get();
void frames_add(anm2::Item* item); void frames_add(anm2::Item* item);
void frames_change();
void frames_delete(anm2::Item* item); void frames_delete(anm2::Item* item);
void frames_bake(int, bool, bool); void frames_bake(int, bool, bool);
void frame_crop_set(anm2::Frame*, glm::vec2);
void frame_size_set(anm2::Frame*, glm::vec2);
void frame_position_set(anm2::Frame*, glm::vec2);
void frame_pivot_set(anm2::Frame*, glm::vec2);
void frame_scale_set(anm2::Frame*, glm::vec2);
void frame_rotation_set(anm2::Frame*, float);
void frame_delay_set(anm2::Frame*, int);
void frame_tint_set(anm2::Frame*, glm::vec4);
void frame_offset_set(anm2::Frame*, glm::vec3);
void frame_is_visible_set(anm2::Frame*, bool);
void frame_is_interpolated_set(anm2::Frame*, bool);
void frame_flip_x(anm2::Frame* frame);
void frame_flip_y(anm2::Frame* frame);
anm2::Item* item_get(); anm2::Item* item_get();
void item_add(anm2::Type, int, std::string&, types::locale::Type, int); void item_add(anm2::Type, int, std::string&, types::locale::Type, int);
void item_remove(anm2::Animation* animation); void item_remove(anm2::Animation* animation);
void item_visible_toggle(anm2::Item*);
anm2::Spritesheet* spritesheet_get(); anm2::Spritesheet* spritesheet_get();
void spritesheet_add(const std::string&); void spritesheet_add(const std::string&);
@@ -97,8 +112,6 @@ namespace anm2ed::document
void events_remove_unused(); void events_remove_unused();
void events_deserialize(const std::string&, types::merge::Type); void events_deserialize(const std::string&, types::merge::Type);
void item_visible_toggle(anm2::Item*);
void animation_add(); void animation_add();
void animation_duplicate(); void animation_duplicate();
void animation_default(); void animation_default();

View File

@@ -25,8 +25,8 @@ namespace anm2ed::frame_properties
auto& anm2 = document.anm2; auto& anm2 = document.anm2;
auto& reference = document.reference; auto& reference = document.reference;
auto& type = reference.itemType; auto& type = reference.itemType;
auto& isRound = settings.propertiesIsRound;
auto frame = document.frame_get(); auto frame = document.frame_get();
auto useFrame = frame ? *frame : anm2::Frame();
ImGui::BeginDisabled(!frame); ImGui::BeginDisabled(!frame);
{ {
@@ -44,77 +44,75 @@ namespace anm2ed::frame_properties
} }
else else
{ {
ImGui::BeginDisabled(type == anm2::ROOT || type == anm2::NULL_); ImGui::BeginDisabled(type == anm2::ROOT || type == anm2::NULL_);
{ {
if (ImGui::InputFloat2("Crop", frame ? value_ptr(frame->crop) : &dummy_value<float>(), if (ImGui::InputFloat2("Crop", frame ? value_ptr(useFrame.crop) : &dummy_value<float>(),
frame ? vec2_format_get(frame->crop) : "")) frame ? vec2_format_get(useFrame.crop) : ""))
if (isRound) frame->crop = ivec2(frame->crop); document.frame_crop_set(frame, useFrame.crop);
ImGui::SetItemTooltip("%s", "Change the crop position the frame uses."); ImGui::SetItemTooltip("%s", "Change the crop position the frame uses.");
if (ImGui::InputFloat2("Size", frame ? value_ptr(frame->size) : &dummy_value<float>(), if (ImGui::InputFloat2("Size", frame ? value_ptr(useFrame.size) : &dummy_value<float>(),
frame ? vec2_format_get(frame->size) : "")) frame ? vec2_format_get(useFrame.size) : ""))
if (isRound) frame->crop = ivec2(frame->size); document.frame_size_set(frame, useFrame.size);
ImGui::SetItemTooltip("%s", "Change the size of the crop the frame uses."); ImGui::SetItemTooltip("%s", "Change the size of the crop the frame uses.");
} }
ImGui::EndDisabled(); ImGui::EndDisabled();
if (ImGui::InputFloat2("Position", frame ? value_ptr(frame->position) : &dummy_value<float>(), if (ImGui::InputFloat2("Position", frame ? value_ptr(useFrame.position) : &dummy_value<float>(),
frame ? vec2_format_get(frame->position) : "")) frame ? vec2_format_get(useFrame.position) : ""))
if (isRound) frame->position = ivec2(frame->position); document.frame_position_set(frame, useFrame.position);
ImGui::SetItemTooltip("%s", "Change the position of the frame."); ImGui::SetItemTooltip("%s", "Change the position of the frame.");
ImGui::BeginDisabled(type == anm2::ROOT || type == anm2::NULL_); ImGui::BeginDisabled(type == anm2::ROOT || type == anm2::NULL_);
{ {
if (ImGui::InputFloat2("Pivot", frame ? value_ptr(frame->pivot) : &dummy_value<float>(), if (ImGui::InputFloat2("Pivot", frame ? value_ptr(useFrame.pivot) : &dummy_value<float>(),
frame ? vec2_format_get(frame->pivot) : "")) frame ? vec2_format_get(useFrame.pivot) : ""))
if (isRound) frame->position = ivec2(frame->position); document.frame_pivot_set(frame, useFrame.pivot);
ImGui::SetItemTooltip("%s", "Change the pivot of the frame; i.e., where it is centered."); ImGui::SetItemTooltip("%s", "Change the pivot of the frame; i.e., where it is centered.");
} }
ImGui::EndDisabled(); ImGui::EndDisabled();
if (ImGui::InputFloat2("Scale", frame ? value_ptr(frame->scale) : &dummy_value<float>(), if (ImGui::InputFloat2("Scale", frame ? value_ptr(useFrame.scale) : &dummy_value<float>(),
frame ? vec2_format_get(frame->scale) : "")) frame ? vec2_format_get(useFrame.scale) : ""))
if (isRound) frame->position = ivec2(frame->position); document.frame_scale_set(frame, useFrame.scale);
ImGui::SetItemTooltip("%s", "Change the scale of the frame, in percent."); ImGui::SetItemTooltip("%s", "Change the scale of the frame, in percent.");
if (ImGui::InputFloat("Rotation", frame ? &frame->rotation : &dummy_value<float>(), step::NORMAL, step::FAST, if (ImGui::InputFloat("Rotation", frame ? &useFrame.rotation : &dummy_value<float>(), step::NORMAL,
frame ? float_format_get(frame->rotation) : "")) step::FAST, frame ? float_format_get(useFrame.rotation) : ""))
if (isRound) frame->rotation = (int)frame->rotation; document.frame_rotation_set(frame, useFrame.rotation);
ImGui::SetItemTooltip("%s", "Change the rotation of the frame."); ImGui::SetItemTooltip("%s", "Change the rotation of the frame.");
ImGui::InputInt("Duration", frame ? &frame->delay : &dummy_value<int>(), step::NORMAL, step::FAST, if (ImGui::InputInt("Duration", frame ? &useFrame.delay : &dummy_value<int>(), step::NORMAL, step::FAST,
!frame ? ImGuiInputTextFlags_DisplayEmptyRefVal : 0); !frame ? ImGuiInputTextFlags_DisplayEmptyRefVal : 0))
document.frame_delay_set(frame, useFrame.delay);
ImGui::SetItemTooltip("%s", "Change how long the frame lasts."); ImGui::SetItemTooltip("%s", "Change how long the frame lasts.");
ImGui::ColorEdit4("Tint", frame ? value_ptr(frame->tint) : &dummy_value<float>()); if (ImGui::ColorEdit4("Tint", frame ? value_ptr(useFrame.tint) : &dummy_value<float>()))
document.frame_tint_set(frame, useFrame.tint);
ImGui::SetItemTooltip("%s", "Change the tint of the frame."); ImGui::SetItemTooltip("%s", "Change the tint of the frame.");
ImGui::ColorEdit3("Color Offset", frame ? value_ptr(frame->offset) : &dummy_value<float>());
if (ImGui::ColorEdit3("Color Offset", frame ? value_ptr(useFrame.offset) : &dummy_value<float>()))
document.frame_offset_set(frame, useFrame.offset);
ImGui::SetItemTooltip("%s", "Change the color added onto the frame."); ImGui::SetItemTooltip("%s", "Change the color added onto the frame.");
ImGui::Checkbox("Visible", frame ? &frame->isVisible : &dummy_value<bool>()); if (ImGui::Checkbox("Visible", frame ? &useFrame.isVisible : &dummy_value<bool>()))
document.frame_is_visible_set(frame, useFrame.isVisible);
ImGui::SetItemTooltip("%s", "Toggle the frame's visibility."); ImGui::SetItemTooltip("%s", "Toggle the frame's visibility.");
ImGui::SameLine(); ImGui::SameLine();
ImGui::Checkbox("Interpolated", frame ? &frame->isInterpolated : &dummy_value<bool>());
if (ImGui::Checkbox("Interpolated", frame ? &useFrame.isInterpolated : &dummy_value<bool>()))
document.frame_is_interpolated_set(frame, useFrame.isInterpolated);
ImGui::SetItemTooltip( ImGui::SetItemTooltip(
"%s", "Toggle the frame interpolating; i.e., blending its values into the next frame based on the time."); "%s", "Toggle the frame interpolating; i.e., blending its values into the next frame based on the time.");
ImGui::SameLine();
ImGui::EndDisabled();
ImGui::Checkbox("Round", &settings.propertiesIsRound);
ImGui::BeginDisabled(!frame);
ImGui::SetItemTooltip(
"%s", "When toggled, decimal values will be snapped to their nearest whole value when changed.");
auto widgetSize = imgui::widget_size_with_row_get(2); auto widgetSize = imgui::widget_size_with_row_get(2);
if (ImGui::Button("Flip X", widgetSize)) if (ImGui::Button("Flip X", widgetSize)) document.frame_flip_x(frame);
if (frame) frame->scale.x = -frame->scale.x;
ImGui::SetItemTooltip("%s", "Flip the horizontal scale of the frame, to cheat mirroring the frame " ImGui::SetItemTooltip("%s", "Flip the horizontal scale of the frame, to cheat mirroring the frame "
"horizontally.\n(Note: the format does not support mirroring.)"); "horizontally.\n(Note: the format does not support mirroring.)");
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::Button("Flip Y", widgetSize)) if (ImGui::Button("Flip Y", widgetSize)) document.frame_flip_y(frame);
if (frame) frame->scale.y = -frame->scale.y;
ImGui::SetItemTooltip("%s", "Flip the vertical scale of the frame, to cheat mirroring the frame " ImGui::SetItemTooltip("%s", "Flip the vertical scale of the frame, to cheat mirroring the frame "
"vertically.\n(Note: the format does not support mirroring.)"); "vertically.\n(Note: the format does not support mirroring.)");
} }

View File

@@ -236,8 +236,8 @@ namespace anm2ed::imgui
bool shortcut(std::string string, shortcut::Type type) bool shortcut(std::string string, shortcut::Type type)
{ {
if (ImGui::GetTopMostPopupModal() != nullptr) return false; if (ImGui::GetTopMostPopupModal() != nullptr) return false;
auto flags = type == shortcut::GLOBAL || type == shortcut::GLOBAL_SET ? ImGuiInputFlags_RouteGlobal int flags = type == shortcut::GLOBAL || type == shortcut::GLOBAL_SET ? ImGuiInputFlags_RouteGlobal
: ImGuiInputFlags_RouteFocused; : ImGuiInputFlags_RouteFocused;
if (type == shortcut::GLOBAL_SET || type == shortcut::FOCUSED_SET) if (type == shortcut::GLOBAL_SET || type == shortcut::FOCUSED_SET)
{ {
ImGui::SetNextItemShortcut(string_to_chord(string), flags); ImGui::SetNextItemShortcut(string_to_chord(string), flags);

View File

@@ -66,25 +66,24 @@ namespace anm2ed::shader
)"; )";
constexpr auto GRID_VERTEX = R"( constexpr auto GRID_VERTEX = R"(
#version 330 core #version 330 core
layout (location = 0) in vec2 i_position; layout (location = 0) in vec2 i_position;
layout (location = 1) in vec2 i_uv; layout (location = 1) in vec2 i_uv;
out vec2 i_uv_out; out vec2 i_uv_out;
void main() { void main()
i_uv_out = i_position; {
gl_Position = vec4(i_position, 0.0, 1.0); i_uv_out = i_position;
} gl_Position = vec4(i_position, 0.0, 1.0);
}
)"; )";
constexpr auto GRID_FRAGMENT = R"( constexpr auto GRID_FRAGMENT = R"(
#version 330 core #version 330 core
in vec2 i_uv_out; in vec2 i_uv_out;
uniform vec2 u_view_size; uniform mat4 u_transform;
uniform vec2 u_pan;
uniform float u_zoom;
uniform vec2 u_size; uniform vec2 u_size;
uniform vec2 u_offset; uniform vec2 u_offset;
uniform vec4 u_color; uniform vec4 u_color;
@@ -93,18 +92,15 @@ void main() {
void main() void main()
{ {
vec2 viewSize = max(u_view_size, vec2(1.0)); vec4 world4 = u_transform * vec4(i_uv_out, 0.0, 1.0);
float zoom = max(u_zoom, 1e-6); vec2 world = world4.xy / world4.w;
vec2 pan = u_pan;
vec2 world = (i_uv_out - (2.0 * pan / viewSize)) * (viewSize / (2.0 * zoom));
world += vec2(0.5); // Half pixel nudge
vec2 cell = max(u_size, vec2(1.0)); vec2 cell = max(u_size, vec2(1.0));
vec2 grid = (world - u_offset) / cell; vec2 grid = (world - u_offset) / cell;
vec2 d = abs(fract(grid) - 0.5); vec2 frac = fract(grid);
float distance = min(d.x, d.y); vec2 distToLine = min(frac, 1.0 - frac);
float distance = min(distToLine.x, distToLine.y);
float fw = min(fwidth(grid.x), fwidth(grid.y)); float fw = min(fwidth(grid.x), fwidth(grid.y));
float alpha = 1.0 - smoothstep(0.0, fw, distance); float alpha = 1.0 - smoothstep(0.0, fw, distance);
@@ -127,9 +123,6 @@ void main() {
constexpr auto UNIFORM_MODEL = "u_model"; constexpr auto UNIFORM_MODEL = "u_model";
constexpr auto UNIFORM_RECT_SIZE = "u_rect_size"; constexpr auto UNIFORM_RECT_SIZE = "u_rect_size";
constexpr auto UNIFORM_TEXTURE = "u_texture"; constexpr auto UNIFORM_TEXTURE = "u_texture";
constexpr auto UNIFORM_VIEW_SIZE = "u_view_size";
constexpr auto UNIFORM_PAN = "u_pan";
constexpr auto UNIFORM_ZOOM = "u_zoom";
enum Type enum Type
{ {

View File

@@ -21,6 +21,9 @@ namespace anm2ed::spritesheet_editor
void SpritesheetEditor::update(Manager& manager, Settings& settings, Resources& resources) void SpritesheetEditor::update(Manager& manager, Settings& settings, Resources& resources)
{ {
auto& document = *manager.get(); auto& document = *manager.get();
auto& anm2 = document.anm2;
auto& reference = document.reference;
auto& referenceSpritesheet = document.referenceSpritesheet;
auto& pan = document.editorPan; auto& pan = document.editorPan;
auto& zoom = document.editorZoom; auto& zoom = document.editorZoom;
auto& backgroundColor = settings.editorBackgroundColor; auto& backgroundColor = settings.editorBackgroundColor;
@@ -59,9 +62,16 @@ namespace anm2ed::spritesheet_editor
auto widgetSize = ImVec2(imgui::row_widget_width_get(2), 0); auto widgetSize = ImVec2(imgui::row_widget_width_get(2), 0);
if (ImGui::Button("Center View", widgetSize)) pan = vec2(); imgui::shortcut(settings.shortcutCenterView);
if (ImGui::Button("Center View", widgetSize)) pan = -size * 0.5f;
imgui::set_item_tooltip_shortcut("Centers the view.", settings.shortcutCenterView);
ImGui::SameLine(); ImGui::SameLine();
ImGui::Button("Fit", widgetSize);
imgui::shortcut(settings.shortcutFit);
if (ImGui::Button("Fit", widgetSize))
if (spritesheet) set_to_rect(zoom, pan, {0, 0, spritesheet->texture.size.x, spritesheet->texture.size.y});
imgui::set_item_tooltip_shortcut("Set the view to match the extent of the spritesheet.", settings.shortcutFit);
ImGui::TextUnformatted(std::format(POSITION_FORMAT, (int)mousePos.x, (int)mousePos.y).c_str()); ImGui::TextUnformatted(std::format(POSITION_FORMAT, (int)mousePos.x, (int)mousePos.y).c_str());
} }
@@ -84,12 +94,27 @@ namespace anm2ed::spritesheet_editor
viewport_set(); viewport_set();
clear(backgroundColor); clear(backgroundColor);
auto frame = document.frame_get();
if (spritesheet) if (spritesheet)
{ {
auto& texture = spritesheet->texture; auto& texture = spritesheet->texture;
auto transform = transform_get(zoom, pan) * math::quad_model_get(texture.size); auto transform = transform_get(zoom, pan);
texture_render(shaderTexture, texture.id, transform);
if (isBorder) rect_render(lineShader, transform); auto spritesheetTransform = transform * math::quad_model_get(texture.size);
texture_render(shaderTexture, texture.id, spritesheetTransform);
if (isBorder) rect_render(lineShader, spritesheetTransform);
if (frame && reference.itemID > -1 &&
anm2.content.layers.at(reference.itemID).spritesheetID == referenceSpritesheet)
{
auto cropTransform = transform * math::quad_model_get(frame->size, frame->crop);
rect_render(lineShader, cropTransform, color::RED);
auto pivotTransform =
transform * math::quad_model_get(canvas::PIVOT_SIZE, frame->crop + frame->pivot, PIVOT_SIZE * 0.5f);
texture_render(shaderTexture, resources.icons[icon::PIVOT].id, pivotTransform, color::RED);
}
} }
if (isGrid) grid_render(shaderGrid, zoom, pan, gridSize, gridOffset, gridColor); if (isGrid) grid_render(shaderGrid, zoom, pan, gridSize, gridOffset, gridColor);
@@ -102,19 +127,78 @@ namespace anm2ed::spritesheet_editor
{ {
ImGui::SetKeyboardFocusHere(-1); ImGui::SetKeyboardFocusHere(-1);
previousMousePos = mousePos;
mousePos = position_translate(zoom, pan, to_vec2(ImGui::GetMousePos()) - to_vec2(cursorScreenPos)); mousePos = position_translate(zoom, pan, to_vec2(ImGui::GetMousePos()) - to_vec2(cursorScreenPos));
auto isMouseClicked = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
auto isMouseDown = ImGui::IsMouseDown(ImGuiMouseButton_Left); auto isMouseDown = ImGui::IsMouseDown(ImGuiMouseButton_Left);
auto isMouseMiddleDown = ImGui::IsMouseDown(ImGuiMouseButton_Middle); auto isMouseMiddleDown = ImGui::IsMouseDown(ImGuiMouseButton_Middle);
auto mouseDelta = ImGui::GetIO().MouseDelta; auto mouseDelta = to_ivec2(ImGui::GetIO().MouseDelta);
auto mouseWheel = ImGui::GetIO().MouseWheel; auto mouseWheel = ImGui::GetIO().MouseWheel;
auto& toolColor = settings.toolColor;
auto isZoomIn = imgui::chord_repeating(imgui::string_to_chord(settings.shortcutZoomIn)); auto isZoomIn = imgui::chord_repeating(imgui::string_to_chord(settings.shortcutZoomIn));
auto isZoomOut = imgui::chord_repeating(imgui::string_to_chord(settings.shortcutZoomOut)); auto isZoomOut = imgui::chord_repeating(imgui::string_to_chord(settings.shortcutZoomOut));
auto isLeft = imgui::chord_repeating(ImGuiKey_LeftArrow);
auto isRight = imgui::chord_repeating(ImGuiKey_RightArrow);
auto isUp = imgui::chord_repeating(ImGuiKey_UpArrow);
auto isDown = imgui::chord_repeating(ImGuiKey_DownArrow);
auto isMod = ImGui::IsKeyDown(ImGuiMod_Shift);
auto step = isMod ? step::FAST : step::NORMAL;
auto useTool = tool;
auto isMouseClick = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
auto isMouseReleased = ImGui::IsMouseReleased(ImGuiMouseButton_Left);
auto isLeftPressed = ImGui::IsKeyPressed(ImGuiKey_LeftArrow, false);
auto isRightPressed = ImGui::IsKeyPressed(ImGuiKey_RightArrow, false);
auto isUpPressed = ImGui::IsKeyPressed(ImGuiKey_UpArrow, false);
auto isDownPressed = ImGui::IsKeyPressed(ImGuiKey_DownArrow, false);
auto isLeftReleased = ImGui::IsKeyReleased(ImGuiKey_LeftArrow);
auto isRightReleased = ImGui::IsKeyReleased(ImGuiKey_RightArrow);
auto isUpReleased = ImGui::IsKeyReleased(ImGuiKey_UpArrow);
auto isDownReleased = ImGui::IsKeyReleased(ImGuiKey_DownArrow);
auto frame = document.frame_get();
auto isKeyPressed = isLeftPressed || isRightPressed || isUpPressed || isDownPressed;
auto isKeyReleased = isLeftReleased || isRightReleased || isUpReleased || isDownReleased;
auto isBegin = isMouseClick || isKeyPressed;
auto isEnd = isMouseReleased || isKeyReleased;
if ((tool == tool::PAN && isMouseDown) || isMouseMiddleDown) pan += vec2(mouseDelta.x, mouseDelta.y); if (isMouseMiddleDown) useTool = tool::PAN;
switch (tool) switch (useTool)
{ {
case tool::PAN:
if (isMouseDown || isMouseMiddleDown) pan += mouseDelta;
break;
case tool::MOVE:
if (!frame) break;
if (isBegin) document.snapshot("Frame Pivot");
if (isMouseDown) frame->pivot = ivec2(mousePos - frame->crop);
if (isLeft) frame->pivot.x -= step;
if (isRight) frame->pivot.x += step;
if (isUp) frame->pivot.y -= step;
if (isDown) frame->pivot.y += step;
if (isEnd) document.change(change::FRAMES);
break;
case tool::CROP:
if (!frame) break;
if (isBegin) document.snapshot(isMod ? "Frame Size" : "Frame Crop");
if (isMouseClicked) frame->crop = ivec2(mousePos);
if (isMouseDown) frame->size = ivec2(mousePos - frame->crop);
if (isLeft) isMod ? frame->size.x -= step : frame->crop.x -= step;
if (isRight) isMod ? frame->size.x += step : frame->crop.x += step;
if (isUp) isMod ? frame->size.y -= step : frame->crop.y -= step;
if (isDown) isMod ? frame->size.y += step : frame->crop.y += step;
if (isEnd) document.change(change::FRAMES);
break;
case tool::DRAW:
case tool::ERASE:
{
if (!spritesheet) break;
if (isMouseClicked) document.snapshot(tool == tool::DRAW ? "Draw" : "Erase");
auto color = tool == tool::DRAW ? toolColor : vec4();
if (isMouseDown) spritesheet->texture.pixel_line(ivec2(previousMousePos), ivec2(mousePos), color);
if (isMouseReleased) document.change(change::FRAMES);
break;
}
default: default:
break; break;
} }

View File

@@ -10,6 +10,7 @@ namespace anm2ed::spritesheet_editor
class SpritesheetEditor : public canvas::Canvas class SpritesheetEditor : public canvas::Canvas
{ {
glm::vec2 mousePos{}; glm::vec2 mousePos{};
glm::vec2 previousMousePos{};
public: public:
SpritesheetEditor(); SpritesheetEditor();

View File

@@ -20,6 +20,9 @@
#pragma GCC diagnostic pop #pragma GCC diagnostic pop
#endif #endif
#include "math.h"
using namespace anm2ed::math;
using namespace glm; using namespace glm;
namespace anm2ed::texture namespace anm2ed::texture
@@ -140,6 +143,55 @@ namespace anm2ed::texture
return stbi_write_png(path.c_str(), size.x, size.y, CHANNELS, this->pixels.data(), size.x * CHANNELS); return stbi_write_png(path.c_str(), size.x, size.y, CHANNELS, this->pixels.data(), size.x * CHANNELS);
} }
void Texture::pixel_set(ivec2 position, vec4 color)
{
if (position.x < 0 || position.y < 0 || position.x >= size.x || position.y >= size.y) return;
uint8 rgba8[4] = {(uint8)float_to_uint8(color.r), (uint8)float_to_uint8(color.g), (uint8)float_to_uint8(color.b),
(uint8)float_to_uint8(color.a)};
glBindTexture(GL_TEXTURE_2D, id);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexSubImage2D(GL_TEXTURE_2D, 0, position.x, position.y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, rgba8);
glBindTexture(GL_TEXTURE_2D, 0);
}
void Texture::pixel_line(ivec2 start, ivec2 end, vec4 color)
{
auto plot = [&](ivec2 pos)
{
pixel_set(pos, color);
};
int x0 = start.x;
int y0 = start.y;
int x1 = end.x;
int y1 = end.y;
int dx = std::abs(x1 - x0);
int dy = -std::abs(y1 - y0);
int sx = x0 < x1 ? 1 : -1;
int sy = y0 < y1 ? 1 : -1;
int err = dx + dy;
while (true)
{
plot({x0, y0});
if (x0 == x1 && y0 == y1) break;
int e2 = 2 * err;
if (e2 >= dy)
{
err += dy;
x0 += sx;
}
if (e2 <= dx)
{
err += dx;
y0 += sy;
}
}
}
void Texture::bind(GLuint unit) void Texture::bind(GLuint unit)
{ {
glActiveTexture(GL_TEXTURE0 + unit); glActiveTexture(GL_TEXTURE0 + unit);

View File

@@ -33,6 +33,8 @@ namespace anm2ed::texture
Texture(const char*, size_t, glm::ivec2); Texture(const char*, size_t, glm::ivec2);
Texture(const std::string&); Texture(const std::string&);
bool write_png(const std::string&); bool write_png(const std::string&);
void pixel_set(glm::ivec2, glm::vec4);
void pixel_line(glm::ivec2, glm::ivec2, glm::vec4);
void bind(GLuint = 0); void bind(GLuint = 0);
void unbind(GLuint = 0); void unbind(GLuint = 0);
}; };

View File

@@ -46,8 +46,8 @@ namespace anm2ed::timeline
constexpr auto HELP_FORMAT = R"(- Press {} to decrement time. constexpr auto HELP_FORMAT = R"(- Press {} to decrement time.
- Press {} to increment time. - Press {} to increment time.
- Press {} to extend the selected frame, by one frame.
- Press {} to shorten the selected frame, by one frame. - Press {} to shorten the selected frame, by one frame.
- Press {} to extend the selected frame, by one frame.
- Hold Alt while clicking a non-trigger frame to toggle interpolation.)"; - Hold Alt while clicking a non-trigger frame to toggle interpolation.)";
void Timeline::item_child(Manager& manager, Document& document, anm2::Animation* animation, Settings& settings, void Timeline::item_child(Manager& manager, Document& document, anm2::Animation* animation, Settings& settings,
@@ -479,7 +479,8 @@ namespace anm2ed::timeline
if (ImGui::Button("##Frame Button", size)) if (ImGui::Button("##Frame Button", size))
{ {
if (type != anm2::TRIGGER && ImGui::IsKeyDown(ImGuiMod_Alt)) frame.isInterpolated = !frame.isInterpolated; if (type != anm2::TRIGGER && ImGui::IsKeyDown(ImGuiMod_Alt))
document.frame_is_interpolated_set(&frame, !frame.isInterpolated);
if (type == anm2::LAYER) if (type == anm2::LAYER)
{ {
document.referenceSpritesheet = anm2.content.layers[id].spritesheetID; document.referenceSpritesheet = anm2.content.layers[id].spritesheetID;