Autosave feature, spritesheet editor dashed lines, refactoring, fixes

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

View File

@@ -63,13 +63,14 @@ if (WIN32)
target_link_options(${PROJECT_NAME} PRIVATE /STACK:0xffffff)
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)

View File

@@ -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);
}

View File

@@ -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();

View File

@@ -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);
}
}
}

View File

@@ -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);
};
}

View File

@@ -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);

View File

@@ -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();

View File

@@ -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();
}

View File

@@ -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&,

View File

@@ -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);

View File

@@ -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();

View File

@@ -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();
}
}

View File

@@ -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&);
};
}

View File

@@ -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();

View File

@@ -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");

View File

@@ -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
{

View File

@@ -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>()))

View File

@@ -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) \

View File

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

View File

@@ -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();

View File

@@ -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();

View File

@@ -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());

View File

@@ -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();
}
}

View File

@@ -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();
};
}

View File

@@ -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();

View File

@@ -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) \

View File

@@ -112,6 +112,79 @@ namespace anm2ed::shader
}
)";
constexpr auto DASHED_VERTEX = R"(
#version 330 core
layout (location = 0) in vec2 i_position;
out vec2 v_local;
uniform mat4 u_transform;
void main()
{
v_local = i_position;
gl_Position = u_transform * vec4(i_position, 0.0, 1.0);
}
)";
constexpr auto DASHED_FRAGMENT = R"(
#version 330 core
in vec2 v_local;
uniform vec4 u_color;
uniform vec2 u_axis_x;
uniform vec2 u_axis_y;
uniform float u_dash_length;
uniform float u_dash_gap;
uniform float u_dash_offset;
out vec4 o_fragColor;
void main()
{
vec2 local = clamp(v_local, 0.0, 1.0);
float lengthX = max(length(u_axis_x), 1e-4);
float lengthY = max(length(u_axis_y), 1e-4);
float dash = max(u_dash_length, 1e-4);
float gap = max(u_dash_gap, 0.0);
float period = max(dash + gap, 1e-4);
vec2 pixel = max(fwidth(v_local), vec2(1e-5));
float bottomMask = 1.0 - smoothstep(pixel.y, pixel.y * 2.0, local.y);
float topMask = 1.0 - smoothstep(pixel.y, pixel.y * 2.0, 1.0 - local.y);
float leftMask = 1.0 - smoothstep(pixel.x, pixel.x * 2.0, local.x);
float rightMask = 1.0 - smoothstep(pixel.x, pixel.x * 2.0, 1.0 - local.x);
float perimeterOffset = u_dash_offset;
float bottomPos = mod(perimeterOffset + local.x * lengthX, period);
if (bottomPos < 0.0) bottomPos += period;
float bottomDash = bottomMask * (bottomPos <= dash ? 1.0 : 0.0);
float rightPos = mod(perimeterOffset + lengthX + local.y * lengthY, period);
if (rightPos < 0.0) rightPos += period;
float rightDash = rightMask * (rightPos <= dash ? 1.0 : 0.0);
float topPos = mod(perimeterOffset + lengthX + lengthY + (1.0 - local.x) * lengthX, period);
if (topPos < 0.0) topPos += period;
float topDash = topMask * (topPos <= dash ? 1.0 : 0.0);
float leftPos = mod(perimeterOffset + 2.0 * lengthX + lengthY + (1.0 - local.y) * lengthY, period);
if (leftPos < 0.0) leftPos += period;
float leftDash = leftMask * (leftPos <= dash ? 1.0 : 0.0);
float alpha = max(max(bottomDash, topDash), max(leftDash, rightDash));
if (alpha <= 0.0)
discard;
o_fragColor = vec4(u_color.rgb, u_color.a * alpha);
}
)";
constexpr auto UNIFORM_AXIS = "u_axis";
constexpr auto UNIFORM_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
{

View File

@@ -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);

View File

@@ -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();

View File

@@ -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)

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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&);
};
};

View File

@@ -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();
}
}

View File

@@ -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&);
};
}

View File

@@ -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;

View File

@@ -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
View File

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

16
src/welcome.h Normal file
View File

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