Autosave feature, spritesheet editor dashed lines, refactoring, fixes

This commit is contained in:
2025-10-28 15:47:54 -04:00
parent dd3aeae6d2
commit 729d5fb216
39 changed files with 1446 additions and 228 deletions

View File

@@ -63,13 +63,14 @@ if (WIN32)
target_link_options(${PROJECT_NAME} PRIVATE /STACK:0xffffff) target_link_options(${PROJECT_NAME} PRIVATE /STACK:0xffffff)
else () else ()
target_compile_options(${PROJECT_NAME} PRIVATE target_compile_options(${PROJECT_NAME} PRIVATE
-O2 -Wall -Wextra -pedantic -Wall -Wextra -pedantic
) )
if (CMAKE_BUILD_TYPE STREQUAL "Debug") if (CMAKE_BUILD_TYPE STREQUAL "Debug")
target_compile_definitions(${PROJECT_NAME} PRIVATE DEBUG) target_compile_definitions(${PROJECT_NAME} PRIVATE DEBUG)
target_compile_options(${PROJECT_NAME} PRIVATE -pg) target_compile_options(${PROJECT_NAME} PRIVATE -O0 -pg)
else () else ()
set(CMAKE_BUILD_TYPE "Release") set(CMAKE_BUILD_TYPE "Release")
target_compile_options(${PROJECT_NAME} PRIVATE -O2)
endif () endif ()
target_link_libraries(${PROJECT_NAME} PRIVATE m) target_link_libraries(${PROJECT_NAME} PRIVATE m)

View File

@@ -48,15 +48,18 @@ namespace anm2ed::animation_preview
auto& isRootTransform = settings.previewIsRootTransform; auto& isRootTransform = settings.previewIsRootTransform;
auto& isPivots = settings.previewIsPivots; auto& isPivots = settings.previewIsPivots;
auto& isAxes = settings.previewIsAxes; auto& isAxes = settings.previewIsAxes;
auto& isIcons = settings.previewIsIcons;
auto& isAltIcons = settings.previewIsAltIcons; auto& isAltIcons = settings.previewIsAltIcons;
auto& isBorder = settings.previewIsBorder; auto& isBorder = settings.previewIsBorder;
auto& tool = settings.tool; auto& tool = settings.tool;
auto& isOnlyShowLayers = settings.timelineIsOnlyShowLayers;
auto& shaderLine = resources.shaders[shader::LINE]; auto& shaderLine = resources.shaders[shader::LINE];
auto& shaderAxes = resources.shaders[shader::AXIS]; auto& shaderAxes = resources.shaders[shader::AXIS];
auto& shaderGrid = resources.shaders[shader::GRID]; auto& shaderGrid = resources.shaders[shader::GRID];
auto& shaderTexture = resources.shaders[shader::TEXTURE]; auto& shaderTexture = resources.shaders[shader::TEXTURE];
settings.previewPan = pan;
settings.previewZoom = zoom;
if (ImGui::Begin("Animation Preview", &settings.windowIsAnimationPreview)) if (ImGui::Begin("Animation Preview", &settings.windowIsAnimationPreview))
{ {
@@ -128,7 +131,6 @@ namespace anm2ed::animation_preview
{ {
ImGui::Checkbox("Root Transform", &isRootTransform); ImGui::Checkbox("Root Transform", &isRootTransform);
ImGui::Checkbox("Pivots", &isPivots); ImGui::Checkbox("Pivots", &isPivots);
ImGui::Checkbox("Icons", &isIcons);
} }
ImGui::EndChild(); ImGui::EndChild();
@@ -152,7 +154,8 @@ namespace anm2ed::animation_preview
if (isAxes) axes_render(shaderAxes, zoom, pan, axesColor); if (isAxes) axes_render(shaderAxes, zoom, pan, axesColor);
if (isGrid) grid_render(shaderGrid, zoom, pan, gridSize, gridOffset, gridColor); if (isGrid) grid_render(shaderGrid, zoom, pan, gridSize, gridOffset, gridColor);
auto render = [&](float time, vec3 colorOffset = {}, float alphaOffset = {}, bool isOnionskin = false) auto render = [&](anm2::Animation* animation, float time, vec3 colorOffset = {}, float alphaOffset = {},
bool isOnionskin = false)
{ {
auto transform = transform_get(zoom, pan); auto transform = transform_get(zoom, pan);
auto root = animation->rootAnimation.frame_generate(time, anm2::ROOT); auto root = animation->rootAnimation.frame_generate(time, anm2::ROOT);
@@ -160,7 +163,7 @@ namespace anm2ed::animation_preview
if (isRootTransform) if (isRootTransform)
transform *= math::quad_model_parent_get(root.position, {}, math::percent_to_unit(root.scale), root.rotation); transform *= math::quad_model_parent_get(root.position, {}, math::percent_to_unit(root.scale), root.rotation);
if (isIcons && root.isVisible && animation->rootAnimation.isVisible) if (!isOnlyShowLayers && root.isVisible && animation->rootAnimation.isVisible)
{ {
auto rootTransform = transform * math::quad_model_get(TARGET_SIZE, root.position, TARGET_SIZE * 0.5f, auto rootTransform = transform * math::quad_model_get(TARGET_SIZE, root.position, TARGET_SIZE * 0.5f,
math::percent_to_unit(root.scale), root.rotation); math::percent_to_unit(root.scale), root.rotation);
@@ -185,13 +188,14 @@ namespace anm2ed::animation_preview
auto& texture = spritesheet->texture; auto& texture = spritesheet->texture;
if (!texture.is_valid()) continue; if (!texture.is_valid()) continue;
auto layerTransform = transform * math::quad_model_get(frame.size, frame.position, frame.pivot, auto layerModel = 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 layerTransform = transform * layerModel;
auto uvMin = frame.crop / vec2(texture.size); auto uvMin = frame.crop / vec2(texture.size);
auto uvMax = (frame.crop + frame.size) / vec2(texture.size); auto uvMax = (frame.crop + frame.size) / vec2(texture.size);
auto vertices = math::uv_vertices_get(uvMin, uvMax); auto vertices = math::uv_vertices_get(uvMin, uvMax);
vec3 frameColorOffset = frame.offset + colorOffset; vec3 frameColorOffset = frame.colorOffset + colorOffset;
vec4 frameTint = frame.tint; vec4 frameTint = frame.tint;
frameTint.a = std::max(0.0f, frameTint.a - alphaOffset); frameTint.a = std::max(0.0f, frameTint.a - alphaOffset);
@@ -199,24 +203,22 @@ namespace anm2ed::animation_preview
auto color = isOnionskin ? vec4(colorOffset, 1.0f - alphaOffset) : color::RED; auto color = isOnionskin ? vec4(colorOffset, 1.0f - alphaOffset) : color::RED;
if (isBorder) rect_render(shaderLine, layerTransform, color); if (isBorder) rect_render(shaderLine, layerTransform, layerModel, color);
if (isPivots) if (isPivots)
{ {
auto pivotTransform = auto pivotModel = math::quad_model_get(PIVOT_SIZE, frame.position, PIVOT_SIZE * 0.5f,
transform * math::quad_model_get(PIVOT_SIZE, frame.position, PIVOT_SIZE * 0.5f,
math::percent_to_unit(frame.scale), frame.rotation); math::percent_to_unit(frame.scale), frame.rotation);
auto pivotTransform = transform * pivotModel;
texture_render(shaderTexture, resources.icons[icon::PIVOT].id, pivotTransform, color); texture_render(shaderTexture, resources.icons[icon::PIVOT].id, pivotTransform, color);
} }
} }
} }
if (isIcons)
{
for (auto& [id, nullAnimation] : animation->nullAnimations) for (auto& [id, nullAnimation] : animation->nullAnimations)
{ {
if (!nullAnimation.isVisible) continue; if (!nullAnimation.isVisible || isOnlyShowLayers) continue;
auto& isShowRect = anm2.content.nulls[id].isShowRect; auto& isShowRect = anm2.content.nulls[id].isShowRect;
@@ -228,19 +230,19 @@ namespace anm2ed::animation_preview
: id == reference.itemID && reference.itemType == anm2::NULL_ ? color::RED : id == reference.itemID && reference.itemType == anm2::NULL_ ? color::RED
: NULL_COLOR; : NULL_COLOR;
auto nullTransform = transform * math::quad_model_get(size, frame.position, size * 0.5f, auto nullModel = math::quad_model_get(size, frame.position, size * 0.5f, math::percent_to_unit(frame.scale),
math::percent_to_unit(frame.scale), frame.rotation); frame.rotation);
auto nullTransform = transform * nullModel;
texture_render(shaderTexture, resources.icons[icon].id, nullTransform, color); texture_render(shaderTexture, resources.icons[icon].id, nullTransform, color);
if (isShowRect) if (isShowRect)
{ {
auto rectTransform = auto rectModel = math::quad_model_get(NULL_RECT_SIZE, frame.position, NULL_RECT_SIZE * 0.5f,
transform * math::quad_model_get(NULL_RECT_SIZE, frame.position, NULL_RECT_SIZE * 0.5f,
math::percent_to_unit(frame.scale), frame.rotation); math::percent_to_unit(frame.scale), frame.rotation);
auto rectTransform = transform * rectModel;
rect_render(shaderLine, rectTransform, color); rect_render(shaderLine, rectTransform, rectModel, color);
}
} }
} }
} }
@@ -252,7 +254,7 @@ namespace anm2ed::animation_preview
{ {
float useTime = time + (float)(direction * i); float useTime = time + (float)(direction * i);
float alphaOffset = (1.0f / (count + 1)) * i; float alphaOffset = (1.0f / (count + 1)) * i;
render(useTime, color, alphaOffset, true); render(animation, useTime, color, alphaOffset, true);
} }
}; };
@@ -270,7 +272,10 @@ namespace anm2ed::animation_preview
auto& isEnabled = settings.onionskinIsEnabled; auto& isEnabled = settings.onionskinIsEnabled;
if (drawOrder == draw_order::BELOW && isEnabled) onionskins_render(frameTime); if (drawOrder == draw_order::BELOW && isEnabled) onionskins_render(frameTime);
render(frameTime); render(animation, frameTime);
if (overlayIndex > 0)
render(document.anm2.animation_get({overlayIndex - 1}), frameTime, {},
1.0f - math::uint8_to_float(overlayTransparency));
if (drawOrder == draw_order::ABOVE && isEnabled) onionskins_render(frameTime); if (drawOrder == draw_order::ABOVE && isEnabled) onionskins_render(frameTime);
} }

View File

