Autosave feature, spritesheet editor dashed lines, refactoring, fixes
This commit is contained in:
@@ -63,13 +63,14 @@ if (WIN32)
|
||||
target_link_options(${PROJECT_NAME} PRIVATE /STACK:0xffffff)
|
||||
else ()
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE
|
||||
-O2 -Wall -Wextra -pedantic
|
||||
-Wall -Wextra -pedantic
|
||||
)
|
||||
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE DEBUG)
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE -pg)
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE -O0 -pg)
|
||||
else ()
|
||||
set(CMAKE_BUILD_TYPE "Release")
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE -O2)
|
||||
endif ()
|
||||
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE m)
|
||||
|
||||
@@ -48,15 +48,18 @@ namespace anm2ed::animation_preview
|
||||
auto& isRootTransform = settings.previewIsRootTransform;
|
||||
auto& isPivots = settings.previewIsPivots;
|
||||
auto& isAxes = settings.previewIsAxes;
|
||||
auto& isIcons = settings.previewIsIcons;
|
||||
auto& isAltIcons = settings.previewIsAltIcons;
|
||||
auto& isBorder = settings.previewIsBorder;
|
||||
auto& tool = settings.tool;
|
||||
auto& isOnlyShowLayers = settings.timelineIsOnlyShowLayers;
|
||||
auto& shaderLine = resources.shaders[shader::LINE];
|
||||
auto& shaderAxes = resources.shaders[shader::AXIS];
|
||||
auto& shaderGrid = resources.shaders[shader::GRID];
|
||||
auto& shaderTexture = resources.shaders[shader::TEXTURE];
|
||||
|
||||
settings.previewPan = pan;
|
||||
settings.previewZoom = zoom;
|
||||
|
||||
if (ImGui::Begin("Animation Preview", &settings.windowIsAnimationPreview))
|
||||
{
|
||||
|
||||
@@ -128,7 +131,6 @@ namespace anm2ed::animation_preview
|
||||
{
|
||||
ImGui::Checkbox("Root Transform", &isRootTransform);
|
||||
ImGui::Checkbox("Pivots", &isPivots);
|
||||
ImGui::Checkbox("Icons", &isIcons);
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
@@ -152,7 +154,8 @@ namespace anm2ed::animation_preview
|
||||
if (isAxes) axes_render(shaderAxes, zoom, pan, axesColor);
|
||||
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 root = animation->rootAnimation.frame_generate(time, anm2::ROOT);
|
||||
@@ -160,7 +163,7 @@ namespace anm2ed::animation_preview
|
||||
if (isRootTransform)
|
||||
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,
|
||||
math::percent_to_unit(root.scale), root.rotation);
|
||||
@@ -185,13 +188,14 @@ namespace anm2ed::animation_preview
|
||||
auto& texture = spritesheet->texture;
|
||||
if (!texture.is_valid()) continue;
|
||||
|
||||
auto layerTransform = transform * math::quad_model_get(frame.size, frame.position, frame.pivot,
|
||||
math::percent_to_unit(frame.scale), frame.rotation);
|
||||
auto layerModel = math::quad_model_get(frame.size, frame.position, frame.pivot,
|
||||
math::percent_to_unit(frame.scale), frame.rotation);
|
||||
auto layerTransform = transform * layerModel;
|
||||
|
||||
auto uvMin = frame.crop / vec2(texture.size);
|
||||
auto uvMax = (frame.crop + frame.size) / vec2(texture.size);
|
||||
auto vertices = math::uv_vertices_get(uvMin, uvMax);
|
||||
vec3 frameColorOffset = frame.offset + colorOffset;
|
||||
vec3 frameColorOffset = frame.colorOffset + colorOffset;
|
||||
vec4 frameTint = frame.tint;
|
||||
frameTint.a = std::max(0.0f, frameTint.a - alphaOffset);
|
||||
|
||||
@@ -199,48 +203,46 @@ namespace anm2ed::animation_preview
|
||||
|
||||
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)
|
||||
{
|
||||
auto pivotTransform =
|
||||
transform * math::quad_model_get(PIVOT_SIZE, frame.position, PIVOT_SIZE * 0.5f,
|
||||
math::percent_to_unit(frame.scale), frame.rotation);
|
||||
auto pivotModel = math::quad_model_get(PIVOT_SIZE, frame.position, PIVOT_SIZE * 0.5f,
|
||||
math::percent_to_unit(frame.scale), frame.rotation);
|
||||
auto pivotTransform = transform * pivotModel;
|
||||
|
||||
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 || isOnlyShowLayers) continue;
|
||||
|
||||
auto& isShowRect = anm2.content.nulls[id].isShowRect;
|
||||
|
||||
if (auto frame = nullAnimation.frame_generate(time, anm2::NULL_); frame.isVisible)
|
||||
{
|
||||
if (!nullAnimation.isVisible) continue;
|
||||
auto icon = isShowRect ? icon::POINT : icon::TARGET;
|
||||
auto& size = isShowRect ? POINT_SIZE : TARGET_SIZE;
|
||||
auto color = isOnionskin ? vec4(colorOffset, 1.0f - alphaOffset)
|
||||
: id == reference.itemID && reference.itemType == anm2::NULL_ ? color::RED
|
||||
: NULL_COLOR;
|
||||
|
||||
auto& isShowRect = anm2.content.nulls[id].isShowRect;
|
||||
auto nullModel = math::quad_model_get(size, frame.position, size * 0.5f, math::percent_to_unit(frame.scale),
|
||||
frame.rotation);
|
||||
auto nullTransform = transform * nullModel;
|
||||
|
||||
if (auto frame = nullAnimation.frame_generate(time, anm2::NULL_); frame.isVisible)
|
||||
texture_render(shaderTexture, resources.icons[icon].id, nullTransform, color);
|
||||
|
||||
if (isShowRect)
|
||||
{
|
||||
auto icon = isShowRect ? icon::POINT : icon::TARGET;
|
||||
auto& size = isShowRect ? POINT_SIZE : TARGET_SIZE;
|
||||
auto color = isOnionskin ? vec4(colorOffset, 1.0f - alphaOffset)
|
||||
: id == reference.itemID && reference.itemType == anm2::NULL_ ? color::RED
|
||||
: NULL_COLOR;
|
||||
auto rectModel = math::quad_model_get(NULL_RECT_SIZE, frame.position, NULL_RECT_SIZE * 0.5f,
|
||||
math::percent_to_unit(frame.scale), frame.rotation);
|
||||
auto rectTransform = transform * rectModel;
|
||||
|
||||
auto nullTransform = transform * math::quad_model_get(size, frame.position, size * 0.5f,
|
||||
math::percent_to_unit(frame.scale), frame.rotation);
|
||||
|
||||
texture_render(shaderTexture, resources.icons[icon].id, nullTransform, color);
|
||||
|
||||
if (isShowRect)
|
||||
{
|
||||
auto rectTransform =
|
||||
transform * math::quad_model_get(NULL_RECT_SIZE, frame.position, NULL_RECT_SIZE * 0.5f,
|
||||
math::percent_to_unit(frame.scale), frame.rotation);
|
||||
|
||||
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 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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@ namespace anm2ed::animations
|
||||
auto& mergeMultiSelect = document.animationMergeMultiSelect;
|
||||
auto& mergeTarget = document.mergeTarget;
|
||||
|
||||
hovered = -1;
|
||||
|
||||
if (ImGui::Begin("Animations", &settings.windowIsAnimations))
|
||||
{
|
||||
auto childSize = imgui::size_without_footer_get();
|
||||
@@ -45,7 +47,7 @@ namespace anm2ed::animations
|
||||
if (imgui::selectable_input_text(animation.name,
|
||||
std::format("###Document #{} Animation #{}", manager.selected, i),
|
||||
animation.name, multiSelect.contains(i)))
|
||||
reference = {(int)i};
|
||||
document.animation_set(i);
|
||||
if (ImGui::IsItemHovered()) hovered = i;
|
||||
ImGui::PopFont();
|
||||
|
||||
@@ -113,18 +115,7 @@ namespace anm2ed::animations
|
||||
auto cut = [&]()
|
||||
{
|
||||
copy();
|
||||
|
||||
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;
|
||||
}
|
||||
document.animations_remove();
|
||||
};
|
||||
|
||||
auto paste = [&]()
|
||||
@@ -191,7 +182,7 @@ namespace anm2ed::animations
|
||||
ImGui::SameLine();
|
||||
|
||||
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::SameLine();
|
||||
|
||||
211
src/anm2.cpp
211
src/anm2.cpp
@@ -17,17 +17,6 @@ using namespace glm;
|
||||
|
||||
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(XMLElement* element)
|
||||
@@ -466,9 +455,9 @@ namespace anm2ed::anm2
|
||||
xml::query_color_attribute(element, "GreenTint", tint.g);
|
||||
xml::query_color_attribute(element, "BlueTint", tint.b);
|
||||
xml::query_color_attribute(element, "AlphaTint", tint.a);
|
||||
xml::query_color_attribute(element, "RedOffset", offset.r);
|
||||
xml::query_color_attribute(element, "GreenOffset", offset.g);
|
||||
xml::query_color_attribute(element, "BlueOffset", offset.b);
|
||||
xml::query_color_attribute(element, "RedOffset", colorOffset.r);
|
||||
xml::query_color_attribute(element, "GreenOffset", colorOffset.g);
|
||||
xml::query_color_attribute(element, "BlueOffset", colorOffset.b);
|
||||
element->QueryFloatAttribute("Rotation", &rotation);
|
||||
element->QueryBoolAttribute("Interpolated", &isInterpolated);
|
||||
}
|
||||
@@ -497,9 +486,9 @@ namespace anm2ed::anm2
|
||||
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(offset.r));
|
||||
element->SetAttribute("GreenOffset", math::float_to_uint8(offset.g));
|
||||
element->SetAttribute("BlueOffset", math::float_to_uint8(offset.b));
|
||||
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;
|
||||
@@ -520,9 +509,9 @@ namespace anm2ed::anm2
|
||||
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(offset.r));
|
||||
element->SetAttribute("GreenOffset", math::float_to_uint8(offset.g));
|
||||
element->SetAttribute("BlueOffset", math::float_to_uint8(offset.b));
|
||||
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;
|
||||
@@ -537,6 +526,69 @@ namespace anm2ed::anm2
|
||||
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()
|
||||
{
|
||||
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.position = glm::mix(frame.position, frameNext->position, 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);
|
||||
}
|
||||
|
||||
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(XMLElement* element)
|
||||
@@ -1048,12 +1185,12 @@ namespace anm2ed::anm2
|
||||
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);
|
||||
}
|
||||
|
||||
Item* Anm2::item_get(Reference& reference)
|
||||
Item* Anm2::item_get(Reference reference)
|
||||
{
|
||||
if (Animation* animation = animation_get(reference))
|
||||
{
|
||||
@@ -1074,7 +1211,7 @@ namespace anm2ed::anm2
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Frame* Anm2::frame_get(Reference& reference)
|
||||
Frame* Anm2::frame_get(Reference reference)
|
||||
{
|
||||
Item* item = item_get(reference);
|
||||
if (!item) return nullptr;
|
||||
@@ -1260,7 +1397,7 @@ namespace anm2ed::anm2
|
||||
baked.rotation = glm::mix(baseFrame.rotation, baseFrameNext.rotation, interpolation);
|
||||
baked.position = glm::mix(baseFrame.position, baseFrameNext.position, 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);
|
||||
|
||||
if (isRoundScale) baked.scale = vec2(ivec2(baked.scale));
|
||||
@@ -1275,4 +1412,26 @@ namespace anm2ed::anm2
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
14
src/anm2.h
14
src/anm2.h
@@ -44,8 +44,6 @@ namespace anm2ed::anm2
|
||||
int frameIndex{-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;
|
||||
};
|
||||
|
||||
@@ -150,7 +148,7 @@ namespace anm2ed::anm2
|
||||
X(position, glm::vec2, {}) \
|
||||
X(size, glm::vec2, {}) \
|
||||
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)
|
||||
|
||||
class Frame
|
||||
@@ -162,6 +160,7 @@ namespace anm2ed::anm2
|
||||
|
||||
Frame();
|
||||
Frame(tinyxml2::XMLElement*, Type);
|
||||
std::string to_string(Type type);
|
||||
void serialize(tinyxml2::XMLDocument&, tinyxml2::XMLElement*, Type);
|
||||
void shorten();
|
||||
void extend();
|
||||
@@ -188,6 +187,8 @@ namespace anm2ed::anm2
|
||||
void serialize(tinyxml2::XMLDocument&, tinyxml2::XMLElement*, Type, int = -1);
|
||||
int length(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
|
||||
@@ -238,9 +239,9 @@ namespace anm2ed::anm2
|
||||
std::string to_string();
|
||||
Anm2(const std::string&, std::string* = nullptr);
|
||||
uint64_t hash();
|
||||
Animation* animation_get(Reference&);
|
||||
Item* item_get(Reference&);
|
||||
Frame* frame_get(Reference&);
|
||||
Animation* animation_get(Reference);
|
||||
Item* item_get(Reference);
|
||||
Frame* frame_get(Reference);
|
||||
bool spritesheet_add(const std::string&, const std::string&, int&);
|
||||
Spritesheet* spritesheet_get(int);
|
||||
void spritesheet_remove(int);
|
||||
@@ -255,5 +256,6 @@ namespace anm2ed::anm2
|
||||
std::set<int> nulls_unused(Reference = REFERENCE_DEFAULT);
|
||||
std::vector<std::string> spritesheet_names_get();
|
||||
void bake(Reference, int = 1, bool = true, bool = true);
|
||||
void generate_from_grid(Reference, glm::ivec2, glm::ivec2, glm::ivec2, int, int, int);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -226,13 +226,35 @@ namespace anm2ed::canvas
|
||||
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);
|
||||
|
||||
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));
|
||||
|
||||
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);
|
||||
glDrawArrays(GL_LINE_LOOP, 0, 4);
|
||||
|
||||
|
||||
@@ -14,6 +14,10 @@ namespace anm2ed::canvas
|
||||
constexpr auto ZOOM_MAX = 2000.0f;
|
||||
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
|
||||
{
|
||||
public:
|
||||
@@ -39,13 +43,14 @@ namespace anm2ed::canvas
|
||||
void framebuffer_set();
|
||||
void framebuffer_resize_check();
|
||||
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 grid_render(shader::Shader&, float, glm::vec2, glm::ivec2 = glm::ivec2(32, 32), glm::ivec2 = {},
|
||||
glm::vec4 = glm::vec4(1.0f));
|
||||
void texture_render(shader::Shader&, GLuint&, glm::mat4&, glm::vec4 = glm::vec4(1.0f), glm::vec3 = {},
|
||||
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 clear(glm::vec4&);
|
||||
void bind();
|
||||
|
||||
@@ -13,6 +13,7 @@ using namespace anm2ed::playback;
|
||||
using namespace anm2ed::resources;
|
||||
using namespace anm2ed::settings;
|
||||
using namespace anm2ed::taskbar;
|
||||
using namespace anm2ed::welcome;
|
||||
|
||||
namespace anm2ed::dockspace
|
||||
{
|
||||
@@ -32,9 +33,9 @@ namespace anm2ed::dockspace
|
||||
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoBringToFrontOnFocus |
|
||||
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.windowIsAnimations) animations.update(manager, settings, resources, clipboard);
|
||||
@@ -45,10 +46,12 @@ namespace anm2ed::dockspace
|
||||
if (settings.windowIsOnionskin) onionskin.update(settings);
|
||||
if (settings.windowIsSpritesheetEditor) spritesheetEditor.update(manager, settings, resources);
|
||||
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);
|
||||
}
|
||||
}
|
||||
else
|
||||
welcome.update(manager, resources, dialog, taskbar, documents);
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "taskbar.h"
|
||||
#include "timeline.h"
|
||||
#include "tools.h"
|
||||
#include "welcome.h"
|
||||
|
||||
namespace anm2ed::dockspace
|
||||
{
|
||||
@@ -29,6 +30,7 @@ namespace anm2ed::dockspace
|
||||
spritesheets::Spritesheets spritesheets;
|
||||
timeline::Timeline timeline;
|
||||
tools::Tools tools;
|
||||
welcome::Welcome welcome;
|
||||
|
||||
public:
|
||||
void update(taskbar::Taskbar&, documents::Documents&, manager::Manager&, settings::Settings&, resources::Resources&,
|
||||
|
||||
131
src/document.cpp
131
src/document.cpp
@@ -1,17 +1,20 @@
|
||||
#include "document.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <ranges>
|
||||
|
||||
#include "anm2.h"
|
||||
#include "filesystem.h"
|
||||
#include "log.h"
|
||||
#include "toast.h"
|
||||
#include "util.h"
|
||||
#include <algorithm>
|
||||
#include <ranges>
|
||||
|
||||
using namespace anm2ed::anm2;
|
||||
using namespace anm2ed::filesystem;
|
||||
using namespace anm2ed::toast;
|
||||
using namespace anm2ed::types;
|
||||
using namespace anm2ed::util;
|
||||
using namespace anm2ed::log;
|
||||
|
||||
using namespace glm;
|
||||
|
||||
@@ -40,9 +43,28 @@ namespace anm2ed::document
|
||||
|
||||
if (anm2.serialize(this->path, errorString))
|
||||
{
|
||||
toasts.info(std::format("Saved document to: {}", path));
|
||||
clean();
|
||||
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;
|
||||
}
|
||||
@@ -56,6 +78,8 @@ namespace anm2ed::document
|
||||
{
|
||||
saveHash = anm2.hash();
|
||||
hash = saveHash;
|
||||
lastAutosaveTime = 0.0f;
|
||||
isForceDirty = false;
|
||||
}
|
||||
|
||||
void Document::change(change::Type type)
|
||||
@@ -88,6 +112,8 @@ namespace anm2ed::document
|
||||
case change::SPRITESHEETS:
|
||||
spritesheet_set();
|
||||
break;
|
||||
case change::ITEMS:
|
||||
break;
|
||||
case change::ALL:
|
||||
layer_set();
|
||||
null_set();
|
||||
@@ -104,14 +130,19 @@ namespace anm2ed::document
|
||||
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();
|
||||
}
|
||||
|
||||
std::string Document::filename_get()
|
||||
std::filesystem::path Document::filename_get()
|
||||
{
|
||||
return path.filename().string();
|
||||
return path.filename();
|
||||
}
|
||||
|
||||
bool Document::is_valid()
|
||||
@@ -224,11 +255,11 @@ namespace anm2ed::document
|
||||
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;
|
||||
snapshot("Frame Color Offset");
|
||||
frame->offset = offset;
|
||||
frame->colorOffset = colorOffset;
|
||||
change(change::FRAMES);
|
||||
}
|
||||
|
||||
@@ -264,6 +295,52 @@ namespace anm2ed::document
|
||||
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()
|
||||
{
|
||||
return anm2.item_get(reference);
|
||||
@@ -446,6 +523,13 @@ namespace anm2ed::document
|
||||
return anm2.animation_get(reference);
|
||||
}
|
||||
|
||||
void Document::animation_set(int index)
|
||||
{
|
||||
snapshot("Select Animation");
|
||||
reference = {index};
|
||||
change(change::ITEMS);
|
||||
}
|
||||
|
||||
void Document::animation_add()
|
||||
{
|
||||
snapshot("Add Animation");
|
||||
@@ -489,12 +573,21 @@ namespace anm2ed::document
|
||||
change(change::ANIMATIONS);
|
||||
}
|
||||
|
||||
void Document::animation_remove()
|
||||
void Document::animations_remove()
|
||||
{
|
||||
snapshot("Remove Animation(s)");
|
||||
for (auto& i : animationMultiSelect | std::views::reverse)
|
||||
anm2.animations.items.erase(anm2.animations.items.begin() + i);
|
||||
animationMultiSelect.clear();
|
||||
|
||||
if (!animationMultiSelect.empty())
|
||||
{
|
||||
for (auto& i : animationMultiSelect | std::views::reverse)
|
||||
anm2.animations.items.erase(anm2.animations.items.begin() + i);
|
||||
animationMultiSelect.clear();
|
||||
}
|
||||
else if (hoveredAnimation > -1)
|
||||
{
|
||||
anm2.animations.items.erase(anm2.animations.items.begin() + hoveredAnimation);
|
||||
hoveredAnimation = -1;
|
||||
}
|
||||
|
||||
change(change::ANIMATIONS);
|
||||
}
|
||||
@@ -508,7 +601,7 @@ namespace anm2ed::document
|
||||
|
||||
void Document::animations_deserialize(const std::string& string)
|
||||
{
|
||||
snapshot("Paste Animations");
|
||||
snapshot("Paste Animation(s)");
|
||||
auto& multiSelect = animationMultiSelect;
|
||||
auto start = multiSelect.empty() ? anm2.animations.items.size() : *multiSelect.rbegin() + 1;
|
||||
std::set<int> indices{};
|
||||
@@ -522,9 +615,21 @@ namespace anm2ed::document
|
||||
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()
|
||||
{
|
||||
snapshot("Merge Animations");
|
||||
snapshot("Merge Animation(s)");
|
||||
int merged{};
|
||||
if (animationMultiSelect.size() > 1)
|
||||
merged = anm2.animations.merge(*animationMultiSelect.begin(), animationMultiSelect);
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace anm2ed::document
|
||||
glm::vec2 previewPan{};
|
||||
glm::vec2 editorPan{};
|
||||
float editorZoom{200};
|
||||
|
||||
int overlayIndex{};
|
||||
|
||||
anm2::Reference reference{};
|
||||
@@ -34,6 +35,8 @@ namespace anm2ed::document
|
||||
imgui::MultiSelectStorage animationMultiSelect;
|
||||
imgui::MultiSelectStorage animationMergeMultiSelect;
|
||||
|
||||
anm2::Reference hoveredFrame{anm2::REFERENCE_DEFAULT};
|
||||
|
||||
int referenceSpritesheet{-1};
|
||||
int hoveredSpritesheet{-1};
|
||||
std::set<int> unusedSpritesheetIDs{};
|
||||
@@ -58,17 +61,22 @@ namespace anm2ed::document
|
||||
|
||||
uint64_t hash{};
|
||||
uint64_t saveHash{};
|
||||
uint64_t autosaveHash{};
|
||||
double lastAutosaveTime{};
|
||||
bool isOpen{true};
|
||||
bool isForceDirty{false};
|
||||
|
||||
Document(const std::string&, bool = false, std::string* = nullptr);
|
||||
bool save(const std::string& = {}, std::string* = nullptr);
|
||||
bool autosave(const std::string&, std::string* = nullptr);
|
||||
void hash_set();
|
||||
void clean();
|
||||
void on_change();
|
||||
void change(types::change::Type);
|
||||
bool is_dirty();
|
||||
std::string directory_get();
|
||||
std::string filename_get();
|
||||
bool is_autosave_dirty();
|
||||
std::filesystem::path directory_get();
|
||||
std::filesystem::path filename_get();
|
||||
bool is_valid();
|
||||
|
||||
anm2::Frame* frame_get();
|
||||
@@ -84,11 +92,15 @@ namespace anm2ed::document
|
||||
void frame_rotation_set(anm2::Frame*, float);
|
||||
void frame_delay_set(anm2::Frame*, int);
|
||||
void frame_tint_set(anm2::Frame*, glm::vec4);
|
||||
void frame_offset_set(anm2::Frame*, glm::vec3);
|
||||
void frame_color_offset_set(anm2::Frame*, glm::vec3);
|
||||
void frame_is_visible_set(anm2::Frame*, bool);
|
||||
void frame_is_interpolated_set(anm2::Frame*, bool);
|
||||
void frame_flip_x(anm2::Frame* frame);
|
||||
void frame_flip_y(anm2::Frame* frame);
|
||||
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();
|
||||
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 animation_add();
|
||||
void animation_set(int);
|
||||
void animation_duplicate();
|
||||
void animation_default();
|
||||
void animation_remove();
|
||||
void animations_remove();
|
||||
void animations_move(std::vector<int>&, int);
|
||||
void animations_merge(types::merge::Type, bool);
|
||||
void animations_merge_quick();
|
||||
anm2::Animation* animation_get();
|
||||
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 undo();
|
||||
void redo();
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
#include "documents.h"
|
||||
|
||||
#include <ranges>
|
||||
#include <vector>
|
||||
|
||||
#include "imgui.h"
|
||||
#include "util.h"
|
||||
|
||||
using namespace anm2ed::taskbar;
|
||||
using namespace anm2ed::manager;
|
||||
using namespace anm2ed::settings;
|
||||
using namespace anm2ed::resources;
|
||||
using namespace anm2ed::types;
|
||||
using namespace anm2ed::util;
|
||||
|
||||
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 windowHeight = ImGui::GetFrameHeightWithSpacing();
|
||||
@@ -19,6 +22,14 @@ namespace anm2ed::documents
|
||||
ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x, viewport->Pos.y + taskbar.height));
|
||||
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,
|
||||
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize |
|
||||
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoBringToFrontOnFocus |
|
||||
@@ -29,14 +40,48 @@ namespace anm2ed::documents
|
||||
|
||||
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 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);
|
||||
|
||||
@@ -46,24 +91,17 @@ namespace anm2ed::documents
|
||||
ImGui::PushFont(resources.fonts[font].get(), font::SIZE);
|
||||
if (ImGui::BeginTabItem(label.c_str(), &document.isOpen, flags))
|
||||
{
|
||||
manager.selected = i;
|
||||
manager.set(i);
|
||||
if (isRequested) manager.pendingSelected = -1;
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
ImGui::PopFont();
|
||||
}
|
||||
|
||||
if (!document.isOpen)
|
||||
{
|
||||
if (isDirty)
|
||||
{
|
||||
isCloseDocument = true;
|
||||
isOpenCloseDocumentPopup = true;
|
||||
closeDocumentIndex = i;
|
||||
document.isOpen = true;
|
||||
}
|
||||
else
|
||||
manager.close(i);
|
||||
}
|
||||
for (auto it = closeIndices.rbegin(); it != closeIndices.rend(); ++it)
|
||||
{
|
||||
if (closePopup.is_open() && closeDocumentIndex > *it) --closeDocumentIndex;
|
||||
manager.close(*it);
|
||||
}
|
||||
|
||||
ImGui::EndTabBar();
|
||||
@@ -71,21 +109,21 @@ namespace anm2ed::documents
|
||||
|
||||
closePopup.trigger();
|
||||
|
||||
if (isCloseDocument)
|
||||
if (ImGui::BeginPopupModal(closePopup.label, &closePopup.isOpen, ImGuiWindowFlags_NoResize))
|
||||
{
|
||||
if (ImGui::BeginPopupModal(closePopup.label, &closePopup.isOpen, ImGuiWindowFlags_NoResize))
|
||||
if (closeDocumentIndex >= 0 && closeDocumentIndex < (int)manager.documents.size())
|
||||
{
|
||||
auto closeDocument = manager.get(closeDocumentIndex);
|
||||
auto& closeDocument = manager.documents[closeDocumentIndex];
|
||||
|
||||
ImGui::TextUnformatted(std::format("The document \"{}\" has been modified.\nDo you want to save it?",
|
||||
closeDocument->filename_get())
|
||||
closeDocument.filename_get().string())
|
||||
.c_str());
|
||||
|
||||
auto widgetSize = imgui::widget_size_with_row_get(3);
|
||||
|
||||
auto close = [&]()
|
||||
{
|
||||
closeDocumentIndex = 0;
|
||||
closeDocumentIndex = -1;
|
||||
closePopup.close();
|
||||
};
|
||||
|
||||
@@ -106,10 +144,19 @@ namespace anm2ed::documents
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::Button("Cancel", widgetSize)) close();
|
||||
|
||||
ImGui::EndPopup();
|
||||
if (ImGui::Button("Cancel", widgetSize))
|
||||
{
|
||||
isQuitting = false;
|
||||
close();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
closeDocumentIndex = -1;
|
||||
closePopup.close();
|
||||
}
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,20 +3,19 @@
|
||||
#include "imgui.h"
|
||||
#include "manager.h"
|
||||
#include "resources.h"
|
||||
#include "settings.h"
|
||||
#include "taskbar.h"
|
||||
|
||||
namespace anm2ed::documents
|
||||
{
|
||||
class Documents
|
||||
{
|
||||
bool isCloseDocument{};
|
||||
bool isOpenCloseDocumentPopup{};
|
||||
int closeDocumentIndex{};
|
||||
int closeDocumentIndex{-1};
|
||||
imgui::PopupHelper closePopup{imgui::PopupHelper("Close Document", imgui::POPUP_TO_CONTENT)};
|
||||
|
||||
public:
|
||||
float height{};
|
||||
|
||||
void update(taskbar::Taskbar&, manager::Manager&, resources::Resources&);
|
||||
void update(taskbar::Taskbar&, manager::Manager&, settings::Settings&, resources::Resources&, bool&);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ namespace anm2ed::events
|
||||
auto& hovered = document.hoveredEvent;
|
||||
auto& multiSelect = document.eventMultiSelect;
|
||||
|
||||
hovered = -1;
|
||||
|
||||
if (ImGui::Begin("Events", &settings.windowIsEvents))
|
||||
{
|
||||
auto childSize = imgui::size_without_footer_get();
|
||||
|
||||
@@ -6,6 +6,11 @@
|
||||
|
||||
namespace anm2ed::filesystem
|
||||
{
|
||||
bool directories_create(const std::string& path)
|
||||
{
|
||||
return std::filesystem::create_directories(path);
|
||||
}
|
||||
|
||||
std::string path_preferences_get()
|
||||
{
|
||||
char* preferencesPath = SDL_GetPrefPath("", "anm2ed");
|
||||
|
||||
@@ -8,6 +8,7 @@ namespace anm2ed::filesystem
|
||||
std::string path_preferences_get();
|
||||
bool path_is_exist(const std::string&);
|
||||
bool path_is_extension(const std::string&, const std::string&);
|
||||
bool directories_create(const std::string&);
|
||||
|
||||
class WorkingDirectory
|
||||
{
|
||||
|
||||
@@ -91,8 +91,8 @@ namespace anm2ed::frame_properties
|
||||
document.frame_tint_set(frame, useFrame.tint);
|
||||
ImGui::SetItemTooltip("%s", "Change the tint of the frame.");
|
||||
|
||||
if (ImGui::ColorEdit3("Color Offset", frame ? value_ptr(useFrame.offset) : &dummy_value<float>()))
|
||||
document.frame_offset_set(frame, useFrame.offset);
|
||||
if (ImGui::ColorEdit3("Color Offset", frame ? value_ptr(useFrame.colorOffset) : &dummy_value<float>()))
|
||||
document.frame_color_offset_set(frame, useFrame.colorOffset);
|
||||
ImGui::SetItemTooltip("%s", "Change the color added onto the frame.");
|
||||
|
||||
if (ImGui::Checkbox("Visible", frame ? &useFrame.isVisible : &dummy_value<bool>()))
|
||||
|
||||
10
src/icon.h
10
src/icon.h
@@ -55,6 +55,14 @@ namespace icon
|
||||
|
||||
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>
|
||||
)";
|
||||
|
||||
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"(
|
||||
@@ -156,6 +164,8 @@ namespace icon
|
||||
X(HIDE_RECT, HIDE_RECT_DATA, SIZE_NORMAL) \
|
||||
X(SHOW_UNUSED, SHOW_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(MOVE, MOVE_DATA, SIZE_NORMAL) \
|
||||
X(ROTATE, ROTATE_DATA, SIZE_NORMAL) \
|
||||
|
||||
@@ -280,6 +280,11 @@ namespace anm2ed::imgui
|
||||
isJustOpened = true;
|
||||
}
|
||||
|
||||
bool PopupHelper::is_open()
|
||||
{
|
||||
return isOpen;
|
||||
}
|
||||
|
||||
void PopupHelper::trigger()
|
||||
{
|
||||
if (isTriggered) ImGui::OpenPopup(label);
|
||||
|
||||
@@ -174,6 +174,7 @@ namespace anm2ed::imgui
|
||||
float percent{};
|
||||
|
||||
PopupHelper(const char*, float = POPUP_NORMAL, bool = false);
|
||||
bool is_open();
|
||||
void open();
|
||||
void trigger();
|
||||
void end();
|
||||
|
||||
@@ -21,6 +21,8 @@ namespace anm2ed::layers
|
||||
auto& multiSelect = document.layersMultiSelect;
|
||||
auto& propertiesPopup = manager.layerPropertiesPopup;
|
||||
|
||||
hovered = -1;
|
||||
|
||||
if (ImGui::Begin("Layers", &settings.windowIsLayers))
|
||||
{
|
||||
auto childSize = imgui::size_without_footer_get();
|
||||
|
||||
@@ -73,7 +73,7 @@ namespace anm2ed::loader
|
||||
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.IniFilename = nullptr;
|
||||
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable | ImGuiConfigFlags_NavEnableKeyboard;
|
||||
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
|
||||
io.ConfigWindowsMoveFromTitleBarOnly = true;
|
||||
|
||||
ImGui::LoadIniSettingsFromDisk(settings_path().c_str());
|
||||
|
||||
219
src/manager.cpp
219
src/manager.cpp
@@ -1,33 +1,73 @@
|
||||
#include "manager.h"
|
||||
|
||||
#include "toast.h"
|
||||
#include <algorithm>
|
||||
|
||||
#include "filesystem.h"
|
||||
#include "log.h"
|
||||
#include "toast.h"
|
||||
#include "util.h"
|
||||
|
||||
using namespace anm2ed::log;
|
||||
using namespace anm2ed::toast;
|
||||
using namespace anm2ed::types;
|
||||
using namespace anm2ed::util;
|
||||
|
||||
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)
|
||||
{
|
||||
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{};
|
||||
Document document = Document(path, isNew, &errorString);
|
||||
if (document.is_valid())
|
||||
documents.emplace_back(path, isNew, &errorString);
|
||||
|
||||
auto& document = documents.back();
|
||||
if (!document.is_valid())
|
||||
{
|
||||
documents.emplace_back(std::move(document));
|
||||
selected = documents.size() - 1;
|
||||
pendingSelected = selected;
|
||||
toasts.info(std::format("Initialized document: {}", path));
|
||||
documents.pop_back();
|
||||
toasts.error(std::format("Failed to open document: {} ({})", path, errorString));
|
||||
return;
|
||||
}
|
||||
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)
|
||||
@@ -50,9 +90,53 @@ namespace anm2ed::manager
|
||||
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)
|
||||
{
|
||||
if (index < 0 || index >= (int)documents.size()) return;
|
||||
|
||||
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)
|
||||
@@ -117,4 +201,119 @@ namespace anm2ed::manager
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,12 +9,20 @@ using namespace anm2ed::document;
|
||||
|
||||
namespace anm2ed::manager
|
||||
{
|
||||
constexpr auto FILE_LABEL_FORMAT = "{} [{}]";
|
||||
|
||||
class Manager
|
||||
{
|
||||
std::filesystem::path recent_files_path_get();
|
||||
std::filesystem::path autosave_path_get();
|
||||
|
||||
public:
|
||||
std::vector<Document> documents{};
|
||||
int selected{};
|
||||
int pendingSelected{};
|
||||
std::vector<std::filesystem::path> recentFiles{};
|
||||
std::vector<std::filesystem::path> autosaveFiles{};
|
||||
|
||||
int selected{-1};
|
||||
int pendingSelected{-1};
|
||||
|
||||
anm2::Layer editLayer{};
|
||||
imgui::PopupHelper layerPropertiesPopup{imgui::PopupHelper("Layer Properties", imgui::POPUP_SMALL, true)};
|
||||
@@ -22,11 +30,16 @@ namespace anm2ed::manager
|
||||
anm2::Null editNull{};
|
||||
imgui::PopupHelper nullPropertiesPopup{imgui::PopupHelper("Null Properties", imgui::POPUP_SMALL, true)};
|
||||
|
||||
Manager();
|
||||
~Manager();
|
||||
|
||||
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 save(int, const std::string& = {});
|
||||
void save(const std::string& = {});
|
||||
void autosave(Document&);
|
||||
void set(int);
|
||||
void close(int);
|
||||
void layer_properties_open(int = -1);
|
||||
void layer_properties_trigger();
|
||||
@@ -36,5 +49,16 @@ namespace anm2ed::manager
|
||||
void null_properties_trigger();
|
||||
void null_properties_end();
|
||||
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();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ namespace anm2ed::nulls
|
||||
auto& multiSelect = document.nullMultiSelect;
|
||||
auto& propertiesPopup = manager.nullPropertiesPopup;
|
||||
|
||||
hovered = -1;
|
||||
|
||||
if (ImGui::Begin("Nulls", &settings.windowIsNulls))
|
||||
{
|
||||
auto childSize = imgui::size_without_footer_get();
|
||||
|
||||
@@ -44,6 +44,9 @@ namespace anm2ed::settings
|
||||
X(IS_VSYNC, isVsync, "Vsync", BOOL, true) \
|
||||
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(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_VISIBLE_SET, changeIsVisibleSet, "##Is Visible", 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_SIZE, changeSize, "Size", 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_INTERPOLATED, changeIsInterpolated, "Interpolated", BOOL, false) \
|
||||
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) \
|
||||
\
|
||||
@@ -80,7 +83,6 @@ namespace anm2ed::settings
|
||||
X(PREVIEW_IS_GRID, previewIsGrid, "Grid", BOOL, true) \
|
||||
X(PREVIEW_IS_ROOT_TRANSFORM, previewIsRootTransform, "Root Transform", BOOL, true) \
|
||||
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_ALT_ICONS, previewIsAltIcons, "Alt Icons", BOOL, false) \
|
||||
X(PREVIEW_OVERLAY_TRANSPARENCY, previewOverlayTransparency, "Alpha", FLOAT, 255) \
|
||||
@@ -101,6 +103,7 @@ namespace anm2ed::settings
|
||||
X(GENERATE_COLUMNS, generateColumns, "Columns", INT, 4) \
|
||||
X(GENERATE_COUNT, generateCount, "Count", INT, 16) \
|
||||
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_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_SOURCE, timelineAddItemSource, "Add Item Source", INT, types::source::NEW) \
|
||||
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_DRAW_ORDER, onionskinDrawOrder, "Draw Order", INT, 0) \
|
||||
|
||||
86
src/shader.h
86
src/shader.h
@@ -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_COLOR = "u_color";
|
||||
constexpr auto UNIFORM_TRANSFORM = "u_transform";
|
||||
@@ -123,18 +196,27 @@ namespace anm2ed::shader
|
||||
constexpr auto UNIFORM_MODEL = "u_model";
|
||||
constexpr auto UNIFORM_RECT_SIZE = "u_rect_size";
|
||||
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
|
||||
{
|
||||
LINE,
|
||||
DASHED,
|
||||
TEXTURE,
|
||||
AXIS,
|
||||
GRID,
|
||||
COUNT
|
||||
};
|
||||
|
||||
const Info SHADERS[COUNT] = {
|
||||
{VERTEX, FRAGMENT}, {VERTEX, TEXTURE_FRAGMENT}, {AXIS_VERTEX, FRAGMENT}, {GRID_VERTEX, GRID_FRAGMENT}};
|
||||
const Info SHADERS[COUNT] = {{VERTEX, FRAGMENT},
|
||||
{DASHED_VERTEX, DASHED_FRAGMENT},
|
||||
{VERTEX, TEXTURE_FRAGMENT},
|
||||
{AXIS_VERTEX, FRAGMENT},
|
||||
{GRID_VERTEX, GRID_FRAGMENT}};
|
||||
|
||||
class Shader
|
||||
{
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace anm2ed::spritesheet_editor
|
||||
auto& tool = settings.tool;
|
||||
auto& shaderGrid = resources.shaders[shader::GRID];
|
||||
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))
|
||||
{
|
||||
@@ -96,20 +96,22 @@ namespace anm2ed::spritesheet_editor
|
||||
|
||||
auto frame = document.frame_get();
|
||||
|
||||
if (spritesheet)
|
||||
if (spritesheet && spritesheet->texture.is_valid())
|
||||
{
|
||||
auto& texture = spritesheet->texture;
|
||||
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);
|
||||
if (isBorder) rect_render(lineShader, spritesheetTransform);
|
||||
if (isBorder) rect_render(dashedShader, spritesheetTransform, spritesheetModel);
|
||||
|
||||
if (frame && reference.itemID > -1 &&
|
||||
anm2.content.layers.at(reference.itemID).spritesheetID == referenceSpritesheet)
|
||||
{
|
||||
auto cropTransform = transform * math::quad_model_get(frame->size, frame->crop);
|
||||
rect_render(lineShader, cropTransform, color::RED);
|
||||
auto cropModel = math::quad_model_get(frame->size, frame->crop);
|
||||
auto cropTransform = transform * cropModel;
|
||||
rect_render(dashedShader, cropTransform, cropModel, color::RED);
|
||||
|
||||
auto pivotTransform =
|
||||
transform * math::quad_model_get(canvas::PIVOT_SIZE, frame->crop + frame->pivot, PIVOT_SIZE * 0.5f);
|
||||
|
||||
@@ -28,6 +28,8 @@ namespace anm2ed::spritesheets
|
||||
auto& hovered = document.hoveredSpritesheet;
|
||||
auto& reference = document.referenceSpritesheet;
|
||||
|
||||
hovered = -1;
|
||||
|
||||
if (ImGui::Begin("Spritesheets", &settings.windowIsSpritesheets))
|
||||
{
|
||||
auto style = ImGui::GetStyle();
|
||||
|
||||
@@ -61,7 +61,7 @@ namespace anm2ed::state
|
||||
break;
|
||||
}
|
||||
case SDL_EVENT_QUIT:
|
||||
isQuit = true;
|
||||
isQuitting = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -72,14 +72,15 @@ namespace anm2ed::state
|
||||
ImGui_ImplOpenGL3_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);
|
||||
toasts.update();
|
||||
|
||||
documents.update(taskbar, manager, resources);
|
||||
|
||||
ImGui::GetStyle().FontScaleMain = settings.displayScale;
|
||||
SDL_GetWindowSize(window, &settings.windowSize.x, &settings.windowSize.y);
|
||||
|
||||
if (isQuitting && manager.documents.empty()) isQuit = true;
|
||||
}
|
||||
|
||||
void State::render(SDL_Window*& window, Settings& settings)
|
||||
|
||||
@@ -14,9 +14,10 @@ namespace anm2ed::state
|
||||
|
||||
public:
|
||||
bool isQuit{};
|
||||
dialog::Dialog dialog;
|
||||
resources::Resources resources;
|
||||
bool isQuitting{};
|
||||
manager::Manager manager;
|
||||
resources::Resources resources;
|
||||
dialog::Dialog dialog;
|
||||
clipboard::Clipboard clipboard;
|
||||
|
||||
taskbar::Taskbar taskbar;
|
||||
|
||||
353
src/taskbar.cpp
353
src/taskbar.cpp
@@ -4,18 +4,25 @@
|
||||
#include <ranges>
|
||||
|
||||
#include "imgui.h"
|
||||
#include "math.h"
|
||||
|
||||
using namespace anm2ed::settings;
|
||||
using namespace anm2ed::canvas;
|
||||
using namespace anm2ed::dialog;
|
||||
using namespace anm2ed::manager;
|
||||
using namespace anm2ed::resources;
|
||||
using namespace anm2ed::settings;
|
||||
using namespace anm2ed::types;
|
||||
using namespace glm;
|
||||
|
||||
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 animation = document ? document->animation_get() : nullptr;
|
||||
|
||||
if (ImGui::BeginMainMenuBar())
|
||||
{
|
||||
@@ -27,6 +34,32 @@ namespace anm2ed::taskbar
|
||||
|
||||
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);
|
||||
{
|
||||
if (ImGui::MenuItem("Save", settings.shortcutSave.c_str())) manager.save();
|
||||
@@ -36,7 +69,7 @@ namespace anm2ed::taskbar
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem("Exit", settings.shortcutExit.c_str())) isQuit = true;
|
||||
if (ImGui::MenuItem("Exit", settings.shortcutExit.c_str())) isQuitting = true;
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
if (dialog.is_selected_file(dialog::ANM2_NEW))
|
||||
@@ -59,11 +92,10 @@ namespace anm2ed::taskbar
|
||||
|
||||
if (ImGui::BeginMenu("Wizard"))
|
||||
{
|
||||
ImGui::BeginDisabled(!animation);
|
||||
{
|
||||
ImGui::MenuItem("Generate Animation From Grid");
|
||||
ImGui::MenuItem("Change All Frame Properties");
|
||||
}
|
||||
auto item = document ? document->item_get() : nullptr;
|
||||
ImGui::BeginDisabled(!item || document->reference.itemType != anm2::LAYER);
|
||||
if (ImGui::MenuItem("Generate Animation From Grid")) generatePopup.open();
|
||||
if (ImGui::MenuItem("Change All Frame Properties")) changePopup.open();
|
||||
ImGui::EndDisabled();
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
@@ -107,6 +139,264 @@ namespace anm2ed::taskbar
|
||||
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();
|
||||
|
||||
if (ImGui::BeginPopupModal(configurePopup.label, &configurePopup.isOpen, ImGuiWindowFlags_NoResize))
|
||||
@@ -115,29 +405,33 @@ namespace anm2ed::taskbar
|
||||
|
||||
if (ImGui::BeginTabBar("##Configure Tabs"))
|
||||
{
|
||||
if (ImGui::BeginTabItem("View"))
|
||||
if (ImGui::BeginTabItem("General"))
|
||||
{
|
||||
if (ImGui::BeginChild("##Tab Child", childSize, true))
|
||||
{
|
||||
ImGui::InputFloat("Zoom Step", &editSettings.viewZoomStep, 10.0f, 10.0f, "%.2f");
|
||||
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::EndChild();
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
ImGui::SeparatorText("File");
|
||||
|
||||
ImGui::Checkbox("Autosaving", &editSettings.fileIsAutosave);
|
||||
ImGui::SetItemTooltip("Enables autosaving of documents.");
|
||||
|
||||
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::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);
|
||||
|
||||
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::SetItemTooltip("%s",
|
||||
"Toggle vertical sync; synchronizes program update rate with monitor refresh rate.");
|
||||
ImGui::SetItemTooltip("Toggle vertical sync; synchronizes program update rate with monitor refresh rate.");
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
@@ -241,11 +535,10 @@ namespace anm2ed::taskbar
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
if (ImGui::Shortcut(imgui::string_to_chord(settings.shortcutNew), ImGuiInputFlags_RouteGlobal)) dialog.anm2_new();
|
||||
if (ImGui::Shortcut(imgui::string_to_chord(settings.shortcutOpen), ImGuiInputFlags_RouteGlobal)) dialog.anm2_open();
|
||||
if (ImGui::Shortcut(imgui::string_to_chord(settings.shortcutSave), ImGuiInputFlags_RouteGlobal)) manager.save();
|
||||
if (ImGui::Shortcut(imgui::string_to_chord(settings.shortcutSaveAs), ImGuiInputFlags_RouteGlobal))
|
||||
dialog.anm2_save();
|
||||
if (ImGui::Shortcut(imgui::string_to_chord(settings.shortcutExit), ImGuiInputFlags_RouteGlobal)) isQuit = true;
|
||||
if (imgui::shortcut(settings.shortcutNew, shortcut::GLOBAL)) dialog.anm2_new();
|
||||
if (imgui::shortcut(settings.shortcutOpen, shortcut::GLOBAL)) dialog.anm2_open();
|
||||
if (imgui::shortcut(settings.shortcutSave, shortcut::GLOBAL)) document->save();
|
||||
if (imgui::shortcut(settings.shortcutSaveAs, shortcut::GLOBAL)) dialog.anm2_save();
|
||||
if (imgui::shortcut(settings.shortcutExit, shortcut::GLOBAL)) isQuitting = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include "canvas.h"
|
||||
#include "dialog.h"
|
||||
#include "imgui.h"
|
||||
#include "manager.h"
|
||||
#include "resources.h"
|
||||
#include "settings.h"
|
||||
|
||||
namespace anm2ed::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 aboutPopup{imgui::PopupHelper("About")};
|
||||
settings::Settings editSettings{};
|
||||
int selectedShortcut{-1};
|
||||
bool isQuittingMode{};
|
||||
|
||||
public:
|
||||
float height{};
|
||||
|
||||
void update(manager::Manager&, settings::Settings&, dialog::Dialog&, bool&);
|
||||
Taskbar();
|
||||
void update(manager::Manager&, settings::Settings&, resources::Resources&, dialog::Dialog&, bool&);
|
||||
};
|
||||
};
|
||||
|
||||
111
src/timeline.cpp
111
src/timeline.cpp
@@ -11,6 +11,7 @@ using namespace anm2ed::manager;
|
||||
using namespace anm2ed::resources;
|
||||
using namespace anm2ed::settings;
|
||||
using namespace anm2ed::playback;
|
||||
using namespace anm2ed::clipboard;
|
||||
using namespace glm;
|
||||
|
||||
namespace anm2ed::timeline
|
||||
@@ -50,14 +51,63 @@ namespace anm2ed::timeline
|
||||
- Press {} to extend the selected frame, by one frame.
|
||||
- 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,
|
||||
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& reference = document.reference;
|
||||
|
||||
auto item = animation ? animation->item_get(type, id) : nullptr;
|
||||
auto& isOnlyShowLayers = settings.timelineIsOnlyShowLayers;
|
||||
auto isVisible = item ? item->isVisible : false;
|
||||
if (isOnlyShowLayers && type != anm2::LAYER) isVisible = false;
|
||||
auto isActive = reference.itemType == type && reference.itemID == id;
|
||||
std::string label = "##None";
|
||||
icon::Type icon{};
|
||||
@@ -129,7 +179,6 @@ namespace anm2ed::timeline
|
||||
ImGui::TextUnformatted(label.c_str());
|
||||
|
||||
anm2::Item* item = animation->item_get(type, id);
|
||||
bool& isVisible = item->isVisible;
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4());
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4());
|
||||
@@ -138,7 +187,7 @@ namespace anm2ed::timeline
|
||||
|
||||
ImGui::SetCursorPos(ImVec2(itemSize.x - ImGui::GetTextLineHeightWithSpacing() - ImGui::GetStyle().ItemSpacing.x,
|
||||
(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()))
|
||||
document.item_visible_toggle(item);
|
||||
@@ -165,22 +214,32 @@ namespace anm2ed::timeline
|
||||
else
|
||||
{
|
||||
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_ButtonActive, ImVec4());
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4());
|
||||
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;
|
||||
if (ImGui::ImageButton("##Unused Toggle", resources.icons[unusedIcon].id, imgui::icon_size_get()))
|
||||
isShowUnused = !isShowUnused;
|
||||
ImGui::SetItemTooltip(isShowUnused ? "Unused layers/nulls are shown. Press to hide."
|
||||
: "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::PopStyleColor(3);
|
||||
|
||||
@@ -201,7 +260,7 @@ namespace anm2ed::timeline
|
||||
}
|
||||
|
||||
void Timeline::items_child(Manager& manager, Document& document, anm2::Animation* animation, Settings& settings,
|
||||
Resources& resources)
|
||||
Resources& resources, Clipboard& clipboard)
|
||||
{
|
||||
auto& reference = document.reference;
|
||||
|
||||
@@ -232,7 +291,7 @@ namespace anm2ed::timeline
|
||||
{
|
||||
ImGui::TableNextRow();
|
||||
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);
|
||||
@@ -302,13 +361,16 @@ namespace anm2ed::timeline
|
||||
}
|
||||
|
||||
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& playback = document.playback;
|
||||
auto& reference = document.reference;
|
||||
auto& hoveredFrame = document.hoveredFrame;
|
||||
auto item = animation ? animation->item_get(type, id) : nullptr;
|
||||
auto& isOnlyShowLayers = settings.timelineIsOnlyShowLayers;
|
||||
auto isVisible = item ? item->isVisible : false;
|
||||
if (isOnlyShowLayers && type != anm2::LAYER) isVisible = false;
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, style.WindowPadding);
|
||||
@@ -489,6 +551,8 @@ namespace anm2ed::timeline
|
||||
reference = frameReference;
|
||||
reference.frameTime = frameTime;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) hoveredFrame = frameReference;
|
||||
|
||||
if (type != anm2::TRIGGER) ImGui::SameLine();
|
||||
|
||||
ImGui::PopStyleColor(3);
|
||||
@@ -502,6 +566,8 @@ namespace anm2ed::timeline
|
||||
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
context_menu(document, settings, clipboard);
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
@@ -511,10 +577,12 @@ namespace anm2ed::timeline
|
||||
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& playback = document.playback;
|
||||
auto& hoveredFrame = document.hoveredFrame;
|
||||
|
||||
auto itemsChildWidth = ImGui::GetTextLineHeightWithSpacing() * 15;
|
||||
|
||||
@@ -571,11 +639,13 @@ namespace anm2ed::timeline
|
||||
{
|
||||
ImGui::TableNextRow();
|
||||
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);
|
||||
|
||||
//hoveredFrame = anm2::REFERENCE_DEFAULT;
|
||||
|
||||
if (animation)
|
||||
{
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 2.0f);
|
||||
@@ -622,6 +692,8 @@ namespace anm2ed::timeline
|
||||
pickerLineDrawList->PopClipRect();
|
||||
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
context_menu(document, settings, clipboard);
|
||||
}
|
||||
ImGui::EndChild();
|
||||
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& playback = document.playback;
|
||||
@@ -931,8 +1003,8 @@ namespace anm2ed::timeline
|
||||
if (ImGui::Begin("Timeline", &settings.windowIsTimeline))
|
||||
{
|
||||
isWindowHovered = ImGui::IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows);
|
||||
frames_child(document, animation, settings, resources);
|
||||
items_child(manager, document, animation, settings, resources);
|
||||
frames_child(document, animation, settings, resources, clipboard);
|
||||
items_child(manager, document, animation, settings, resources, clipboard);
|
||||
}
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::End();
|
||||
@@ -956,10 +1028,7 @@ namespace anm2ed::timeline
|
||||
}
|
||||
}
|
||||
|
||||
if (imgui::chord_repeating(imgui::string_to_chord(settings.shortcutShortenFrame)))
|
||||
if (auto frame = anm2.frame_get(reference); frame) frame->shorten();
|
||||
|
||||
if (imgui::chord_repeating(imgui::string_to_chord(settings.shortcutExtendFrame)))
|
||||
if (auto frame = anm2.frame_get(reference); frame) frame->extend();
|
||||
if (imgui::chord_repeating(imgui::string_to_chord(settings.shortcutShortenFrame))) document.frame_shorten();
|
||||
if (imgui::chord_repeating(imgui::string_to_chord(settings.shortcutExtendFrame))) document.frame_extend();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "anm2.h"
|
||||
#include "clipboard.h"
|
||||
#include "document.h"
|
||||
#include "manager.h"
|
||||
#include "resources.h"
|
||||
@@ -25,16 +26,19 @@ namespace anm2ed::timeline
|
||||
ImDrawList* pickerLineDrawList{};
|
||||
ImGuiStyle style{};
|
||||
|
||||
void context_menu(document::Document&, settings::Settings&, clipboard::Clipboard&);
|
||||
void item_child(manager::Manager&, Document&, anm2::Animation*, settings::Settings&, resources::Resources&,
|
||||
anm2::Type, int, int&);
|
||||
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,
|
||||
int&, float);
|
||||
void frames_child(document::Document&, anm2::Animation*, settings::Settings&, resources::Resources&);
|
||||
clipboard::Clipboard&, anm2::Type, int, int&);
|
||||
void items_child(manager::Manager&, Document&, anm2::Animation*, settings::Settings&, resources::Resources&,
|
||||
clipboard::Clipboard&);
|
||||
void frame_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&);
|
||||
|
||||
public:
|
||||
void update(manager::Manager&, settings::Settings&, resources::Resources&);
|
||||
void update(manager::Manager&, settings::Settings&, resources::Resources&, clipboard::Clipboard&);
|
||||
};
|
||||
}
|
||||
|
||||
10
src/types.h
10
src/types.h
@@ -69,6 +69,16 @@ namespace anm2ed::types::merge
|
||||
};
|
||||
}
|
||||
|
||||
namespace anm2ed::types::frame_change
|
||||
{
|
||||
enum Type
|
||||
{
|
||||
ADD,
|
||||
SUBTRACT,
|
||||
ADJUST
|
||||
};
|
||||
}
|
||||
|
||||
namespace anm2ed::types::color
|
||||
{
|
||||
using namespace glm;
|
||||
|
||||
12
src/util.h
12
src/util.h
@@ -10,6 +10,9 @@
|
||||
|
||||
namespace anm2ed::util::time
|
||||
{
|
||||
constexpr auto SECOND_S = 1.0;
|
||||
constexpr auto SECOND_M = 60.0;
|
||||
|
||||
std::string get(const char*);
|
||||
}
|
||||
|
||||
@@ -95,4 +98,13 @@ namespace anm2ed::util::vector
|
||||
|
||||
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
106
src/welcome.cpp
Normal 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
16
src/welcome.h
Normal 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&);
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user