original editor compliance and some fixes

This commit is contained in:
2025-11-14 14:08:41 -05:00
parent 911085ef47
commit 5470368b6a
22 changed files with 202 additions and 78 deletions

View File

@@ -295,4 +295,18 @@ namespace anm2ed::anm2
if (frame.atFrame == atFrame) return i;
return -1;
}
float Item::frame_time_from_index_get(int index)
{
if (!vector::in_bounds(frames, index)) return 0.0f;
float time{};
for (auto [i, frame] : std::views::enumerate(frames))
{
if (i == index) return time;
time += frame.duration;
}
return time;
}
}

View File

@@ -26,5 +26,6 @@ namespace anm2ed::anm2
void frames_generate_from_grid(glm::ivec2, glm::ivec2, glm::ivec2, int, int, int);
void frames_sort_by_at_frame();
int frame_index_from_at_frame_get(int);
float frame_time_from_index_get(int);
};
}

View File

@@ -228,6 +228,7 @@ namespace anm2ed
{
anm2::Spritesheet& spritesheet = anm2.content.spritesheets[id];
this->spritesheet.selection = {id};
this->spritesheet.reference = id;
toasts.info(std::format("Initialized spritesheet #{}: {}", id, spritesheet.path.string()));
}
else

View File

@@ -62,7 +62,7 @@ namespace anm2ed::imgui
}
bool selectable_input_text(const std::string& label, const std::string& id, std::string& text, bool isSelected,
ImGuiSelectableFlags flags, bool* isRenamed)
ImGuiSelectableFlags flags, bool* isRenamed, bool isBeginEditing)
{
static std::string editID{};
static bool isJustEdit{};
@@ -90,7 +90,8 @@ namespace anm2ed::imgui
{
if (ImGui::Selectable(label.c_str(), isSelected, flags)) isActivated = true;
if ((ImGui::IsWindowFocused() && ImGui::IsKeyPressed(ImGuiKey_F2) && isSelected) ||
if (isBeginEditing ||
(ImGui::IsWindowFocused() && ImGui::IsKeyPressed(ImGuiKey_F2) && isSelected) ||
(ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)))
{
editID = id;

View File

@@ -1,7 +1,7 @@
#pragma once
#include <imgui/imgui.h>
#include <glm/glm.hpp>
#include <imgui/imgui.h>
#include <set>
#include <string>
#include <unordered_map>
@@ -170,7 +170,7 @@ namespace anm2ed::imgui
ImGuiInputTextFlags = 0);
bool combo_negative_one_indexed(const std::string&, int*, std::vector<const char*>&);
bool selectable_input_text(const std::string&, const std::string&, std::string&, bool = false,
ImGuiSelectableFlags = 0, bool* = nullptr);
ImGuiSelectableFlags = 0, bool* = nullptr, bool = false);
void set_item_tooltip_shortcut(const char*, const std::string& = {});
void external_storage_set(ImGuiSelectionExternalStorage*, int, bool);
void render_checker_background(ImDrawList*, ImVec2, ImVec2, glm::vec2, float);

View File

@@ -354,7 +354,8 @@ namespace anm2ed::imgui
auto render = [&](anm2::Animation* animation, float time, vec3 colorOffset = {}, float alphaOffset = {},
bool isOnionskin = false)
{
auto transform = transform_get(zoom, pan);
auto baseTransform = transform_get(zoom, pan);
auto transform = baseTransform;
auto root = animation->rootAnimation.frame_generate(time, anm2::ROOT);
if (isRootTransform)
@@ -362,8 +363,8 @@ namespace anm2ed::imgui
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);
auto rootTransform = baseTransform * math::quad_model_get(TARGET_SIZE, root.position, TARGET_SIZE * 0.5f,
math::percent_to_unit(root.scale), root.rotation);
vec4 color = isOnionskin ? vec4(colorOffset, alphaOffset) : color::GREEN;
@@ -511,9 +512,10 @@ namespace anm2ed::imgui
{
auto isMouseClicked = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
auto isMouseReleased = ImGui::IsMouseReleased(ImGuiMouseButton_Left);
auto isMouseDown = ImGui::IsMouseDown(ImGuiMouseButton_Left);
auto isMouseLeftDown = ImGui::IsMouseDown(ImGuiMouseButton_Left);
auto isMouseMiddleDown = ImGui::IsMouseDown(ImGuiMouseButton_Middle);
auto isMouseRightDown = ImGui::IsMouseDown(ImGuiMouseButton_Right);
auto isMouseDown = isMouseLeftDown || isMouseMiddleDown || isMouseRightDown;
auto mouseDelta = to_ivec2(ImGui::GetIO().MouseDelta);
auto mouseWheel = ImGui::GetIO().MouseWheel;
@@ -589,7 +591,11 @@ namespace anm2ed::imgui
case tool::SCALE:
if (!frame) break;
if (isBegin) document.snapshot("Frame Scale");
if (isMouseDown) frame->scale += mouseDelta;
if (isMouseDown)
{
frame->scale += mouseDelta;
if (isMod) frame->scale = {frame->scale.x, frame->scale.x};
}
if (isLeftPressed) frame->scale.x -= step;
if (isRightPressed) frame->scale.x += step;
if (isUpPressed) frame->scale.y -= step;

View File

@@ -54,6 +54,7 @@ namespace anm2ed::imgui
auto isDefault = anm2.animations.defaultAnimation == animation.name;
auto isReferenced = reference.animationIndex == (int)i;
auto isNewSelection = newAnimationSelectedIndex == (int)i;
auto font = isDefault && isReferenced ? font::BOLD_ITALICS
: isDefault ? font::BOLD
@@ -62,13 +63,20 @@ namespace anm2ed::imgui
ImGui::PushFont(resources.fonts[font].get(), font::SIZE);
ImGui::SetNextItemSelectionUserData((int)i);
if (selectable_input_text(animation.name, std::format("###Document #{} Animation #{}", manager.selected, i),
animation.name, selection.contains((int)i)))
animation.name, selection.contains((int)i), ImGuiSelectableFlags_None, nullptr,
isNewSelection))
{
reference = {(int)i};
document.frames.clear();
}
if (ImGui::IsItemHovered()) hovered = (int)i;
if (isNewSelection)
{
ImGui::SetScrollHereY(0.5f);
newAnimationSelectedIndex = -1;
}
ImGui::PopFont();
if (ImGui::BeginItemTooltip())
@@ -190,13 +198,17 @@ namespace anm2ed::imgui
}
animation.rootAnimation.frames.emplace_back(anm2::Frame());
auto index = 0;
if (!anm2.animations.items.empty())
index = selection.empty() ? (int)anm2.animations.items.size() - 1 : *selection.rbegin() + 1;
auto index = (int)anm2.animations.items.size();
if (!selection.empty())
{
index = *selection.rbegin() + 1;
index = std::min(index, (int)anm2.animations.items.size());
}
anm2.animations.items.insert(anm2.animations.items.begin() + index, animation);
selection = {index};
reference = {index};
newAnimationSelectedIndex = index;
};
DOCUMENT_EDIT(document, "Add Animation", Document::ANIMATIONS, add());