@@ -20,6 +20,8 @@ namespace anm2ed::animations
auto& mergeMultiSelect = document.animationMergeMultiSelect; auto& mergeMultiSelect = document.animationMergeMultiSelect;
auto& mergeTarget = document.mergeTarget; auto& mergeTarget = document.mergeTarget;
hovered = -1;
if (ImGui::Begin("Animations", &settings.windowIsAnimations)) if (ImGui::Begin("Animations", &settings.windowIsAnimations))
{ {
auto childSize = imgui::size_without_footer_get(); auto childSize = imgui::size_without_footer_get();
@@ -45,7 +47,7 @@ namespace anm2ed::animations
if (imgui::selectable_input_text(animation.name, if (imgui::selectable_input_text(animation.name,
std::format("###Document #{} Animation #{}", manager.selected, i), std::format("###Document #{} Animation #{}", manager.selected, i),
animation.name, multiSelect.contains(i))) animation.name, multiSelect.contains(i)))
reference = {(int)i}; document.animation_set(i);
if (ImGui::IsItemHovered()) hovered = i; if (ImGui::IsItemHovered()) hovered = i;
ImGui::PopFont(); ImGui::PopFont();
@@ -113,18 +115,7 @@ namespace anm2ed::animations
auto cut = [&]() auto cut = [&]()
{ {
copy(); copy();
document.animations_remove();
if (!multiSelect.empty())
{
for (auto& i : multiSelect | std::views::reverse)
anm2.animations.items.erase(anm2.animations.items.begin() + i);
multiSelect.clear();
}
else if (hovered > -1)
{
anm2.animations.items.erase(anm2.animations.items.begin() + hovered);
hovered = -1;
}
}; };
auto paste = [&]() auto paste = [&]()
@@ -191,7 +182,7 @@ namespace anm2ed::animations
ImGui::SameLine(); ImGui::SameLine();
imgui::shortcut(settings.shortcutRemove); imgui::shortcut(settings.shortcutRemove);
if (ImGui::Button("Remove", widgetSize)) document.animation_remove(); if (ImGui::Button("Remove", widgetSize)) document.animations_remove();
imgui::set_item_tooltip_shortcut("Remove the selected animation(s).", settings.shortcutDuplicate); imgui::set_item_tooltip_shortcut("Remove the selected animation(s).", settings.shortcutDuplicate);
ImGui::SameLine(); ImGui::SameLine();

View File

@@ -17,17 +17,6 @@ using namespace glm;
namespace anm2ed::anm2 namespace anm2ed::anm2
{ {
void Reference::previous_frame(int max)
{
frameIndex = glm::clamp(--frameIndex, 0, max);
}
void Reference::next_frame(int max)
{
frameIndex = glm::clamp(++frameIndex, 0, max);
}
Info::Info() = default; Info::Info() = default;
Info::Info(XMLElement* element) Info::Info(XMLElement* element)
@@ -466,9 +455,9 @@ namespace anm2ed::anm2
xml::query_color_attribute(element, "GreenTint", tint.g); xml::query_color_attribute(element, "GreenTint", tint.g);
xml::query_color_attribute(element, "BlueTint", tint.b); xml::query_color_attribute(element, "BlueTint", tint.b);
xml::query_color_attribute(element, "AlphaTint", tint.a); xml::query_color_attribute(element, "AlphaTint", tint.a);
xml::query_color_attribute(element, "RedOffset", offset.r); xml::query_color_attribute(element, "RedOffset", colorOffset.r);
xml::query_color_attribute(element, "GreenOffset", offset.g); xml::query_color_attribute(element, "GreenOffset", colorOffset.g);
xml::query_color_attribute(element, "BlueOffset", offset.b); xml::query_color_attribute(element, "BlueOffset", colorOffset.b);
element->QueryFloatAttribute("Rotation", &rotation); element->QueryFloatAttribute("Rotation", &rotation);
element->QueryBoolAttribute("Interpolated", &isInterpolated); element->QueryBoolAttribute("Interpolated", &isInterpolated);
} }
@@ -497,9 +486,9 @@ namespace anm2ed::anm2
element->SetAttribute("GreenTint", math::float_to_uint8(tint.g)); element->SetAttribute("GreenTint", math::float_to_uint8(tint.g));
element->SetAttribute("BlueTint", math::float_to_uint8(tint.b)); element->SetAttribute("BlueTint", math::float_to_uint8(tint.b));
element->SetAttribute("AlphaTint", math::float_to_uint8(tint.a)); element->SetAttribute("AlphaTint", math::float_to_uint8(tint.a));
element->SetAttribute("RedOffset", math::float_to_uint8(offset.r)); element->SetAttribute("RedOffset", math::float_to_uint8(colorOffset.r));
element->SetAttribute("GreenOffset", math::float_to_uint8(offset.g)); element->SetAttribute("GreenOffset", math::float_to_uint8(colorOffset.g));
element->SetAttribute("BlueOffset", math::float_to_uint8(offset.b)); element->SetAttribute("BlueOffset", math::float_to_uint8(colorOffset.b));
element->SetAttribute("Rotation", rotation); element->SetAttribute("Rotation", rotation);
element->SetAttribute("Interpolated", isInterpolated); element->SetAttribute("Interpolated", isInterpolated);
break; break;
@@ -520,9 +509,9 @@ namespace anm2ed::anm2
element->SetAttribute("GreenTint", math::float_to_uint8(tint.g)); element->SetAttribute("GreenTint", math::float_to_uint8(tint.g));
element->SetAttribute("BlueTint", math::float_to_uint8(tint.b)); element->SetAttribute("BlueTint", math::float_to_uint8(tint.b));
element->SetAttribute("AlphaTint", math::float_to_uint8(tint.a)); element->SetAttribute("AlphaTint", math::float_to_uint8(tint.a));
element->SetAttribute("RedOffset", math::float_to_uint8(offset.r)); element->SetAttribute("RedOffset", math::float_to_uint8(colorOffset.r));
element->SetAttribute("GreenOffset", math::float_to_uint8(offset.g)); element->SetAttribute("GreenOffset", math::float_to_uint8(colorOffset.g));
element->SetAttribute("BlueOffset", math::float_to_uint8(offset.b)); element->SetAttribute("BlueOffset", math::float_to_uint8(colorOffset.b));
element->SetAttribute("Rotation", rotation); element->SetAttribute("Rotation", rotation);
element->SetAttribute("Interpolated", isInterpolated); element->SetAttribute("Interpolated", isInterpolated);
break; break;
@@ -537,6 +526,69 @@ namespace anm2ed::anm2
parent->InsertEndChild(element); parent->InsertEndChild(element);
} }
std::string Frame::to_string(Type type)
{
XMLDocument document;
auto element = document.NewElement(type == TRIGGER ? "Trigger" : "Frame");
switch (type)
{
case ROOT:
case NULL_:
element->SetAttribute("XPosition", position.x);
element->SetAttribute("YPosition", position.y);
element->SetAttribute("Delay", delay);
element->SetAttribute("Visible", isVisible);
element->SetAttribute("XScale", scale.x);
element->SetAttribute("YScale", scale.y);
element->SetAttribute("RedTint", math::float_to_uint8(tint.r));
element->SetAttribute("GreenTint", math::float_to_uint8(tint.g));
element->SetAttribute("BlueTint", math::float_to_uint8(tint.b));
element->SetAttribute("AlphaTint", math::float_to_uint8(tint.a));
element->SetAttribute("RedOffset", math::float_to_uint8(colorOffset.r));
element->SetAttribute("GreenOffset", math::float_to_uint8(colorOffset.g));
element->SetAttribute("BlueOffset", math::float_to_uint8(colorOffset.b));
element->SetAttribute("Rotation", rotation);
element->SetAttribute("Interpolated", isInterpolated);
break;
case LAYER:
element->SetAttribute("XPosition", position.x);
element->SetAttribute("YPosition", position.y);
element->SetAttribute("XPivot", pivot.x);
element->SetAttribute("YPivot", pivot.y);
element->SetAttribute("XCrop", crop.x);
element->SetAttribute("YCrop", crop.y);
element->SetAttribute("Width", size.x);
element->SetAttribute("Height", size.y);
element->SetAttribute("XScale", scale.x);
element->SetAttribute("YScale", scale.y);
element->SetAttribute("Delay", delay);
element->SetAttribute("Visible", isVisible);
element->SetAttribute("RedTint", math::float_to_uint8(tint.r));
element->SetAttribute("GreenTint", math::float_to_uint8(tint.g));
element->SetAttribute("BlueTint", math::float_to_uint8(tint.b));
element->SetAttribute("AlphaTint", math::float_to_uint8(tint.a));
element->SetAttribute("RedOffset", math::float_to_uint8(colorOffset.r));
element->SetAttribute("GreenOffset", math::float_to_uint8(colorOffset.g));
element->SetAttribute("BlueOffset", math::float_to_uint8(colorOffset.b));
element->SetAttribute("Rotation", rotation);
element->SetAttribute("Interpolated", isInterpolated);
break;
case TRIGGER:
element->SetAttribute("EventId", eventID);
element->SetAttribute("AtFrame", atFrame);
break;
default:
break;
}
document.InsertFirstChild(element);
XMLPrinter printer;
document.Print(&printer);
return std::string(printer.CStr());
}
void Frame::shorten() void Frame::shorten()
{ {
delay = glm::clamp(--delay, FRAME_DELAY_MIN, FRAME_DELAY_MAX); delay = glm::clamp(--delay, FRAME_DELAY_MIN, FRAME_DELAY_MAX);
@@ -640,13 +692,98 @@ namespace anm2ed::anm2
frame.rotation = glm::mix(frame.rotation, frameNext->rotation, interpolation); frame.rotation = glm::mix(frame.rotation, frameNext->rotation, interpolation);
frame.position = glm::mix(frame.position, frameNext->position, interpolation); frame.position = glm::mix(frame.position, frameNext->position, interpolation);
frame.scale = glm::mix(frame.scale, frameNext->scale, interpolation); frame.scale = glm::mix(frame.scale, frameNext->scale, interpolation);
frame.offset = glm::mix(frame.offset, frameNext->offset, interpolation); frame.colorOffset = glm::mix(frame.colorOffset, frameNext->colorOffset, interpolation);
frame.tint = glm::mix(frame.tint, frameNext->tint, interpolation); frame.tint = glm::mix(frame.tint, frameNext->tint, interpolation);
} }
return frame; return frame;
} }
void Item::frames_change(anm2::FrameChange& change, frame_change::Type type, int start, int numberFrames)
{
auto useStart = numberFrames > -1 ? start : 0;
auto end = numberFrames > -1 ? start + numberFrames : (int)frames.size();
vector::clamp_in_bounds(frames, useStart);
end = glm::clamp(end, start, (int)frames.size());
for (int i = useStart; i < end; i++)
{
Frame& frame = frames[i];
if (change.isVisible) frame.isVisible = *change.isVisible;
if (change.isInterpolated) frame.isInterpolated = *change.isInterpolated;
switch (type)
{
case frame_change::ADJUST:
if (change.rotation) frame.rotation = *change.rotation;
if (change.delay) frame.delay = std::max(FRAME_DELAY_MIN, *change.delay);
if (change.crop) frame.crop = *change.crop;
if (change.pivot) frame.pivot = *change.pivot;
if (change.position) frame.position = *change.position;
if (change.size) frame.size = *change.size;
if (change.scale) frame.scale = *change.scale;
if (change.colorOffset) frame.colorOffset = glm::clamp(*change.colorOffset, 0.0f, 1.0f);
if (change.tint) frame.tint = glm::clamp(*change.tint, 0.0f, 1.0f);
break;
case frame_change::ADD:
if (change.rotation) frame.rotation += *change.rotation;
if (change.delay) frame.delay = std::max(FRAME_DELAY_MIN, frame.delay + *change.delay);
if (change.crop) frame.crop += *change.crop;
if (change.pivot) frame.pivot += *change.pivot;
if (change.position) frame.position += *change.position;
if (change.size) frame.size += *change.size;
if (change.scale) frame.scale += *change.scale;
if (change.colorOffset) frame.colorOffset = glm::clamp(frame.colorOffset + *change.colorOffset, 0.0f, 1.0f);
if (change.tint) frame.tint = glm::clamp(frame.tint + *change.tint, 0.0f, 1.0f);
break;
case frame_change::SUBTRACT:
if (change.rotation) frame.rotation -= *change.rotation;
if (change.delay) frame.delay = std::max(FRAME_DELAY_MIN, frame.delay - *change.delay);
if (change.crop) frame.crop -= *change.crop;
if (change.pivot) frame.pivot -= *change.pivot;
if (change.position) frame.position -= *change.position;
if (change.size) frame.size -= *change.size;
if (change.scale) frame.scale -= *change.scale;
if (change.colorOffset) frame.colorOffset = glm::clamp(frame.colorOffset - *change.colorOffset, 0.0f, 1.0f);
if (change.tint) frame.tint = glm::clamp(frame.tint - *change.tint, 0.0f, 1.0f);
break;
}
}
}
bool Item::frames_deserialize(const std::string& string, Type type, int start, std::set<int>& indices,
std::string* errorString)
{
XMLDocument document{};
if (document.Parse(string.c_str()) == XML_SUCCESS)
{
if (!document.FirstChildElement("Frame"))
{
if (errorString) *errorString = "No valid frame(s).";
return false;
}
int count{};
for (auto element = document.FirstChildElement("Frame"); element; element = element->NextSiblingElement("Frame"))
{
auto index = start + count;
frames.insert(frames.begin() + start + count, Frame(element, type));
indices.insert(index);
count++;
}
return true;
}
else if (errorString)
*errorString = document.ErrorStr();
return false;
}
Animation::Animation() = default; Animation::Animation() = default;
Animation::Animation(XMLElement* element) Animation::Animation(XMLElement* element)
@@ -1048,12 +1185,12 @@ namespace anm2ed::anm2
return std::hash<std::string>{}(to_string()); return std::hash<std::string>{}(to_string());
} }
Animation* Anm2::animation_get(Reference& reference) Animation* Anm2::animation_get(Reference reference)
{ {
return vector::find(animations.items, reference.animationIndex); return vector::find(animations.items, reference.animationIndex);
} }
Item* Anm2::item_get(Reference& reference) Item* Anm2::item_get(Reference reference)
{ {
if (Animation* animation = animation_get(reference)) if (Animation* animation = animation_get(reference))
{ {
@@ -1074,7 +1211,7 @@ namespace anm2ed::anm2
return nullptr; return nullptr;
} }
Frame* Anm2::frame_get(Reference& reference) Frame* Anm2::frame_get(Reference reference)
{ {
Item* item = item_get(reference); Item* item = item_get(reference);
if (!item) return nullptr; if (!item) return nullptr;
@@ -1260,7 +1397,7 @@ namespace anm2ed::anm2
baked.rotation = glm::mix(baseFrame.rotation, baseFrameNext.rotation, interpolation); baked.rotation = glm::mix(baseFrame.rotation, baseFrameNext.rotation, interpolation);
baked.position = glm::mix(baseFrame.position, baseFrameNext.position, interpolation); baked.position = glm::mix(baseFrame.position, baseFrameNext.position, interpolation);
baked.scale = glm::mix(baseFrame.scale, baseFrameNext.scale, interpolation); baked.scale = glm::mix(baseFrame.scale, baseFrameNext.scale, interpolation);
baked.offset = glm::mix(baseFrame.offset, baseFrameNext.offset, interpolation); baked.colorOffset = glm::mix(baseFrame.colorOffset, baseFrameNext.colorOffset, interpolation);
baked.tint = glm::mix(baseFrame.tint, baseFrameNext.tint, interpolation); baked.tint = glm::mix(baseFrame.tint, baseFrameNext.tint, interpolation);
if (isRoundScale) baked.scale = vec2(ivec2(baked.scale)); if (isRoundScale) baked.scale = vec2(ivec2(baked.scale));
@@ -1275,4 +1412,26 @@ namespace anm2ed::anm2
delay += baked.delay; delay += baked.delay;
} }
} }
void Anm2::generate_from_grid(Reference reference, ivec2 startPosition, ivec2 size, ivec2 pivot, int columns,
int count, int delay)
{
auto item = item_get(reference);
if (!item) return;
for (int i = 0; i < count; i++)
{
auto row = i / columns;
auto column = i % columns;
Frame frame{};
frame.delay = delay;
frame.pivot = pivot;
frame.size = size;
frame.crop = startPosition + ivec2(size.x * column, size.y * row);
item->frames.emplace_back(frame);
}
}
} }

View File

@@ -44,8 +44,6 @@ namespace anm2ed::anm2
int frameIndex{-1}; int frameIndex{-1};
int frameTime{-1}; int frameTime{-1};
void previous_frame(int = FRAME_NUM_MAX - 1);
void next_frame(int = FRAME_NUM_MAX - 1);
auto operator<=>(const Reference&) const = default; auto operator<=>(const Reference&) const = default;
}; };
@@ -150,7 +148,7 @@ namespace anm2ed::anm2
X(position, glm::vec2, {}) \ X(position, glm::vec2, {}) \
X(size, glm::vec2, {}) \ X(size, glm::vec2, {}) \
X(scale, glm::vec2, glm::vec2(100.0f)) \ X(scale, glm::vec2, glm::vec2(100.0f)) \
X(offset, glm::vec3, types::color::TRANSPARENT) \ X(colorOffset, glm::vec3, types::color::TRANSPARENT) \
X(tint, glm::vec4, types::color::WHITE) X(tint, glm::vec4, types::color::WHITE)
class Frame class Frame
@@ -162,6 +160,7 @@ namespace anm2ed::anm2
Frame(); Frame();
Frame(tinyxml2::XMLElement*, Type); Frame(tinyxml2::XMLElement*, Type);
std::string to_string(Type type);
void serialize(tinyxml2::XMLDocument&, tinyxml2::XMLElement*, Type); void serialize(tinyxml2::XMLDocument&, tinyxml2::XMLElement*, Type);
void shorten(); void shorten();
void extend(); void extend();
@@ -188,6 +187,8 @@ namespace anm2ed::anm2
void serialize(tinyxml2::XMLDocument&, tinyxml2::XMLElement*, Type, int = -1); void serialize(tinyxml2::XMLDocument&, tinyxml2::XMLElement*, Type, int = -1);
int length(Type); int length(Type);
Frame frame_generate(float, Type); Frame frame_generate(float, Type);
void frames_change(anm2::FrameChange&, types::frame_change::Type, int, int = 0);
bool frames_deserialize(const std::string&, Type, int, std::set<int>&, std::string*);
}; };
class Animation class Animation
@@ -238,9 +239,9 @@ namespace anm2ed::anm2
std::string to_string(); std::string to_string();
Anm2(const std::string&, std::string* = nullptr); Anm2(const std::string&, std::string* = nullptr);
uint64_t hash(); uint64_t hash();
Animation* animation_get(Reference&); Animation* animation_get(Reference);
Item* item_get(Reference&); Item* item_get(Reference);
Frame* frame_get(Reference&); Frame* frame_get(Reference);
bool spritesheet_add(const std::string&, const std::string&, int&); bool spritesheet_add(const std::string&, const std::string&, int&);
Spritesheet* spritesheet_get(int); Spritesheet* spritesheet_get(int);
void spritesheet_remove(int); void spritesheet_remove(int);
@@ -255,5 +256,6 @@ namespace anm2ed::anm2
std::set<int> nulls_unused(Reference = REFERENCE_DEFAULT); std::set<int> nulls_unused(Reference = REFERENCE_DEFAULT);
std::vector<std::string> spritesheet_names_get(); std::vector<std::string> spritesheet_names_get();
void bake(Reference, int = 1, bool = true, bool = true); void bake(Reference, int = 1, bool = true, bool = true);
void generate_from_grid(Reference, glm::ivec2, glm::ivec2, glm::ivec2, int, int, int);
}; };
} }

View File

