817 lines
34 KiB
C++
817 lines
34 KiB
C++
#include "spritesheet_editor.hpp"
|
|
|
|
#include <cmath>
|
|
#include <format>
|
|
#include <utility>
|
|
|
|
#include "imgui_.hpp"
|
|
#include "imgui_internal.h"
|
|
#include "math_.hpp"
|
|
#include "strings.hpp"
|
|
#include "tool.hpp"
|
|
#include "types.hpp"
|
|
|
|
using namespace anm2ed::types;
|
|
using namespace anm2ed::resource;
|
|
using namespace anm2ed::util;
|
|
using namespace glm;
|
|
|
|
namespace anm2ed::imgui
|
|
{
|
|
SpritesheetEditor::SpritesheetEditor() : Canvas(vec2()) {}
|
|
|
|
void SpritesheetEditor::update(Manager& manager, Settings& settings, Resources& resources)
|
|
{
|
|
auto& document = *manager.get();
|
|
auto& anm2 = document.anm2;
|
|
auto& reference = document.reference;
|
|
auto& referenceSpritesheet = document.spritesheet.reference;
|
|
auto& pan = document.editorPan;
|
|
auto& zoom = document.editorZoom;
|
|
auto& backgroundColor = settings.editorBackgroundColor;
|
|
auto& gridColor = settings.editorGridColor;
|
|
auto& gridSize = settings.editorGridSize;
|
|
auto& gridOffset = settings.editorGridOffset;
|
|
auto& toolColor = settings.toolColor;
|
|
auto& isGrid = settings.editorIsGrid;
|
|
auto& isGridSnap = settings.editorIsGridSnap;
|
|
auto& zoomStep = settings.inputZoomStep;
|
|
auto& isBorder = settings.editorIsBorder;
|
|
auto& isTransparent = settings.editorIsTransparent;
|
|
auto spritesheet = document.spritesheet_get();
|
|
auto& tool = settings.tool;
|
|
auto& shaderGrid = resources.shaders[shader::GRID];
|
|
auto& shaderTexture = resources.shaders[shader::TEXTURE];
|
|
auto& shaderLine = resources.shaders[shader::LINE];
|
|
auto& dashedShader = resources.shaders[shader::DASHED];
|
|
auto& frames = document.frames.selection;
|
|
auto& regionReference = document.region.reference;
|
|
auto& regionSelection = document.region.selection;
|
|
|
|
auto reset_checker_pan = [&]()
|
|
{
|
|
checkerPan = pan;
|
|
checkerSyncPan = pan;
|
|
checkerSyncZoom = zoom;
|
|
isCheckerPanInitialized = true;
|
|
hasPendingZoomPanAdjust = false;
|
|
};
|
|
|
|
auto sync_checker_pan = [&]()
|
|
{
|
|
if (!isCheckerPanInitialized)
|
|
{
|
|
reset_checker_pan();
|
|
return;
|
|
}
|
|
|
|
if (pan != checkerSyncPan || zoom != checkerSyncZoom)
|
|
{
|
|
bool ignorePanDelta = hasPendingZoomPanAdjust && zoom != checkerSyncZoom;
|
|
if (!ignorePanDelta) checkerPan += pan - checkerSyncPan;
|
|
checkerSyncPan = pan;
|
|
checkerSyncZoom = zoom;
|
|
if (ignorePanDelta) hasPendingZoomPanAdjust = false;
|
|
}
|
|
};
|
|
|
|
auto center_view = [&]() { pan = -size * 0.5f; };
|
|
|
|
auto fit_view = [&]()
|
|
{
|
|
if (spritesheet && spritesheet->texture.is_valid())
|
|
set_to_rect(zoom, pan, {0, 0, (float)spritesheet->texture.size.x, (float)spritesheet->texture.size.y});
|
|
};
|
|
|
|
auto zoom_adjust = [&](float delta)
|
|
{
|
|
auto focus = position_translate(zoom, pan, size * 0.5f);
|
|
auto previousZoom = zoom;
|
|
zoom_set(zoom, pan, focus, delta);
|
|
if (zoom != previousZoom) hasPendingZoomPanAdjust = true;
|
|
};
|
|
|
|
auto zoom_in = [&]() { zoom_adjust(zoomStep); };
|
|
auto zoom_out = [&]() { zoom_adjust(-zoomStep); };
|
|
|
|
if (ImGui::Begin(localize.get(LABEL_SPRITESHEET_EDITOR_WINDOW), &settings.windowIsSpritesheetEditor))
|
|
{
|
|
|
|
auto childSize = ImVec2(imgui::row_widget_width_get(3),
|
|
(ImGui::GetTextLineHeightWithSpacing() * 4) + (ImGui::GetStyle().WindowPadding.y * 2));
|
|
|
|
if (ImGui::BeginChild("##Grid Child", childSize, true, ImGuiWindowFlags_HorizontalScrollbar))
|
|
{
|
|
ImGui::Checkbox(localize.get(BASIC_GRID), &isGrid);
|
|
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_GRID_VISIBILITY));
|
|
ImGui::SameLine();
|
|
ImGui::Checkbox(localize.get(LABEL_SNAP), &isGridSnap);
|
|
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_GRID_SNAP));
|
|
ImGui::SameLine();
|
|
ImGui::ColorEdit4(localize.get(BASIC_COLOR), value_ptr(gridColor), ImGuiColorEditFlags_NoInputs);
|
|
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_GRID_COLOR));
|
|
|
|
input_int2_range(localize.get(BASIC_SIZE), gridSize, ivec2(GRID_SIZE_MIN), ivec2(GRID_SIZE_MAX));
|
|
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_GRID_SIZE));
|
|
|
|
input_int2_range(localize.get(BASIC_OFFSET), gridOffset, ivec2(GRID_OFFSET_MIN), ivec2(GRID_OFFSET_MAX));
|
|
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_GRID_OFFSET));
|
|
}
|
|
ImGui::EndChild();
|
|
|
|
ImGui::SameLine();
|
|
|
|
if (ImGui::BeginChild("##View Child", childSize, true, ImGuiWindowFlags_HorizontalScrollbar))
|
|
{
|
|
ImGui::InputFloat(localize.get(BASIC_ZOOM), &zoom, zoomStep, zoomStep, "%.0f%%");
|
|
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_EDITOR_ZOOM));
|
|
|
|
auto widgetSize = ImVec2(imgui::row_widget_width_get(2), 0);
|
|
|
|
imgui::shortcut(manager.chords[SHORTCUT_CENTER_VIEW]);
|
|
if (ImGui::Button(localize.get(LABEL_CENTER_VIEW), widgetSize)) center_view();
|
|
imgui::set_item_tooltip_shortcut(localize.get(TOOLTIP_CENTER_VIEW), settings.shortcutCenterView);
|
|
|
|
ImGui::SameLine();
|
|
|
|
imgui::shortcut(manager.chords[SHORTCUT_FIT]);
|
|
if (ImGui::Button(localize.get(LABEL_FIT), widgetSize)) fit_view();
|
|
imgui::set_item_tooltip_shortcut(localize.get(TOOLTIP_FIT), settings.shortcutFit);
|
|
|
|
auto mousePosInt = ivec2(mousePos);
|
|
ImGui::TextUnformatted(
|
|
std::vformat(localize.get(FORMAT_POSITION_SPACED), std::make_format_args(mousePosInt.x, mousePosInt.y))
|
|
.c_str());
|
|
}
|
|
ImGui::EndChild();
|
|
|
|
ImGui::SameLine();
|
|
|
|
if (ImGui::BeginChild("##Background Child", childSize, true, ImGuiWindowFlags_HorizontalScrollbar))
|
|
{
|
|
auto subChildSize = ImVec2(row_widget_width_get(2), ImGui::GetContentRegionAvail().y);
|
|
|
|
if (ImGui::BeginChild("##Background Child 1", subChildSize))
|
|
{
|
|
ImGui::BeginDisabled(isTransparent);
|
|
{
|
|
ImGui::ColorEdit3(localize.get(LABEL_BACKGROUND_COLOR), value_ptr(backgroundColor),
|
|
ImGuiColorEditFlags_NoInputs);
|
|
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_BACKGROUND_COLOR));
|
|
}
|
|
ImGui::EndDisabled();
|
|
|
|
ImGui::Checkbox(localize.get(LABEL_BORDER), &isBorder);
|
|
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_SPRITESHEET_BORDER));
|
|
}
|
|
ImGui::EndChild();
|
|
|
|
ImGui::SameLine();
|
|
|
|
if (ImGui::BeginChild("##Background Child 2", subChildSize))
|
|
{
|
|
ImGui::Checkbox(localize.get(LABEL_TRANSPARENT), &isTransparent);
|
|
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_TRANSPARENT));
|
|
}
|
|
|
|
ImGui::EndChild();
|
|
}
|
|
ImGui::EndChild();
|
|
|
|
auto drawList = ImGui::GetCurrentWindow()->DrawList;
|
|
size_set(to_vec2(ImGui::GetContentRegionAvail()));
|
|
|
|
auto cursorScreenPos = ImGui::GetCursorScreenPos();
|
|
auto min = ImGui::GetCursorScreenPos();
|
|
auto max = to_imvec2(to_vec2(min) + size);
|
|
|
|
auto mouseScreenPos = ImGui::GetIO().MousePos;
|
|
bool isMouseOverCanvas = mouseScreenPos.x >= min.x && mouseScreenPos.x <= max.x && mouseScreenPos.y >= min.y &&
|
|
mouseScreenPos.y <= max.y;
|
|
auto hoverMousePos = vec2();
|
|
if (isMouseOverCanvas)
|
|
hoverMousePos = position_translate(zoom, pan, to_ivec2(mouseScreenPos) - to_ivec2(cursorScreenPos));
|
|
|
|
bind();
|
|
viewport_set();
|
|
clear(isTransparent ? vec4(0) : vec4(backgroundColor, 1.0f));
|
|
|
|
auto frame = document.frame_get();
|
|
auto item = document.item_get();
|
|
|
|
if (spritesheet && spritesheet->texture.is_valid())
|
|
{
|
|
auto& texture = spritesheet->texture;
|
|
auto transform = transform_get(zoom, pan);
|
|
|
|
auto spritesheetModel = math::quad_model_get(texture.size);
|
|
auto spritesheetTransform = transform * spritesheetModel;
|
|
|
|
texture_render(shaderTexture, texture.id, spritesheetTransform);
|
|
|
|
if (isGrid) grid_render(shaderGrid, zoom, pan, gridSize, gridOffset, gridColor);
|
|
|
|
if (isBorder)
|
|
rect_render(dashedShader, spritesheetTransform, spritesheetModel, color::WHITE, BORDER_DASH_LENGTH,
|
|
BORDER_DASH_GAP, BORDER_DASH_OFFSET);
|
|
|
|
if (hoveredRegionId != -1)
|
|
{
|
|
auto regionIt = spritesheet->regions.find(hoveredRegionId);
|
|
if (regionIt != spritesheet->regions.end())
|
|
{
|
|
auto& region = regionIt->second;
|
|
auto cropModel = math::quad_model_get(region.size, region.crop);
|
|
auto cropTransform = transform * cropModel;
|
|
rect_fill_render(shaderLine, cropTransform, cropModel, vec4(1.0f, 1.0f, 1.0f, 0.5f));
|
|
}
|
|
}
|
|
|
|
int highlightedRegionId = -1;
|
|
if (frame && reference.itemID > -1 &&
|
|
anm2.content.layers.at(reference.itemID).spritesheetID == referenceSpritesheet && frame->regionID != -1 &&
|
|
spritesheet->regions.contains(frame->regionID))
|
|
{
|
|
highlightedRegionId = frame->regionID;
|
|
}
|
|
else if (regionReference != -1 && spritesheet->regions.contains(regionReference))
|
|
{
|
|
highlightedRegionId = regionReference;
|
|
}
|
|
|
|
auto draw_region_rect = [&](anm2::Spritesheet::Region& region, vec4 regionColor)
|
|
{
|
|
auto cropModel = math::quad_model_get(region.size, region.crop);
|
|
auto cropTransform = transform * cropModel;
|
|
rect_render(dashedShader, cropTransform, cropModel, regionColor, BORDER_DASH_LENGTH, BORDER_DASH_GAP,
|
|
BORDER_DASH_OFFSET);
|
|
};
|
|
|
|
for (auto& [id, region] : spritesheet->regions)
|
|
{
|
|
if (id == highlightedRegionId) continue;
|
|
draw_region_rect(region, color::WHITE);
|
|
|
|
auto pivotTransform =
|
|
transform * math::quad_model_get(PIVOT_SIZE, region.crop + region.pivot, PIVOT_SIZE * 0.5f);
|
|
texture_render(shaderTexture, resources.icons[icon::PIVOT].id, pivotTransform, color::WHITE);
|
|
}
|
|
|
|
if (highlightedRegionId != -1)
|
|
{
|
|
auto regionIt = spritesheet->regions.find(highlightedRegionId);
|
|
if (regionIt != spritesheet->regions.end())
|
|
{
|
|
auto& region = regionIt->second;
|
|
draw_region_rect(region, color::RED);
|
|
|
|
auto pivotTransform =
|
|
transform * math::quad_model_get(PIVOT_SIZE, region.crop + region.pivot, PIVOT_SIZE * 0.5f);
|
|
texture_render(shaderTexture, resources.icons[icon::PIVOT].id, pivotTransform, PIVOT_COLOR);
|
|
}
|
|
}
|
|
|
|
bool isFrameOnSpritesheet =
|
|
frame && reference.itemID > -1 && anm2.content.layers.at(reference.itemID).spritesheetID == referenceSpritesheet;
|
|
if (isFrameOnSpritesheet && frame->regionID == -1)
|
|
{
|
|
auto frameModel = math::quad_model_get(frame->size, frame->crop);
|
|
auto frameTransform = transform * frameModel;
|
|
rect_render(shaderLine, frameTransform, frameModel, color::RED);
|
|
|
|
auto pivotTransform = transform * math::quad_model_get(PIVOT_SIZE, frame->crop + frame->pivot, PIVOT_SIZE * 0.5f);
|
|
texture_render(shaderTexture, resources.icons[icon::PIVOT].id, pivotTransform, PIVOT_COLOR);
|
|
}
|
|
}
|
|
|
|
unbind();
|
|
|
|
sync_checker_pan();
|
|
if (isTransparent)
|
|
render_checker_background(drawList, min, max, -size * 0.5f - checkerPan, CHECKER_SIZE);
|
|
else
|
|
drawList->AddRectFilled(min, max, ImGui::GetColorU32(to_imvec4(vec4(backgroundColor, 1.0f))));
|
|
ImGui::Image(texture, to_imvec2(size));
|
|
|
|
if (ImGui::IsItemHovered())
|
|
{
|
|
auto isMouseLeftClicked = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
|
|
auto isMouseLeftReleased = ImGui::IsMouseReleased(ImGuiMouseButton_Left);
|
|
auto isMouseLeftDown = ImGui::IsMouseDown(ImGuiMouseButton_Left);
|
|
auto isMouseMiddleDown = ImGui::IsMouseDown(ImGuiMouseButton_Middle);
|
|
auto isMouseRightClicked = ImGui::IsMouseClicked(ImGuiMouseButton_Right);
|
|
auto isMouseRightDown = ImGui::IsMouseDown(ImGuiMouseButton_Right);
|
|
auto isMouseRightReleased = ImGui::IsMouseReleased(ImGuiMouseButton_Right);
|
|
auto mouseDelta = to_ivec2(ImGui::GetIO().MouseDelta);
|
|
auto mouseWheel = ImGui::GetIO().MouseWheel;
|
|
auto isMouseClicked = isMouseLeftClicked || isMouseRightClicked;
|
|
auto isMouseDown = isMouseLeftDown || isMouseRightDown;
|
|
auto isMouseReleased = isMouseLeftReleased || isMouseRightReleased;
|
|
|
|
auto isLeftJustPressed = ImGui::IsKeyPressed(ImGuiKey_LeftArrow, false);
|
|
auto isRightJustPressed = ImGui::IsKeyPressed(ImGuiKey_RightArrow, false);
|
|
auto isUpJustPressed = ImGui::IsKeyPressed(ImGuiKey_UpArrow, false);
|
|
auto isDownJustPressed = ImGui::IsKeyPressed(ImGuiKey_DownArrow, false);
|
|
auto isLeftPressed = ImGui::IsKeyPressed(ImGuiKey_LeftArrow);
|
|
auto isRightPressed = ImGui::IsKeyPressed(ImGuiKey_RightArrow);
|
|
auto isUpPressed = ImGui::IsKeyPressed(ImGuiKey_UpArrow);
|
|
auto isDownPressed = ImGui::IsKeyPressed(ImGuiKey_DownArrow);
|
|
auto isLeftDown = ImGui::IsKeyDown(ImGuiKey_LeftArrow);
|
|
auto isRightDown = ImGui::IsKeyDown(ImGuiKey_RightArrow);
|
|
auto isUpDown = ImGui::IsKeyDown(ImGuiKey_UpArrow);
|
|
auto isDownDown = ImGui::IsKeyDown(ImGuiKey_DownArrow);
|
|
auto isLeftReleased = ImGui::IsKeyReleased(ImGuiKey_LeftArrow);
|
|
auto isRightReleased = ImGui::IsKeyReleased(ImGuiKey_RightArrow);
|
|
auto isUpReleased = ImGui::IsKeyReleased(ImGuiKey_UpArrow);
|
|
auto isDownReleased = ImGui::IsKeyReleased(ImGuiKey_DownArrow);
|
|
auto isKeyJustPressed = isLeftJustPressed || isRightJustPressed || isUpJustPressed || isDownJustPressed;
|
|
auto isKeyDown = isLeftDown || isRightDown || isUpDown || isDownDown;
|
|
auto isKeyReleased = isLeftReleased || isRightReleased || isUpReleased || isDownReleased;
|
|
|
|
auto isZoomIn = shortcut(manager.chords[SHORTCUT_ZOOM_IN], shortcut::GLOBAL);
|
|
auto isZoomOut = shortcut(manager.chords[SHORTCUT_ZOOM_OUT], shortcut::GLOBAL);
|
|
|
|
auto isBegin = isMouseClicked || isKeyJustPressed;
|
|
auto isDuring = isMouseDown || isKeyDown;
|
|
auto isEnd = isMouseReleased || isKeyReleased;
|
|
|
|
auto isMod = ImGui::IsKeyDown(ImGuiMod_Shift);
|
|
|
|
auto frame = document.frame_get();
|
|
auto useTool = tool;
|
|
auto step = isMod ? STEP_FAST : STEP;
|
|
auto stepX = isGridSnap ? step * gridSize.x : step;
|
|
auto stepY = isGridSnap ? step * gridSize.y : step;
|
|
previousMousePos = mousePos;
|
|
mousePos = position_translate(zoom, pan, to_ivec2(ImGui::GetMousePos()) - to_ivec2(cursorScreenPos));
|
|
|
|
auto snap_rect = [&](glm::vec2 minPoint, glm::vec2 maxPoint)
|
|
{
|
|
if (isGridSnap)
|
|
{
|
|
if (gridSize.x != 0)
|
|
{
|
|
auto offsetX = (float)(gridOffset.x);
|
|
auto sizeX = (float)(gridSize.x);
|
|
minPoint.x = std::floor((minPoint.x - offsetX) / sizeX) * sizeX + offsetX;
|
|
maxPoint.x = std::ceil((maxPoint.x - offsetX) / sizeX) * sizeX + offsetX;
|
|
}
|
|
if (gridSize.y != 0)
|
|
{
|
|
auto offsetY = (float)(gridOffset.y);
|
|
auto sizeY = (float)(gridSize.y);
|
|
minPoint.y = std::floor((minPoint.y - offsetY) / sizeY) * sizeY + offsetY;
|
|
maxPoint.y = std::ceil((maxPoint.y - offsetY) / sizeY) * sizeY + offsetY;
|
|
}
|
|
}
|
|
return std::pair{minPoint, maxPoint};
|
|
};
|
|
|
|
auto frame_change_apply = [&](anm2::FrameChange frameChange, anm2::ChangeType changeType = anm2::ADJUST)
|
|
{ item->frames_change(frameChange, reference.itemType, changeType, frames); };
|
|
auto clamp_vec2_to_int = [](const vec2& value)
|
|
{ return vec2(ivec2(value)); };
|
|
auto region_set_all = [&](const vec2& crop, const vec2& size)
|
|
{
|
|
if (!spritesheet) return;
|
|
for (auto id : regionSelection)
|
|
{
|
|
auto it = spritesheet->regions.find(id);
|
|
if (it == spritesheet->regions.end()) continue;
|
|
it->second.crop = clamp_vec2_to_int(crop);
|
|
it->second.size = clamp_vec2_to_int(size);
|
|
}
|
|
};
|
|
auto region_offset_all = [&](const vec2& delta)
|
|
{
|
|
if (!spritesheet) return;
|
|
for (auto id : regionSelection)
|
|
{
|
|
auto it = spritesheet->regions.find(id);
|
|
if (it == spritesheet->regions.end()) continue;
|
|
it->second.crop = clamp_vec2_to_int(it->second.crop + delta);
|
|
it->second.size = clamp_vec2_to_int(it->second.size);
|
|
}
|
|
};
|
|
|
|
if (isMouseMiddleDown) useTool = tool::PAN;
|
|
if (tool == tool::MOVE && isMouseRightDown) useTool = tool::CROP;
|
|
if (tool == tool::CROP && isMouseRightDown) useTool = tool::MOVE;
|
|
if (tool == tool::DRAW && isMouseRightDown) useTool = tool::ERASE;
|
|
if (tool == tool::ERASE && isMouseRightDown) useTool = tool::DRAW;
|
|
|
|
hoveredRegionId = -1;
|
|
|
|
if (useTool == tool::PAN && spritesheet && spritesheet->texture.is_valid() && isMouseOverCanvas)
|
|
{
|
|
for (auto& [id, region] : spritesheet->regions)
|
|
{
|
|
auto minPoint = glm::min(region.crop, region.crop + region.size);
|
|
auto maxPoint = glm::max(region.crop, region.crop + region.size);
|
|
if (hoverMousePos.x >= minPoint.x && hoverMousePos.x <= maxPoint.x && hoverMousePos.y >= minPoint.y &&
|
|
hoverMousePos.y <= maxPoint.y)
|
|
{
|
|
hoveredRegionId = id;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
auto& toolInfo = tool::INFO[useTool];
|
|
auto& areaType = toolInfo.areaType;
|
|
bool isAreaAllowed = areaType == tool::ALL || areaType == tool::SPRITESHEET_EDITOR;
|
|
bool isFrameRequired =
|
|
!(useTool == tool::PAN || useTool == tool::DRAW || useTool == tool::ERASE || useTool == tool::COLOR_PICKER);
|
|
bool isRegionInUse = frame && frame->regionID != -1 && (useTool == tool::CROP || useTool == tool::MOVE);
|
|
bool isFrameAvailable = !isFrameRequired || (frame && !isRegionInUse) ||
|
|
(useTool == tool::CROP && !frame && !regionSelection.empty()) ||
|
|
(useTool == tool::MOVE && !frame && regionReference != -1);
|
|
bool isSpritesheetRequired = useTool == tool::DRAW || useTool == tool::ERASE || useTool == tool::COLOR_PICKER;
|
|
bool isSpritesheetAvailable = !isSpritesheetRequired || (spritesheet && spritesheet->texture.is_valid());
|
|
auto cursor = (isAreaAllowed && isFrameAvailable && isSpritesheetAvailable) ? toolInfo.cursor
|
|
: ImGuiMouseCursor_NotAllowed;
|
|
ImGui::SetMouseCursor(cursor);
|
|
ImGui::SetKeyboardFocusHere();
|
|
|
|
switch (useTool)
|
|
{
|
|
case tool::PAN:
|
|
if (isMouseLeftClicked && hoveredRegionId != -1)
|
|
{
|
|
regionReference = hoveredRegionId;
|
|
regionSelection = {hoveredRegionId};
|
|
if (frame && reference.itemID > -1 &&
|
|
anm2.content.layers.at(reference.itemID).spritesheetID == referenceSpritesheet)
|
|
{
|
|
DOCUMENT_EDIT(document, localize.get(EDIT_FRAME_REGION), Document::FRAMES,
|
|
{
|
|
anm2::FrameChange change{};
|
|
change.regionID = hoveredRegionId;
|
|
if (spritesheet)
|
|
{
|
|
auto regionIt = spritesheet->regions.find(hoveredRegionId);
|
|
if (regionIt != spritesheet->regions.end())
|
|
{
|
|
change.cropX = regionIt->second.crop.x;
|
|
change.cropY = regionIt->second.crop.y;
|
|
change.sizeX = regionIt->second.size.x;
|
|
change.sizeY = regionIt->second.size.y;
|
|
change.pivotX = regionIt->second.pivot.x;
|
|
change.pivotY = regionIt->second.pivot.y;
|
|
}
|
|
}
|
|
frame_change_apply(change);
|
|
});
|
|
}
|
|
}
|
|
if (isMouseDown || isMouseMiddleDown) pan += mouseDelta;
|
|
break;
|
|
case tool::MOVE:
|
|
if (isRegionInUse) break;
|
|
if (!frame && regionReference != -1)
|
|
{
|
|
if (!spritesheet) break;
|
|
auto regionIt = spritesheet->regions.find(regionReference);
|
|
if (regionIt == spritesheet->regions.end()) break;
|
|
|
|
auto& region = regionIt->second;
|
|
if (isBegin) document.snapshot(localize.get(EDIT_REGION_MOVE));
|
|
bool isPivotEdited = isMouseDown || isLeftPressed || isRightPressed || isUpPressed || isDownPressed;
|
|
if (isPivotEdited) region.origin = anm2::Spritesheet::Region::CUSTOM;
|
|
if (isMouseDown) region.pivot = ivec2(mousePos) - ivec2(region.crop);
|
|
if (isLeftPressed) region.pivot.x -= step;
|
|
if (isRightPressed) region.pivot.x += step;
|
|
if (isUpPressed) region.pivot.y -= step;
|
|
if (isDownPressed) region.pivot.y += step;
|
|
|
|
if (isDuring)
|
|
{
|
|
if (ImGui::BeginTooltip())
|
|
{
|
|
ImGui::TextUnformatted(
|
|
std::vformat(localize.get(FORMAT_PIVOT), std::make_format_args(region.pivot.x, region.pivot.y))
|
|
.c_str());
|
|
ImGui::EndTooltip();
|
|
}
|
|
}
|
|
|
|
if (isEnd) document.change(Document::SPRITESHEETS);
|
|
break;
|
|
}
|
|
|
|
if (!item || frames.empty()) break;
|
|
if (isBegin) document.snapshot(localize.get(EDIT_FRAME_PIVOT));
|
|
if (isMouseDown)
|
|
{
|
|
frame->crop = ivec2(frame->crop);
|
|
frame_change_apply(
|
|
{.pivotX = (int)(mousePos.x - frame->crop.x), .pivotY = (int)(mousePos.y - frame->crop.y)});
|
|
}
|
|
if (isLeftPressed) frame_change_apply({.pivotX = step}, anm2::SUBTRACT);
|
|
if (isRightPressed) frame_change_apply({.pivotX = step}, anm2::ADD);
|
|
if (isUpPressed) frame_change_apply({.pivotY = step}, anm2::SUBTRACT);
|
|
if (isDownPressed) frame_change_apply({.pivotY = step}, anm2::ADD);
|
|
|
|
frame_change_apply({.pivotX = (int)frame->pivot.x, .pivotY = (int)frame->pivot.y});
|
|
|
|
if (isDuring)
|
|
{
|
|
if (ImGui::BeginTooltip())
|
|
{
|
|
ImGui::TextUnformatted(
|
|
std::vformat(localize.get(FORMAT_PIVOT), std::make_format_args(frame->pivot.x, frame->pivot.y))
|
|
.c_str());
|
|
ImGui::EndTooltip();
|
|
}
|
|
}
|
|
|
|
if (isEnd) document.change(Document::FRAMES);
|
|
break;
|
|
case tool::CROP:
|
|
if (isRegionInUse) break;
|
|
if (frames.empty())
|
|
{
|
|
if (!spritesheet || regionSelection.empty()) break;
|
|
if (isBegin) document.snapshot(localize.get(EDIT_REGION_CROP));
|
|
|
|
if (isMouseClicked)
|
|
{
|
|
cropAnchor = mousePos;
|
|
region_set_all(vec2((int)cropAnchor.x, (int)cropAnchor.y), vec2());
|
|
}
|
|
if (isMouseDown)
|
|
{
|
|
auto [minPoint, maxPoint] = snap_rect(glm::min(cropAnchor, mousePos), glm::max(cropAnchor, mousePos));
|
|
region_set_all(vec2(minPoint), vec2(maxPoint - minPoint));
|
|
}
|
|
if (isLeftPressed) region_offset_all(vec2(stepX * -1, 0));
|
|
if (isRightPressed) region_offset_all(vec2(stepX, 0));
|
|
if (isUpPressed) region_offset_all(vec2(0, stepY * -1));
|
|
if (isDownPressed) region_offset_all(vec2(0, stepY));
|
|
|
|
if (isDuring)
|
|
{
|
|
if (!isMouseDown)
|
|
{
|
|
for (auto id : regionSelection)
|
|
{
|
|
auto it = spritesheet->regions.find(id);
|
|
if (it == spritesheet->regions.end()) continue;
|
|
|
|
auto& region = it->second;
|
|
auto minPoint = glm::min(region.crop, region.crop + region.size);
|
|
auto maxPoint = glm::max(region.crop, region.crop + region.size);
|
|
region.crop = clamp_vec2_to_int(minPoint);
|
|
region.size = clamp_vec2_to_int(maxPoint - minPoint);
|
|
|
|
if (isGridSnap)
|
|
{
|
|
auto [snapMin, snapMax] = snap_rect(region.crop, region.crop + region.size);
|
|
region.crop = clamp_vec2_to_int(snapMin);
|
|
region.size = clamp_vec2_to_int(snapMax - snapMin);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ImGui::BeginTooltip())
|
|
{
|
|
auto it = spritesheet->regions.find(*regionSelection.begin());
|
|
if (it != spritesheet->regions.end())
|
|
{
|
|
ImGui::TextUnformatted(std::vformat(localize.get(FORMAT_CROP),
|
|
std::make_format_args(it->second.crop.x, it->second.crop.y))
|
|
.c_str());
|
|
ImGui::TextUnformatted(std::vformat(localize.get(FORMAT_SIZE),
|
|
std::make_format_args(it->second.size.x, it->second.size.y))
|
|
.c_str());
|
|
}
|
|
ImGui::EndTooltip();
|
|
}
|
|
}
|
|
|
|
if (isEnd) document.change(Document::SPRITESHEETS);
|
|
break;
|
|
}
|
|
|
|
if (!item) break;
|
|
if (isBegin) document.snapshot(localize.get(EDIT_FRAME_CROP));
|
|
|
|
if (isMouseClicked)
|
|
{
|
|
cropAnchor = mousePos;
|
|
frame_change_apply({.cropX = (int)cropAnchor.x, .cropY = (int)cropAnchor.y, .sizeX = {}, .sizeY = {}});
|
|
}
|
|
if (isMouseDown)
|
|
{
|
|
auto [minPoint, maxPoint] = snap_rect(glm::min(cropAnchor, mousePos), glm::max(cropAnchor, mousePos));
|
|
frame_change_apply({.cropX = minPoint.x,
|
|
.cropY = minPoint.y,
|
|
.sizeX = maxPoint.x - minPoint.x,
|
|
.sizeY = maxPoint.y - minPoint.y});
|
|
}
|
|
if (isLeftPressed) frame_change_apply({.cropX = stepX}, anm2::SUBTRACT);
|
|
if (isRightPressed) frame_change_apply({.cropX = stepX}, anm2::ADD);
|
|
if (isUpPressed) frame_change_apply({.cropY = stepY}, anm2::SUBTRACT);
|
|
if (isDownPressed) frame_change_apply({.cropY = stepY}, anm2::ADD);
|
|
|
|
frame_change_apply(
|
|
{.cropX = frame->crop.x, .cropY = frame->crop.y, .sizeX = frame->size.x, .sizeY = frame->size.y});
|
|
|
|
if (isDuring)
|
|
{
|
|
if (!isMouseDown)
|
|
{
|
|
auto minPoint = glm::min(frame->crop, frame->crop + frame->size);
|
|
auto maxPoint = glm::max(frame->crop, frame->crop + frame->size);
|
|
|
|
frame_change_apply({.cropX = minPoint.x,
|
|
.cropY = minPoint.y,
|
|
.sizeX = maxPoint.x - minPoint.x,
|
|
.sizeY = maxPoint.y - minPoint.y});
|
|
|
|
if (isGridSnap)
|
|
{
|
|
auto [snapMin, snapMax] = snap_rect(frame->crop, frame->crop + frame->size);
|
|
|
|
frame_change_apply({.cropX = snapMin.x,
|
|
.cropY = snapMin.y,
|
|
.sizeX = snapMax.x - snapMin.x,
|
|
.sizeY = snapMax.y - snapMin.y});
|
|
}
|
|
}
|
|
if (ImGui::BeginTooltip())
|
|
{
|
|
ImGui::TextUnformatted(
|
|
std::vformat(localize.get(FORMAT_CROP), std::make_format_args(frame->crop.x, frame->crop.y))
|
|
.c_str());
|
|
ImGui::TextUnformatted(
|
|
std::vformat(localize.get(FORMAT_SIZE), std::make_format_args(frame->size.x, frame->size.y))
|
|
.c_str());
|
|
ImGui::EndTooltip();
|
|
}
|
|
}
|
|
if (isEnd) document.change(Document::FRAMES);
|
|
break;
|
|
case tool::DRAW:
|
|
case tool::ERASE:
|
|
{
|
|
if (!spritesheet) break;
|
|
auto color = useTool == tool::DRAW ? toolColor : vec4();
|
|
if (isMouseClicked)
|
|
document.snapshot(useTool == tool::DRAW ? localize.get(EDIT_DRAW) : localize.get(EDIT_ERASE));
|
|
if (isMouseDown) spritesheet->texture.pixel_line(ivec2(previousMousePos), ivec2(mousePos), color);
|
|
if (isMouseReleased)
|
|
{
|
|
document.change(Document::SPRITESHEETS);
|
|
}
|
|
break;
|
|
}
|
|
case tool::COLOR_PICKER:
|
|
{
|
|
if (spritesheet && isDuring)
|
|
{
|
|
toolColor = spritesheet->texture.pixel_read(mousePos);
|
|
if (ImGui::BeginTooltip())
|
|
{
|
|
ImGui::ColorButton("##Color Picker Button", to_imvec4(toolColor));
|
|
ImGui::SameLine();
|
|
auto rgba8 = glm::clamp(ivec4(toolColor * 255.0f + 0.5f), ivec4(0), ivec4(255));
|
|
auto hex = std::format("#{:02X}{:02X}{:02X}{:02X}", rgba8.r, rgba8.g, rgba8.b, rgba8.a);
|
|
ImGui::TextUnformatted(hex.c_str());
|
|
ImGui::SameLine();
|
|
ImGui::Text("(%d, %d, %d, %d)", rgba8.r, rgba8.g, rgba8.b, rgba8.a);
|
|
ImGui::EndTooltip();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (tool == tool::PAN && hoveredRegionId != -1 && spritesheet)
|
|
{
|
|
auto regionIt = spritesheet->regions.find(hoveredRegionId);
|
|
if (regionIt != spritesheet->regions.end())
|
|
{
|
|
if (ImGui::BeginTooltip())
|
|
{
|
|
auto& region = regionIt->second;
|
|
ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE);
|
|
ImGui::TextUnformatted(region.name.c_str());
|
|
ImGui::PopFont();
|
|
ImGui::TextUnformatted(
|
|
std::vformat(localize.get(FORMAT_ID), std::make_format_args(hoveredRegionId)).c_str());
|
|
ImGui::TextUnformatted(
|
|
std::vformat(localize.get(FORMAT_CROP), std::make_format_args(region.crop.x, region.crop.y)).c_str());
|
|
ImGui::TextUnformatted(
|
|
std::vformat(localize.get(FORMAT_SIZE), std::make_format_args(region.size.x, region.size.y)).c_str());
|
|
if (region.origin == anm2::Spritesheet::Region::CUSTOM)
|
|
{
|
|
ImGui::TextUnformatted(
|
|
std::vformat(localize.get(FORMAT_PIVOT), std::make_format_args(region.pivot.x, region.pivot.y))
|
|
.c_str());
|
|
}
|
|
else
|
|
{
|
|
StringType originString = LABEL_REGION_ORIGIN_CENTER;
|
|
if (region.origin == anm2::Spritesheet::Region::TOP_LEFT) originString = LABEL_REGION_ORIGIN_TOP_LEFT;
|
|
auto originLabel = localize.get(originString);
|
|
ImGui::TextUnformatted(
|
|
std::vformat(localize.get(FORMAT_ORIGIN), std::make_format_args(originLabel)).c_str());
|
|
}
|
|
ImGui::EndTooltip();
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((isMouseDown || isKeyDown) && useTool != tool::PAN)
|
|
{
|
|
if (!isAreaAllowed && areaType == tool::ANIMATION_PREVIEW)
|
|
{
|
|
if (ImGui::BeginTooltip())
|
|
{
|
|
ImGui::TextUnformatted(localize.get(TEXT_TOOL_ANIMATION_PREVIEW));
|
|
ImGui::EndTooltip();
|
|
}
|
|
}
|
|
else if (isSpritesheetRequired && !isSpritesheetAvailable)
|
|
{
|
|
if (ImGui::BeginTooltip())
|
|
{
|
|
ImGui::TextUnformatted(localize.get(TEXT_SELECT_SPRITESHEET));
|
|
ImGui::EndTooltip();
|
|
}
|
|
}
|
|
else if (isFrameRequired && !isFrameAvailable)
|
|
{
|
|
if (ImGui::BeginTooltip())
|
|
{
|
|
if (isRegionInUse)
|
|
ImGui::TextUnformatted(localize.get(TEXT_REGION_IN_USE));
|
|
else if (useTool == tool::CROP)
|
|
ImGui::TextUnformatted(localize.get(TEXT_SELECT_FRAME_OR_REGION));
|
|
else
|
|
ImGui::TextUnformatted(localize.get(TEXT_SELECT_FRAME));
|
|
ImGui::EndTooltip();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mouseWheel != 0 || isZoomIn || isZoomOut)
|
|
{
|
|
auto focus = mouseWheel != 0 ? vec2(mousePos) : vec2();
|
|
if (auto spritesheet = document.spritesheet_get(); spritesheet && mouseWheel == 0)
|
|
focus = spritesheet->texture.size / 2;
|
|
|
|
auto previousZoom = zoom;
|
|
zoom_set(zoom, pan, focus, (mouseWheel > 0 || isZoomIn) ? zoomStep : -zoomStep);
|
|
if (zoom != previousZoom) hasPendingZoomPanAdjust = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (tool == tool::PAN &&
|
|
ImGui::BeginPopupContextWindow("##Spritesheet Editor Context Menu", ImGuiMouseButton_Right))
|
|
{
|
|
if (ImGui::MenuItem(localize.get(SHORTCUT_STRING_UNDO), settings.shortcutUndo.c_str(), false,
|
|
document.is_able_to_undo()))
|
|
document.undo();
|
|
|
|
if (ImGui::MenuItem(localize.get(SHORTCUT_STRING_REDO), settings.shortcutRedo.c_str(), false,
|
|
document.is_able_to_redo()))
|
|
document.redo();
|
|
|
|
ImGui::Separator();
|
|
|
|
if (ImGui::MenuItem(localize.get(LABEL_CENTER_VIEW), settings.shortcutCenterView.c_str())) center_view();
|
|
|
|
if (ImGui::MenuItem(localize.get(LABEL_FIT), settings.shortcutFit.c_str(), false,
|
|
spritesheet && spritesheet->texture.is_valid()))
|
|
fit_view();
|
|
|
|
ImGui::Separator();
|
|
|
|
if (ImGui::MenuItem(localize.get(SHORTCUT_STRING_ZOOM_IN), settings.shortcutZoomIn.c_str())) zoom_in();
|
|
if (ImGui::MenuItem(localize.get(SHORTCUT_STRING_ZOOM_OUT), settings.shortcutZoomOut.c_str())) zoom_out();
|
|
|
|
ImGui::EndPopup();
|
|
}
|
|
|
|
if (!document.isSpritesheetEditorSet)
|
|
{
|
|
size = settings.editorSize;
|
|
zoom = settings.editorStartZoom;
|
|
set();
|
|
center_view();
|
|
reset_checker_pan();
|
|
document.isSpritesheetEditorSet = true;
|
|
}
|
|
|
|
settings.editorSize = size;
|
|
settings.editorStartZoom = zoom;
|
|
ImGui::End();
|
|
}
|
|
|
|
}
|