View File

@@ -10,6 +10,7 @@ namespace anm2ed::imgui
class Animations
{
PopupHelper mergePopup{PopupHelper("Merge Animations")};
int newAnimationSelectedIndex{-1};
public:
void update(Manager&, Settings&, Resources&, Clipboard&);

View File

@@ -34,9 +34,15 @@ namespace anm2ed::imgui
{
ImGui::PushID(id);
ImGui::SetNextItemSelectionUserData(id);
const bool isNewEvent = (newEventId == id);
if (selectable_input_text(event.name, std::format("###Document #{} Event #{}", manager.selected, id),
event.name, selection.contains(id)))
event.name, selection.contains(id), ImGuiSelectableFlags_None, nullptr, isNewEvent))
if (ImGui::IsItemHovered()) hovered = id;
if (isNewEvent)
{
ImGui::SetScrollHereY(0.5f);
newEventId = -1;
}
if (ImGui::BeginItemTooltip())
{
@@ -105,6 +111,7 @@ namespace anm2ed::imgui
anm2.content.events[id] = anm2::Event();
selection = {id};
reference = {id};
newEventId = id;
};
DOCUMENT_EDIT(document, "Add Event", Document::EVENTS, add());

View File

@@ -9,6 +9,8 @@ namespace anm2ed::imgui
{
class Events
{
int newEventId{-1};
public:
void update(Manager&, Settings&, Resources&, Clipboard&);
};

View File

@@ -39,6 +39,11 @@ namespace anm2ed::imgui
ImGui::SetNextItemSelectionUserData(id);
ImGui::Selectable(std::format(anm2::LAYER_FORMAT, id, layer.name, layer.spritesheetID).c_str(), isSelected);
if (newLayerId == id)
{
ImGui::SetScrollHereY(0.5f);
newLayerId = -1;
}
if (ImGui::IsItemHovered())
{
hovered = id;
@@ -152,25 +157,28 @@ namespace anm2ed::imgui
if (ImGui::Button(reference == -1 ? "Add" : "Confirm", widgetSize))
{
auto add = [&]()
{
auto id = map::next_id_get(anm2.content.layers);
anm2.content.layers[id] = layer;
selection = {id};
};
auto set = [&]()
{
anm2.content.layers[reference] = layer;
selection = {reference};
};
if (reference == -1)
{
auto add = [&]()
{
auto id = map::next_id_get(anm2.content.layers);
anm2.content.layers[id] = layer;
selection = {id};
newLayerId = id;
};
DOCUMENT_EDIT(document, "Add Layer", Document::LAYERS, add());
}
else
{
auto set = [&]()
{
anm2.content.layers[reference] = layer;
selection = {reference};
};
DOCUMENT_EDIT(document, "Set Layer Properties", Document::LAYERS, set());
}
manager.layer_properties_close();
}

View File

@@ -9,6 +9,8 @@ namespace anm2ed::imgui
{
class Layers
{
int newLayerId{-1};
public:
void update(Manager&, Settings&, Resources&, Clipboard&);
};

View File

@@ -40,6 +40,11 @@ namespace anm2ed::imgui
ImGui::SetNextItemSelectionUserData(id);
if (isReferenced) ImGui::PushFont(resources.fonts[font::ITALICS].get(), font::SIZE);
ImGui::Selectable(std::format(anm2::NULL_FORMAT, id, null.name).c_str(), isSelected);
if (newNullId == id)
{
ImGui::SetScrollHereY(0.5f);
newNullId = -1;
}
if (ImGui::IsItemHovered())
{
hovered = id;
@@ -152,25 +157,28 @@ namespace anm2ed::imgui
if (ImGui::Button(reference == -1 ? "Add" : "Confirm", widgetSize))
{
auto add = [&]()
{
auto id = map::next_id_get(anm2.content.nulls);
anm2.content.nulls[id] = null;
selection = {id};
};
auto set = [&]()
{
anm2.content.nulls[reference] = null;
selection = {reference};
};
if (reference == -1)
{
auto add = [&]()
{
auto id = map::next_id_get(anm2.content.nulls);
anm2.content.nulls[id] = null;
selection = {id};
newNullId = id;
};
DOCUMENT_EDIT(document, "Add Null", Document::NULLS, add());
}
else
{
auto set = [&]()
{
anm2.content.nulls[reference] = null;
selection = {reference};
};
DOCUMENT_EDIT(document, "Set Null Properties", Document::NULLS, set());
}
manager.null_properties_close();
}
@@ -184,4 +192,4 @@ namespace anm2ed::imgui
manager.null_properties_end();
}
}
}

View File

@@ -9,7 +9,9 @@ namespace anm2ed::imgui
{
class Nulls
{
int newNullId{-1};
public:
void update(Manager&, Settings&, Resources&, Clipboard&);
};
}
}