@@ -226,13 +226,35 @@ namespace anm2ed::canvas
glUseProgram(0); glUseProgram(0);
} }
void Canvas::rect_render(Shader& shader, mat4& transform, vec4 color) void Canvas::rect_render(Shader& shader, const mat4& transform, const mat4& model, vec4 color, float dashLength,
float dashGap, float dashOffset)
{ {
glUseProgram(shader.id); glUseProgram(shader.id);
glUniformMatrix4fv(glGetUniformLocation(shader.id, shader::UNIFORM_TRANSFORM), 1, GL_FALSE, value_ptr(transform)); glUniformMatrix4fv(glGetUniformLocation(shader.id, shader::UNIFORM_TRANSFORM), 1, GL_FALSE, value_ptr(transform));
if (auto location = glGetUniformLocation(shader.id, shader::UNIFORM_MODEL); location != -1)
glUniformMatrix4fv(location, 1, GL_FALSE, value_ptr(model));
glUniform4fv(glGetUniformLocation(shader.id, shader::UNIFORM_COLOR), 1, value_ptr(color)); glUniform4fv(glGetUniformLocation(shader.id, shader::UNIFORM_COLOR), 1, value_ptr(color));
auto origin = model * vec4(0.0f, 0.0f, 0.0f, 1.0f);
auto edgeX = model * vec4(1.0f, 0.0f, 0.0f, 1.0f);
auto edgeY = model * vec4(0.0f, 1.0f, 0.0f, 1.0f);
auto axisX = vec2(edgeX - origin);
auto axisY = vec2(edgeY - origin);
if (auto location = glGetUniformLocation(shader.id, shader::UNIFORM_AXIS_X); location != -1)
glUniform2fv(location, 1, value_ptr(axisX));
if (auto location = glGetUniformLocation(shader.id, shader::UNIFORM_AXIS_Y); location != -1)
glUniform2fv(location, 1, value_ptr(axisY));
if (auto location = glGetUniformLocation(shader.id, shader::UNIFORM_DASH_LENGTH); location != -1)
glUniform1f(location, dashLength);
if (auto location = glGetUniformLocation(shader.id, shader::UNIFORM_DASH_GAP); location != -1)
glUniform1f(location, dashGap);
if (auto location = glGetUniformLocation(shader.id, shader::UNIFORM_DASH_OFFSET); location != -1)
glUniform1f(location, dashOffset);
glBindVertexArray(rectVAO); glBindVertexArray(rectVAO);
glDrawArrays(GL_LINE_LOOP, 0, 4); glDrawArrays(GL_LINE_LOOP, 0, 4);

View File

@@ -14,6 +14,10 @@ namespace anm2ed::canvas
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})";
constexpr auto DASH_LENGTH = 4.0f;
constexpr auto DASH_GAP = 1.0f;
constexpr auto DASH_OFFSET = 1.0f;
class Canvas class Canvas
{ {
public: public:
@@ -39,13 +43,14 @@ namespace anm2ed::canvas
void framebuffer_set(); void framebuffer_set();
void framebuffer_resize_check(); void framebuffer_resize_check();
void size_set(glm::vec2); void size_set(glm::vec2);
glm::mat4 transform_get(float, glm::vec2); glm::mat4 transform_get(float = 100.0f, glm::vec2 = {});
void axes_render(shader::Shader&, float, glm::vec2, glm::vec4 = glm::vec4(1.0f)); void axes_render(shader::Shader&, float, glm::vec2, glm::vec4 = glm::vec4(1.0f));
void grid_render(shader::Shader&, float, glm::vec2, glm::ivec2 = glm::ivec2(32, 32), glm::ivec2 = {}, void grid_render(shader::Shader&, float, glm::vec2, glm::ivec2 = glm::ivec2(32, 32), glm::ivec2 = {},
glm::vec4 = glm::vec4(1.0f)); glm::vec4 = glm::vec4(1.0f));
void texture_render(shader::Shader&, GLuint&, glm::mat4&, glm::vec4 = glm::vec4(1.0f), glm::vec3 = {}, void texture_render(shader::Shader&, GLuint&, glm::mat4&, glm::vec4 = glm::vec4(1.0f), glm::vec3 = {},
float* = (float*)TEXTURE_VERTICES); float* = (float*)TEXTURE_VERTICES);
void rect_render(shader::Shader&, glm::mat4&, glm::vec4 = glm::vec4(1.0f)); void rect_render(shader::Shader&, const glm::mat4&, const glm::mat4&, glm::vec4 = glm::vec4(1.0f),
float dashLength = DASH_LENGTH, float dashGap = DASH_GAP, float dashOffset = DASH_OFFSET);
void viewport_set(); void viewport_set();
void clear(glm::vec4&); void clear(glm::vec4&);
void bind(); void bind();

View File

@@ -13,6 +13,7 @@ using namespace anm2ed::playback;
using namespace anm2ed::resources; using namespace anm2ed::resources;
using namespace anm2ed::settings; using namespace anm2ed::settings;
using namespace anm2ed::taskbar; using namespace anm2ed::taskbar;
using namespace anm2ed::welcome;
namespace anm2ed::dockspace namespace anm2ed::dockspace
{ {
@@ -31,10 +32,10 @@ namespace anm2ed::dockspace
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoBringToFrontOnFocus |
ImGuiWindowFlags_NoNavFocus)) ImGuiWindowFlags_NoNavFocus))
{
if (ImGui::DockSpace(ImGui::GetID("##DockSpace"), ImVec2(), ImGuiDockNodeFlags_PassthruCentralNode))
{ {
if (auto document = manager.get(); document) if (auto document = manager.get(); document)
{
if (ImGui::DockSpace(ImGui::GetID("##DockSpace"), ImVec2(), ImGuiDockNodeFlags_PassthruCentralNode))
{ {
if (settings.windowIsAnimationPreview) animationPreview.update(manager, settings, resources); if (settings.windowIsAnimationPreview) animationPreview.update(manager, settings, resources);
if (settings.windowIsAnimations) animations.update(manager, settings, resources, clipboard); if (settings.windowIsAnimations) animations.update(manager, settings, resources, clipboard);
@@ -45,10 +46,12 @@ namespace anm2ed::dockspace
if (settings.windowIsOnionskin) onionskin.update(settings); if (settings.windowIsOnionskin) onionskin.update(settings);
if (settings.windowIsSpritesheetEditor) spritesheetEditor.update(manager, settings, resources); if (settings.windowIsSpritesheetEditor) spritesheetEditor.update(manager, settings, resources);
if (settings.windowIsSpritesheets) spritesheets.update(manager, settings, resources, dialog, clipboard); if (settings.windowIsSpritesheets) spritesheets.update(manager, settings, resources, dialog, clipboard);
if (settings.windowIsTimeline) timeline.update(manager, settings, resources); if (settings.windowIsTimeline) timeline.update(manager, settings, resources, clipboard);
if (settings.windowIsTools) tools.update(manager, settings, resources); if (settings.windowIsTools) tools.update(manager, settings, resources);
} }
} }
else
welcome.update(manager, resources, dialog, taskbar, documents);
} }
ImGui::End(); ImGui::End();
} }

View File

@@ -13,6 +13,7 @@
#include "taskbar.h" #include "taskbar.h"
#include "timeline.h" #include "timeline.h"
#include "tools.h" #include "tools.h"
#include "welcome.h"
namespace anm2ed::dockspace namespace anm2ed::dockspace
{ {
@@ -29,6 +30,7 @@ namespace anm2ed::dockspace
spritesheets::Spritesheets spritesheets; spritesheets::Spritesheets spritesheets;
timeline::Timeline timeline; timeline::Timeline timeline;
tools::Tools tools; tools::Tools tools;
welcome::Welcome welcome;
public: public:
void update(taskbar::Taskbar&, documents::Documents&, manager::Manager&, settings::Settings&, resources::Resources&, void update(taskbar::Taskbar&, documents::Documents&, manager::Manager&, settings::Settings&, resources::Resources&,

View File

@@ -1,17 +1,20 @@
#include "document.h" #include "document.h"
#include <algorithm>
#include <ranges>
#include "anm2.h" #include "anm2.h"
#include "filesystem.h" #include "filesystem.h"
#include "log.h"
#include "toast.h" #include "toast.h"
#include "util.h" #include "util.h"
#include <algorithm>
#include <ranges>
using namespace anm2ed::anm2; using namespace anm2ed::anm2;
using namespace anm2ed::filesystem; using namespace anm2ed::filesystem;
using namespace anm2ed::toast; using namespace anm2ed::toast;
using namespace anm2ed::types; using namespace anm2ed::types;
using namespace anm2ed::util; using namespace anm2ed::util;
using namespace anm2ed::log;
using namespace glm; using namespace glm;
@@ -40,9 +43,28 @@ namespace anm2ed::document
if (anm2.serialize(this->path, errorString)) if (anm2.serialize(this->path, errorString))
{ {
toasts.info(std::format("Saved document to: {}", path));
clean(); clean();
return true; return true;
} }
else if (errorString)
toasts.warning(std::format("Could not save document to: {} ({})", path, *errorString));
return false;
}
bool Document::autosave(const std::string& path, std::string* errorString)
{
if (anm2.serialize(path, errorString))
{
autosaveHash = hash;
lastAutosaveTime = 0.0f;
toasts.info("Autosaving...");
logger.info(std::format("Autosaved document to: {}", path));
return true;
}
else if (errorString)
toasts.warning(std::format("Could not autosave document to: {} ({})", path, *errorString));
return false; return false;
} }
@@ -56,6 +78,8 @@ namespace anm2ed::document
{ {
saveHash = anm2.hash(); saveHash = anm2.hash();
hash = saveHash; hash = saveHash;
lastAutosaveTime = 0.0f;
isForceDirty = false;
} }
void Document::change(change::Type type) void Document::change(change::Type type)
@@ -88,6 +112,8 @@ namespace anm2ed::document
case change::SPRITESHEETS: case change::SPRITESHEETS:
spritesheet_set(); spritesheet_set();
break; break;
case change::ITEMS:
break;
case change::ALL: case change::ALL:
layer_set(); layer_set();
null_set(); null_set();
@@ -104,14 +130,19 @@ namespace anm2ed::document
return hash != saveHash; return hash != saveHash;
} }
std::string Document::directory_get() bool Document::is_autosave_dirty()
{
return hash != autosaveHash;
}
std::filesystem::path Document::directory_get()
{ {
return path.parent_path(); return path.parent_path();
} }
std::string Document::filename_get() std::filesystem::path Document::filename_get()
{ {
return path.filename().string(); return path.filename();
} }
bool Document::is_valid() bool Document::is_valid()
@@ -224,11 +255,11 @@ namespace anm2ed::document
change(change::FRAMES); change(change::FRAMES);
} }
void Document::frame_offset_set(anm2::Frame* frame, vec3 offset) void Document::frame_color_offset_set(anm2::Frame* frame, vec3 colorOffset)
{ {
if (!frame) return; if (!frame) return;
snapshot("Frame Color Offset"); snapshot("Frame Color Offset");
frame->offset = offset; frame->colorOffset = colorOffset;
change(change::FRAMES); change(change::FRAMES);
} }
@@ -264,6 +295,52 @@ namespace anm2ed::document
change(change::FRAMES); change(change::FRAMES);
} }
void Document::frame_shorten()
{
auto frame = frame_get();
if (!frame) return;
snapshot("Shorten Frame");
frame->shorten();
change(change::FRAMES);
}
void Document::frame_extend()
{
auto frame = frame_get();
if (!frame) return;
snapshot("Extend Frame");
frame->extend();
change(change::FRAMES);
}
void Document::frames_change(anm2::FrameChange& frameChange, frame_change::Type type, bool isFromSelectedFrame,
int numberFrames)
{
auto item = item_get();
if (!item) return;
snapshot("Change All Frame Properties");
item->frames_change(frameChange, type, isFromSelectedFrame && frame_get() ? reference.frameIndex : 0,
isFromSelectedFrame ? numberFrames : -1);
change(change::FRAMES);
}
void Document::frames_deserialize(const std::string& string)
{
if (auto item = item_get())
{
snapshot("Paste Frame(s)");
std::set<int> indices{};
std::string errorString{};
auto start = reference.frameIndex + 1;
if (item->frames_deserialize(string, reference.itemType, start, indices, &errorString))
change(change::FRAMES);
else
toasts.error(std::format("Failed to deserialize frame(s): {}", errorString));
}
else
toasts.error(std::format("Failed to deserialize frame(s): select an item first!"));
}
anm2::Item* Document::item_get() anm2::Item* Document::item_get()
{ {
return anm2.item_get(reference); return anm2.item_get(reference);
@@ -446,6 +523,13 @@ namespace anm2ed::document
return anm2.animation_get(reference); return anm2.animation_get(reference);
} }
void Document::animation_set(int index)
{
snapshot("Select Animation");
reference = {index};
change(change::ITEMS);
}
void Document::animation_add() void Document::animation_add()
{ {
snapshot("Add Animation"); snapshot("Add Animation");
@@ -489,12 +573,21 @@ namespace anm2ed::document
change(change::ANIMATIONS); change(change::ANIMATIONS);
} }
void Document::animation_remove() void Document::animations_remove()
{ {
snapshot("Remove Animation(s)"); snapshot("Remove Animation(s)");
if (!animationMultiSelect.empty())
{
for (auto& i : animationMultiSelect | std::views::reverse) for (auto& i : animationMultiSelect | std::views::reverse)
anm2.animations.items.erase(anm2.animations.items.begin() + i); anm2.animations.items.erase(anm2.animations.items.begin() + i);
animationMultiSelect.clear(); animationMultiSelect.clear();
}
else if (hoveredAnimation > -1)
{
anm2.animations.items.erase(anm2.animations.items.begin() + hoveredAnimation);
hoveredAnimation = -1;
}
change(change::ANIMATIONS); change(change::ANIMATIONS);
} }
@@ -508,7 +601,7 @@ namespace anm2ed::document
void Document::animations_deserialize(const std::string& string) void Document::animations_deserialize(const std::string& string)
{ {
snapshot("Paste Animations"); snapshot("Paste Animation(s)");
auto& multiSelect = animationMultiSelect; auto& multiSelect = animationMultiSelect;
auto start = multiSelect.empty() ? anm2.animations.items.size() : *multiSelect.rbegin() + 1; auto start = multiSelect.empty() ? anm2.animations.items.size() : *multiSelect.rbegin() + 1;
std::set<int> indices{}; std::set<int> indices{};
@@ -522,9 +615,21 @@ namespace anm2ed::document
toasts.error(std::format("Failed to deserialize animation(s): {}", errorString)); toasts.error(std::format("Failed to deserialize animation(s): {}", errorString));
} }
void Document::generate_animation_from_grid(ivec2 startPosition, ivec2 size, ivec2 pivot, int columns, int count,
int delay)
{
snapshot("Generate Animation from Grid");
anm2.generate_from_grid(reference, startPosition, size, pivot, columns, count, delay);
if (auto animation = animation_get()) animation->frameNum = animation->length();
change(change::ALL);
}
void Document::animations_merge_quick() void Document::animations_merge_quick()
{ {
snapshot("Merge Animations"); snapshot("Merge Animation(s)");
int merged{}; int merged{};
if (animationMultiSelect.size() > 1) if (animationMultiSelect.size() > 1)
merged = anm2.animations.merge(*animationMultiSelect.begin(), animationMultiSelect); merged = anm2.animations.merge(*animationMultiSelect.begin(), animationMultiSelect);

View File

@@ -26,6 +26,7 @@ namespace anm2ed::document
glm::vec2 previewPan{}; glm::vec2 previewPan{};
glm::vec2 editorPan{}; glm::vec2 editorPan{};
float editorZoom{200}; float editorZoom{200};
int overlayIndex{}; int overlayIndex{};
anm2::Reference reference{}; anm2::Reference reference{};
@@ -34,6 +35,8 @@ namespace anm2ed::document
imgui::MultiSelectStorage animationMultiSelect; imgui::MultiSelectStorage animationMultiSelect;
imgui::MultiSelectStorage animationMergeMultiSelect; imgui::MultiSelectStorage animationMergeMultiSelect;
anm2::Reference hoveredFrame{anm2::REFERENCE_DEFAULT};
int referenceSpritesheet{-1}; int referenceSpritesheet{-1};
int hoveredSpritesheet{-1}; int hoveredSpritesheet{-1};
std::set<int> unusedSpritesheetIDs{}; std::set<int> unusedSpritesheetIDs{};
@@ -58,17 +61,22 @@ namespace anm2ed::document
uint64_t hash{}; uint64_t hash{};
uint64_t saveHash{}; uint64_t saveHash{};
uint64_t autosaveHash{};
double lastAutosaveTime{};
bool isOpen{true}; bool isOpen{true};
bool isForceDirty{false};
Document(const std::string&, bool = false, std::string* = nullptr); Document(const std::string&, bool = false, std::string* = nullptr);
bool save(const std::string& = {}, std::string* = nullptr); bool save(const std::string& = {}, std::string* = nullptr);
bool autosave(const std::string&, std::string* = nullptr);
void hash_set(); void hash_set();
void clean(); void clean();
void on_change(); void on_change();
void change(types::change::Type); void change(types::change::Type);
bool is_dirty(); bool is_dirty();
std::string directory_get(); bool is_autosave_dirty();
std::string filename_get(); std::filesystem::path directory_get();
std::filesystem::path filename_get();
bool is_valid(); bool is_valid();
anm2::Frame* frame_get(); anm2::Frame* frame_get();
@@ -84,11 +92,15 @@ namespace anm2ed::document
void frame_rotation_set(anm2::Frame*, float); void frame_rotation_set(anm2::Frame*, float);
void frame_delay_set(anm2::Frame*, int); void frame_delay_set(anm2::Frame*, int);
void frame_tint_set(anm2::Frame*, glm::vec4); void frame_tint_set(anm2::Frame*, glm::vec4);
void frame_offset_set(anm2::Frame*, glm::vec3); void frame_color_offset_set(anm2::Frame*, glm::vec3);
void frame_is_visible_set(anm2::Frame*, bool); void frame_is_visible_set(anm2::Frame*, bool);
void frame_is_interpolated_set(anm2::Frame*, bool); void frame_is_interpolated_set(anm2::Frame*, bool);
void frame_flip_x(anm2::Frame* frame); void frame_flip_x(anm2::Frame* frame);
void frame_flip_y(anm2::Frame* frame); void frame_flip_y(anm2::Frame* frame);
void frame_shorten();
void frame_extend();
void frames_change(anm2::FrameChange&, types::frame_change::Type, bool, int = -1);
void frames_deserialize(const std::string&);
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);
@@ -113,15 +125,18 @@ namespace anm2ed::document
void events_deserialize(const std::string&, types::merge::Type); void events_deserialize(const std::string&, types::merge::Type);
void animation_add(); void animation_add();
void animation_set(int);
void animation_duplicate(); void animation_duplicate();
void animation_default(); void animation_default();
void animation_remove(); void animations_remove();
void animations_move(std::vector<int>&, int); void animations_move(std::vector<int>&, int);
void animations_merge(types::merge::Type, bool); void animations_merge(types::merge::Type, bool);
void animations_merge_quick(); void animations_merge_quick();
anm2::Animation* animation_get(); anm2::Animation* animation_get();
void animations_deserialize(const std::string& string); void animations_deserialize(const std::string& string);
void generate_animation_from_grid(glm::ivec2, glm::ivec2, glm::ivec2, int, int, int);
void snapshot(const std::string& message); void snapshot(const std::string& message);
void undo(); void undo();
void redo(); void redo();

View File

@@ -1,16 +1,19 @@
#include "documents.h" #include "documents.h"
#include <ranges> #include <vector>
#include "imgui.h" #include "util.h"
using namespace anm2ed::taskbar; using namespace anm2ed::taskbar;
using namespace anm2ed::manager; using namespace anm2ed::manager;
using namespace anm2ed::settings;
using namespace anm2ed::resources; using namespace anm2ed::resources;
using namespace anm2ed::types;
using namespace anm2ed::util;
namespace anm2ed::documents namespace anm2ed::documents
{ {
void Documents::update(Taskbar& taskbar, Manager& manager, Resources& resources) void Documents::update(Taskbar& taskbar, Manager& manager, Settings& settings, Resources& resources, bool& isQuitting)
{ {
auto viewport = ImGui::GetMainViewport(); auto viewport = ImGui::GetMainViewport();
auto windowHeight = ImGui::GetFrameHeightWithSpacing(); auto windowHeight = ImGui::GetFrameHeightWithSpacing();
@@ -19,6 +22,14 @@ namespace anm2ed::documents
ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x, viewport->Pos.y + taskbar.height)); ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x, viewport->Pos.y + taskbar.height));
ImGui::SetNextWindowSize(ImVec2(viewport->Size.x, windowHeight)); ImGui::SetNextWindowSize(ImVec2(viewport->Size.x, windowHeight));
for (auto& document : manager.documents)
{
auto isDirty = document.is_dirty() && document.is_autosave_dirty();
document.lastAutosaveTime += ImGui::GetIO().DeltaTime;
if (isDirty && document.lastAutosaveTime > time::SECOND_S) manager.autosave(document);
}
if (ImGui::Begin("##Documents", nullptr, if (ImGui::Begin("##Documents", nullptr,
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoBringToFrontOnFocus |
@@ -29,14 +40,48 @@ namespace anm2ed::documents
if (ImGui::BeginTabBar("Documents Bar", ImGuiTabBarFlags_Reorderable)) if (ImGui::BeginTabBar("Documents Bar", ImGuiTabBarFlags_Reorderable))
{ {
for (auto [i, document] : std::views::enumerate(manager.documents)) auto documentsCount = (int)manager.documents.size();
bool closeShortcut = imgui::shortcut(settings.shortcutClose, shortcut::GLOBAL) && !closePopup.is_open();
int closeShortcutIndex =
closeShortcut && manager.selected >= 0 && manager.selected < documentsCount ? manager.selected : -1;
std::vector<int> closeIndices{};
closeIndices.reserve(documentsCount);
for (int i = 0; i < documentsCount; ++i)
{ {
auto isDirty = document.is_dirty(); auto& document = manager.documents[i];
auto isDirty = document.is_dirty() || document.isForceDirty;
if (!closePopup.is_open())
{
if (isQuitting)
document.isOpen = false;
else if (i == closeShortcutIndex)
document.isOpen = false;
}
if (!closePopup.is_open() && !document.isOpen)
{
if (isDirty)
{
closePopup.open();
closeDocumentIndex = i;
document.isOpen = true;
}
else
{
closeIndices.push_back(i);
continue;
}
}
auto isRequested = i == manager.pendingSelected; auto isRequested = i == manager.pendingSelected;
auto font = isDirty ? font::ITALICS : font::REGULAR; auto font = isDirty ? font::ITALICS : font::REGULAR;
auto string = isDirty ? std::format("[Not Saved] {}", document.filename_get()) : document.filename_get(); auto string = isDirty ? std::format("[Not Saved] {}", document.filename_get().string())
: document.filename_get().string();
auto label = std::format("{}###Document{}", string, i); auto label = std::format("{}###Document{}", string, i);
@@ -46,24 +91,17 @@ namespace anm2ed::documents
ImGui::PushFont(resources.fonts[font].get(), font::SIZE); ImGui::PushFont(resources.fonts[font].get(), font::SIZE);
if (ImGui::BeginTabItem(label.c_str(), &document.isOpen, flags)) if (ImGui::BeginTabItem(label.c_str(), &document.isOpen, flags))
{ {
manager.selected = i; manager.set(i);
if (isRequested) manager.pendingSelected = -1; if (isRequested) manager.pendingSelected = -1;
ImGui::EndTabItem(); ImGui::EndTabItem();
} }
ImGui::PopFont(); ImGui::PopFont();
}
if (!document.isOpen) for (auto it = closeIndices.rbegin(); it != closeIndices.rend(); ++it)
{ {
if (isDirty) if (closePopup.is_open() && closeDocumentIndex > *it) --closeDocumentIndex;
{ manager.close(*it);
isCloseDocument = true;
isOpenCloseDocumentPopup = true;
closeDocumentIndex = i;
document.isOpen = true;
}
else
manager.close(i);
}
} }
ImGui::EndTabBar(); ImGui::EndTabBar();
@@ -71,21 +109,21 @@ namespace anm2ed::documents
closePopup.trigger(); closePopup.trigger();
if (isCloseDocument)
{
if (ImGui::BeginPopupModal(closePopup.label, &closePopup.isOpen, ImGuiWindowFlags_NoResize)) if (ImGui::BeginPopupModal(closePopup.label, &closePopup.isOpen, ImGuiWindowFlags_NoResize))
{ {
auto closeDocument = manager.get(closeDocumentIndex); if (closeDocumentIndex >= 0 && closeDocumentIndex < (int)manager.documents.size())
{
auto& closeDocument = manager.documents[closeDocumentIndex];
ImGui::TextUnformatted(std::format("The document \"{}\" has been modified.\nDo you want to save it?", ImGui::TextUnformatted(std::format("The document \"{}\" has been modified.\nDo you want to save it?",
closeDocument->filename_get()) closeDocument.filename_get().string())
.c_str()); .c_str());
auto widgetSize = imgui::widget_size_with_row_get(3); auto widgetSize = imgui::widget_size_with_row_get(3);
auto close = [&]() auto close = [&]()
{ {
closeDocumentIndex = 0; closeDocumentIndex = -1;
closePopup.close(); closePopup.close();
}; };
@@ -106,12 +144,21 @@ namespace anm2ed::documents
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::Button("Cancel", widgetSize)) close(); if (ImGui::Button("Cancel", widgetSize))
{
isQuitting = false;
close();
}
}
else
{
closeDocumentIndex = -1;
closePopup.close();
}
ImGui::EndPopup(); ImGui::EndPopup();
} }
} }
}
ImGui::End(); ImGui::End();
} }

View File

@@ -3,20 +3,19 @@
#include "imgui.h" #include "imgui.h"
#include "manager.h" #include "manager.h"
#include "resources.h" #include "resources.h"
#include "settings.h"
#include "taskbar.h" #include "taskbar.h"
namespace anm2ed::documents namespace anm2ed::documents
{ {
class Documents class Documents
{ {
bool isCloseDocument{}; int closeDocumentIndex{-1};
bool isOpenCloseDocumentPopup{};
int closeDocumentIndex{};
imgui::PopupHelper closePopup{imgui::PopupHelper("Close Document", imgui::POPUP_TO_CONTENT)}; imgui::PopupHelper closePopup{imgui::PopupHelper("Close Document", imgui::POPUP_TO_CONTENT)};
public: public:
float height{}; float height{};
void update(taskbar::Taskbar&, manager::Manager&, resources::Resources&); void update(taskbar::Taskbar&, manager::Manager&, settings::Settings&, resources::Resources&, bool&);
}; };
} }

View File

@@ -18,6 +18,8 @@ namespace anm2ed::events
auto& hovered = document.hoveredEvent; auto& hovered = document.hoveredEvent;
auto& multiSelect = document.eventMultiSelect; auto& multiSelect = document.eventMultiSelect;
hovered = -1;
if (ImGui::Begin("Events", &settings.windowIsEvents)) if (ImGui::Begin("Events", &settings.windowIsEvents))
{ {
auto childSize = imgui::size_without_footer_get(); auto childSize = imgui::size_without_footer_get();

View File

@@ -6,6 +6,11 @@
namespace anm2ed::filesystem namespace anm2ed::filesystem
{ {
bool directories_create(const std::string& path)
{
return std::filesystem::create_directories(path);
}
std::string path_preferences_get() std::string path_preferences_get()
{ {
char* preferencesPath = SDL_GetPrefPath("", "anm2ed"); char* preferencesPath = SDL_GetPrefPath("", "anm2ed");

View File

@@ -8,6 +8,7 @@ namespace anm2ed::filesystem
std::string path_preferences_get(); std::string path_preferences_get();
bool path_is_exist(const std::string&); bool path_is_exist(const std::string&);
bool path_is_extension(const std::string&, const std::string&); bool path_is_extension(const std::string&, const std::string&);
bool directories_create(const std::string&);
class WorkingDirectory class WorkingDirectory
{ {

View File

@@ -91,8 +91,8 @@ namespace anm2ed::frame_properties
document.frame_tint_set(frame, useFrame.tint); 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.");
if (ImGui::ColorEdit3("Color Offset", frame ? value_ptr(useFrame.offset) : &dummy_value<float>())) if (ImGui::ColorEdit3("Color Offset", frame ? value_ptr(useFrame.colorOffset) : &dummy_value<float>()))
document.frame_offset_set(frame, useFrame.offset); document.frame_color_offset_set(frame, useFrame.colorOffset);
ImGui::SetItemTooltip("%s", "Change the color added onto the frame."); ImGui::SetItemTooltip("%s", "Change the color added onto the frame.");
if (ImGui::Checkbox("Visible", frame ? &useFrame.isVisible : &dummy_value<bool>())) if (ImGui::Checkbox("Visible", frame ? &useFrame.isVisible : &dummy_value<bool>()))

View File

@@ -55,6 +55,14 @@ namespace icon
constexpr auto HIDE_UNUSED_DATA = R"( constexpr auto HIDE_UNUSED_DATA = R"(
<svg viewBox="0 0 24 24" fill="#FFF" xmlns="http://www.w3.org/2000/svg"><path d="M21 4V6H20L15 13.5V22H9V13.5L4 6H3V4H21ZM6.4037 6L11 12.8944V20H13V12.8944L17.5963 6H6.4037Z"/></svg> <svg viewBox="0 0 24 24" fill="#FFF" xmlns="http://www.w3.org/2000/svg"><path d="M21 4V6H20L15 13.5V22H9V13.5L4 6H3V4H21ZM6.4037 6L11 12.8944V20H13V12.8944L17.5963 6H6.4037Z"/></svg>
)";
constexpr auto SHOW_LAYERS_DATA = R"(
<svg viewBox="0 0 24 24" fill="#FFF" xmlns="http://www.w3.org/2000/svg"><path d="M20.0833 10.4999L21.2854 11.2212C21.5221 11.3633 21.5989 11.6704 21.4569 11.9072C21.4146 11.9776 21.3557 12.0365 21.2854 12.0787L11.9999 17.6499L2.71451 12.0787C2.47772 11.9366 2.40093 11.6295 2.54301 11.3927C2.58523 11.3223 2.64413 11.2634 2.71451 11.2212L3.9166 10.4999L11.9999 15.3499L20.0833 10.4999ZM20.0833 15.1999L21.2854 15.9212C21.5221 16.0633 21.5989 16.3704 21.4569 16.6072C21.4146 16.6776 21.3557 16.7365 21.2854 16.7787L12.5144 22.0412C12.1977 22.2313 11.8021 22.2313 11.4854 22.0412L2.71451 16.7787C2.47772 16.6366 2.40093 16.3295 2.54301 16.0927C2.58523 16.0223 2.64413 15.9634 2.71451 15.9212L3.9166 15.1999L11.9999 20.0499L20.0833 15.1999ZM12.5144 1.30864L21.2854 6.5712C21.5221 6.71327 21.5989 7.0204 21.4569 7.25719C21.4146 7.32757 21.3557 7.38647 21.2854 7.42869L11.9999 12.9999L2.71451 7.42869C2.47772 7.28662 2.40093 6.97949 2.54301 6.7427C2.58523 6.67232 2.64413 6.61343 2.71451 6.5712L11.4854 1.30864C11.8021 1.11864 12.1977 1.11864 12.5144 1.30864Z"/></svg>
)";
constexpr auto HIDE_LAYERS_DATA = R"(
<svg viewBox="0 0 24 24" fill="#FFF" xmlns="http://www.w3.org/2000/svg"><path d="M20.0833 15.1999L21.2854 15.9212C21.5221 16.0633 21.5989 16.3704 21.4569 16.6072C21.4146 16.6776 21.3557 16.7365 21.2854 16.7787L12.5144 22.0412C12.1977 22.2313 11.8021 22.2313 11.4854 22.0412L2.71451 16.7787C2.47772 16.6366 2.40093 16.3295 2.54301 16.0927C2.58523 16.0223 2.64413 15.9634 2.71451 15.9212L3.9166 15.1999L11.9999 20.0499L20.0833 15.1999ZM20.0833 10.4999L21.2854 11.2212C21.5221 11.3633 21.5989 11.6704 21.4569 11.9072C21.4146 11.9776 21.3557 12.0365 21.2854 12.0787L11.9999 17.6499L2.71451 12.0787C2.47772 11.9366 2.40093 11.6295 2.54301 11.3927C2.58523 11.3223 2.64413 11.2634 2.71451 11.2212L3.9166 10.4999L11.9999 15.3499L20.0833 10.4999ZM12.5144 1.30864L21.2854 6.5712C21.5221 6.71327 21.5989 7.0204 21.4569 7.25719C21.4146 7.32757 21.3557 7.38647 21.2854 7.42869L11.9999 12.9999L2.71451 7.42869C2.47772 7.28662 2.40093 6.97949 2.54301 6.7427C2.58523 6.67232 2.64413 6.61343 2.71451 6.5712L11.4854 1.30864C11.8021 1.11864 12.1977 1.11864 12.5144 1.30864ZM11.9999 3.33233L5.88723 6.99995L11.9999 10.6676L18.1126 6.99995L11.9999 3.33233Z"/></svg>
)"; )";
constexpr auto SHOW_RECT_DATA = R"( constexpr auto SHOW_RECT_DATA = R"(
@@ -156,6 +164,8 @@ namespace icon
X(HIDE_RECT, HIDE_RECT_DATA, SIZE_NORMAL) \ X(HIDE_RECT, HIDE_RECT_DATA, SIZE_NORMAL) \
X(SHOW_UNUSED, SHOW_UNUSED_DATA, SIZE_NORMAL) \ X(SHOW_UNUSED, SHOW_UNUSED_DATA, SIZE_NORMAL) \
X(HIDE_UNUSED, HIDE_UNUSED_DATA, SIZE_NORMAL) \ X(HIDE_UNUSED, HIDE_UNUSED_DATA, SIZE_NORMAL) \
X(SHOW_LAYERS, SHOW_LAYERS_DATA, SIZE_NORMAL) \
X(HIDE_LAYERS, HIDE_LAYERS_DATA, SIZE_NORMAL) \
X(PAN, PAN_DATA, SIZE_NORMAL) \ X(PAN, PAN_DATA, SIZE_NORMAL) \
X(MOVE, MOVE_DATA, SIZE_NORMAL) \ X(MOVE, MOVE_DATA, SIZE_NORMAL) \
X(ROTATE, ROTATE_DATA, SIZE_NORMAL) \ X(ROTATE, ROTATE_DATA, SIZE_NORMAL) \

View File

@@ -280,6 +280,11 @@ namespace anm2ed::imgui
isJustOpened = true; isJustOpened = true;
} }
bool PopupHelper::is_open()
{
return isOpen;
}
void PopupHelper::trigger() void PopupHelper::trigger()
{ {
if (isTriggered) ImGui::OpenPopup(label); if (isTriggered) ImGui::OpenPopup(label);

View File

@@ -174,6 +174,7 @@ namespace anm2ed::imgui
float percent{}; float percent{};
PopupHelper(const char*, float = POPUP_NORMAL, bool = false); PopupHelper(const char*, float = POPUP_NORMAL, bool = false);
bool is_open();
void open(); void open();
void trigger(); void trigger();
void end(); void end();

View File

@@ -21,6 +21,8 @@ namespace anm2ed::layers
auto& multiSelect = document.layersMultiSelect; auto& multiSelect = document.layersMultiSelect;
auto& propertiesPopup = manager.layerPropertiesPopup; auto& propertiesPopup = manager.layerPropertiesPopup;
hovered = -1;
if (ImGui::Begin("Layers", &settings.windowIsLayers)) if (ImGui::Begin("Layers", &settings.windowIsLayers))
{ {
auto childSize = imgui::size_without_footer_get(); auto childSize = imgui::size_without_footer_get();

View File

@@ -73,7 +73,7 @@ namespace anm2ed::loader
ImGuiIO& io = ImGui::GetIO(); ImGuiIO& io = ImGui::GetIO();
io.IniFilename = nullptr; io.IniFilename = nullptr;
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable | ImGuiConfigFlags_NavEnableKeyboard; io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
io.ConfigWindowsMoveFromTitleBarOnly = true; io.ConfigWindowsMoveFromTitleBarOnly = true;
ImGui::LoadIniSettingsFromDisk(settings_path().c_str()); ImGui::LoadIniSettingsFromDisk(settings_path().c_str());

View File

@@ -1,33 +1,73 @@
#include "manager.h" #include "manager.h"
#include "toast.h" #include <algorithm>
#include "filesystem.h"
#include "log.h"
#include "toast.h"
#include "util.h" #include "util.h"
using namespace anm2ed::log;
using namespace anm2ed::toast; using namespace anm2ed::toast;
using namespace anm2ed::types; using namespace anm2ed::types;
using namespace anm2ed::util; using namespace anm2ed::util;
namespace anm2ed::manager namespace anm2ed::manager
{ {
constexpr std::size_t RECENT_LIMIT = 10;
std::filesystem::path Manager::recent_files_path_get()
{
return filesystem::path_preferences_get() + "recent.txt";
}
std::filesystem::path Manager::autosave_path_get()
{
return filesystem::path_preferences_get() + "autosave.txt";
}
std::filesystem::path Manager::autosave_directory_get()
{
return filesystem::path_preferences_get() + "autosave";
}
Manager::Manager()
{
recent_files_load();
autosave_files_load();
}
Document* Manager::get(int index) Document* Manager::get(int index)
{ {
return vector::find(documents, index > -1 ? index : selected); return vector::find(documents, index > -1 ? index : selected);
} }
void Manager::open(const std::string& path, bool isNew) void Manager::open(const std::string& path, bool isNew, bool isRecent)
{ {
std::string errorString{}; std::string errorString{};
Document document = Document(path, isNew, &errorString); documents.emplace_back(path, isNew, &errorString);
if (document.is_valid())
auto& document = documents.back();
if (!document.is_valid())
{ {
documents.emplace_back(std::move(document)); documents.pop_back();
selected = documents.size() - 1; toasts.error(std::format("Failed to open document: {} ({})", path, errorString));
pendingSelected = selected; return;
toasts.info(std::format("Initialized document: {}", path));
} }
else
toasts.error(std::format("Failed to initialize document: {} ({})", path, errorString)); if (isRecent)
{
recentFiles.erase(std::remove(recentFiles.begin(), recentFiles.end(), path), recentFiles.end());
recentFiles.insert(recentFiles.begin(), path);
if (recentFiles.size() > RECENT_LIMIT) recentFiles.resize(RECENT_LIMIT);
recent_files_write();
}
selected = (int)documents.size() - 1;
pendingSelected = selected;
toasts.info(std::format("Opened document: {}", path));
} }
void Manager::new_(const std::string& path) void Manager::new_(const std::string& path)
@@ -50,9 +90,53 @@ namespace anm2ed::manager
save(selected, path); save(selected, path);
} }
void Manager::autosave(Document& document)
{
auto filename = "." + document.filename_get().string() + ".autosave";
auto path = document.directory_get() / filename;
std::string errorString{};
document.autosave(path, &errorString);
autosaveFiles.erase(std::remove(autosaveFiles.begin(), autosaveFiles.end(), path), autosaveFiles.end());
autosaveFiles.insert(autosaveFiles.begin(), path);
autosave_files_write();
}
void Manager::close(int index) void Manager::close(int index)
{ {
if (index < 0 || index >= (int)documents.size()) return;
documents.erase(documents.begin() + index); documents.erase(documents.begin() + index);
if (documents.empty())
{
selected = -1;
pendingSelected = -1;
return;
}
if (selected >= index) selected = std::max(0, selected - 1);
selected = std::clamp(selected, 0, (int)documents.size() - 1);
pendingSelected = selected;
if (selected >= 0 && selected < (int)documents.size()) documents[selected].change(change::ALL);
}
void Manager::set(int index)
{
if (documents.empty())
{
selected = -1;
pendingSelected = -1;
return;
}
index = std::clamp(index, 0, (int)documents.size() - 1);
selected = index;
if (auto document = get()) document->change(change::ALL);
} }
void Manager::layer_properties_open(int id) void Manager::layer_properties_open(int id)
@@ -117,4 +201,119 @@ namespace anm2ed::manager
nullPropertiesPopup.close(); nullPropertiesPopup.close();
} }
void Manager::recent_files_load()
{
auto path = recent_files_path_get();
std::ifstream file(path);
if (!file)
{
logger.warning(std::format("Could not load recent files from: {}. Skipping...", path.string()));
return;
}
logger.info(std::format("Loading recent files from: {}", path.string()));
std::string line{};
while (std::getline(file, line))
{
if (line.empty()) continue;
if (std::find(recentFiles.begin(), recentFiles.end(), line) != recentFiles.end()) continue;
recentFiles.emplace_back(line);
}
}
void Manager::recent_files_write()
{
auto path = recent_files_path_get();
std::ofstream file;
file.open(path, std::ofstream::out | std::ofstream::trunc);
if (!file.is_open())
{
logger.warning(std::format("Could not write recent files to: {}. Skipping...", path.string()));
return;
}
for (auto& path : recentFiles)
file << path.string() << '\n';
}
void Manager::recent_files_clear()
{
recentFiles.clear();
recent_files_write();
}
void Manager::autosave_files_open()
{
for (auto& path : autosaveFiles)
{
auto fileName = path.filename().string();
if (!fileName.empty() && fileName.front() == '.') fileName.erase(fileName.begin());
auto restorePath = path.parent_path() / fileName;
restorePath.replace_extension("");
open(path.string(), false, false);
if (auto document = get())
{
document->isForceDirty = true;
document->path = restorePath;
document->change(change::ALL);
}
}
autosave_files_clear();
}
void Manager::autosave_files_load()
{
auto path = autosave_path_get();
std::ifstream file(path);
if (!file)
{
logger.warning(std::format("Could not load autosave files from: {}. Skipping...", path.string()));
return;
}
logger.info(std::format("Loading autosave files from: {}", path.string()));
std::string line{};
while (std::getline(file, line))
{
if (line.empty()) continue;
if (std::find(autosaveFiles.begin(), autosaveFiles.end(), line) != autosaveFiles.end()) continue;
autosaveFiles.emplace_back(line);
}
}
void Manager::autosave_files_write()
{
std::ofstream autosaveWriteFile;
autosaveWriteFile.open(autosave_path_get(), std::ofstream::out | std::ofstream::trunc);
for (auto& path : autosaveFiles)
autosaveWriteFile << path.string() << "\n";
autosaveWriteFile.close();
}
void Manager::autosave_files_clear()
{
for (auto& path : autosaveFiles)
std::filesystem::remove(path);
autosaveFiles.clear();
autosave_files_write();
}
Manager::~Manager()
{
autosave_files_clear();
}
} }

View File

@@ -9,12 +9,20 @@ using namespace anm2ed::document;
namespace anm2ed::manager namespace anm2ed::manager
{ {
constexpr auto FILE_LABEL_FORMAT = "{} [{}]";
class Manager class Manager
{ {
std::filesystem::path recent_files_path_get();
std::filesystem::path autosave_path_get();
public: public:
std::vector<Document> documents{}; std::vector<Document> documents{};
int selected{}; std::vector<std::filesystem::path> recentFiles{};
int pendingSelected{}; std::vector<std::filesystem::path> autosaveFiles{};
int selected{-1};
int pendingSelected{-1};
anm2::Layer editLayer{}; anm2::Layer editLayer{};
imgui::PopupHelper layerPropertiesPopup{imgui::PopupHelper("Layer Properties", imgui::POPUP_SMALL, true)}; imgui::PopupHelper layerPropertiesPopup{imgui::PopupHelper("Layer Properties", imgui::POPUP_SMALL, true)};
@@ -22,11 +30,16 @@ namespace anm2ed::manager
anm2::Null editNull{}; anm2::Null editNull{};
imgui::PopupHelper nullPropertiesPopup{imgui::PopupHelper("Null Properties", imgui::POPUP_SMALL, true)}; imgui::PopupHelper nullPropertiesPopup{imgui::PopupHelper("Null Properties", imgui::POPUP_SMALL, true)};
Manager();
~Manager();
Document* get(int = -1); Document* get(int = -1);
void open(const std::string&, bool = false); void open(const std::string&, bool = false, bool = true);
void new_(const std::string&); void new_(const std::string&);
void save(int, const std::string& = {}); void save(int, const std::string& = {});
void save(const std::string& = {}); void save(const std::string& = {});
void autosave(Document&);
void set(int);
void close(int); void close(int);
void layer_properties_open(int = -1); void layer_properties_open(int = -1);
void layer_properties_trigger(); void layer_properties_trigger();
@@ -36,5 +49,16 @@ namespace anm2ed::manager
void null_properties_trigger(); void null_properties_trigger();
void null_properties_end(); void null_properties_end();
void null_properties_close(); void null_properties_close();
void recent_files_load();
void recent_files_write();
void recent_files_clear();
void autosave_files_load();
void autosave_files_open();
void autosave_files_write();
void autosave_files_clear();
std::filesystem::path autosave_directory_get();
}; };
} }

View File

@@ -20,6 +20,8 @@ namespace anm2ed::nulls
auto& multiSelect = document.nullMultiSelect; auto& multiSelect = document.nullMultiSelect;
auto& propertiesPopup = manager.nullPropertiesPopup; auto& propertiesPopup = manager.nullPropertiesPopup;
hovered = -1;
if (ImGui::Begin("Nulls", &settings.windowIsNulls)) if (ImGui::Begin("Nulls", &settings.windowIsNulls))
{ {
auto childSize = imgui::size_without_footer_get(); auto childSize = imgui::size_without_footer_get();

View File

@@ -44,6 +44,9 @@ namespace anm2ed::settings
X(IS_VSYNC, isVsync, "Vsync", BOOL, true) \ X(IS_VSYNC, isVsync, "Vsync", BOOL, true) \
X(DISPLAY_SCALE, displayScale, "Display Scale", FLOAT, 1.0f) \ X(DISPLAY_SCALE, displayScale, "Display Scale", FLOAT, 1.0f) \
\ \
X(FILE_IS_AUTOSAVE, fileIsAutosave, "Autosave", BOOL, true) \
X(FILE_AUTOSAVE_TIME, fileAutosaveTime, "Autosave Time", INT, 1) \
\
X(VIEW_ZOOM_STEP, viewZoomStep, "Zoom Step", FLOAT, 50.0f) \ X(VIEW_ZOOM_STEP, viewZoomStep, "Zoom Step", FLOAT, 50.0f) \
\ \
X(PLAYBACK_IS_LOOP, playbackIsLoop, "Loop", BOOL, true) \ X(PLAYBACK_IS_LOOP, playbackIsLoop, "Loop", BOOL, true) \
@@ -60,7 +63,6 @@ namespace anm2ed::settings
X(CHANGE_IS_COLOR_OFFSET, changeIsColorOffset, "##Is Color Offset", BOOL, false) \ X(CHANGE_IS_COLOR_OFFSET, changeIsColorOffset, "##Is Color Offset", BOOL, false) \
X(CHANGE_IS_VISIBLE_SET, changeIsVisibleSet, "##Is Visible", BOOL, false) \ X(CHANGE_IS_VISIBLE_SET, changeIsVisibleSet, "##Is Visible", BOOL, false) \
X(CHANGE_IS_INTERPOLATED_SET, changeIsInterpolatedSet, "##Is Interpolated", BOOL, false) \ X(CHANGE_IS_INTERPOLATED_SET, changeIsInterpolatedSet, "##Is Interpolated", BOOL, false) \
X(CHANGE_IS_FROM_SELECTED_FRAME, changeIsFromSelectedFrame, "From Selected Frame", BOOL, false) \
X(CHANGE_CROP, changeCrop, "Crop", VEC2, {}) \ X(CHANGE_CROP, changeCrop, "Crop", VEC2, {}) \
X(CHANGE_SIZE, changeSize, "Size", VEC2, {}) \ X(CHANGE_SIZE, changeSize, "Size", VEC2, {}) \
X(CHANGE_POSITION, changePosition, "Position", VEC2, {}) \ X(CHANGE_POSITION, changePosition, "Position", VEC2, {}) \
@@ -73,6 +75,7 @@ namespace anm2ed::settings
X(CHANGE_IS_VISIBLE, changeIsVisible, "Visible", BOOL, false) \ X(CHANGE_IS_VISIBLE, changeIsVisible, "Visible", BOOL, false) \
X(CHANGE_IS_INTERPOLATED, changeIsInterpolated, "Interpolated", BOOL, false) \ X(CHANGE_IS_INTERPOLATED, changeIsInterpolated, "Interpolated", BOOL, false) \
X(CHANGE_NUMBER_FRAMES, changeNumberFrames, "Frame Count", INT, 1) \ X(CHANGE_NUMBER_FRAMES, changeNumberFrames, "Frame Count", INT, 1) \
X(CHANGE_IS_FROM_SELECTED_FRAME, changeIsFromSelectedFrame, "From Selected Frame", BOOL, false) \
\ \
X(SCALE_VALUE, scaleValue, "Scale", FLOAT, 1.0f) \ X(SCALE_VALUE, scaleValue, "Scale", FLOAT, 1.0f) \
\ \
@@ -80,7 +83,6 @@ namespace anm2ed::settings
X(PREVIEW_IS_GRID, previewIsGrid, "Grid", BOOL, true) \ X(PREVIEW_IS_GRID, previewIsGrid, "Grid", BOOL, true) \
X(PREVIEW_IS_ROOT_TRANSFORM, previewIsRootTransform, "Root Transform", BOOL, true) \ X(PREVIEW_IS_ROOT_TRANSFORM, previewIsRootTransform, "Root Transform", BOOL, true) \
X(PREVIEW_IS_PIVOTS, previewIsPivots, "Pivots", BOOL, false) \ X(PREVIEW_IS_PIVOTS, previewIsPivots, "Pivots", BOOL, false) \
X(PREVIEW_IS_ICONS, previewIsIcons, "Icons", BOOL, true) \
X(PREVIEW_IS_BORDER, previewIsBorder, "Border", BOOL, false) \ X(PREVIEW_IS_BORDER, previewIsBorder, "Border", BOOL, false) \
X(PREVIEW_IS_ALT_ICONS, previewIsAltIcons, "Alt Icons", BOOL, false) \ X(PREVIEW_IS_ALT_ICONS, previewIsAltIcons, "Alt Icons", BOOL, false) \
X(PREVIEW_OVERLAY_TRANSPARENCY, previewOverlayTransparency, "Alpha", FLOAT, 255) \ X(PREVIEW_OVERLAY_TRANSPARENCY, previewOverlayTransparency, "Alpha", FLOAT, 255) \
@@ -101,6 +103,7 @@ namespace anm2ed::settings
X(GENERATE_COLUMNS, generateColumns, "Columns", INT, 4) \ X(GENERATE_COLUMNS, generateColumns, "Columns", INT, 4) \
X(GENERATE_COUNT, generateCount, "Count", INT, 16) \ X(GENERATE_COUNT, generateCount, "Count", INT, 16) \
X(GENERATE_DELAY, generateDelay, "Delay", INT, 1) \ X(GENERATE_DELAY, generateDelay, "Delay", INT, 1) \
X(GENERATE_ZOOM, generateZoom, "Zoom", FLOAT, 100.0f) \
\ \
X(EDITOR_IS_GRID, editorIsGrid, "Grid", BOOL, true) \ X(EDITOR_IS_GRID, editorIsGrid, "Grid", BOOL, true) \
X(EDITOR_IS_GRID_SNAP, editorIsGridSnap, "Snap", BOOL, true) \ X(EDITOR_IS_GRID_SNAP, editorIsGridSnap, "Snap", BOOL, true) \
@@ -123,6 +126,7 @@ namespace anm2ed::settings
X(TIMELINE_ADD_ITEM_LOCALITY, timelineAddItemLocale, "Add Item Locale", INT, types::locale::GLOBAL) \ X(TIMELINE_ADD_ITEM_LOCALITY, timelineAddItemLocale, "Add Item Locale", INT, types::locale::GLOBAL) \
X(TIMELINE_ADD_ITEM_SOURCE, timelineAddItemSource, "Add Item Source", INT, types::source::NEW) \ X(TIMELINE_ADD_ITEM_SOURCE, timelineAddItemSource, "Add Item Source", INT, types::source::NEW) \
X(TIMELINE_IS_SHOW_UNUSED, timelineIsShowUnused, "##Show Unused", BOOL, true) \ X(TIMELINE_IS_SHOW_UNUSED, timelineIsShowUnused, "##Show Unused", BOOL, true) \
X(TIMELINE_IS_ONLY_SHOW_LAYERS, timelineIsOnlyShowLayers, "##Only Show Layers", BOOL, true) \
\ \
X(ONIONSKIN_IS_ENABLED, onionskinIsEnabled, "Enabled", BOOL, false) \ X(ONIONSKIN_IS_ENABLED, onionskinIsEnabled, "Enabled", BOOL, false) \
X(ONIONSKIN_DRAW_ORDER, onionskinDrawOrder, "Draw Order", INT, 0) \ X(ONIONSKIN_DRAW_ORDER, onionskinDrawOrder, "Draw Order", INT, 0) \

View File

@@ -112,6 +112,79 @@ namespace anm2ed::shader
} }
)"; )";
constexpr auto DASHED_VERTEX = R"(
#version 330 core
layout (location = 0) in vec2 i_position;
out vec2 v_local;
uniform mat4 u_transform;
void main()
{
v_local = i_position;
gl_Position = u_transform * vec4(i_position, 0.0, 1.0);
}
)";
constexpr auto DASHED_FRAGMENT = R"(
#version 330 core
in vec2 v_local;
uniform vec4 u_color;
uniform vec2 u_axis_x;
uniform vec2 u_axis_y;
uniform float u_dash_length;
uniform float u_dash_gap;
uniform float u_dash_offset;
out vec4 o_fragColor;
void main()
{
vec2 local = clamp(v_local, 0.0, 1.0);
float lengthX = max(length(u_axis_x), 1e-4);
float lengthY = max(length(u_axis_y), 1e-4);
float dash = max(u_dash_length, 1e-4);
float gap = max(u_dash_gap, 0.0);
float period = max(dash + gap, 1e-4);
vec2 pixel = max(fwidth(v_local), vec2(1e-5));
float bottomMask = 1.0 - smoothstep(pixel.y, pixel.y * 2.0, local.y);
float topMask = 1.0 - smoothstep(pixel.y, pixel.y * 2.0, 1.0 - local.y);
float leftMask = 1.0 - smoothstep(pixel.x, pixel.x * 2.0, local.x);
float rightMask = 1.0 - smoothstep(pixel.x, pixel.x * 2.0, 1.0 - local.x);
float perimeterOffset = u_dash_offset;
float bottomPos = mod(perimeterOffset + local.x * lengthX, period);
if (bottomPos < 0.0) bottomPos += period;
float bottomDash = bottomMask * (bottomPos <= dash ? 1.0 : 0.0);
float rightPos = mod(perimeterOffset + lengthX + local.y * lengthY, period);
if (rightPos < 0.0) rightPos += period;
float rightDash = rightMask * (rightPos <= dash ? 1.0 : 0.0);
float topPos = mod(perimeterOffset + lengthX + lengthY + (1.0 - local.x) * lengthX, period);
if (topPos < 0.0) topPos += period;
float topDash = topMask * (topPos <= dash ? 1.0 : 0.0);
float leftPos = mod(perimeterOffset + 2.0 * lengthX + lengthY + (1.0 - local.y) * lengthY, period);
if (leftPos < 0.0) leftPos += period;
float leftDash = leftMask * (leftPos <= dash ? 1.0 : 0.0);
float alpha = max(max(bottomDash, topDash), max(leftDash, rightDash));
if (alpha <= 0.0)
discard;
o_fragColor = vec4(u_color.rgb, u_color.a * alpha);
}
)";
constexpr auto UNIFORM_AXIS = "u_axis"; constexpr auto UNIFORM_AXIS = "u_axis";
constexpr auto UNIFORM_COLOR = "u_color"; constexpr auto UNIFORM_COLOR = "u_color";
constexpr auto UNIFORM_TRANSFORM = "u_transform"; constexpr auto UNIFORM_TRANSFORM = "u_transform";
@@ -123,18 +196,27 @@ namespace anm2ed::shader
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_AXIS_X = "u_axis_x";
constexpr auto UNIFORM_AXIS_Y = "u_axis_y";
constexpr auto UNIFORM_DASH_LENGTH = "u_dash_length";
constexpr auto UNIFORM_DASH_GAP = "u_dash_gap";
constexpr auto UNIFORM_DASH_OFFSET = "u_dash_offset";
enum Type enum Type
{ {
LINE, LINE,
DASHED,
TEXTURE, TEXTURE,
AXIS, AXIS,
GRID, GRID,
COUNT COUNT
}; };
const Info SHADERS[COUNT] = { const Info SHADERS[COUNT] = {{VERTEX, FRAGMENT},
{VERTEX, FRAGMENT}, {VERTEX, TEXTURE_FRAGMENT}, {AXIS_VERTEX, FRAGMENT}, {GRID_VERTEX, GRID_FRAGMENT}}; {DASHED_VERTEX, DASHED_FRAGMENT},
{VERTEX, TEXTURE_FRAGMENT},
{AXIS_VERTEX, FRAGMENT},
{GRID_VERTEX, GRID_FRAGMENT}};
class Shader class Shader
{ {

View File

@@ -37,7 +37,7 @@ namespace anm2ed::spritesheet_editor
auto& tool = settings.tool; auto& tool = settings.tool;
auto& shaderGrid = resources.shaders[shader::GRID]; auto& shaderGrid = resources.shaders[shader::GRID];
auto& shaderTexture = resources.shaders[shader::TEXTURE]; auto& shaderTexture = resources.shaders[shader::TEXTURE];
auto& lineShader = resources.shaders[shader::LINE]; auto& dashedShader = resources.shaders[shader::DASHED];
if (ImGui::Begin("Spritesheet Editor", &settings.windowIsSpritesheetEditor)) if (ImGui::Begin("Spritesheet Editor", &settings.windowIsSpritesheetEditor))
{ {
@@ -96,20 +96,22 @@ namespace anm2ed::spritesheet_editor
auto frame = document.frame_get(); auto frame = document.frame_get();
if (spritesheet) if (spritesheet && spritesheet->texture.is_valid())
{ {
auto& texture = spritesheet->texture; auto& texture = spritesheet->texture;
auto transform = transform_get(zoom, pan); auto transform = transform_get(zoom, pan);
auto spritesheetTransform = transform * math::quad_model_get(texture.size); auto spritesheetModel = math::quad_model_get(texture.size);
auto spritesheetTransform = transform * spritesheetModel;
texture_render(shaderTexture, texture.id, spritesheetTransform); texture_render(shaderTexture, texture.id, spritesheetTransform);
if (isBorder) rect_render(lineShader, spritesheetTransform); if (isBorder) rect_render(dashedShader, spritesheetTransform, spritesheetModel);
if (frame && reference.itemID > -1 && if (frame && reference.itemID > -1 &&
anm2.content.layers.at(reference.itemID).spritesheetID == referenceSpritesheet) anm2.content.layers.at(reference.itemID).spritesheetID == referenceSpritesheet)
{ {
auto cropTransform = transform * math::quad_model_get(frame->size, frame->crop); auto cropModel = math::quad_model_get(frame->size, frame->crop);
rect_render(lineShader, cropTransform, color::RED); auto cropTransform = transform * cropModel;
rect_render(dashedShader, cropTransform, cropModel, color::RED);
auto pivotTransform = auto pivotTransform =
transform * math::quad_model_get(canvas::PIVOT_SIZE, frame->crop + frame->pivot, PIVOT_SIZE * 0.5f); transform * math::quad_model_get(canvas::PIVOT_SIZE, frame->crop + frame->pivot, PIVOT_SIZE * 0.5f);

View File

@@ -28,6 +28,8 @@ namespace anm2ed::spritesheets
auto& hovered = document.hoveredSpritesheet; auto& hovered = document.hoveredSpritesheet;
auto& reference = document.referenceSpritesheet; auto& reference = document.referenceSpritesheet;
hovered = -1;
if (ImGui::Begin("Spritesheets", &settings.windowIsSpritesheets)) if (ImGui::Begin("Spritesheets", &settings.windowIsSpritesheets))
{ {
auto style = ImGui::GetStyle(); auto style = ImGui::GetStyle();

View File

@@ -61,7 +61,7 @@ namespace anm2ed::state
break; break;
} }
case SDL_EVENT_QUIT: case SDL_EVENT_QUIT:
isQuit = true; isQuitting = true;
break; break;
default: default:
break; break;
@@ -72,14 +72,15 @@ namespace anm2ed::state
ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplOpenGL3_NewFrame();
ImGui::NewFrame(); ImGui::NewFrame();
taskbar.update(manager, settings, dialog, isQuit); taskbar.update(manager, settings, resources, dialog, isQuitting);
documents.update(taskbar, manager, settings, resources, isQuitting);
dockspace.update(taskbar, documents, manager, settings, resources, dialog, clipboard); dockspace.update(taskbar, documents, manager, settings, resources, dialog, clipboard);
toasts.update(); toasts.update();
documents.update(taskbar, manager, resources);
ImGui::GetStyle().FontScaleMain = settings.displayScale; ImGui::GetStyle().FontScaleMain = settings.displayScale;
SDL_GetWindowSize(window, &settings.windowSize.x, &settings.windowSize.y); SDL_GetWindowSize(window, &settings.windowSize.x, &settings.windowSize.y);
if (isQuitting && manager.documents.empty()) isQuit = true;
} }
void State::render(SDL_Window*& window, Settings& settings) void State::render(SDL_Window*& window, Settings& settings)

View File

@@ -14,9 +14,10 @@ namespace anm2ed::state
public: public:
bool isQuit{}; bool isQuit{};
dialog::Dialog dialog; bool isQuitting{};
resources::Resources resources;
manager::Manager manager; manager::Manager manager;
resources::Resources resources;
dialog::Dialog dialog;
clipboard::Clipboard clipboard; clipboard::Clipboard clipboard;
taskbar::Taskbar taskbar; taskbar::Taskbar taskbar;

View File

@@ -4,18 +4,25 @@
#include <ranges> #include <ranges>
#include "imgui.h" #include "imgui.h"
#include "math.h"
using namespace anm2ed::settings; using namespace anm2ed::canvas;
using namespace anm2ed::dialog; using namespace anm2ed::dialog;
using namespace anm2ed::manager; using namespace anm2ed::manager;
using namespace anm2ed::resources;
using namespace anm2ed::settings;
using namespace anm2ed::types; using namespace anm2ed::types;
using namespace glm;
namespace anm2ed::taskbar namespace anm2ed::taskbar
{ {
void Taskbar::update(Manager& manager, Settings& settings, Dialog& dialog, bool& isQuit) Taskbar::Taskbar() : generate(vec2())
{
}
void Taskbar::update(Manager& manager, Settings& settings, Resources& resources, Dialog& dialog, bool& isQuitting)
{ {
auto document = manager.get(); auto document = manager.get();
auto animation = document ? document->animation_get() : nullptr;
if (ImGui::BeginMainMenuBar()) if (ImGui::BeginMainMenuBar())
{ {
@@ -27,6 +34,32 @@ namespace anm2ed::taskbar
if (ImGui::MenuItem("Open", settings.shortcutOpen.c_str())) dialog.anm2_open(); if (ImGui::MenuItem("Open", settings.shortcutOpen.c_str())) dialog.anm2_open();
if (manager.recentFiles.empty())
{
ImGui::BeginDisabled();
ImGui::MenuItem("Open Recent");
ImGui::EndDisabled();
}
else
{
if (ImGui::BeginMenu("Open Recent"))
{
for (auto [i, file] : std::views::enumerate(manager.recentFiles))
{
auto label = std::format(FILE_LABEL_FORMAT, file.filename().string(), file.string());
ImGui::PushID(i);
if (ImGui::MenuItem(label.c_str())) manager.open(file);
ImGui::PopID();
}
if (!manager.recentFiles.empty())
if (ImGui::MenuItem("Clear List")) manager.recent_files_clear();
ImGui::EndMenu();
}
}
ImGui::BeginDisabled(!document); ImGui::BeginDisabled(!document);
{ {
if (ImGui::MenuItem("Save", settings.shortcutSave.c_str())) manager.save(); if (ImGui::MenuItem("Save", settings.shortcutSave.c_str())) manager.save();
@@ -36,7 +69,7 @@ namespace anm2ed::taskbar
ImGui::EndDisabled(); ImGui::EndDisabled();
ImGui::Separator(); ImGui::Separator();
if (ImGui::MenuItem("Exit", settings.shortcutExit.c_str())) isQuit = true; if (ImGui::MenuItem("Exit", settings.shortcutExit.c_str())) isQuitting = true;
ImGui::EndMenu(); ImGui::EndMenu();
} }
if (dialog.is_selected_file(dialog::ANM2_NEW)) if (dialog.is_selected_file(dialog::ANM2_NEW))
@@ -59,11 +92,10 @@ namespace anm2ed::taskbar
if (ImGui::BeginMenu("Wizard")) if (ImGui::BeginMenu("Wizard"))
{ {
ImGui::BeginDisabled(!animation); auto item = document ? document->item_get() : nullptr;
{ ImGui::BeginDisabled(!item || document->reference.itemType != anm2::LAYER);
ImGui::MenuItem("Generate Animation From Grid"); if (ImGui::MenuItem("Generate Animation From Grid")) generatePopup.open();
ImGui::MenuItem("Change All Frame Properties"); if (ImGui::MenuItem("Change All Frame Properties")) changePopup.open();
}
ImGui::EndDisabled(); ImGui::EndDisabled();
ImGui::EndMenu(); ImGui::EndMenu();
} }
@@ -107,6 +139,264 @@ namespace anm2ed::taskbar
ImGui::EndMainMenuBar(); ImGui::EndMainMenuBar();
} }
generatePopup.trigger();
if (ImGui::BeginPopupModal(generatePopup.label, &generatePopup.isOpen, ImGuiWindowFlags_NoResize))
{
auto& startPosition = settings.generateStartPosition;
auto& size = settings.generateSize;
auto& pivot = settings.generatePivot;
auto& rows = settings.generateRows;
auto& columns = settings.generateColumns;
auto& count = settings.generateCount;
auto& delay = settings.generateDelay;
auto& zoom = settings.generateZoom;
auto& zoomStep = settings.viewZoomStep;
auto childSize = ImVec2(imgui::row_widget_width_get(2), imgui::size_without_footer_get().y);
if (ImGui::BeginChild("##Options Child", childSize, ImGuiChildFlags_Borders))
{
ImGui::InputInt2("Start Position", value_ptr(startPosition));
ImGui::InputInt2("Frame Size", value_ptr(size));
ImGui::InputInt2("Pivot", value_ptr(pivot));
ImGui::InputInt("Rows", &rows, step::NORMAL, step::FAST);
ImGui::InputInt("Columns", &columns, step::NORMAL, step::FAST);
ImGui::InputInt("Count", &count, step::NORMAL, step::FAST);
count = glm::min(count, rows * columns);
ImGui::InputInt("Delay", &delay, step::NORMAL, step::FAST);
}
ImGui::EndChild();
ImGui::SameLine();
if (ImGui::BeginChild("##Preview Child", childSize, ImGuiChildFlags_Borders))
{
auto& backgroundColor = settings.previewBackgroundColor;
auto& time = generateTime;
auto& shaderTexture = resources.shaders[shader::TEXTURE];
auto previewSize = ImVec2(ImGui::GetContentRegionAvail().x, imgui::size_without_footer_get(2).y);
generate.size_set(to_vec2(previewSize));
generate.bind();
generate.viewport_set();
generate.clear(backgroundColor);
if (document && document->reference.itemType == anm2::LAYER)
{
auto& texture = document->anm2.content
.spritesheets[document->anm2.content.layers[document->reference.itemID].spritesheetID]
.texture;
auto index = std::clamp((int)(time * count), 0, count);
auto row = index / columns;
auto column = index % columns;
auto crop = startPosition + ivec2(size.x * column, size.y * row);
auto uvMin = (vec2(crop) + vec2(0.5f)) / vec2(texture.size);
auto uvMax = (vec2(crop) + vec2(size) - vec2(0.5f)) / vec2(texture.size);
mat4 transform = generate.transform_get(zoom) * math::quad_model_get(size, {}, pivot);
generate.texture_render(shaderTexture, texture.id, transform, vec4(1.0f), {},
math::uv_vertices_get(uvMin, uvMax).data());
}
generate.unbind();
ImGui::Image(generate.texture, previewSize);
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
ImGui::SliderFloat("##Time", &time, 0.0f, 1.0f, "");
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
ImGui::InputFloat("##Zoom", &zoom, zoomStep, zoomStep, "%.0f%%");
zoom = glm::clamp(zoom, canvas::ZOOM_MIN, canvas::ZOOM_MAX);
}
ImGui::EndChild();
auto widgetSize = imgui::widget_size_with_row_get(2);
if (ImGui::Button("Generate", widgetSize))
{
document->generate_animation_from_grid(startPosition, size, pivot, columns, count, delay);
generatePopup.close();
}
ImGui::SameLine();
if (ImGui::Button("Cancel", widgetSize)) generatePopup.close();
ImGui::EndPopup();
}
changePopup.trigger();
if (ImGui::BeginPopupModal(changePopup.label, &changePopup.isOpen, ImGuiWindowFlags_NoResize))
{
auto& isCrop = settings.changeIsCrop;
auto& isSize = settings.changeIsSize;
auto& isPosition = settings.changeIsPosition;
auto& isPivot = settings.changeIsPivot;
auto& isScale = settings.changeIsScale;
auto& isRotation = settings.changeIsRotation;
auto& isDelay = settings.changeIsDelay;
auto& isTint = settings.changeIsTint;
auto& isColorOffset = settings.changeIsColorOffset;
auto& isVisibleSet = settings.changeIsVisibleSet;
auto& isInterpolatedSet = settings.changeIsInterpolatedSet;
auto& crop = settings.changeCrop;
auto& size = settings.changeSize;
auto& position = settings.changePosition;
auto& pivot = settings.changePivot;
auto& scale = settings.changeScale;
auto& rotation = settings.changeRotation;
auto& delay = settings.changeDelay;
auto& tint = settings.changeTint;
auto& colorOffset = settings.changeColorOffset;
auto& isVisible = settings.changeIsVisible;
auto& isInterpolated = settings.changeIsInterpolated;
auto& isFromSelectedFrame = settings.changeIsFromSelectedFrame;
auto& numberFrames = settings.changeNumberFrames;
auto propertiesSize = imgui::child_size_get(10);
if (ImGui::BeginChild("##Properties", propertiesSize, ImGuiChildFlags_Borders))
{
auto start = [&](const char* checkboxLabel, bool& isEnabled)
{
ImGui::Checkbox(checkboxLabel, &isEnabled);
ImGui::SameLine();
ImGui::BeginDisabled(!isEnabled);
};
auto end = [&]() { ImGui::EndDisabled(); };
auto bool_value = [&](const char* checkboxLabel, const char* valueLabel, bool& isEnabled, bool& value)
{
start(checkboxLabel, isEnabled);
ImGui::Checkbox(valueLabel, &value);
end();
};
auto color3_value = [&](const char* checkboxLabel, const char* valueLabel, bool& isEnabled, vec3& value)
{
start(checkboxLabel, isEnabled);
ImGui::ColorEdit3(valueLabel, value_ptr(value));
end();
};
auto color4_value = [&](const char* checkboxLabel, const char* valueLabel, bool& isEnabled, vec4& value)
{
start(checkboxLabel, isEnabled);
ImGui::ColorEdit4(valueLabel, value_ptr(value));
end();
};
auto float2_value = [&](const char* checkboxLabel, const char* valueLabel, bool& isEnabled, vec2& value)
{
start(checkboxLabel, isEnabled);
ImGui::InputFloat2(valueLabel, value_ptr(value), math::vec2_format_get(value));
end();
};
auto float_value = [&](const char* checkboxLabel, const char* valueLabel, bool& isEnabled, float& value)
{
start(checkboxLabel, isEnabled);
ImGui::InputFloat(valueLabel, &value, step::NORMAL, step::FAST, math::float_format_get(value));
end();
};
auto int_value = [&](const char* checkboxLabel, const char* valueLabel, bool& isEnabled, int& value)
{
start(checkboxLabel, isEnabled);
ImGui::InputInt(valueLabel, &value, step::NORMAL, step::FAST);
end();
};
float2_value("##Is Crop", "Crop", isCrop, crop);
float2_value("##Is Size", "Size", isSize, size);
float2_value("##Is Position", "Position", isPosition, position);
float2_value("##Is Pivot", "Pivot", isPivot, pivot);
float2_value("##Is Scale", "Scale", isScale, scale);
float_value("##Is Rotation", "Rotation", isRotation, rotation);
int_value("##Is Delay", "Delay", isDelay, delay);
color4_value("##Is Tint", "Tint", isTint, tint);
color3_value("##Is Color Offset", "Color Offset", isColorOffset, colorOffset);
bool_value("##Is Visible", "Visible", isVisibleSet, isVisible);
ImGui::SameLine();
bool_value("##Is Interpolated", "Interpolated", isInterpolatedSet, isInterpolated);
}
ImGui::EndChild();
auto settingsSize = imgui::child_size_get(2);
if (ImGui::BeginChild("##Settings", settingsSize, ImGuiChildFlags_Borders))
{
ImGui::Checkbox("From Selected Frame", &isFromSelectedFrame);
ImGui::SetItemTooltip("The frames after the currently referenced frame will be changed with these values.\nIf"
"off, will use all frames.");
ImGui::BeginDisabled(!isFromSelectedFrame);
ImGui::InputInt("Number of Frames", &numberFrames, step::NORMAL, step::FAST);
numberFrames = glm::clamp(numberFrames, anm2::FRAME_NUM_MIN,
(int)document->item_get()->frames.size() - document->reference.frameIndex);
ImGui::SetItemTooltip("Set the number of frames that will be changed.");
ImGui::EndDisabled();
}
ImGui::EndChild();
auto widgetSize = imgui::widget_size_with_row_get(4);
auto frame_change = [&](frame_change::Type type)
{
anm2::FrameChange frameChange;
frameChange.crop = isCrop ? std::make_optional(crop) : std::nullopt;
frameChange.size = isSize ? std::make_optional(size) : std::nullopt;
frameChange.position = isPosition ? std::make_optional(position) : std::nullopt;
frameChange.pivot = isPivot ? std::make_optional(pivot) : std::nullopt;
frameChange.scale = isScale ? std::make_optional(scale) : std::nullopt;
frameChange.rotation = isRotation ? std::make_optional(rotation) : std::nullopt;
frameChange.delay = isDelay ? std::make_optional(delay) : std::nullopt;
frameChange.tint = isTint ? std::make_optional(tint) : std::nullopt;
frameChange.colorOffset = isColorOffset ? std::make_optional(colorOffset) : std::nullopt;
frameChange.isVisible = isVisibleSet ? std::make_optional(isVisible) : std::nullopt;
frameChange.isInterpolated = isInterpolatedSet ? std::make_optional(isInterpolated) : std::nullopt;
document->frames_change(frameChange, type, isFromSelectedFrame, numberFrames);
};
if (ImGui::Button("Add", widgetSize))
{
frame_change(frame_change::ADD);
changePopup.close();
}
ImGui::SameLine();
if (ImGui::Button("Subtract", widgetSize))
{
frame_change(frame_change::SUBTRACT);
changePopup.close();
}
ImGui::SameLine();
if (ImGui::Button("Adjust", widgetSize))
{
frame_change(frame_change::ADJUST);
changePopup.close();
}
ImGui::SameLine();
if (ImGui::Button("Cancel", widgetSize)) changePopup.close();
ImGui::EndPopup();
}
configurePopup.trigger(); configurePopup.trigger();
if (ImGui::BeginPopupModal(configurePopup.label, &configurePopup.isOpen, ImGuiWindowFlags_NoResize)) if (ImGui::BeginPopupModal(configurePopup.label, &configurePopup.isOpen, ImGuiWindowFlags_NoResize))
@@ -115,29 +405,33 @@ namespace anm2ed::taskbar
if (ImGui::BeginTabBar("##Configure Tabs")) if (ImGui::BeginTabBar("##Configure Tabs"))
{ {
if (ImGui::BeginTabItem("View")) if (ImGui::BeginTabItem("General"))
{ {
if (ImGui::BeginChild("##Tab Child", childSize, true)) if (ImGui::BeginChild("##Tab Child", childSize, true))
{ {
ImGui::InputFloat("Zoom Step", &editSettings.viewZoomStep, 10.0f, 10.0f, "%.2f"); ImGui::SeparatorText("File");
ImGui::SetItemTooltip("%s", "When zooming in/out with mouse or shortcut, this value will be used.");
editSettings.viewZoomStep = glm::clamp(editSettings.viewZoomStep, 1.0f, 250.0f); ImGui::Checkbox("Autosaving", &editSettings.fileIsAutosave);
} ImGui::SetItemTooltip("Enables autosaving of documents.");
ImGui::EndChild();
ImGui::EndTabItem(); ImGui::BeginDisabled(!editSettings.fileIsAutosave);
} ImGui::InputInt("Autosave Time (minutes", &editSettings.fileAutosaveTime, step::NORMAL, step::FAST);
editSettings.fileAutosaveTime = glm::clamp(editSettings.fileAutosaveTime, 0, 10);
ImGui::SetItemTooltip("If changed, will autosave documents using this interval.");
ImGui::EndDisabled();
ImGui::SeparatorText("View");
if (ImGui::BeginTabItem("Video"))
{
if (ImGui::BeginChild("##Tab Child", childSize, true))
{
ImGui::InputFloat("Display Scale", &editSettings.displayScale, 0.25f, 0.25f, "%.2f"); ImGui::InputFloat("Display Scale", &editSettings.displayScale, 0.25f, 0.25f, "%.2f");
ImGui::SetItemTooltip("%s", "Change the scale of the display."); ImGui::SetItemTooltip("Change the scale of the display.");
editSettings.displayScale = glm::clamp(editSettings.displayScale, 0.5f, 2.0f); editSettings.displayScale = glm::clamp(editSettings.displayScale, 0.5f, 2.0f);
ImGui::InputFloat("Zoom Step", &editSettings.viewZoomStep, 10.0f, 10.0f, "%.2f");
ImGui::SetItemTooltip("When zooming in/out with mouse or shortcut, this value will be used.");
editSettings.viewZoomStep = glm::clamp(editSettings.viewZoomStep, 1.0f, 250.0f);
ImGui::Checkbox("Vsync", &editSettings.isVsync); ImGui::Checkbox("Vsync", &editSettings.isVsync);
ImGui::SetItemTooltip("%s", ImGui::SetItemTooltip("Toggle vertical sync; synchronizes program update rate with monitor refresh rate.");
"Toggle vertical sync; synchronizes program update rate with monitor refresh rate.");
} }
ImGui::EndChild(); ImGui::EndChild();
@@ -241,11 +535,10 @@ namespace anm2ed::taskbar
ImGui::EndPopup(); ImGui::EndPopup();
} }
if (ImGui::Shortcut(imgui::string_to_chord(settings.shortcutNew), ImGuiInputFlags_RouteGlobal)) dialog.anm2_new(); if (imgui::shortcut(settings.shortcutNew, shortcut::GLOBAL)) dialog.anm2_new();
if (ImGui::Shortcut(imgui::string_to_chord(settings.shortcutOpen), ImGuiInputFlags_RouteGlobal)) dialog.anm2_open(); if (imgui::shortcut(settings.shortcutOpen, shortcut::GLOBAL)) dialog.anm2_open();
if (ImGui::Shortcut(imgui::string_to_chord(settings.shortcutSave), ImGuiInputFlags_RouteGlobal)) manager.save(); if (imgui::shortcut(settings.shortcutSave, shortcut::GLOBAL)) document->save();
if (ImGui::Shortcut(imgui::string_to_chord(settings.shortcutSaveAs), ImGuiInputFlags_RouteGlobal)) if (imgui::shortcut(settings.shortcutSaveAs, shortcut::GLOBAL)) dialog.anm2_save();
dialog.anm2_save(); if (imgui::shortcut(settings.shortcutExit, shortcut::GLOBAL)) isQuitting = true;
if (ImGui::Shortcut(imgui::string_to_chord(settings.shortcutExit), ImGuiInputFlags_RouteGlobal)) isQuit = true;
} }
} }

View File

@@ -1,22 +1,31 @@
#pragma once #pragma once
#include "canvas.h"
#include "dialog.h" #include "dialog.h"
#include "imgui.h" #include "imgui.h"
#include "manager.h" #include "manager.h"
#include "resources.h"
#include "settings.h" #include "settings.h"
namespace anm2ed::taskbar namespace anm2ed::taskbar
{ {
class Taskbar class Taskbar
{ {
canvas::Canvas generate;
float generateTime{};
imgui::PopupHelper generatePopup{imgui::PopupHelper("Generate Animation from Grid")};
imgui::PopupHelper changePopup{imgui::PopupHelper("Change All Frame Properties", imgui::POPUP_SMALL, true)};
imgui::PopupHelper renderPopup{imgui::PopupHelper("Render Animation")};
imgui::PopupHelper configurePopup{imgui::PopupHelper("Configure")}; imgui::PopupHelper configurePopup{imgui::PopupHelper("Configure")};
imgui::PopupHelper aboutPopup{imgui::PopupHelper("About")}; imgui::PopupHelper aboutPopup{imgui::PopupHelper("About")};
settings::Settings editSettings{}; settings::Settings editSettings{};
int selectedShortcut{-1}; int selectedShortcut{-1};
bool isQuittingMode{};
public: public:
float height{}; float height{};
void update(manager::Manager&, settings::Settings&, dialog::Dialog&, bool&); Taskbar();
void update(manager::Manager&, settings::Settings&, resources::Resources&, dialog::Dialog&, bool&);
}; };
}; };

View File

@@ -11,6 +11,7 @@ using namespace anm2ed::manager;
using namespace anm2ed::resources; using namespace anm2ed::resources;
using namespace anm2ed::settings; using namespace anm2ed::settings;
using namespace anm2ed::playback; using namespace anm2ed::playback;
using namespace anm2ed::clipboard;
using namespace glm; using namespace glm;
namespace anm2ed::timeline namespace anm2ed::timeline
@@ -50,14 +51,63 @@ namespace anm2ed::timeline
- Press {} to extend 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::context_menu(Document& document, Settings& settings, Clipboard& clipboard)
{
auto& hoveredFrame = document.hoveredFrame;
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, style.WindowPadding);
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing);
auto copy = [&]()
{
if (auto frame = document.anm2.frame_get(hoveredFrame)) clipboard.set(frame->to_string(hoveredFrame.itemType));
};
auto cut = [&]()
{
if (auto frame = document.anm2.frame_get(hoveredFrame))
{
if (auto item = document.anm2.item_get(hoveredFrame))
{
clipboard.set(frame->to_string(hoveredFrame.itemType));
document.frames_delete(item);
hoveredFrame = anm2::REFERENCE_DEFAULT;
}
}
};
auto paste = [&]() { document.frames_deserialize(clipboard.get()); };
if (imgui::shortcut(settings.shortcutCut, shortcut::FOCUSED)) cut();
if (imgui::shortcut(settings.shortcutCopy, shortcut::FOCUSED)) copy();
if (imgui::shortcut(settings.shortcutPaste, shortcut::FOCUSED)) paste();
if (ImGui::BeginPopupContextWindow("##Context Menu", ImGuiPopupFlags_MouseButtonRight))
{
ImGui::BeginDisabled(hoveredFrame == anm2::REFERENCE_DEFAULT);
if (ImGui::MenuItem("Cut", settings.shortcutCut.c_str())) cut();
if (ImGui::MenuItem("Copy", settings.shortcutCopy.c_str())) copy();
ImGui::EndDisabled();
ImGui::BeginDisabled(clipboard.is_empty());
if (ImGui::MenuItem("Paste")) paste();
ImGui::EndDisabled();
ImGui::EndPopup();
}
ImGui::PopStyleVar(2);
}
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,
Resources& resources, anm2::Type type, int id, int& index) Resources& resources, Clipboard& clipboard, anm2::Type type, int id, int& index)
{ {
auto& anm2 = document.anm2; auto& anm2 = document.anm2;
auto& reference = document.reference; auto& reference = document.reference;
auto item = animation ? animation->item_get(type, id) : nullptr; auto item = animation ? animation->item_get(type, id) : nullptr;
auto& isOnlyShowLayers = settings.timelineIsOnlyShowLayers;
auto isVisible = item ? item->isVisible : false; auto isVisible = item ? item->isVisible : false;
if (isOnlyShowLayers && type != anm2::LAYER) isVisible = false;
auto isActive = reference.itemType == type && reference.itemID == id; auto isActive = reference.itemType == type && reference.itemID == id;
std::string label = "##None"; std::string label = "##None";
icon::Type icon{}; icon::Type icon{};
@@ -129,7 +179,6 @@ namespace anm2ed::timeline
ImGui::TextUnformatted(label.c_str()); ImGui::TextUnformatted(label.c_str());
anm2::Item* item = animation->item_get(type, id); anm2::Item* item = animation->item_get(type, id);
bool& isVisible = item->isVisible;
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4()); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4());
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4()); ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4());
@@ -138,7 +187,7 @@ namespace anm2ed::timeline
ImGui::SetCursorPos(ImVec2(itemSize.x - ImGui::GetTextLineHeightWithSpacing() - ImGui::GetStyle().ItemSpacing.x, ImGui::SetCursorPos(ImVec2(itemSize.x - ImGui::GetTextLineHeightWithSpacing() - ImGui::GetStyle().ItemSpacing.x,
(itemSize.y - ImGui::GetTextLineHeightWithSpacing()) / 2)); (itemSize.y - ImGui::GetTextLineHeightWithSpacing()) / 2));
int visibleIcon = isVisible ? icon::VISIBLE : icon::INVISIBLE; int visibleIcon = item->isVisible ? icon::VISIBLE : icon::INVISIBLE;
if (ImGui::ImageButton("##Visible Toggle", resources.icons[visibleIcon].id, imgui::icon_size_get())) if (ImGui::ImageButton("##Visible Toggle", resources.icons[visibleIcon].id, imgui::icon_size_get()))
document.item_visible_toggle(item); document.item_visible_toggle(item);
@@ -165,22 +214,32 @@ namespace anm2ed::timeline
else else
{ {
auto cursorPos = ImGui::GetCursorPos(); auto cursorPos = ImGui::GetCursorPos();
auto& isShowUnused = settings.timelineIsShowUnused;
ImGui::SetCursorPos(ImVec2(itemSize.x - ImGui::GetTextLineHeightWithSpacing() - ImGui::GetStyle().ItemSpacing.x,
(itemSize.y - ImGui::GetTextLineHeightWithSpacing()) / 2));
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4()); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4());
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4()); ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4());
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4()); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4());
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2()); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2());
ImGui::SetCursorPos(ImVec2(itemSize.x - ImGui::GetTextLineHeightWithSpacing() - ImGui::GetStyle().ItemSpacing.x,
(itemSize.y - ImGui::GetTextLineHeightWithSpacing()) / 2));
auto& isShowUnused = settings.timelineIsShowUnused;
auto unusedIcon = isShowUnused ? icon::SHOW_UNUSED : icon::HIDE_UNUSED; auto unusedIcon = isShowUnused ? icon::SHOW_UNUSED : icon::HIDE_UNUSED;
if (ImGui::ImageButton("##Unused Toggle", resources.icons[unusedIcon].id, imgui::icon_size_get())) if (ImGui::ImageButton("##Unused Toggle", resources.icons[unusedIcon].id, imgui::icon_size_get()))
isShowUnused = !isShowUnused; isShowUnused = !isShowUnused;
ImGui::SetItemTooltip(isShowUnused ? "Unused layers/nulls are shown. Press to hide." ImGui::SetItemTooltip(isShowUnused ? "Unused layers/nulls are shown. Press to hide."
: "Unused layers/nulls are hidden. Press to show."); : "Unused layers/nulls are hidden. Press to show.");
auto onlyShowLayersIcon = isOnlyShowLayers ? icon::SHOW_LAYERS : icon::HIDE_LAYERS;
ImGui::SetCursorPos(
ImVec2(itemSize.x - (ImGui::GetTextLineHeightWithSpacing() * 2) - ImGui::GetStyle().ItemSpacing.x,
(itemSize.y - ImGui::GetTextLineHeightWithSpacing()) / 2));
if (ImGui::ImageButton("##Layers Visibility Toggle", resources.icons[onlyShowLayersIcon].id,
imgui::icon_size_get()))
isOnlyShowLayers = !isOnlyShowLayers;
ImGui::SetItemTooltip(isOnlyShowLayers
? "Only layers are visible. Press to toggle visibility for all items."
: "Non-layer items are visible. Press to toggle visiblity only for layers.");
ImGui::PopStyleVar(); ImGui::PopStyleVar();
ImGui::PopStyleColor(3); ImGui::PopStyleColor(3);
@@ -201,7 +260,7 @@ namespace anm2ed::timeline
} }
void Timeline::items_child(Manager& manager, Document& document, anm2::Animation* animation, Settings& settings, void Timeline::items_child(Manager& manager, Document& document, anm2::Animation* animation, Settings& settings,
Resources& resources) Resources& resources, Clipboard& clipboard)
{ {
auto& reference = document.reference; auto& reference = document.reference;
@@ -232,7 +291,7 @@ namespace anm2ed::timeline
{ {
ImGui::TableNextRow(); ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0); ImGui::TableSetColumnIndex(0);
item_child(manager, document, animation, settings, resources, type, id, index); item_child(manager, document, animation, settings, resources, clipboard, type, id, index);
}; };
item_child_row(anm2::NONE); item_child_row(anm2::NONE);
@@ -302,13 +361,16 @@ namespace anm2ed::timeline
} }
void Timeline::frame_child(Document& document, anm2::Animation* animation, Settings& settings, Resources& resources, void Timeline::frame_child(Document& document, anm2::Animation* animation, Settings& settings, Resources& resources,
anm2::Type type, int id, int& index, float width) Clipboard& clipboard, anm2::Type type, int id, int& index, float width)
{ {
auto& anm2 = document.anm2; auto& anm2 = document.anm2;
auto& playback = document.playback; auto& playback = document.playback;
auto& reference = document.reference; auto& reference = document.reference;
auto& hoveredFrame = document.hoveredFrame;
auto item = animation ? animation->item_get(type, id) : nullptr; auto item = animation ? animation->item_get(type, id) : nullptr;
auto& isOnlyShowLayers = settings.timelineIsOnlyShowLayers;
auto isVisible = item ? item->isVisible : false; auto isVisible = item ? item->isVisible : false;
if (isOnlyShowLayers && type != anm2::LAYER) isVisible = false;
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, style.WindowPadding); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, style.WindowPadding);
@@ -489,6 +551,8 @@ namespace anm2ed::timeline
reference = frameReference; reference = frameReference;
reference.frameTime = frameTime; reference.frameTime = frameTime;
} }
if (ImGui::IsItemHovered()) hoveredFrame = frameReference;
if (type != anm2::TRIGGER) ImGui::SameLine(); if (type != anm2::TRIGGER) ImGui::SameLine();
ImGui::PopStyleColor(3); ImGui::PopStyleColor(3);
@@ -502,6 +566,8 @@ namespace anm2ed::timeline
ImGui::PopID(); ImGui::PopID();
} }
context_menu(document, settings, clipboard);
} }
} }
ImGui::EndChild(); ImGui::EndChild();
@@ -511,10 +577,12 @@ namespace anm2ed::timeline
ImGui::PopID(); ImGui::PopID();
} }
void Timeline::frames_child(Document& document, anm2::Animation* animation, Settings& settings, Resources& resources) void Timeline::frames_child(Document& document, anm2::Animation* animation, Settings& settings, Resources& resources,
Clipboard& clipboard)
{ {
auto& anm2 = document.anm2; auto& anm2 = document.anm2;
auto& playback = document.playback; auto& playback = document.playback;
auto& hoveredFrame = document.hoveredFrame;
auto itemsChildWidth = ImGui::GetTextLineHeightWithSpacing() * 15; auto itemsChildWidth = ImGui::GetTextLineHeightWithSpacing() * 15;
@@ -571,11 +639,13 @@ namespace anm2ed::timeline
{ {
ImGui::TableNextRow(); ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0); ImGui::TableSetColumnIndex(0);
frame_child(document, animation, settings, resources, type, id, index, childWidth); frame_child(document, animation, settings, resources, clipboard, type, id, index, childWidth);
}; };
frames_child_row(anm2::NONE); frames_child_row(anm2::NONE);
//hoveredFrame = anm2::REFERENCE_DEFAULT;
if (animation) if (animation)
{ {
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 2.0f); ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 2.0f);
@@ -622,6 +692,8 @@ namespace anm2ed::timeline
pickerLineDrawList->PopClipRect(); pickerLineDrawList->PopClipRect();
ImGui::PopStyleVar(); ImGui::PopStyleVar();
context_menu(document, settings, clipboard);
} }
ImGui::EndChild(); ImGui::EndChild();
ImGui::PopStyleVar(); ImGui::PopStyleVar();
@@ -917,7 +989,7 @@ namespace anm2ed::timeline
} }
} }
void Timeline::update(Manager& manager, Settings& settings, Resources& resources) void Timeline::update(Manager& manager, Settings& settings, Resources& resources, Clipboard& clipboard)
{ {
auto& document = *manager.get(); auto& document = *manager.get();
auto& playback = document.playback; auto& playback = document.playback;
@@ -931,8 +1003,8 @@ namespace anm2ed::timeline
if (ImGui::Begin("Timeline", &settings.windowIsTimeline)) if (ImGui::Begin("Timeline", &settings.windowIsTimeline))
{ {
isWindowHovered = ImGui::IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows); isWindowHovered = ImGui::IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows);
frames_child(document, animation, settings, resources); frames_child(document, animation, settings, resources, clipboard);
items_child(manager, document, animation, settings, resources); items_child(manager, document, animation, settings, resources, clipboard);
} }
ImGui::PopStyleVar(); ImGui::PopStyleVar();
ImGui::End(); ImGui::End();
@@ -956,10 +1028,7 @@ namespace anm2ed::timeline
} }
} }
if (imgui::chord_repeating(imgui::string_to_chord(settings.shortcutShortenFrame))) if (imgui::chord_repeating(imgui::string_to_chord(settings.shortcutShortenFrame))) document.frame_shorten();
if (auto frame = anm2.frame_get(reference); frame) frame->shorten(); if (imgui::chord_repeating(imgui::string_to_chord(settings.shortcutExtendFrame))) document.frame_extend();
if (imgui::chord_repeating(imgui::string_to_chord(settings.shortcutExtendFrame)))
if (auto frame = anm2.frame_get(reference); frame) frame->extend();
} }
} }