View File

@@ -42,6 +42,11 @@ namespace anm2ed::imgui
if (isReferenced) ImGui::PushFont(resources.fonts[font::ITALICS].get(), font::SIZE);
if (ImGui::Selectable(pathLabel, isSelected)) sound.play();
if (ImGui::IsItemHovered()) hovered = id;
if (newSoundId == id)
{
ImGui::SetScrollHereY(0.5f);
newSoundId = -1;
}
if (isReferenced) ImGui::PopFont();
@@ -137,6 +142,7 @@ namespace anm2ed::imgui
if (anm2.sound_add(document.directory_get().string(), dialog.path, id))
{
selection = {id};
newSoundId = id;
toasts.info(std::format("Initialized sound #{}: {}", id, dialog.path));
}
else

View File

@@ -10,6 +10,8 @@ namespace anm2ed::imgui
{
class Sounds
{
int newSoundId{-1};
public:
void update(Manager&, Settings&, Resources&, Dialog&, Clipboard&);
};

View File

@@ -104,6 +104,11 @@ namespace anm2ed::imgui
ImGui::SetNextItemStorageID(id);
if (ImGui::Selectable("##Spritesheet Selectable", isSelected, 0, spritesheetChildSize)) reference = id;
if (ImGui::IsItemHovered()) hovered = id;
if (newSpritesheetId == id)
{
ImGui::SetScrollHereY(0.5f);
newSpritesheetId = -1;
}
auto viewport = ImGui::GetMainViewport();
auto textureSize = texture.size.x * texture.size.y > (viewport->Size.x * viewport->Size.y) * 0.5f
@@ -192,6 +197,7 @@ namespace anm2ed::imgui
if (dialog.is_selected(dialog::SPRITESHEET_OPEN))
{
document.spritesheet_add(dialog.path);
newSpritesheetId = document.spritesheet.reference;
dialog.reset();
}

View File

@@ -10,6 +10,8 @@ namespace anm2ed::imgui
{
class Spritesheets
{
int newSpritesheetId{-1};
public:
void update(Manager&, Settings&, Resources&, Dialog&, Clipboard& clipboard);
};

View File

@@ -36,6 +36,10 @@ namespace anm2ed::imgui
- Press {} to increment time.
- Press {} to shorten the selected frame, by one frame.
- Press {} to extend the selected frame, by one frame.
- Press {} to go to the previous frame.
- Press {} to go to the next frame.
- Click and hold on a frame while holding CTRL to change its duration.
- Click and hold on a trigger to change its At Frame.
- Hold Alt while clicking a non-trigger frame to toggle interpolation.)";
void Timeline::update(Manager& manager, Settings& settings, Resources& resources, Clipboard& clipboard)
@@ -313,10 +317,11 @@ namespace anm2ed::imgui
ImGui::BeginDisabled();
ImGui::Text("(?)");
ImGui::SetItemTooltip("%s",
std::format(HELP_FORMAT, settings.shortcutNextFrame, settings.shortcutPreviousFrame,
settings.shortcutShortenFrame, settings.shortcutExtendFrame)
.c_str());
ImGui::SetItemTooltip("%s", std::format(HELP_FORMAT, settings.shortcutMovePlayheadBack,
settings.shortcutMovePlayheadForward, settings.shortcutShortenFrame,
settings.shortcutExtendFrame, settings.shortcutPreviousFrame,
settings.shortcutNextFrame)
.c_str());
ImGui::EndDisabled();
}
}
@@ -600,12 +605,15 @@ namespace anm2ed::imgui
reference = frameReference;
}
if (type == anm2::TRIGGER && ImGui::IsItemHovered() && ImGui::IsMouseDown(ImGuiMouseButton_Left) &&
!draggedTrigger)
if (ImGui::IsItemHovered() && ImGui::IsMouseDown(ImGuiMouseButton_Left))
{
draggedTrigger = &frame;
draggedTriggerIndex = (int)i;
draggedTriggerAtFrameStart = hoveredTime;
if (type == anm2::TRIGGER || ImGui::IsKeyDown(ImGuiMod_Ctrl))
{
draggedFrame = &frame;
draggedFrameIndex = (int)i;
draggedFrameStart = hoveredTime;
if (type != anm2::TRIGGER) draggedFrameStartDuration = draggedFrame->duration;
}
}
ImGui::PopStyleColor(3);
@@ -781,33 +789,42 @@ namespace anm2ed::imgui
frameSelectionSnapshotReference = reference;
}
if (draggedTrigger)
if (draggedFrame)
{
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
if (!isDraggedTriggerSnapshot && hoveredTime != draggedTriggerAtFrameStart)
if (!isDraggedFrameSnapshot && hoveredTime != draggedFrameStart)
{
isDraggedTriggerSnapshot = true;
document.snapshot("Trigger At Frame");
isDraggedFrameSnapshot = true;
document.snapshot(type == anm2::TRIGGER ? "Trigger At Frame" : "Frame Duration");
}
draggedTrigger->atFrame = glm::clamp(
hoveredTime, 0, settings.playbackIsClamp ? animation->frameNum - 1 : anm2::FRAME_NUM_MAX - 1);
for (std::size_t triggerIndex = 0; triggerIndex < animation->triggers.frames.size(); ++triggerIndex)
if (type == anm2::TRIGGER)
{
if ((int)triggerIndex == draggedTriggerIndex) continue;
auto& trigger = animation->triggers.frames[triggerIndex];
if (trigger.atFrame == draggedTrigger->atFrame) draggedTrigger->atFrame--;
draggedFrame->atFrame = glm::clamp(
hoveredTime, 0, settings.playbackIsClamp ? animation->frameNum - 1 : anm2::FRAME_NUM_MAX - 1);
for (auto [i, trigger] : std::views::enumerate(animation->triggers.frames))
{
if ((int)i == draggedFrameIndex) continue;
if (trigger.atFrame == draggedFrame->atFrame) draggedFrame->atFrame--;
}
}
else
{
draggedFrame->duration = glm::clamp(draggedFrameStartDuration + (hoveredTime - draggedFrameStart),
anm2::FRAME_DURATION_MIN, anm2::FRAME_DURATION_MAX);
}
if (ImGui::IsMouseReleased(ImGuiMouseButton_Left))
{
document.change(Document::FRAMES);
draggedTrigger = nullptr;
draggedTriggerIndex = -1;
draggedTriggerAtFrameStart = -1;
isDraggedTriggerSnapshot = false;
item->frames_sort_by_at_frame();
draggedFrame = nullptr;
draggedFrameIndex = -1;
draggedFrameStart = -1;
draggedFrameStartDuration = -1;
isDraggedFrameSnapshot = false;
if (type == anm2::TRIGGER) item->frames_sort_by_at_frame();
}
}
}
@@ -958,6 +975,7 @@ namespace anm2ed::imgui
ImGui::BeginDisabled(!item);
{
shortcut(manager.chords[SHORTCUT_ADD]);
shortcut(manager.chords[SHORTCUT_INSERT_FRAME]);
if (ImGui::Button("Insert", widgetSize))
{
auto insert_frame = [&]()
@@ -1000,7 +1018,7 @@ namespace anm2ed::imgui
DOCUMENT_EDIT(document, "Insert Frame", Document::FRAMES, insert_frame());
}
set_item_tooltip_shortcut("Insert a frame, based on the current selection.", settings.shortcutAdd);
set_item_tooltip_shortcut("Insert a frame, based on the current selection.", settings.shortcutInsertFrame);
ImGui::SameLine();
@@ -1292,13 +1310,13 @@ namespace anm2ed::imgui
{
if (shortcut(manager.chords[SHORTCUT_PLAY_PAUSE], shortcut::GLOBAL)) playback.toggle();
if (shortcut(manager.chords[SHORTCUT_PREVIOUS_FRAME], shortcut::GLOBAL, true))
if (shortcut(manager.chords[SHORTCUT_MOVE_PLAYHEAD_BACK], shortcut::GLOBAL, true))
{
playback.decrement(settings.playbackIsClamp ? animation->frameNum : anm2::FRAME_NUM_MAX);
document.frameTime = playback.time;
}
if (shortcut(manager.chords[SHORTCUT_NEXT_FRAME], shortcut::GLOBAL, true))
if (shortcut(manager.chords[SHORTCUT_MOVE_PLAYHEAD_FORWARD], shortcut::GLOBAL, true))
{
playback.increment(settings.playbackIsClamp ? animation->frameNum : anm2::FRAME_NUM_MAX);
document.frameTime = playback.time;
@@ -1325,6 +1343,28 @@ namespace anm2ed::imgui
document.change(Document::FRAMES);
}
}
if (shortcut(manager.chords[SHORTCUT_PREVIOUS_FRAME], shortcut::GLOBAL, true))
{
if (auto item = document.item_get())
{
reference.frameIndex--;
reference.frameIndex = glm::clamp(reference.frameIndex--, -1, (int)item->frames.size() - 1);
frames.selection = {reference.frameIndex};
document.frameTime = item->frame_time_from_index_get(reference.frameIndex);
}
}
if (shortcut(manager.chords[SHORTCUT_NEXT_FRAME], shortcut::GLOBAL, true))
{
if (auto item = document.item_get())
{
reference.frameIndex++;
reference.frameIndex = glm::clamp(reference.frameIndex, -1, (int)item->frames.size() - 1);
frames.selection = {reference.frameIndex};
document.frameTime = item->frame_time_from_index_get(reference.frameIndex);
}
}
}
}
}