View File

@@ -1,6 +1,7 @@
#pragma once #pragma once
#include "anm2.h" #include "anm2.h"
#include "clipboard.h"
#include "document.h" #include "document.h"
#include "manager.h" #include "manager.h"
#include "resources.h" #include "resources.h"
@@ -25,16 +26,19 @@ namespace anm2ed::timeline
ImDrawList* pickerLineDrawList{}; ImDrawList* pickerLineDrawList{};
ImGuiStyle style{}; ImGuiStyle style{};
void context_menu(document::Document&, settings::Settings&, clipboard::Clipboard&);
void item_child(manager::Manager&, Document&, anm2::Animation*, settings::Settings&, resources::Resources&, void item_child(manager::Manager&, Document&, anm2::Animation*, settings::Settings&, resources::Resources&,
anm2::Type, int, int&); clipboard::Clipboard&, anm2::Type, int, int&);
void items_child(manager::Manager&, Document&, anm2::Animation*, settings::Settings&, resources::Resources&); void items_child(manager::Manager&, Document&, anm2::Animation*, settings::Settings&, resources::Resources&,
void frame_child(document::Document&, anm2::Animation*, settings::Settings&, resources::Resources&, anm2::Type, int, clipboard::Clipboard&);
int&, float); void frame_child(document::Document&, anm2::Animation*, settings::Settings&, resources::Resources&,
void frames_child(document::Document&, anm2::Animation*, settings::Settings&, resources::Resources&); clipboard::Clipboard&, anm2::Type, int, int&, float);
void frames_child(document::Document&, anm2::Animation*, settings::Settings&, resources::Resources&,
clipboard::Clipboard&);
void popups(document::Document&, anm2::Animation*, settings::Settings&); void popups(document::Document&, anm2::Animation*, settings::Settings&);
public: public:
void update(manager::Manager&, settings::Settings&, resources::Resources&); void update(manager::Manager&, settings::Settings&, resources::Resources&, clipboard::Clipboard&);
}; };
} }

View File

@@ -69,6 +69,16 @@ namespace anm2ed::types::merge
}; };
} }
namespace anm2ed::types::frame_change
{
enum Type
{
ADD,
SUBTRACT,
ADJUST
};
}
namespace anm2ed::types::color namespace anm2ed::types::color
{ {
using namespace glm; using namespace glm;

View File

@@ -10,6 +10,9 @@
namespace anm2ed::util::time namespace anm2ed::util::time
{ {
constexpr auto SECOND_S = 1.0;
constexpr auto SECOND_M = 60.0;
std::string get(const char*); std::string get(const char*);
} }
@@ -95,4 +98,13 @@ namespace anm2ed::util::vector
return moveIndices; return moveIndices;
} }
template <typename T> bool in_bounds(std::vector<T>& v, int& index)
{
return index >= 0 || index <= (int)v.size() - 1;
}
template <typename T> void clamp_in_bounds(std::vector<T>& v, int& index)
{
index = std::clamp(index, 0, (int)v.size() - 1);
}
} }

106
src/welcome.cpp Normal file
View File

@@ -0,0 +1,106 @@
#include "welcome.h"
#include <ranges>
using namespace anm2ed::dialog;
using namespace anm2ed::taskbar;
using namespace anm2ed::documents;
using namespace anm2ed::resources;
using namespace anm2ed::manager;
namespace anm2ed::welcome
{
void Welcome::update(Manager& manager, Resources& resources, Dialog& dialog, Taskbar& taskbar, Documents& documents)
{
auto viewport = ImGui::GetMainViewport();
auto windowHeight = viewport->Size.y - taskbar.height - documents.height;
ImGui::SetNextWindowViewport(viewport->ID);
ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x, viewport->Pos.y + taskbar.height + documents.height));
ImGui::SetNextWindowSize(ImVec2(viewport->Size.x, windowHeight));
if (ImGui::Begin("##Welcome", nullptr,
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoNavFocus | ImGuiWindowFlags_NoScrollbar |
ImGuiWindowFlags_NoScrollWithMouse))
{
ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE_LARGE);
ImGui::Text("Anm2Ed");
ImGui::PopFont();
ImGui::Text(
"Select a recent file or an option below. You can also drag and drop files into the window to open them.");
auto widgetSize = imgui::widget_size_with_row_get(2);
if (ImGui::Button("New", widgetSize)) dialog.anm2_new(); // handled in taskbar.cpp
ImGui::SameLine();
if (ImGui::Button("Open", widgetSize)) dialog.anm2_open(); // handled in taskbar.cpp
if (ImGui::BeginChild("##Recent Child", ImVec2(), ImGuiChildFlags_Borders))
{
for (auto [i, file] : std::views::enumerate(manager.recentFiles))
{
ImGui::PushID(i);
auto label = std::format(FILE_LABEL_FORMAT, file.filename().string(), file.string());
if (ImGui::Selectable(label.c_str()))
{
manager.open(file);
ImGui::PopID();
break;
}
ImGui::PopID();
}
}
ImGui::EndChild();
}
ImGui::End();
if (!manager.autosaveFiles.empty() && !restorePopup.is_open()) restorePopup.open();
restorePopup.trigger();
if (ImGui::BeginPopupModal(restorePopup.label, &restorePopup.isOpen, ImGuiWindowFlags_NoResize))
{
ImGui::Text("Autosaved files detected. Would you like to restore them?");
auto childSize = imgui::child_size_get(5);
if (ImGui::BeginChild("##Autosave Documents", childSize, ImGuiChildFlags_Borders,
ImGuiWindowFlags_HorizontalScrollbar))
{
for (auto& file : manager.autosaveFiles)
{
auto label = std::format(FILE_LABEL_FORMAT, file.filename().string(), file.string());
ImGui::TextUnformatted(label.c_str());
}
}
ImGui::EndChild();
auto widgetSize = imgui::widget_size_with_row_get(2);
if (ImGui::Button("Yes", widgetSize))
{
manager.autosave_files_open();
restorePopup.close();
}
ImGui::SameLine();
if (ImGui::Button("No", widgetSize))
{
manager.autosave_files_clear();
restorePopup.close();
}
ImGui::EndPopup();
}
}
}

16
src/welcome.h Normal file
View File

@@ -0,0 +1,16 @@
#pragma once
#include "documents.h"
#include "manager.h"
#include "taskbar.h"
namespace anm2ed::welcome
{
class Welcome
{
imgui::PopupHelper restorePopup{imgui::PopupHelper("Restore", imgui::POPUP_SMALL, true)};
public:
void update(manager::Manager&, resources::Resources&, dialog::Dialog&, taskbar::Taskbar&, documents::Documents&);
};
};