View File

@@ -30,10 +30,11 @@ namespace anm2ed::imgui
int addItemID{-1};
int addItemSpritesheetID{-1};
int hoveredTime{};
anm2::Frame* draggedTrigger{};
int draggedTriggerIndex{-1};
int draggedTriggerAtFrameStart{-1};
bool isDraggedTriggerSnapshot{};
anm2::Frame* draggedFrame{};
int draggedFrameIndex{-1};
int draggedFrameStart{-1};
int draggedFrameStartDuration{-1};
bool isDraggedFrameSnapshot{};
FrameDragDrop frameDragDrop{};
std::vector<int> frameSelectionSnapshot{};
std::vector<int> frameSelectionLocked{};

View File

@@ -190,11 +190,13 @@ namespace anm2ed
X(SHORTCUT_ZOOM_OUT, shortcutZoomOut, "Zoom Out", STRING, "Ctrl+Minus") \
/* Timeline / Playback */ \
X(SHORTCUT_PLAY_PAUSE, shortcutPlayPause, "Play/Pause", STRING, "Space") \
X(SHORTCUT_PREVIOUS_FRAME, shortcutPreviousFrame, "Previous Frame", STRING, "Comma") \
X(SHORTCUT_NEXT_FRAME, shortcutNextFrame, "Next Frame", STRING, "Period") \
X(SHORTCUT_INSERT_FRAME, shortcutInsertFrame, "Insert Frame", STRING, "F6") \
X(SHORTCUT_MOVE_PLAYHEAD_BACK, shortcutMovePlayheadBack, "Playhead Back", STRING, "Comma") \
X(SHORTCUT_MOVE_PLAYHEAD_FORWARD, shortcutMovePlayheadForward, "Playhead Forward", STRING, "Period") \
X(SHORTCUT_SHORTEN_FRAME, shortcutShortenFrame, "Shorten Frame", STRING, "F4") \
X(SHORTCUT_EXTEND_FRAME, shortcutExtendFrame, "Extend Frame", STRING, "F5") \
X(SHORTCUT_INSERT_FRAME, shortcutInsertFrame, "Insert Frame", STRING, "F6") \
X(SHORTCUT_PREVIOUS_FRAME, shortcutPreviousFrame, "Previous Frame", STRING, "F7") \
X(SHORTCUT_NEXT_FRAME, shortcutNextFrame, "Next Frame", STRING, "F8") \
/* Toggles */ \
X(SHORTCUT_ONIONSKIN, shortcutOnionskin, "Onionskin", STRING, "O")

View File

@@ -54,7 +54,7 @@ namespace anm2ed::tool
{ImGuiMouseCursor_ResizeNESW, resource::icon::SCALE, SHORTCUT_SCALE, ANIMATION_PREVIEW, "##Scale",
"Use the scale tool.\nWill scale the selected item as the cursor is dragged, or directional keys are "
"pressed.\n(Animation Preview only.)"},
"pressed.\nHold SHIFT to lock scaling to one dimension.\n(Animation Preview only.)"},
{ImGuiMouseCursor_Arrow, resource::icon::CROP, SHORTCUT_CROP, SPRITESHEET_EDITOR, "##Crop",
"Use the crop tool.\nWill produce a crop rectangle based on how the cursor is dragged, or directional keys are "