Holy vibecode Batman!

This commit is contained in:
2026-05-21 21:00:57 -04:00
parent 7db835e6d4
commit 1bc5199e3b
140 changed files with 11888 additions and 9481 deletions
+122
View File
@@ -0,0 +1,122 @@
#include "actions.hpp"
#include <utility>
namespace anm2ed::imgui
{
bool is_action_shortcut_valid(int shortcutType) { return shortcutType >= 0 && shortcutType < SHORTCUT_COUNT; }
std::string action_shortcut_text_get(const Action& action, Settings& settings)
{
if (!is_action_shortcut_valid(action.shortcut)) return {};
return settings.*SHORTCUT_MEMBERS[action.shortcut];
}
ImGuiKeyChord action_chord_get(const Action& action, Manager& manager)
{
if (!is_action_shortcut_valid(action.shortcut)) return ImGuiKey_None;
return manager.chords[action.shortcut];
}
bool is_action_enabled(const Action& action) { return !action.isEnabled || action.isEnabled(); }
Action action_make(ActionType type, std::function<bool()> isEnabled, std::function<void()> run, StringType tooltip,
int shortcut)
{
auto info = ACTION_INFOS[type];
return {.type = type,
.label = info.label,
.tooltip = tooltip,
.shortcut = shortcut == ACTION_COUNT ? info.shortcut : shortcut,
.isEnabled = std::move(isEnabled),
.run = std::move(run)};
}
void Actions::add(Action action) { items.push_back(std::move(action)); }
void Actions::add(ActionType type, std::function<bool()> isEnabled, std::function<void()> run, StringType tooltip,
int shortcut)
{
add(action_make(type, std::move(isEnabled), std::move(run), tooltip, shortcut));
}
void Actions::separator() { items.push_back({.isSeparator = true}); }
void actions_undo_redo_add(Actions& actions, Manager& manager, Document& document)
{
actions.add(ACTION_UNDO, [&document]() { return document.is_able_to_undo(); },
[&manager]()
{ manager.command_push({manager.selected, [](Manager&, Document& document) { document.undo(); }}); });
actions.add(ACTION_REDO, [&document]() { return document.is_able_to_redo(); },
[&manager]()
{ manager.command_push({manager.selected, [](Manager&, Document& document) { document.redo(); }}); });
}
void actions_menu_draw(Actions& actions, Settings& settings)
{
bool isPreviousSeparator{};
for (auto& action : actions.items)
{
if (action.isSeparator)
{
if (!isPreviousSeparator) ImGui::Separator();
isPreviousSeparator = true;
continue;
}
auto shortcutText = action_shortcut_text_get(action, settings);
auto shortcutLabel = shortcutText.empty() ? nullptr : shortcutText.c_str();
if (ImGui::MenuItem(localize.get(action.label), shortcutLabel, false, is_action_enabled(action)) && action.run)
action.run();
if (action.tooltip != STRING_UNDEFINED && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled))
ImGui::SetItemTooltip("%s", localize.get(action.tooltip));
isPreviousSeparator = false;
}
}
bool actions_context_window_draw(const char* label, Actions& actions, Settings& settings, ImGuiPopupFlags flags)
{
if (!ImGui::BeginPopupContextWindow(label, flags)) return false;
actions_menu_draw(actions, settings);
ImGui::EndPopup();
return true;
}
bool actions_popup_draw(const char* label, Actions& actions, Settings& settings)
{
if (!ImGui::BeginPopup(label)) return false;
actions_menu_draw(actions, settings);
ImGui::EndPopup();
return true;
}
void actions_shortcuts_update(Actions& actions, Manager& manager, types::shortcut::Type shortcutType)
{
for (auto& action : actions.items)
{
if (action.isSeparator || !action.run || !is_action_enabled(action)) continue;
auto chord = action_chord_get(action, manager);
if (!shortcut(chord, shortcutType)) continue;
action.run();
break;
}
}
void action_button_draw(Action& action, Manager& manager, Settings& settings, ImVec2 widgetSize, bool& isSameLine)
{
if (isSameLine) ImGui::SameLine();
ImGui::BeginDisabled(!is_action_enabled(action));
shortcut(action_chord_get(action, manager));
if (ImGui::Button(localize.get(action.label), widgetSize) && action.run) action.run();
ImGui::EndDisabled();
if (action.tooltip != STRING_UNDEFINED)
{
auto shortcutText = action_shortcut_text_get(action, settings);
if (shortcutText.empty())
ImGui::SetItemTooltip("%s", localize.get(action.tooltip));
else
set_item_tooltip_shortcut(localize.get(action.tooltip), shortcutText);
}
isSameLine = true;
}
}
+90
View File
@@ -0,0 +1,90 @@
#pragma once
#include <functional>
#include <vector>
#include "util/imgui/imgui.hpp"
#include "manager.hpp"
#include "settings.hpp"
namespace anm2ed::imgui
{
#define ACTIONS(X) \
X(ACTION_UNDO, SHORTCUT_STRING_UNDO, SHORTCUT_UNDO) \
X(ACTION_REDO, SHORTCUT_STRING_REDO, SHORTCUT_REDO) \
X(ACTION_ADD, BASIC_ADD, SHORTCUT_ADD) \
X(ACTION_REMOVE, BASIC_REMOVE, SHORTCUT_REMOVE) \
X(ACTION_REMOVE_UNUSED, BASIC_REMOVE_UNUSED, SHORTCUT_REMOVE) \
X(ACTION_DUPLICATE, BASIC_DUPLICATE, SHORTCUT_DUPLICATE) \
X(ACTION_MERGE, BASIC_MERGE, SHORTCUT_MERGE) \
X(ACTION_GROUP, BASIC_GROUP, SHORTCUT_GROUP) \
X(ACTION_DEFAULT, BASIC_DEFAULT, SHORTCUT_DEFAULT) \
X(ACTION_RENAME, BASIC_RENAME, SHORTCUT_RENAME) \
X(ACTION_PROPERTIES, BASIC_PROPERTIES, -1) \
X(ACTION_CUT, BASIC_CUT, SHORTCUT_CUT) \
X(ACTION_COPY, BASIC_COPY, SHORTCUT_COPY) \
X(ACTION_PASTE, BASIC_PASTE, SHORTCUT_PASTE) \
X(ACTION_RELOAD, BASIC_RELOAD, -1) \
X(ACTION_REPLACE, BASIC_REPLACE, -1) \
X(ACTION_SAVE, BASIC_SAVE, -1) \
X(ACTION_OPEN_DIRECTORY, BASIC_OPEN_DIRECTORY, -1) \
X(ACTION_SET_FILE_PATH, BASIC_SET_FILE_PATH, -1) \
X(ACTION_PACK, BASIC_PACK, -1) \
X(ACTION_TRIM, BASIC_TRIM, -1) \
X(ACTION_PLAY, LABEL_PLAY, SHORTCUT_PLAY_PAUSE) \
X(ACTION_CENTER_VIEW, LABEL_CENTER_VIEW, SHORTCUT_CENTER_VIEW) \
X(ACTION_FIT_VIEW, LABEL_FIT, SHORTCUT_FIT) \
X(ACTION_ZOOM_IN, SHORTCUT_STRING_ZOOM_IN, SHORTCUT_ZOOM_IN) \
X(ACTION_ZOOM_OUT, SHORTCUT_STRING_ZOOM_OUT, SHORTCUT_ZOOM_OUT)
enum ActionType
{
#define X(name, label, shortcut) name,
ACTIONS(X)
#undef X
ACTION_COUNT
};
struct ActionInfo
{
StringType label{};
int shortcut{-1};
};
constexpr ActionInfo ACTION_INFOS[] = {
#define X(name, label, shortcut) {label, shortcut},
ACTIONS(X)
#undef X
};
struct Action
{
ActionType type{};
StringType label{STRING_UNDEFINED};
StringType tooltip{STRING_UNDEFINED};
int shortcut{-1};
bool isSeparator{};
std::function<bool()> isEnabled{};
std::function<void()> run{};
};
struct Actions
{
std::vector<Action> items{};
void add(Action);
void add(ActionType, std::function<bool()>, std::function<void()>, StringType = STRING_UNDEFINED,
int = ACTION_COUNT);
void separator();
};
bool is_action_enabled(const Action&);
Action action_make(ActionType, std::function<bool()>, std::function<void()>, StringType = STRING_UNDEFINED,
int = ACTION_COUNT);
void actions_undo_redo_add(Actions&, Manager&, Document&);
void actions_menu_draw(Actions&, Settings&);
bool actions_context_window_draw(const char*, Actions&, Settings&, ImGuiPopupFlags = ImGuiPopupFlags_MouseButtonRight);
bool actions_popup_draw(const char*, Actions&, Settings&);
void actions_shortcuts_update(Actions&, Manager&, types::shortcut::Type = types::shortcut::FOCUSED);
void action_button_draw(Action&, Manager&, Settings&, ImVec2, bool&);
}
+13 -7
View File
@@ -2,6 +2,8 @@
namespace anm2ed::imgui
{
bool Dockspace::is_canvas_focused_get() const { return isCanvasFocused; }
void Dockspace::tick(Manager& manager, Settings& settings, float deltaSeconds)
{
if (auto document = manager.get(); document)
@@ -11,6 +13,7 @@ namespace anm2ed::imgui
void Dockspace::update(Taskbar& taskbar, Documents& documents, Manager& manager, Settings& settings,
Resources& resources, Dialog& dialog, Clipboard& clipboard)
{
isCanvasFocused = false;
auto viewport = ImGui::GetMainViewport();
auto windowHeight = viewport->Size.y - taskbar.height - documents.height;
@@ -30,18 +33,21 @@ namespace anm2ed::imgui
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);
if (settings.windowIsRegions) regions.update(manager, settings, resources, clipboard);
if (settings.windowIsEvents) events.update(manager, settings, resources, clipboard);
if (settings.windowIsAnimations) window_update(animations, manager, settings, resources, dialog, clipboard);
if (settings.windowIsRegions) window_update(regions, manager, settings, resources, dialog, clipboard);
if (settings.windowIsEvents) window_update(events, manager, settings, resources, dialog, clipboard);
if (settings.windowIsFrameProperties) frameProperties.update(manager, settings);
if (settings.windowIsLayers) layers.update(manager, settings, resources, clipboard);
if (settings.windowIsNulls) nulls.update(manager, settings, resources, clipboard);
if (settings.windowIsLayers) window_update(layers, manager, settings, resources, dialog, clipboard);
if (settings.windowIsNulls) window_update(nulls, manager, settings, resources, dialog, clipboard);
if (settings.windowIsOnionskin) onionskin.update(manager, settings);
if (settings.windowIsSounds) sounds.update(manager, settings, resources, dialog, clipboard);
if (settings.windowIsSounds) window_update(sounds, manager, settings, resources, dialog, clipboard);
if (settings.windowIsSpritesheetEditor) spritesheetEditor.update(manager, settings, resources);
if (settings.windowIsSpritesheets) spritesheets.update(manager, settings, resources, dialog, clipboard);
if (settings.windowIsSpritesheets)
window_update(spritesheets, manager, settings, resources, dialog, clipboard);
if (settings.windowIsTimeline) timeline.update(manager, settings, resources, clipboard);
if (settings.windowIsTools) tools.update(manager, settings, resources);
isCanvasFocused = (settings.windowIsAnimationPreview && animationPreview.is_focused_get()) ||
(settings.windowIsSpritesheetEditor && spritesheetEditor.is_focused_get());
}
}
else
+11 -15
View File
@@ -1,44 +1,40 @@
#pragma once
#include "autosave_restore.hpp"
#include "popup/autosave_restore.hpp"
#include "documents.hpp"
#include "taskbar.hpp"
#include "window/animation_preview.hpp"
#include "window/animations.hpp"
#include "window/regions.hpp"
#include "window/events.hpp"
#include "window/frame_properties.hpp"
#include "window/layers.hpp"
#include "window/nulls.hpp"
#include "window/onionskin.hpp"
#include "window/sounds.hpp"
#include "window/spritesheet_editor.hpp"
#include "window/spritesheets.hpp"
#include "window/timeline.hpp"
#include "window/tools.hpp"
#include "window/welcome.hpp"
#include "window/window.hpp"
namespace anm2ed::imgui
{
class Dockspace
{
AnimationPreview animationPreview;
Animations animations;
Regions regions;
Events events;
bool isCanvasFocused{};
Window animations{animations_window_register()};
Window regions{regions_window_register()};
Window events{events_window_register()};
FrameProperties frameProperties;
Layers layers;
Nulls nulls;
Window layers{layers_window_register()};
Window nulls{nulls_window_register()};
Onionskin onionskin;
SpritesheetEditor spritesheetEditor;
Spritesheets spritesheets;
Sounds sounds;
Window spritesheets{spritesheets_window_register()};
Window sounds{sounds_window_register()};
Timeline timeline;
Tools tools;
Welcome welcome;
AutosaveRestore autosaveRestore;
public:
bool is_canvas_focused_get() const;
void tick(Manager&, Settings&, float);
void update(Taskbar&, Documents&, Manager&, Settings&, Resources&, Dialog&, Clipboard&);
};
+68 -46
View File
@@ -3,11 +3,11 @@
#include <format>
#include <vector>
#include "path_.hpp"
#include "strings.hpp"
#include "time_.hpp"
#include "toast.hpp"
#include "log.hpp"
#include "path.hpp"
#include "strings.hpp"
#include "time.hpp"
#include "toast.hpp"
using namespace anm2ed::resource;
using namespace anm2ed::types;
@@ -31,16 +31,24 @@ namespace anm2ed::imgui
pushedStyle = true;
}
for (auto& document : manager.documents)
for (auto i = 0; i < (int)manager.documents.size(); ++i)
{
auto& document = manager.documents[i];
auto isDirty = document.is_dirty() && document.is_autosave_dirty();
if (isDirty)
{
document.lastAutosaveTime += ImGui::GetIO().DeltaTime;
if (document.lastAutosaveTime > time::SECOND_M)
manager.autosave(document, (anm2::Compatibility)settings.fileCompatibility,
settings.fileBakeSpecialInterpolatedFramesOnSave, settings.bakeIsRoundScale,
settings.bakeIsRoundRotation);
{
auto compatibility = (Compatibility)settings.fileCompatibility;
auto bakeFrames = settings.fileBakeSpecialInterpolatedFramesOnSave;
auto isRoundScale = settings.bakeIsRoundScale;
auto isRoundRotation = settings.bakeIsRoundRotation;
manager.command_push({i,
[compatibility, bakeFrames, isRoundScale, isRoundRotation](Manager& manager,
Document& document)
{ manager.autosave(document, compatibility, bakeFrames, isRoundScale, isRoundRotation); }});
}
}
}
@@ -95,8 +103,8 @@ namespace anm2ed::imgui
auto isRequested = i == manager.pendingSelected;
auto font = isDocumentDirty ? font::ITALICS : font::REGULAR;
auto filename = path::to_utf8(document.filename_get());
auto string =
isDocumentDirty ? std::vformat(localize.get(FORMAT_NOT_SAVED), std::make_format_args(filename)) : filename;
auto string = isDocumentDirty ? std::vformat(localize.get(FORMAT_NOT_SAVED), std::make_format_args(filename))
: filename;
auto label = std::format("{}###Document{}", string, i);
auto flags = isDocumentDirty ? ImGuiTabItemFlags_UnsavedDocument : 0;
@@ -120,7 +128,8 @@ namespace anm2ed::imgui
for (auto it = closeIndices.rbegin(); it != closeIndices.rend(); ++it)
{
if (closePopup.is_open() && closeDocumentIndex > *it) --closeDocumentIndex;
manager.close(*it);
auto index = *it;
manager.command_push({.runManager = [index](Manager& manager) { manager.close(index); }});
}
ImGui::EndTabBar();
@@ -137,10 +146,10 @@ namespace anm2ed::imgui
auto filename = path::to_utf8(closeDocument.filename_get());
auto isDocumentDirty = closeDocument.is_dirty() || closeDocument.isForceDirty;
auto isSpritesheetDirty = closeDocument.spritesheet_any_dirty();
auto promptLabel = isDocumentDirty && isSpritesheetDirty
? LABEL_DOCUMENT_AND_SPRITESHEETS_MODIFIED_PROMPT
: (isDocumentDirty ? LABEL_DOCUMENT_MODIFIED_PROMPT
: LABEL_SPRITESHEETS_MODIFIED_PROMPT);
auto promptLabel =
isDocumentDirty && isSpritesheetDirty
? LABEL_DOCUMENT_AND_SPRITESHEETS_MODIFIED_PROMPT
: (isDocumentDirty ? LABEL_DOCUMENT_MODIFIED_PROMPT : LABEL_SPRITESHEETS_MODIFIED_PROMPT);
auto prompt = std::vformat(localize.get(promptLabel), std::make_format_args(filename));
ImGui::TextUnformatted(prompt.c_str());
@@ -156,8 +165,7 @@ namespace anm2ed::imgui
if (ImGui::Button(localize.get(BASIC_YES), widgetSize))
{
bool isSaved = true;
if (isDocumentDirty)
isSaved = taskbar.save_manual(manager, settings, closeDocumentIndex);
if (isDocumentDirty) isSaved = taskbar.save_manual(manager, settings, closeDocumentIndex);
if (!isSaved)
{
@@ -167,27 +175,38 @@ namespace anm2ed::imgui
if (isSpritesheetDirty)
{
for (auto& [id, spritesheet] : closeDocument.anm2.content.spritesheets)
auto spritesheets = closeDocument.anm2.element_get(ElementType::SPRITESHEETS);
if (spritesheets)
{
if (!closeDocument.spritesheet_is_dirty(id)) continue;
auto pathString = path::to_utf8(spritesheet.path);
if (spritesheet.save(closeDocument.directory_get()))
for (auto& spritesheet : spritesheets->children)
{
closeDocument.spritesheet_hash_set_saved(id);
toasts.push(std::vformat(localize.get(TOAST_SAVE_SPRITESHEET), std::make_format_args(id, pathString)));
logger.info(std::vformat(localize.get(TOAST_SAVE_SPRITESHEET, anm2ed::ENGLISH),
std::make_format_args(id, pathString)));
}
else
{
toasts.push(
std::vformat(localize.get(TOAST_SAVE_SPRITESHEET_FAILED), std::make_format_args(id, pathString)));
logger.error(std::vformat(localize.get(TOAST_SAVE_SPRITESHEET_FAILED, anm2ed::ENGLISH),
std::make_format_args(id, pathString)));
if (spritesheet.type != ElementType::SPRITESHEET) continue;
auto id = spritesheet.id;
auto texture = closeDocument.texture_get(id);
if (!texture || !closeDocument.spritesheet_is_dirty(id)) continue;
auto pathString = path::to_utf8(spritesheet.path);
auto savePath = closeDocument.directory_get() / spritesheet.path;
path::ensure_directory(savePath.parent_path());
if (texture->write_png(savePath))
{
closeDocument.spritesheet_hash_set_saved(id);
toasts.push(
std::vformat(localize.get(TOAST_SAVE_SPRITESHEET), std::make_format_args(id, pathString)));
logger.info(std::vformat(localize.get(TOAST_SAVE_SPRITESHEET, anm2ed::ENGLISH),
std::make_format_args(id, pathString)));
}
else
{
toasts.push(std::vformat(localize.get(TOAST_SAVE_SPRITESHEET_FAILED),
std::make_format_args(id, pathString)));
logger.error(std::vformat(localize.get(TOAST_SAVE_SPRITESHEET_FAILED, anm2ed::ENGLISH),
std::make_format_args(id, pathString)));
}
}
}
}
manager.close(closeDocumentIndex);
auto index = closeDocumentIndex;
manager.command_push({.runManager = [index](Manager& manager) { manager.close(index); }});
close();
}
@@ -195,7 +214,8 @@ namespace anm2ed::imgui
if (ImGui::Button(localize.get(BASIC_NO), widgetSize))
{
manager.close(closeDocumentIndex);
auto index = closeDocumentIndex;
manager.command_push({.runManager = [index](Manager& manager) { manager.close(index); }});
close();
}
@@ -244,8 +264,12 @@ namespace anm2ed::imgui
if (ImGui::MenuItem(manager.anm2DragDropPaths.size() > 1 ? localize.get(LABEL_DOCUMENTS_OPEN_MANY)
: localize.get(LABEL_DOCUMENTS_OPEN_NEW)))
{
for (auto& path : manager.anm2DragDropPaths)
manager.open(path);
auto paths = manager.anm2DragDropPaths;
manager.command_push({.runManager =
[paths](Manager& manager)
{
for (auto& path : paths) manager.open(path);
}});
drag_drop_reset();
}
@@ -254,16 +278,14 @@ namespace anm2ed::imgui
{
if (document)
{
auto merge_anm2s = [&]()
{
for (auto& path : manager.anm2DragDropPaths)
{
anm2::Anm2 source(path);
document->anm2.merge(source, document->directory_get(), path.parent_path());
}
};
DOCUMENT_EDIT_PTR(document, localize.get(EDIT_MERGE_ANM2), Document::ALL, merge_anm2s());
auto paths = manager.anm2DragDropPaths;
manager.command_push({manager.selected,
[paths](Manager&, Document& document)
{
document.snapshot(localize.get(EDIT_MERGE_ANM2));
for (auto& path : paths) document.file_merge(path);
document.change(Document::ALL);
}});
drag_drop_reset();
}
}
-695
View File
@@ -1,695 +0,0 @@
#include <imgui/imgui_internal.h>
#include <algorithm>
#include <cmath>
#include <format>
#include <sstream>
#include <unordered_map>
#include "imgui_.hpp"
#include "path_.hpp"
#include "strings.hpp"
using namespace anm2ed::types;
using namespace anm2ed::util;
using namespace glm;
namespace anm2ed::imgui
{
static auto isRenaming = false;
namespace
{
const std::vector<std::pair<ImGuiKey, const char*>> CANONICAL_KEY_NAMES = {
{ImGuiKey_A, "A"},
{ImGuiKey_B, "B"},
{ImGuiKey_C, "C"},
{ImGuiKey_D, "D"},
{ImGuiKey_E, "E"},
{ImGuiKey_F, "F"},
{ImGuiKey_G, "G"},
{ImGuiKey_H, "H"},
{ImGuiKey_I, "I"},
{ImGuiKey_J, "J"},
{ImGuiKey_K, "K"},
{ImGuiKey_L, "L"},
{ImGuiKey_M, "M"},
{ImGuiKey_N, "N"},
{ImGuiKey_O, "O"},
{ImGuiKey_P, "P"},
{ImGuiKey_Q, "Q"},
{ImGuiKey_R, "R"},
{ImGuiKey_S, "S"},
{ImGuiKey_T, "T"},
{ImGuiKey_U, "U"},
{ImGuiKey_V, "V"},
{ImGuiKey_W, "W"},
{ImGuiKey_X, "X"},
{ImGuiKey_Y, "Y"},
{ImGuiKey_Z, "Z"},
{ImGuiKey_0, "0"},
{ImGuiKey_1, "1"},
{ImGuiKey_2, "2"},
{ImGuiKey_3, "3"},
{ImGuiKey_4, "4"},
{ImGuiKey_5, "5"},
{ImGuiKey_6, "6"},
{ImGuiKey_7, "7"},
{ImGuiKey_8, "8"},
{ImGuiKey_9, "9"},
{ImGuiKey_Keypad0, "Num0"},
{ImGuiKey_Keypad1, "Num1"},
{ImGuiKey_Keypad2, "Num2"},
{ImGuiKey_Keypad3, "Num3"},
{ImGuiKey_Keypad4, "Num4"},
{ImGuiKey_Keypad5, "Num5"},
{ImGuiKey_Keypad6, "Num6"},
{ImGuiKey_Keypad7, "Num7"},
{ImGuiKey_Keypad8, "Num8"},
{ImGuiKey_Keypad9, "Num9"},
{ImGuiKey_KeypadAdd, "NumAdd"},
{ImGuiKey_KeypadSubtract, "NumSubtract"},
{ImGuiKey_KeypadMultiply, "NumMultiply"},
{ImGuiKey_KeypadDivide, "NumDivide"},
{ImGuiKey_KeypadEnter, "NumEnter"},
{ImGuiKey_KeypadDecimal, "NumDecimal"},
{ImGuiKey_KeypadEqual, "NumEqual"},
{ImGuiKey_F1, "F1"},
{ImGuiKey_F2, "F2"},
{ImGuiKey_F3, "F3"},
{ImGuiKey_F4, "F4"},
{ImGuiKey_F5, "F5"},
{ImGuiKey_F6, "F6"},
{ImGuiKey_F7, "F7"},
{ImGuiKey_F8, "F8"},
{ImGuiKey_F9, "F9"},
{ImGuiKey_F10, "F10"},
{ImGuiKey_F11, "F11"},
{ImGuiKey_F12, "F12"},
{ImGuiKey_UpArrow, "Up"},
{ImGuiKey_DownArrow, "Down"},
{ImGuiKey_LeftArrow, "Left"},
{ImGuiKey_RightArrow, "Right"},
{ImGuiKey_Space, "Space"},
{ImGuiKey_Enter, "Enter"},
{ImGuiKey_Escape, "Escape"},
{ImGuiKey_Tab, "Tab"},
{ImGuiKey_Backspace, "Backspace"},
{ImGuiKey_Delete, "Delete"},
{ImGuiKey_Insert, "Insert"},
{ImGuiKey_Home, "Home"},
{ImGuiKey_End, "End"},
{ImGuiKey_PageUp, "PageUp"},
{ImGuiKey_PageDown, "PageDown"},
{ImGuiKey_Minus, "Minus"},
{ImGuiKey_Equal, "Equal"},
{ImGuiKey_LeftBracket, "LeftBracket"},
{ImGuiKey_RightBracket, "RightBracket"},
{ImGuiKey_Semicolon, "Semicolon"},
{ImGuiKey_Apostrophe, "Apostrophe"},
{ImGuiKey_Comma, "Comma"},
{ImGuiKey_Period, "Period"},
{ImGuiKey_Slash, "Slash"},
{ImGuiKey_Backslash, "Backslash"},
{ImGuiKey_GraveAccent, "GraveAccent"},
};
const char* canonical_key_name(ImGuiKey key)
{
for (const auto& [mappedKey, name] : CANONICAL_KEY_NAMES)
if (mappedKey == key) return name;
return nullptr;
}
}
constexpr ImVec4 COLOR_LIGHT_BUTTON{0.98f, 0.98f, 0.98f, 1.0f};
constexpr ImVec4 COLOR_LIGHT_TITLE_BG{0.78f, 0.78f, 0.78f, 1.0f};
constexpr ImVec4 COLOR_LIGHT_TITLE_BG_ACTIVE{0.64f, 0.64f, 0.64f, 1.0f};
constexpr ImVec4 COLOR_LIGHT_TITLE_BG_COLLAPSED{0.74f, 0.74f, 0.74f, 1.0f};
constexpr ImVec4 COLOR_LIGHT_TABLE_HEADER{0.78f, 0.78f, 0.78f, 1.0f};
constexpr ImVec4 COLOR_LIGHT_TAB{0.74f, 0.74f, 0.74f, 1.0f};
constexpr ImVec4 COLOR_LIGHT_TAB_HOVERED{0.82f, 0.82f, 0.82f, 1.0f};
constexpr ImVec4 COLOR_LIGHT_TAB_SELECTED{0.92f, 0.92f, 0.92f, 1.0f};
constexpr ImVec4 COLOR_LIGHT_TAB_DIMMED{0.70f, 0.70f, 0.70f, 1.0f};
constexpr ImVec4 COLOR_LIGHT_TAB_DIMMED_SELECTED{0.86f, 0.86f, 0.86f, 1.0f};
constexpr ImVec4 COLOR_LIGHT_TAB_OVERLINE{0.55f, 0.55f, 0.55f, 1.0f};
constexpr ImVec4 COLOR_LIGHT_TAB_DIMMED_OVERLINE{0.50f, 0.50f, 0.50f, 1.0f};
constexpr ImVec4 COLOR_LIGHT_CHECK_MARK{0.0f, 0.0f, 0.0f, 1.0f};
constexpr auto FRAME_BORDER_SIZE = 1.0f;
void theme_set(theme::Type theme)
{
switch (theme)
{
case theme::LIGHT:
ImGui::StyleColorsLight();
break;
case theme::DARK:
default:
ImGui::StyleColorsDark();
break;
case theme::CLASSIC:
ImGui::StyleColorsClassic();
break;
}
auto& style = ImGui::GetStyle();
style.FrameBorderSize = FRAME_BORDER_SIZE;
if (theme == theme::LIGHT)
{
auto& colors = style.Colors;
colors[ImGuiCol_Button] = COLOR_LIGHT_BUTTON;
colors[ImGuiCol_TitleBg] = COLOR_LIGHT_TITLE_BG;
colors[ImGuiCol_TitleBgActive] = COLOR_LIGHT_TITLE_BG_ACTIVE;
colors[ImGuiCol_TitleBgCollapsed] = COLOR_LIGHT_TITLE_BG_COLLAPSED;
colors[ImGuiCol_TableHeaderBg] = COLOR_LIGHT_TABLE_HEADER;
colors[ImGuiCol_Tab] = COLOR_LIGHT_TAB;
colors[ImGuiCol_TabHovered] = COLOR_LIGHT_TAB_HOVERED;
colors[ImGuiCol_TabSelected] = COLOR_LIGHT_TAB_SELECTED;
colors[ImGuiCol_TabSelectedOverline] = COLOR_LIGHT_TAB_OVERLINE;
colors[ImGuiCol_TabDimmed] = COLOR_LIGHT_TAB_DIMMED;
colors[ImGuiCol_TabDimmedSelected] = COLOR_LIGHT_TAB_DIMMED_SELECTED;
colors[ImGuiCol_TabDimmedSelectedOverline] = COLOR_LIGHT_TAB_DIMMED_OVERLINE;
colors[ImGuiCol_CheckMark] = COLOR_LIGHT_CHECK_MARK;
}
}
int input_text_callback(ImGuiInputTextCallbackData* data)
{
if (data->EventFlag == ImGuiInputTextFlags_CallbackResize)
{
auto* string = (std::string*)(data->UserData);
string->resize(data->BufTextLen);
data->Buf = string->data();
}
return 0;
}
bool input_text_string(const char* label, std::string* string, ImGuiInputTextFlags flags)
{
flags |= ImGuiInputTextFlags_CallbackResize;
return ImGui::InputText(label, string->data(), string->capacity() + 1, flags, input_text_callback, string);
}
bool input_text_path(const char* label, std::filesystem::path* path, ImGuiInputTextFlags flags)
{
if (!path) return false;
auto pathUtf8 = path::to_utf8(*path);
auto edited = input_text_string(label, &pathUtf8, flags);
if (edited) *path = path::from_utf8(pathUtf8);
return edited;
}
bool combo_negative_one_indexed(const std::string& label, int* index, std::vector<const char*>& strings)
{
*index += 1;
bool isActivated = ImGui::Combo(label.c_str(), index, strings.data(), (int)strings.size());
*index -= 1;
return isActivated;
}
bool combo_id_mapped(const std::string& label, int* id, const std::vector<int>& ids, std::vector<const char*>& labels)
{
if (!id) return false;
int index = -1;
if (!ids.empty())
{
auto it = std::find(ids.begin(), ids.end(), *id);
if (it != ids.end()) index = (int)std::distance(ids.begin(), it);
}
bool isActivated = ImGui::Combo(label.c_str(), &index, labels.data(), (int)labels.size());
if (isActivated)
{
if (index >= 0 && index < (int)ids.size())
*id = ids[index];
else
*id = -1;
}
return isActivated;
}
edit::Type drag_int_persistent(const char* label, int* value, float speed, int min, int max, const char* format,
ImGuiSliderFlags flags)
{
static bool isEditing{};
static int start{INT_MAX};
auto persistent = value ? *value : 0;
ImGui::DragInt(label, &persistent, speed, min, max, format, flags);
if (!value) return edit::NONE;
if (ImGui::IsItemActivated() && persistent != start)
{
isEditing = true;
start = *value;
return edit::START;
}
else if (ImGui::IsItemDeactivatedAfterEdit())
{
isEditing = false;
*value = persistent;
start = INT_MAX;
return edit::END;
}
else if (isEditing)
{
*value = persistent;
return edit::DURING;
}
return edit::NONE;
}
edit::Type drag_float_persistent(const char* label, float* value, float speed, float min, float max,
const char* format, ImGuiSliderFlags flags)
{
static bool isEditing{};
static float start{NAN};
auto persistent = value ? *value : 0;
ImGui::DragFloat(label, &persistent, speed, min, max, format, flags);
if (!value) return edit::NONE;
if (ImGui::IsItemActivated() && persistent != start)
{
isEditing = true;
start = *value;
return edit::START;
}
else if (ImGui::IsItemDeactivatedAfterEdit())
{
isEditing = false;
*value = persistent;
start = NAN;
return edit::END;
}
else if (isEditing)
{
*value = persistent;
return edit::DURING;
}
return edit::NONE;
}
edit::Type drag_float2_persistent(const char* label, vec2* value, float speed, float min, float max,
const char* format, ImGuiSliderFlags flags)
{
static bool isEditing{};
static vec2 start{NAN};
auto persistent = value ? *value : vec2();
ImGui::DragFloat2(label, value_ptr(persistent), speed, min, max, format, flags);
if (!value) return edit::NONE;
if (ImGui::IsItemActivated() && persistent != start)
{
isEditing = true;
start = *value;
return edit::START;
}
else if (ImGui::IsItemDeactivatedAfterEdit())
{
isEditing = false;
*value = persistent;
start = vec2{NAN};
return edit::END;
}
else if (isEditing)
{
*value = persistent;
return edit::DURING;
}
return edit::NONE;
}
edit::Type color_edit3_persistent(const char* label, vec3* value, ImGuiColorEditFlags flags)
{
static bool isEditing{};
static vec3 start{NAN};
auto persistent = value ? *value : vec4();
ImGui::ColorEdit3(label, value_ptr(persistent), flags);
if (!value) return edit::NONE;
if (ImGui::IsItemActivated() && persistent != start)
{
isEditing = true;
start = *value;
return edit::START;
}
else if (ImGui::IsItemDeactivatedAfterEdit())
{
isEditing = false;
*value = persistent;
start = vec4{NAN};
return edit::END;
}
else if (isEditing)
{
*value = persistent;
return edit::DURING;
}
return edit::NONE;
}
edit::Type color_edit4_persistent(const char* label, vec4* value, ImGuiColorEditFlags flags)
{
static bool isEditing{};
static vec4 start{NAN};
auto persistent = value ? *value : vec4();
ImGui::ColorEdit4(label, value_ptr(persistent), flags);
if (!value) return edit::NONE;
if (ImGui::IsItemActivated() && persistent != start)
{
isEditing = true;
start = *value;
return edit::START;
}
else if (ImGui::IsItemDeactivatedAfterEdit())
{
isEditing = false;
*value = persistent;
start = vec4{NAN};
return edit::END;
}
else if (isEditing)
{
*value = persistent;
return edit::DURING;
}
return edit::NONE;
}
bool input_int_range(const char* label, int& value, int min, int max, int step, int stepFast,
ImGuiInputTextFlags flags)
{
auto isActivated = ImGui::InputInt(label, &value, step, stepFast, flags);
value = glm::clamp(value, min, max);
return isActivated;
}
bool input_int2_range(const char* label, ivec2& value, ivec2 min, ivec2 max, ImGuiInputTextFlags flags)
{
auto isActivated = ImGui::InputInt2(label, value_ptr(value), flags);
value = glm::clamp(value, min, max);
return isActivated;
}
bool input_float_range(const char* label, float& value, float min, float max, float step, float stepFast,
const char* format, ImGuiInputTextFlags flags)
{
auto isActivated = ImGui::InputFloat(label, &value, step, stepFast, format, flags);
value = glm::clamp(value, min, max);
return isActivated;
}
std::string& selectable_input_text_id()
{
static std::string editID{};
return editID;
}
bool selectable_input_text(const std::string& label, const std::string& id, std::string& text, bool isSelected,
ImGuiSelectableFlags flags, RenameState& state)
{
auto& editID = selectable_input_text_id();
auto isRename = editID == id;
bool isActivated{};
if (isRename)
{
auto finish = [&]()
{
editID.clear();
isActivated = true;
state = RENAME_FINISHED;
isRenaming = false;
};
if (state == RENAME_BEGIN)
{
ImGui::SetKeyboardFocusHere();
state = RENAME_EDITING;
}
ImGui::SetNextItemWidth(-FLT_MIN);
if (input_text_string("##Edit", &text, ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll))
finish();
if (ImGui::IsItemDeactivatedAfterEdit() || ImGui::IsKeyPressed(ImGuiKey_Escape)) finish();
}
else
{
if (ImGui::Selectable(label.c_str(), isSelected, flags)) isActivated = true;
if (state == RENAME_FORCE_EDIT || (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)))
{
state = RENAME_BEGIN;
editID = id;
isActivated = true;
isRenaming = true;
}
}
return isActivated;
}
void set_item_tooltip_shortcut(const char* tooltip, const std::string& shortcut)
{
ImGui::SetItemTooltip(
"%s", std::vformat(localize.get(FORMAT_TOOLTIP_SHORTCUT), std::make_format_args(tooltip, shortcut)).c_str());
}
namespace
{
struct CheckerStart
{
float position{};
long long index{};
};
CheckerStart checker_start(float minCoord, float offset, float step)
{
float world = minCoord + offset;
long long idx = static_cast<long long>(std::floor(world / step));
float first = minCoord - (world - static_cast<float>(idx) * step);
return {first, idx};
}
}
void render_checker_background(ImDrawList* drawList, ImVec2 min, ImVec2 max, vec2 offset, float step)
{
if (!drawList || step <= 0.0f) return;
const ImU32 colorLight = IM_COL32(204, 204, 204, 255);
const ImU32 colorDark = IM_COL32(128, 128, 128, 255);
auto [startY, rowIndex] = checker_start(min.y, offset.y, step);
for (float y = startY; y < max.y; y += step, ++rowIndex)
{
float y1 = glm::max(y, min.y);
float y2 = glm::min(y + step, max.y);
if (y2 <= y1) continue;
auto [startX, columnIndex] = checker_start(min.x, offset.x, step);
for (float x = startX; x < max.x; x += step, ++columnIndex)
{
float x1 = glm::max(x, min.x);
float x2 = glm::min(x + step, max.x);
if (x2 <= x1) continue;
bool isDark = ((rowIndex + columnIndex) & 1LL) != 0;
drawList->AddRectFilled(ImVec2(x1, y1), ImVec2(x2, y2), isDark ? colorDark : colorLight);
}
}
}
void external_storage_set(ImGuiSelectionExternalStorage* self, int id, bool isSelected)
{
auto* storage = static_cast<MultiSelectStorage*>(self->UserData);
auto value = storage ? storage->resolve_index(id) : id;
if (isSelected)
storage->insert(value);
else
storage->erase(value);
};
std::string chord_to_string(ImGuiKeyChord chord)
{
std::string result;
if (chord & ImGuiMod_Ctrl) result += "Ctrl+";
if (chord & ImGuiMod_Shift) result += "Shift+";
if (chord & ImGuiMod_Alt) result += "Alt+";
if (chord & ImGuiMod_Super) result += "Super+";
if (auto key = (ImGuiKey)(chord & ~ImGuiMod_Mask_); key != ImGuiKey_None)
{
if (const char* name = canonical_key_name(key); name && *name)
result += name;
else
result += ImGui::GetKeyName(key);
}
if (!result.empty() && result.back() == '+') result.pop_back();
return result;
}
ImGuiKeyChord string_to_chord(const std::string& string)
{
ImGuiKeyChord chord = 0;
ImGuiKey baseKey = ImGuiKey_None;
std::stringstream ss(string);
std::string token;
while (std::getline(ss, token, '+'))
{
token.erase(0, token.find_first_not_of(" \t\r\n"));
token.erase(token.find_last_not_of(" \t\r\n") + 1);
if (token.empty()) continue;
if (auto it = MOD_MAP.find(token); it != MOD_MAP.end())
chord |= it->second;
else if (baseKey == ImGuiKey_None)
if (auto it2 = KEY_MAP.find(token); it2 != KEY_MAP.end()) baseKey = it2->second;
}
if (baseKey != ImGuiKey_None) chord |= baseKey;
return chord;
}
float row_widget_width_get(int count, float width)
{
return (width - (ImGui::GetStyle().ItemSpacing.x * (float)(count - 1))) / (float)count;
}
ImVec2 widget_size_with_row_get(int count, float width) { return ImVec2(row_widget_width_get(count, width), 0); }
float footer_height_get(int itemCount)
{
return ImGui::GetTextLineHeightWithSpacing() * itemCount + ImGui::GetStyle().WindowPadding.y +
ImGui::GetStyle().ItemSpacing.y * (itemCount);
}
ImVec2 footer_size_get(int itemCount)
{
return ImVec2(ImGui::GetContentRegionAvail().x, footer_height_get(itemCount));
}
ImVec2 size_without_footer_get(int rowCount)
{
return ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y - footer_height_get(rowCount));
}
ImVec2 child_size_get(int rowCount)
{
return ImVec2(ImGui::GetContentRegionAvail().x,
(ImGui::GetFrameHeightWithSpacing() * rowCount) + (ImGui::GetStyle().WindowPadding.y * 2.0f));
}
ImVec2 icon_size_get()
{
return ImVec2(ImGui::GetTextLineHeightWithSpacing(), ImGui::GetTextLineHeightWithSpacing());
}
bool shortcut(ImGuiKeyChord chord, shortcut::Type type)
{
if (chord == ImGuiKey_None) return false;
if (ImGui::GetTopMostPopupModal() != nullptr &&
(type == shortcut::GLOBAL || type == shortcut::GLOBAL_SET))
return false;
int flags = type == shortcut::GLOBAL || type == shortcut::GLOBAL_SET ? ImGuiInputFlags_RouteGlobal
: ImGuiInputFlags_RouteFocused;
flags |= ImGuiInputFlags_Repeat;
if (type == shortcut::GLOBAL_SET || type == shortcut::FOCUSED_SET)
{
ImGui::SetNextItemShortcut(chord, flags);
return false;
}
return ImGui::Shortcut(chord, flags);
}
MultiSelectStorage::MultiSelectStorage() { internal.AdapterSetItemSelected = external_storage_set; }
void MultiSelectStorage::start(size_t size, ImGuiMultiSelectFlags flags)
{
internal.UserData = this;
io = ImGui::BeginMultiSelect(flags, this->size(), size);
apply();
}
void MultiSelectStorage::apply() { internal.ApplyRequests(io); }
void MultiSelectStorage::finish()
{
io = ImGui::EndMultiSelect();
apply();
}
void MultiSelectStorage::set_index_map(std::vector<int>* map) { indexMap = map; }
int MultiSelectStorage::resolve_index(int index) const
{
if (!indexMap) return index;
if (index < 0 || index >= (int)indexMap->size()) return index;
return (*indexMap)[index];
}
PopupHelper::PopupHelper(StringType labelId, PopupType type, PopupPosition position)
{
this->labelId = labelId;
this->type = type;
this->position = position;
}
void PopupHelper::open()
{
isOpen = true;
isTriggered = true;
isJustOpened = true;
}
bool PopupHelper::is_open() { return isOpen; }
void PopupHelper::trigger()
{
if (isTriggered) ImGui::OpenPopup(localize.get(labelId));
isTriggered = false;
auto viewport = ImGui::GetMainViewport();
switch (position)
{
case POPUP_CENTER:
ImGui::SetNextWindowPos(viewport->GetCenter(), ImGuiCond_None, to_imvec2(vec2(0.5f)));
if (POPUP_IS_HEIGHT_SET[type])
ImGui::SetNextWindowSize(to_imvec2(to_vec2(viewport->Size) * POPUP_MULTIPLIERS[type]));
else
ImGui::SetNextWindowSize(ImVec2(viewport->Size.x * POPUP_MULTIPLIERS[type], 0));
break;
case POPUP_BY_ITEM:
ImGui::SetNextWindowPos(ImGui::GetItemRectMin(), ImGuiCond_None);
case POPUP_BY_CURSOR:
default:
break;
}
}
void PopupHelper::end() { isJustOpened = false; }
void PopupHelper::close() { isOpen = false; }
}
-271
View File
@@ -1,271 +0,0 @@
#pragma once
#include <filesystem>
#include <glm/glm.hpp>
#include <imgui/imgui.h>
#include <set>
#include <string>
#include <unordered_map>
#include <vector>
#include "strings.hpp"
#include "types.hpp"
namespace anm2ed::imgui
{
constexpr auto DRAG_SPEED = 0.25f;
constexpr auto DRAG_SPEED_FAST = 1.00f;
constexpr auto STEP = 1.0f;
constexpr auto STEP_FAST = 5.0f;
#define POPUP_LIST \
X(POPUP_SMALL, 0.25f, true) \
X(POPUP_NORMAL, 0.5f, true) \
X(POPUP_TO_CONTENT, 0.0f, true) \
X(POPUP_SMALL_NO_HEIGHT, 0.25f, false) \
X(POPUP_NORMAL_NO_HEIGHT, 0.5f, false)
enum PopupType
{
#define X(name, multiplier, isHeightSet) name,
POPUP_LIST
#undef X
};
enum PopupPosition
{
POPUP_CENTER,
POPUP_BY_ITEM,
POPUP_BY_CURSOR
};
enum RenameState
{
RENAME_SELECTABLE,
RENAME_BEGIN,
RENAME_EDITING,
RENAME_FINISHED,
RENAME_FORCE_EDIT
};
constexpr float POPUP_MULTIPLIERS[] = {
#define X(name, multiplier, isHeightSet) multiplier,
POPUP_LIST
#undef X
};
constexpr bool POPUP_IS_HEIGHT_SET[] = {
#define X(name, multiplier, isHeightSet) isHeightSet,
POPUP_LIST
#undef X
};
const std::unordered_map<std::string, ImGuiKey> KEY_MAP = {
{"A", ImGuiKey_A},
{"B", ImGuiKey_B},
{"C", ImGuiKey_C},
{"D", ImGuiKey_D},
{"E", ImGuiKey_E},
{"F", ImGuiKey_F},
{"G", ImGuiKey_G},
{"H", ImGuiKey_H},
{"I", ImGuiKey_I},
{"J", ImGuiKey_J},
{"K", ImGuiKey_K},
{"L", ImGuiKey_L},
{"M", ImGuiKey_M},
{"N", ImGuiKey_N},
{"O", ImGuiKey_O},
{"P", ImGuiKey_P},
{"Q", ImGuiKey_Q},
{"R", ImGuiKey_R},
{"S", ImGuiKey_S},
{"T", ImGuiKey_T},
{"U", ImGuiKey_U},
{"V", ImGuiKey_V},
{"W", ImGuiKey_W},
{"X", ImGuiKey_X},
{"Y", ImGuiKey_Y},
{"Z", ImGuiKey_Z},
{"0", ImGuiKey_0},
{"1", ImGuiKey_1},
{"2", ImGuiKey_2},
{"3", ImGuiKey_3},
{"4", ImGuiKey_4},
{"5", ImGuiKey_5},
{"6", ImGuiKey_6},
{"7", ImGuiKey_7},
{"8", ImGuiKey_8},
{"9", ImGuiKey_9},
{"Num0", ImGuiKey_Keypad0},
{"Num1", ImGuiKey_Keypad1},
{"Num2", ImGuiKey_Keypad2},
{"Num3", ImGuiKey_Keypad3},
{"Num4", ImGuiKey_Keypad4},
{"Num5", ImGuiKey_Keypad5},
{"Num6", ImGuiKey_Keypad6},
{"Num7", ImGuiKey_Keypad7},
{"Num8", ImGuiKey_Keypad8},
{"Num9", ImGuiKey_Keypad9},
{"NumAdd", ImGuiKey_KeypadAdd},
{"NumSubtract", ImGuiKey_KeypadSubtract},
{"NumMultiply", ImGuiKey_KeypadMultiply},
{"NumDivide", ImGuiKey_KeypadDivide},
{"NumEnter", ImGuiKey_KeypadEnter},
{"NumDecimal", ImGuiKey_KeypadDecimal},
{"NumEqual", ImGuiKey_KeypadEqual},
{"F1", ImGuiKey_F1},
{"F2", ImGuiKey_F2},
{"F3", ImGuiKey_F3},
{"F4", ImGuiKey_F4},
{"F5", ImGuiKey_F5},
{"F6", ImGuiKey_F6},
{"F7", ImGuiKey_F7},
{"F8", ImGuiKey_F8},
{"F9", ImGuiKey_F9},
{"F10", ImGuiKey_F10},
{"F11", ImGuiKey_F11},
{"F12", ImGuiKey_F12},
{"Up", ImGuiKey_UpArrow},
{"Down", ImGuiKey_DownArrow},
{"Left", ImGuiKey_LeftArrow},
{"Right", ImGuiKey_RightArrow},
{"Space", ImGuiKey_Space},
{"Enter", ImGuiKey_Enter},
{"Escape", ImGuiKey_Escape},
{"Tab", ImGuiKey_Tab},
{"Backspace", ImGuiKey_Backspace},
{"Delete", ImGuiKey_Delete},
{"Insert", ImGuiKey_Insert},
{"Home", ImGuiKey_Home},
{"End", ImGuiKey_End},
{"PageUp", ImGuiKey_PageUp},
{"PageDown", ImGuiKey_PageDown},
{"Minus", ImGuiKey_Minus},
{"Equal", ImGuiKey_Equal},
{"LeftBracket", ImGuiKey_LeftBracket},
{"RightBracket", ImGuiKey_RightBracket},
{"Semicolon", ImGuiKey_Semicolon},
{"Apostrophe", ImGuiKey_Apostrophe},
{"Comma", ImGuiKey_Comma},
{"Period", ImGuiKey_Period},
{"Slash", ImGuiKey_Slash},
{"Backslash", ImGuiKey_Backslash},
{"GraveAccent", ImGuiKey_GraveAccent},
// Legacy aliases for older saved shortcut strings.
{"Keypad0", ImGuiKey_Keypad0},
{"Keypad1", ImGuiKey_Keypad1},
{"Keypad2", ImGuiKey_Keypad2},
{"Keypad3", ImGuiKey_Keypad3},
{"Keypad4", ImGuiKey_Keypad4},
{"Keypad5", ImGuiKey_Keypad5},
{"Keypad6", ImGuiKey_Keypad6},
{"Keypad7", ImGuiKey_Keypad7},
{"Keypad8", ImGuiKey_Keypad8},
{"Keypad9", ImGuiKey_Keypad9},
{"KeypadAdd", ImGuiKey_KeypadAdd},
{"KeypadSubtract", ImGuiKey_KeypadSubtract},
{"KeypadMultiply", ImGuiKey_KeypadMultiply},
{"KeypadDivide", ImGuiKey_KeypadDivide},
{"KeypadEnter", ImGuiKey_KeypadEnter},
{"KeypadDecimal", ImGuiKey_KeypadDecimal},
{"KeypadEqual", ImGuiKey_KeypadEqual},
{"UpArrow", ImGuiKey_UpArrow},
{"DownArrow", ImGuiKey_DownArrow},
{"LeftArrow", ImGuiKey_LeftArrow},
{"RightArrow", ImGuiKey_RightArrow},
};
const std::unordered_map<std::string, ImGuiKey> MOD_MAP = {
{"Ctrl", ImGuiMod_Ctrl},
{"Shift", ImGuiMod_Shift},
{"Alt", ImGuiMod_Alt},
{"Super", ImGuiMod_Super},
};
void theme_set(types::theme::Type theme);
std::string chord_to_string(ImGuiKeyChord);
ImGuiKeyChord string_to_chord(const std::string&);
float row_widget_width_get(int, float = ImGui::GetContentRegionAvail().x);
ImVec2 widget_size_with_row_get(int, float = ImGui::GetContentRegionAvail().x);
float footer_height_get(int = 1);
ImVec2 footer_size_get(int = 1);
ImVec2 size_without_footer_get(int = 1);
ImVec2 child_size_get(int = 1);
int input_text_callback(ImGuiInputTextCallbackData*);
bool input_text_string(const char*, std::string*, ImGuiInputTextFlags = 0);
bool input_text_path(const char*, std::filesystem::path*, ImGuiInputTextFlags = 0);
bool input_int_range(const char*, int&, int, int, int = STEP, int = STEP_FAST, ImGuiInputTextFlags = 0);
bool input_int2_range(const char*, glm::ivec2&, glm::ivec2, glm::ivec2, ImGuiInputTextFlags = 0);
bool input_float_range(const char*, float&, float, float, float = STEP, float = STEP_FAST, const char* = "%.3f",
ImGuiInputTextFlags = 0);
types::edit::Type drag_int_persistent(const char*, int*, float = DRAG_SPEED, int = {}, int = {}, const char* = "%d",
ImGuiSliderFlags = 0);
types::edit::Type drag_float_persistent(const char*, float*, float = DRAG_SPEED, float = {}, float = {},
const char* = "%.3f", ImGuiSliderFlags = 0);
types::edit::Type drag_float2_persistent(const char*, glm::vec2*, float = DRAG_SPEED, float = {}, float = {},
const char* = "%.3f", ImGuiSliderFlags = 0);
types::edit::Type color_edit3_persistent(const char*, glm::vec3*, ImGuiColorEditFlags = 0);
types::edit::Type color_edit4_persistent(const char*, glm::vec4*, ImGuiColorEditFlags = 0);
bool combo_negative_one_indexed(const std::string&, int*, std::vector<const char*>&);
bool combo_id_mapped(const std::string&, int*, const std::vector<int>&, std::vector<const char*>&);
std::string& selectable_input_text_id();
bool selectable_input_text(const std::string& label, const std::string& id, std::string& text, bool isSelected,
ImGuiSelectableFlags flags, RenameState& state);
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);
ImVec2 icon_size_get();
bool shortcut(ImGuiKeyChord, types::shortcut::Type = types::shortcut::FOCUSED_SET);
class MultiSelectStorage : public std::set<int>
{
public:
ImGuiSelectionExternalStorage internal{};
ImGuiMultiSelectIO* io{};
std::vector<int>* indexMap{};
using std::set<int>::set;
using std::set<int>::operator=;
using std::set<int>::begin;
using std::set<int>::rbegin;
using std::set<int>::end;
using std::set<int>::size;
using std::set<int>::insert;
using std::set<int>::erase;
MultiSelectStorage();
void start(size_t,
ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_ScopeWindow);
void apply();
void finish();
void set_index_map(std::vector<int>*);
int resolve_index(int) const;
};
class PopupHelper
{
public:
StringType labelId{};
PopupType type{};
PopupPosition position{};
bool isOpen{};
bool isTriggered{};
bool isJustOpened{};
PopupHelper(StringType, PopupType = POPUP_NORMAL, PopupPosition = POPUP_CENTER);
const char* label() const { return localize.get(labelId); }
bool is_open();
void open();
void trigger();
void end();
void close();
};
}
@@ -2,7 +2,8 @@
#include <format>
#include "path_.hpp"
#include "path.hpp"
#include "util/imgui/imgui.hpp"
using namespace anm2ed::resource;
using namespace anm2ed::util;
+50 -35
View File
@@ -2,7 +2,7 @@
#include <format>
#include "imgui_.hpp"
#include "util/imgui/imgui.hpp"
using namespace anm2ed::resource;
using namespace anm2ed::types;
@@ -16,11 +16,11 @@ namespace anm2ed::imgui::popup
addItemSpritesheetID = {};
}
std::set<int> ItemProperties::unused_items_get(anm2::Anm2& anm2, anm2::Animation* animation, anm2::Type type)
std::set<int> ItemProperties::unused_items_get(Anm2& anm2, const Element* animation, int type)
{
if (!animation) return {};
if (type == anm2::LAYER) return anm2.layers_unused(*animation);
if (type == anm2::NULL_) return anm2.nulls_unused(*animation);
if (type == LAYER) return anm2.element_unused(ElementType::LAYER_ELEMENT, *animation);
if (type == NULL_) return anm2.element_unused(ElementType::NULL_ELEMENT, *animation);
return {};
}
@@ -30,11 +30,11 @@ namespace anm2ed::imgui::popup
popup.open();
}
void ItemProperties::update(Manager& manager, Settings& settings, Document& document, anm2::Animation* animation,
anm2::Reference& reference,
const std::function<void(anm2::Type, int)>& referenceSetItem)
void ItemProperties::update(Manager& manager, Settings& settings, Document& document, Reference& reference,
const std::function<void(int, int)>& referenceSetItem)
{
auto& anm2 = document.anm2;
auto animation = anm2.element_get(ElementType::ANIMATION, reference.animationIndex);
popup.trigger();
@@ -70,18 +70,18 @@ namespace anm2ed::imgui::popup
spaced_pair(
[&]()
{
ImGui::RadioButton(localize.get(LABEL_LAYER), &type, anm2::LAYER);
ImGui::RadioButton(localize.get(LABEL_LAYER), &type, LAYER);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_LAYER_TYPE));
},
[&]()
{
ImGui::RadioButton(localize.get(LABEL_NULL), &type, anm2::NULL_);
ImGui::RadioButton(localize.get(LABEL_NULL), &type, NULL_);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_NULL_TYPE));
});
ImGui::SeparatorText(localize.get(LABEL_SOURCE));
auto isUnusedItems = animation && !unused_items_get(anm2, animation, (anm2::Type)type).empty();
auto isUnusedItems = animation && !unused_items_get(anm2, animation, (int)type).empty();
spaced_pair(
[&]()
{
@@ -117,13 +117,13 @@ namespace anm2ed::imgui::popup
input_text_string(localize.get(BASIC_NAME), &addItemName);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_ITEM_NAME));
if (type == anm2::LAYER)
if (type == LAYER)
{
combo_id_mapped(localize.get(LABEL_SPRITESHEET), &addItemSpritesheetID, document.spritesheet.ids,
document.spritesheet.labels);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_LAYER_SPRITESHEET));
}
else if (type == anm2::NULL_)
else if (type == NULL_)
{
ImGui::Checkbox(localize.get(LABEL_RECT), &addItemIsShowRect);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_NULL_RECT));
@@ -131,11 +131,11 @@ namespace anm2ed::imgui::popup
}
else if (animation)
{
ImGui::SeparatorText(localize.get(type == anm2::LAYER ? LABEL_LAYER : LABEL_NULL));
ImGui::SeparatorText(localize.get(type == LAYER ? LABEL_LAYER : LABEL_NULL));
if (ImGui::BeginChild("##Existing Items", ImVec2(0, 0)))
{
auto unusedItems = unused_items_get(anm2, animation, (anm2::Type)type);
auto unusedItems = unused_items_get(anm2, animation, (int)type);
if (addItemID != -1 && !unusedItems.contains(addItemID)) addItemID = -1;
for (auto id : unusedItems)
@@ -144,30 +144,30 @@ namespace anm2ed::imgui::popup
ImGui::PushID(id);
if (type == anm2::LAYER)
if (type == LAYER)
{
if (auto it = anm2.content.layers.find(id); it != anm2.content.layers.end())
if (auto layer = anm2.element_get(ElementType::LAYER_ELEMENT, id))
{
auto& layer = it->second;
auto label = std::vformat(localize.get(FORMAT_LAYER),
std::make_format_args(id, layer.name, layer.spritesheetID));
std::make_format_args(id, layer->name, layer->spritesheetId));
if (ImGui::Selectable(label.c_str(), isSelected))
{
addItemID = id;
addItemSpritesheetID = layer.spritesheetID;
addItemSpritesheetID = layer->spritesheetId;
}
}
}
else if (type == anm2::NULL_)
else if (type == NULL_)
{
if (auto it = anm2.content.nulls.find(id); it != anm2.content.nulls.end())
auto nulls = anm2.element_get(ElementType::NULLS);
auto null = nulls ? element_child_id_get(*nulls, ElementType::NULL_ELEMENT, id) : nullptr;
if (null)
{
auto& null = it->second;
auto label = std::vformat(localize.get(FORMAT_NULL), std::make_format_args(id, null.name));
auto label = std::vformat(localize.get(FORMAT_NULL), std::make_format_args(id, null->name));
if (ImGui::Selectable(label.c_str(), isSelected))
{
addItemID = id;
addItemIsShowRect = null.isShowRect;
addItemIsShowRect = null->isShowRect;
}
}
}
@@ -186,20 +186,35 @@ namespace anm2ed::imgui::popup
shortcut(manager.chords[SHORTCUT_CONFIRM]);
if (ImGui::Button(localize.get(BASIC_ADD), widgetSize))
{
anm2::Reference addReference{};
int insertBeforeID = reference.itemType == anm2::LAYER ? reference.itemID : -1;
auto queuedType = type;
auto queuedDestination = destination;
auto queuedAnimationIndex = reference.animationIndex;
auto queuedInsertBeforeID = reference.itemType == LAYER ? reference.itemID : -1;
auto queuedAddItemID = addItemID;
auto queuedAddItemName = addItemName;
auto queuedAddItemSpritesheetID = addItemSpritesheetID;
auto queuedAddItemIsShowRect = addItemIsShowRect;
auto queuedReferenceSetItem = referenceSetItem;
document.snapshot(localize.get(EDIT_ADD_ITEM));
if (type == anm2::LAYER)
addReference = anm2.layer_animation_add({reference.animationIndex, anm2::LAYER, addItemID}, insertBeforeID,
addItemName, addItemSpritesheetID, (destination::Type)destination);
else if (type == anm2::NULL_)
addReference = anm2.null_animation_add({reference.animationIndex, anm2::NULL_, addItemID}, addItemName,
addItemIsShowRect, (destination::Type)destination);
manager.command_push({manager.selected,
[=](Manager&, Document& document)
{
int addId{-1};
document.change(Document::ITEMS);
document.snapshot(localize.get(EDIT_ADD_ITEM));
if (queuedType == LAYER)
addId = document.anm2.layer_animation_add(
queuedAnimationIndex, queuedAddItemID, queuedInsertBeforeID, queuedAddItemName,
queuedAddItemSpritesheetID, (destination::Type)queuedDestination);
else if (queuedType == NULL_)
addId = document.anm2.null_animation_add(queuedAnimationIndex, queuedAddItemID,
queuedAddItemName, queuedAddItemIsShowRect,
(destination::Type)queuedDestination);
referenceSetItem(addReference.itemType, addReference.itemID);
document.anm2_change(Document::ITEMS);
if (addId != -1) queuedReferenceSetItem((int)queuedType, addId);
}});
close();
}
+2 -3
View File
@@ -20,11 +20,10 @@ namespace anm2ed::imgui::popup
int addItemSpritesheetID{-1};
void reset();
std::set<int> unused_items_get(anm2::Anm2&, anm2::Animation*, anm2::Type);
std::set<int> unused_items_get(Anm2&, const Element*, int);
public:
void open();
void update(Manager&, Settings&, Document&, anm2::Animation*, anm2::Reference&,
const std::function<void(anm2::Type, int)>&);
void update(Manager&, Settings&, Document&, Reference&, const std::function<void(int, int)>&);
};
}
+84 -41
View File
@@ -9,7 +9,7 @@
#include "document.hpp"
#include "log.hpp"
#include "path_.hpp"
#include "path.hpp"
#include "strings.hpp"
#include "toast.hpp"
#include "types.hpp"
@@ -25,35 +25,54 @@ namespace anm2ed::imgui
{
auto* document = manager.get(index);
return document && settings.fileIsSpecialInterpolatedFramesOnSaveReminder &&
document->anm2.has_special_interpolated_frames();
document->anm2.is_special_interpolated_frames();
}
void Taskbar::save_execute(Manager& manager, Settings& settings, const PendingSave& request, bool bakeFrames)
bool Taskbar::save_execute(Manager& manager, Settings& settings, const PendingSave& request, bool bakeFrames)
{
manager.save(request.index, request.path, (anm2::Compatibility)settings.fileCompatibility, bakeFrames,
settings.bakeIsRoundScale, settings.bakeIsRoundRotation);
return manager.save(request.index, request.path, (Compatibility)settings.fileCompatibility, bakeFrames,
settings.bakeIsRoundScale, settings.bakeIsRoundRotation);
}
bool Taskbar::save_request(Manager& manager, Settings& settings, int index, const std::filesystem::path& path)
void Taskbar::save_enqueue(Manager& manager, Settings& settings, const PendingSave& request, bool bakeFrames)
{
auto index = request.index;
auto path = request.path;
auto compatibility = (Compatibility)settings.fileCompatibility;
auto isRoundScale = settings.bakeIsRoundScale;
auto isRoundRotation = settings.bakeIsRoundRotation;
manager.command_push({.runManager =
[=](Manager& manager)
{ manager.save(index, path, compatibility, bakeFrames, isRoundScale, isRoundRotation); }});
}
bool Taskbar::save_request(Manager& manager, Settings& settings, int index, const std::filesystem::path& path,
bool isQueued)
{
auto* document = manager.get(index);
if (!document) return false;
if (settings.fileIsSpecialInterpolatedFramesOnSaveReminder && document->anm2.has_special_interpolated_frames())
if (settings.fileIsSpecialInterpolatedFramesOnSaveReminder && document->anm2.is_special_interpolated_frames())
{
pendingSave = {.index = index,
.path = path,
.isOpen = true,
.disableReminder = false,
.autoBakeFrames = settings.fileBakeSpecialInterpolatedFramesOnSave};
.autoBakeFrames = settings.fileBakeSpecialInterpolatedFramesOnSave,
.isQueued = isQueued};
specialInterpolatedFramesReminderPopup.open();
return false;
}
PendingSave request{.index = index, .path = path};
auto bakeFrames = settings.fileBakeSpecialInterpolatedFramesOnSave;
save_execute(manager, settings, request, bakeFrames);
return true;
if (isQueued)
{
save_enqueue(manager, settings, request, bakeFrames);
return true;
}
return save_execute(manager, settings, request, bakeFrames);
}
bool Taskbar::save_manual(Manager& manager, Settings& settings, int index, const std::filesystem::path& path)
@@ -64,19 +83,25 @@ namespace anm2ed::imgui
void Taskbar::update(Manager& manager, Settings& settings, Resources& resources, Dialog& dialog, bool& isQuitting)
{
auto document = manager.get();
auto animation = document ? document->animation_get() : nullptr;
auto item = document ? document->item_get() : nullptr;
auto itemType = document ? (ItemType)document->reference.itemType : ItemType::NONE;
auto animation =
document ? document->anm2.element_get(ElementType::ANIMATION, document->reference.animationIndex) : nullptr;
auto item =
document ? document->anm2.element_get(document->reference.animationIndex, itemType, document->reference.itemID)
: nullptr;
auto frames = document ? &document->frames : nullptr;
bool hasRegions = false;
if (document)
{
for (auto& spritesheet : document->anm2.content.spritesheets | std::views::values)
if (auto spritesheets = document->anm2.element_get(ElementType::SPRITESHEETS))
{
if (!spritesheet.regions.empty())
{
hasRegions = true;
break;
}
for (auto& spritesheet : spritesheets->children)
for (auto& child : spritesheet.children)
if (spritesheet.type == ElementType::SPRITESHEET && child.type == ElementType::REGION)
{
hasRegions = true;
break;
}
}
}
@@ -86,9 +111,10 @@ namespace anm2ed::imgui
if (ImGui::BeginMenu(localize.get(LABEL_FILE_MENU)))
{
if (ImGui::MenuItem(localize.get(BASIC_NEW), settings.shortcutNew.c_str())) dialog.file_save(Dialog::ANM2_NEW);
if (ImGui::MenuItem(localize.get(BASIC_NEW), settings.shortcutNew.c_str()))
dialog.file_save(Dialog::ANM2_CREATE);
if (ImGui::MenuItem(localize.get(BASIC_OPEN), settings.shortcutOpen.c_str()))
dialog.file_open(Dialog::ANM2_OPEN);
dialog.file_open(Dialog::ANM2_OPEN, true);
auto recentFiles = manager.recent_files_ordered();
if (ImGui::BeginMenu(localize.get(LABEL_OPEN_RECENT), !recentFiles.empty()))
@@ -99,7 +125,8 @@ namespace anm2ed::imgui
auto fileNameUtf8 = path::to_utf8(file.filename());
auto filePathUtf8 = path::to_utf8(file);
auto label = std::format(FILE_LABEL_FORMAT, fileNameUtf8, filePathUtf8);
if (ImGui::MenuItem(label.c_str())) manager.open(file);
if (ImGui::MenuItem(label.c_str()))
manager.command_push({.runManager = [file](Manager& manager) { manager.open(file); }});
ImGui::PopID();
}
@@ -112,11 +139,11 @@ namespace anm2ed::imgui
if (ImGui::MenuItem(localize.get(BASIC_SAVE), settings.shortcutSave.c_str(), false, document))
{
if (save_requires_special_prompt(manager, settings, manager.selected))
save_request(manager, settings, manager.selected, document->path);
save_request(manager, settings, manager.selected, document->path, true);
else if (settings.fileIsWarnOverwrite)
overwritePopup.open();
else
save_request(manager, settings, manager.selected, document->path);
save_request(manager, settings, manager.selected, document->path, true);
}
if (ImGui::MenuItem(localize.get(LABEL_SAVE_AS), settings.shortcutSaveAs.c_str(), false, document))
@@ -128,33 +155,38 @@ namespace anm2ed::imgui
if (ImGui::MenuItem(localize.get(LABEL_EXIT), settings.shortcutExit.c_str())) isQuitting = true;
ImGui::EndMenu();
}
if (dialog.is_selected(Dialog::ANM2_NEW))
if (dialog.is_selected(Dialog::ANM2_CREATE))
{
manager.new_(dialog.path);
auto path = dialog.path;
manager.command_push({.runManager = [path](Manager& manager) { manager.new_(path); }});
dialog.reset();
}
if (dialog.is_selected(Dialog::ANM2_OPEN))
{
manager.open(dialog.path);
auto paths = dialog.paths;
manager.command_push({.runManager =
[paths](Manager& manager)
{
for (auto& path : paths) manager.open(path);
}});
dialog.reset();
}
if (dialog.is_selected(Dialog::ANM2_SAVE))
{
save_request(manager, settings, manager.selected, dialog.path);
save_request(manager, settings, manager.selected, dialog.path, true);
dialog.reset();
}
if (ImGui::BeginMenu(localize.get(LABEL_WIZARD_MENU)))
{
if (ImGui::MenuItem(localize.get(LABEL_TASKBAR_GENERATE_ANIMATION_FROM_GRID), nullptr, false,
item && document->reference.itemType == anm2::LAYER))
item && itemType == ItemType::LAYER))
generatePopup.open();
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_WIZARD_GENERATE_ANIMATION_FROM_GRID));
bool isChangeAllFramesAvailable =
frames && !frames->selection.empty() && document->reference.itemType != anm2::TRIGGER;
bool isChangeAllFramesAvailable = frames && !frames->selection.empty() && itemType != ItemType::TRIGGER;
bool isChangeAllAnimationsAvailable = document && !document->animation.selection.empty();
if (ImGui::MenuItem(localize.get(LABEL_CHANGE_ALL_FRAME_PROPERTIES), nullptr, false,
isChangeAllFramesAvailable || isChangeAllAnimationsAvailable))
@@ -163,8 +195,13 @@ namespace anm2ed::imgui
if (ImGui::MenuItem(localize.get(LABEL_SCAN_AND_SET_REGIONS), nullptr, false, document && hasRegions))
{
DOCUMENT_EDIT_PTR(document, localize.get(EDIT_SCAN_AND_SET_REGIONS), Document::FRAMES,
document->anm2.scan_and_set_regions());
manager.command_push({manager.selected,
[](Manager&, Document& document)
{
document.snapshot(localize.get(EDIT_SCAN_AND_SET_REGIONS));
document.scan_and_set_regions();
document.change(Document::FRAMES);
}});
toasts.push(localize.get(TOAST_SCAN_AND_SET_REGIONS));
logger.info(localize.get(TOAST_SCAN_AND_SET_REGIONS, anm2ed::ENGLISH));
}
@@ -223,7 +260,7 @@ namespace anm2ed::imgui
{
if (document)
{
generateAnimationFromGrid.update(*document, resources, settings);
generateAnimationFromGrid.update(manager, *document, resources, settings);
if (generateAnimationFromGrid.isEnd) generatePopup.close();
}
ImGui::EndPopup();
@@ -235,7 +272,7 @@ namespace anm2ed::imgui
{
if (document)
{
changeAllFrameProperties.update(*document, settings, true);
changeAllFrameProperties.update(manager, *document, settings, true);
if (changeAllFrameProperties.isChanged) changePopup.close();
}
ImGui::EndPopup();
@@ -285,7 +322,7 @@ namespace anm2ed::imgui
if (ImGui::Button(localize.get(BASIC_YES), widgetSize))
{
save_request(manager, settings);
save_request(manager, settings, manager.selected, {}, true);
overwritePopup.close();
}
@@ -312,7 +349,10 @@ namespace anm2ed::imgui
{
if (pendingSave.disableReminder) settings.fileIsSpecialInterpolatedFramesOnSaveReminder = false;
settings.fileBakeSpecialInterpolatedFramesOnSave = pendingSave.autoBakeFrames;
save_execute(manager, settings, pendingSave, true);
if (pendingSave.isQueued)
save_enqueue(manager, settings, pendingSave, true);
else
save_execute(manager, settings, pendingSave, true);
pendingSave = {};
specialInterpolatedFramesReminderPopup.close();
}
@@ -322,7 +362,10 @@ namespace anm2ed::imgui
{
if (pendingSave.disableReminder) settings.fileIsSpecialInterpolatedFramesOnSaveReminder = false;
settings.fileBakeSpecialInterpolatedFramesOnSave = pendingSave.autoBakeFrames;
save_execute(manager, settings, pendingSave, false);
if (pendingSave.isQueued)
save_enqueue(manager, settings, pendingSave, false);
else
save_execute(manager, settings, pendingSave, false);
pendingSave = {};
specialInterpolatedFramesReminderPopup.close();
}
@@ -340,16 +383,16 @@ namespace anm2ed::imgui
aboutPopup.end();
if (shortcut(manager.chords[SHORTCUT_NEW], shortcut::GLOBAL)) dialog.file_save(Dialog::ANM2_NEW);
if (shortcut(manager.chords[SHORTCUT_OPEN], shortcut::GLOBAL)) dialog.file_open(Dialog::ANM2_OPEN);
if (shortcut(manager.chords[SHORTCUT_NEW], shortcut::GLOBAL)) dialog.file_save(Dialog::ANM2_CREATE);
if (shortcut(manager.chords[SHORTCUT_OPEN], shortcut::GLOBAL)) dialog.file_open(Dialog::ANM2_OPEN, true);
if (shortcut(manager.chords[SHORTCUT_SAVE], shortcut::GLOBAL))
{
if (save_requires_special_prompt(manager, settings))
save_request(manager, settings);
save_request(manager, settings, manager.selected, {}, true);
else if (settings.fileIsWarnOverwrite)
overwritePopup.open();
else
save_request(manager, settings);
save_request(manager, settings, manager.selected, {}, true);
}
if (shortcut(manager.chords[SHORTCUT_SAVE_AS], shortcut::GLOBAL)) dialog.file_save(Dialog::ANM2_SAVE);
if (shortcut(manager.chords[SHORTCUT_EXIT], shortcut::GLOBAL)) isQuitting = true;
+5 -3
View File
@@ -4,7 +4,7 @@
#include "canvas.hpp"
#include "dialog.hpp"
#include "imgui_.hpp"
#include "util/imgui/imgui.hpp"
#include "manager.hpp"
#include "resources.hpp"
#include "settings.hpp"
@@ -27,6 +27,7 @@ namespace anm2ed::imgui
bool isOpen{};
bool disableReminder{};
bool autoBakeFrames{};
bool isQueued{};
};
wizard::ChangeAllFrameProperties changeAllFrameProperties{};
@@ -49,8 +50,9 @@ namespace anm2ed::imgui
PendingSave pendingSave{};
bool save_requires_special_prompt(Manager&, Settings&, int = -1) const;
void save_execute(Manager&, Settings&, const PendingSave&, bool);
bool save_request(Manager&, Settings&, int = -1, const std::filesystem::path& = {});
bool save_execute(Manager&, Settings&, const PendingSave&, bool);
void save_enqueue(Manager&, Settings&, const PendingSave&, bool);
bool save_request(Manager&, Settings&, int = -1, const std::filesystem::path& = {}, bool = false);
public:
float height{};
+232 -182
View File
@@ -7,15 +7,15 @@
#include <format>
#include <map>
#include <optional>
#include <ranges>
#include <system_error>
#include <glm/gtc/type_ptr.hpp>
#include "imgui_.hpp"
#include "actions.hpp"
#include "util/imgui/imgui.hpp"
#include "log.hpp"
#include "math_.hpp"
#include "path_.hpp"
#include "math.hpp"
#include "path.hpp"
#include "strings.hpp"
#include "toast.hpp"
#include "tool.hpp"
@@ -103,7 +103,7 @@ namespace anm2ed::imgui
pixels[index + 2] = (uint8_t)glm::clamp((float)std::round((float)pixels[index + 2] / alphaUnit), 0.0f, 255.0f);
}
}
bool render_audio_stream_generate(AudioStream& audioStream, std::map<int, anm2::Sound>& sounds,
bool render_audio_stream_generate(AudioStream& audioStream, std::map<int, Audio>& sounds,
const std::vector<int>& frameSoundIDs, int fps)
{
audioStream.stream.clear();
@@ -122,7 +122,7 @@ namespace anm2ed::imgui
for (auto soundID : frameSoundIDs)
{
if (soundID != -1 && sounds.contains(soundID)) sounds.at(soundID).audio.play(false, mixer);
if (soundID != -1 && sounds.contains(soundID)) sounds.at(soundID).play(false, mixer);
sampleFrameAccumulator += framesPerStep;
auto sampleFramesToGenerate = (int)std::floor(sampleFrameAccumulator);
@@ -133,7 +133,7 @@ namespace anm2ed::imgui
if (!MIX_Generate(mixer, frameBuffer.data(), (int)(frameBuffer.size() * sizeof(float))))
{
for (auto& [_, sound] : sounds)
sound.audio.track_detach(mixer);
sound.track_detach(mixer);
MIX_DestroyMixer(mixer);
audioStream.stream.clear();
return false;
@@ -143,7 +143,7 @@ namespace anm2ed::imgui
}
for (auto& [_, sound] : sounds)
sound.audio.track_detach(mixer);
sound.track_detach(mixer);
MIX_DestroyMixer(mixer);
return true;
}
@@ -151,6 +151,8 @@ namespace anm2ed::imgui
AnimationPreview::AnimationPreview() : Canvas(vec2()) {}
bool AnimationPreview::is_focused_get() const { return isFocused; }
void AnimationPreview::tick(Manager& manager, Settings& settings, float deltaSeconds)
{
auto& document = *manager.get();
@@ -164,8 +166,24 @@ namespace anm2ed::imgui
auto stop_all_sounds = [&]()
{
for (auto& sound : anm2.content.sounds | std::views::values)
sound.audio.stop(mixer);
for (auto& [_, sound] : document.sounds)
sound.stop(mixer);
};
auto trigger_sound_id_get = [&](Element* animation, float time)
{
if (!animation) return -1;
auto triggers = animation_item_get(*animation, ItemType::TRIGGER);
if (!triggers || !triggers->isVisible) return -1;
auto trigger = frame_generate(*triggers, time);
if (!trigger.isVisible || trigger.soundIds.empty()) return -1;
auto soundIndex =
trigger.soundIds.size() > 1 ? (size_t)math::random_in_range(0.0f, (float)trigger.soundIds.size()) : (size_t)0;
soundIndex = std::min(soundIndex, trigger.soundIds.size() - 1);
auto soundID = trigger.soundIds[soundIndex];
return document.sound_get(soundID) ? soundID : -1;
};
if (manager.isRecording)
@@ -261,7 +279,7 @@ namespace anm2ed::imgui
{
if (settings.timelineIsSound && type != render::GIF)
{
if (!render_audio_stream_generate(audioStream, anm2.content.sounds, renderFrameSoundIDs, renderFrameRate))
if (!render_audio_stream_generate(audioStream, document.sounds, renderFrameSoundIDs, renderFrameRate))
{
toasts.push(localize.get(TOAST_EXPORT_RENDERED_ANIMATION_FAILED));
logger.error("Failed to generate deterministic render audio stream; exporting without audio.");
@@ -322,26 +340,13 @@ namespace anm2ed::imgui
{
if (settings.timelineIsSound && renderTempFrames.empty()) audioStream.capture_begin(mixer);
auto frameSoundID = -1;
if (settings.timelineIsSound && !anm2.content.sounds.empty())
if (settings.timelineIsSound && !document.sounds.empty())
{
auto soundTime = (int)std::floor(playback.time);
if (auto animation = document.animation_get(); soundTime != renderFrameSoundTimePrev && animation &&
animation->triggers.isVisible &&
(!settings.timelineIsOnlyShowLayers || manager.isRecording))
{
if (auto trigger = animation->triggers.frame_generate(playback.time, anm2::TRIGGER); trigger.isVisible)
{
if (!trigger.soundIDs.empty())
{
auto soundIndex = trigger.soundIDs.size() > 1
? (size_t)math::random_in_range(0.0f, (float)trigger.soundIDs.size())
: (size_t)0;
soundIndex = std::min(soundIndex, trigger.soundIDs.size() - 1);
auto soundID = trigger.soundIDs[soundIndex];
if (anm2.content.sounds.contains(soundID)) frameSoundID = soundID;
}
}
}
auto animation = anm2.element_get(ElementType::ANIMATION, document.reference.animationIndex);
if (soundTime != renderFrameSoundTimePrev && animation &&
(!settings.timelineIsOnlyShowLayers || manager.isRecording))
frameSoundID = trigger_sound_id_get(animation, playback.time);
renderFrameSoundTimePrev = soundTime;
}
renderFrameSoundIDs.push_back(frameSoundID);
@@ -376,7 +381,7 @@ namespace anm2ed::imgui
if (playback.isPlaying)
{
auto animation = document.animation_get();
auto animation = anm2.element_get(ElementType::ANIMATION, document.reference.animationIndex);
auto& isSound = settings.timelineIsSound;
auto& isOnlyShowLayers = settings.timelineIsOnlyShowLayers;
@@ -388,27 +393,15 @@ namespace anm2ed::imgui
}
else
{
if (!manager.isRecording && !anm2.content.sounds.empty() && isSound)
if (!manager.isRecording && !document.sounds.empty() && isSound)
{
if (animation->triggers.isVisible && (!isOnlyShowLayers || manager.isRecording))
{
if (auto trigger = animation->triggers.frame_generate(playback.time, anm2::TRIGGER); trigger.isVisible)
{
if (!trigger.soundIDs.empty())
{
auto soundIndex = trigger.soundIDs.size() > 1
? (size_t)math::random_in_range(0.0f, (float)trigger.soundIDs.size())
: (size_t)0;
soundIndex = std::min(soundIndex, trigger.soundIDs.size() - 1);
auto soundID = trigger.soundIDs[soundIndex];
if (anm2.content.sounds.contains(soundID)) anm2.content.sounds[soundID].audio.play(false, mixer);
}
}
}
if (!isOnlyShowLayers || manager.isRecording)
if (auto soundID = trigger_sound_id_get(animation, playback.time); soundID != -1)
if (auto sound = document.sound_get(soundID)) sound->play(false, mixer);
}
auto fps = std::max(anm2.info.fps, 1);
auto info = element_first_get(anm2.root, ElementType::INFO);
auto fps = std::max(info ? info->fps : 30, 1);
playback.tick(fps, animation->frameNum, (animation->isLoop || settings.playbackIsLoop) && !manager.isRecording,
deltaSeconds);
@@ -422,11 +415,13 @@ namespace anm2ed::imgui
void AnimationPreview::update(Manager& manager, Settings& settings, Resources& resources)
{
isFocused = false;
auto& document = *manager.get();
auto& anm2 = document.anm2;
auto& playback = document.playback;
auto& reference = document.reference;
auto animation = document.animation_get();
auto animation = anm2.element_get(ElementType::ANIMATION, reference.animationIndex);
auto& pan = document.previewPan;
auto& zoom = document.previewZoom;
auto& backgroundColor = settings.previewBackgroundColor;
@@ -502,6 +497,7 @@ namespace anm2ed::imgui
if (ImGui::Begin(localize.get(LABEL_ANIMATION_PREVIEW_WINDOW), &settings.windowIsAnimationPreview))
{
isFocused = ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows);
manager.isAbleToRecord = true;
auto childSize = ImVec2(row_widget_width_get(4),
@@ -636,11 +632,12 @@ namespace anm2ed::imgui
savedZoom = zoom;
savedPan = pan;
if (auto rect = anm2.animation_rect(*document.animation_get(), isRootTransform); rect != vec4(-1.0f))
{
size_set(vec2(rect.z, rect.w) * settings.renderScale);
set_to_rect(zoom, pan, rect);
}
if (animation)
if (auto rect = anm2.animation_rect(*animation, isRootTransform); rect != vec4(-1.0f))
{
size_set(vec2(rect.z, rect.w) * settings.renderScale);
set_to_rect(zoom, pan, rect);
}
isSizeTrySet = false;
}
@@ -738,43 +735,56 @@ namespace anm2ed::imgui
add_samples(settings.onionskinAfterCount, 1, settings.onionskinAfterColor);
}
auto render = [&](anm2::Animation* animation, float time, vec3 colorOffset = {}, float alphaOffset = {},
auto referenceItemType = static_cast<ItemType>(reference.itemType);
auto render = [&](Element* animation, float time, vec3 colorOffset = {}, float alphaOffset = {},
const std::vector<OnionskinSample>* layeredOnions = nullptr, bool isIndexMode = false)
{
auto sample_time_for_item = [&](anm2::Item& item, const OnionskinSample& sample) -> std::optional<float>
auto sample_time_for_item = [&](Element& item, const OnionskinSample& sample) -> std::optional<float>
{
if (!isIndexMode)
{
if (sample.time < 0.0f || sample.time > animation->frameNum) return std::nullopt;
return sample.time;
}
if (item.frames.empty()) return std::nullopt;
int baseIndex = item.frame_index_from_time_get(frameTime);
if (item.children.empty()) return std::nullopt;
int baseIndex = frame_index_from_time_get(item, frameTime);
if (baseIndex < 0) return std::nullopt;
int sampleIndex = baseIndex + sample.indexOffset;
if (sampleIndex < 0 || sampleIndex >= (int)item.frames.size()) return std::nullopt;
return item.frame_time_from_index_get(sampleIndex);
if (!track_frame_get(item, sampleIndex)) return std::nullopt;
return frame_time_from_index_get(item, sampleIndex);
};
auto transform_for_time = [&](anm2::Animation* anim, float t)
auto root = animation_item_get(*animation, ItemType::ROOT);
auto transform_for_time = [&](float t)
{
auto sampleTransform = baseTransform;
if (isRootTransform)
if (isRootTransform && root)
{
auto rootFrame = anim->rootAnimation.frame_generate(t, anm2::ROOT);
auto rootFrame = frame_generate(*root, t);
sampleTransform *= math::quad_model_parent_get(rootFrame.position, {},
math::percent_to_unit(rootFrame.scale), rootFrame.rotation);
}
return sampleTransform;
};
auto transform = transform_for_time(animation, time);
auto transform = transform_for_time(time);
auto is_track_group_visible = [](const Element& container, const Element& track)
{
if (track.groupId == -1) return true;
for (const auto& child : container.children)
if (child.type == ElementType::GROUP && child.id == track.groupId) return child.isVisible;
return true;
};
auto draw_root =
[&](float sampleTime, const glm::mat4& sampleTransform, vec3 sampleColor, float sampleAlpha, bool isOnion)
{
auto rootFrame = animation->rootAnimation.frame_generate(sampleTime, anm2::ROOT);
if (isOnlyShowLayers || !rootFrame.isVisible || !animation->rootAnimation.isVisible) return;
if (!root) return;
auto rootFrame = frame_generate(*root, sampleTime);
if (isOnlyShowLayers || !rootFrame.isVisible || !root->isVisible) return;
auto rootModel = isRootTransform
? math::quad_model_get(TARGET_SIZE, {}, TARGET_SIZE * 0.5f)
@@ -788,34 +798,45 @@ namespace anm2ed::imgui
texture_render(shaderTexture, resources.icons[icon].id, rootTransform, color);
};
if (layeredOnions)
if (layeredOnions && root)
for (auto& sample : *layeredOnions)
if (auto sampleTime = sample_time_for_item(animation->rootAnimation, sample))
if (auto sampleTime = sample_time_for_item(*root, sample))
{
auto sampleTransform = transform_for_time(animation, *sampleTime);
auto sampleTransform = transform_for_time(*sampleTime);
draw_root(*sampleTime, sampleTransform, sample.colorOffset, sample.alphaOffset, true);
}
draw_root(time, transform, {}, 0.0f, false);
for (auto& id : animation->layerOrder)
if (auto layerAnimations = element_child_first_get(*animation, ElementType::LAYER_ANIMATIONS))
{
if (!animation->layerAnimations.contains(id)) continue;
auto& layerAnimation = animation->layerAnimations[id];
if (!layerAnimation.isVisible) continue;
if (!anm2.content.layers.contains(id)) continue;
auto& layer = anm2.content.layers.at(id);
auto spritesheet = anm2.spritesheet_get(layer.spritesheetID);
if (!spritesheet || !spritesheet->is_valid()) continue;
auto draw_layer =
[&](float sampleTime, const glm::mat4& sampleTransform, vec3 sampleColor, float sampleAlpha, bool isOnion)
auto layer_animation_draw = [&](auto&& self, Element& layerAnimation, bool isParentVisible = true) -> void
{
if (auto frame = layerAnimation.frame_generate(sampleTime, anm2::LAYER); frame.isVisible)
if (layerAnimation.type == ElementType::GROUP)
{
auto& texture = spritesheet->texture;
for (auto& child : layerAnimation.children)
self(self, child, isParentVisible && layerAnimation.isVisible);
return;
}
if (layerAnimation.type != ElementType::LAYER_ANIMATION || !isParentVisible ||
!layerAnimation.isVisible || !is_track_group_visible(*layerAnimations, layerAnimation))
return;
auto id = layerAnimation.layerId;
auto layer = anm2.element_get(ElementType::LAYER_ELEMENT, id);
if (!layer) return;
auto spritesheet = anm2.element_get(ElementType::SPRITESHEET, layer->spritesheetId);
auto textureInfo = document.texture_get(layer->spritesheetId);
if (!spritesheet || !textureInfo || !textureInfo->is_valid()) return;
auto draw_layer = [&](float sampleTime, const glm::mat4& sampleTransform, vec3 sampleColor,
float sampleAlpha, bool isOnion)
{
auto frame = frame_generate(layerAnimation, sampleTime);
if (!frame.isVisible) return;
auto& texture = *textureInfo;
auto texSize = vec2(texture.size);
if (texSize.x <= 0.0f || texSize.y <= 0.0f) return;
@@ -835,9 +856,9 @@ namespace anm2ed::imgui
vec3 frameColorOffset = frame.colorOffset + colorOffset + sampleColor;
vec4 frameTint = frame.tint;
if (isRootTransform)
if (isRootTransform && root)
{
auto rootFrame = animation->rootAnimation.frame_generate(sampleTime, anm2::ROOT);
auto rootFrame = frame_generate(*root, sampleTime);
frameColorOffset += rootFrame.colorOffset;
frameTint *= rootFrame.tint;
}
@@ -860,39 +881,54 @@ namespace anm2ed::imgui
texture_render(shaderTexture, resources.icons[icon::PIVOT].id, pivotTransform, color);
}
}
};
if (layeredOnions)
for (auto& sample : *layeredOnions)
if (auto sampleTime = sample_time_for_item(layerAnimation, sample))
{
auto sampleTransform = transform_for_time(*sampleTime);
draw_layer(*sampleTime, sampleTransform, sample.colorOffset, sample.alphaOffset, true);
}
draw_layer(time, transform, {}, 0.0f, false);
};
if (layeredOnions)
for (auto& sample : *layeredOnions)
if (auto sampleTime = sample_time_for_item(layerAnimation, sample))
{
auto sampleTransform = transform_for_time(animation, *sampleTime);
draw_layer(*sampleTime, sampleTransform, sample.colorOffset, sample.alphaOffset, true);
}
draw_layer(time, transform, {}, 0.0f, false);
for (auto& layerAnimation : layerAnimations->children)
layer_animation_draw(layer_animation_draw, layerAnimation);
}
for (auto& [id, nullAnimation] : animation->nullAnimations)
if (auto nullAnimations = element_child_first_get(*animation, ElementType::NULL_ANIMATIONS))
{
if (!nullAnimation.isVisible || isOnlyShowLayers) continue;
auto nullInfo = anm2.content.nulls.find(id);
if (nullInfo == anm2.content.nulls.end()) continue;
auto& isShowRect = nullInfo->second.isShowRect;
auto draw_null =
[&](float sampleTime, const glm::mat4& sampleTransform, vec3 sampleColor, float sampleAlpha, bool isOnion)
auto null_animation_draw = [&](auto&& self, Element& nullAnimation, bool isParentVisible = true) -> void
{
if (auto frame = nullAnimation.frame_generate(sampleTime, anm2::NULL_); frame.isVisible)
if (nullAnimation.type == ElementType::GROUP)
{
for (auto& child : nullAnimation.children)
self(self, child, isParentVisible && nullAnimation.isVisible);
return;
}
if (nullAnimation.type != ElementType::NULL_ANIMATION || !isParentVisible || !nullAnimation.isVisible ||
!is_track_group_visible(*nullAnimations, nullAnimation) || isOnlyShowLayers)
return;
auto id = nullAnimation.nullId;
auto nulls = anm2.element_get(ElementType::NULLS);
auto nullInfo = nulls ? element_child_id_get(*nulls, ElementType::NULL_ELEMENT, id) : nullptr;
if (!nullInfo) return;
auto isShowRect = nullInfo->isShowRect;
auto draw_null = [&](float sampleTime, const glm::mat4& sampleTransform, vec3 sampleColor,
float sampleAlpha, bool isOnion)
{
auto frame = frame_generate(nullAnimation, sampleTime);
if (!frame.isVisible) return;
auto icon = isShowRect ? icon::POINT : isAltIcons ? icon::TARGET_ALT : icon::TARGET;
auto& size = isShowRect ? POINT_SIZE : TARGET_SIZE;
auto color = isOnion ? vec4(sampleColor, 1.0f - sampleAlpha)
: id == reference.itemID && reference.itemType == anm2::NULL_ ? color::RED
: NULL_COLOR;
: id == reference.itemID && referenceItemType == ItemType::NULL_ ? color::RED
: NULL_COLOR;
auto nullModel = math::quad_model_get(size, frame.position, size * 0.5f,
math::percent_to_unit(frame.scale), frame.rotation);
@@ -908,18 +944,20 @@ namespace anm2ed::imgui
rect_render(shaderLine, rectTransform, rectModel, color);
}
}
};
if (layeredOnions)
for (auto& sample : *layeredOnions)
if (auto sampleTime = sample_time_for_item(nullAnimation, sample))
{
auto sampleTransform = transform_for_time(*sampleTime);
draw_null(*sampleTime, sampleTransform, sample.colorOffset, sample.alphaOffset, true);
}
draw_null(time, transform, {}, 0.0f, false);
};
if (layeredOnions)
for (auto& sample : *layeredOnions)
if (auto sampleTime = sample_time_for_item(nullAnimation, sample))
{
auto sampleTransform = transform_for_time(animation, *sampleTime);
draw_null(*sampleTime, sampleTransform, sample.colorOffset, sample.alphaOffset, true);
}
draw_null(time, transform, {}, 0.0f, false);
for (auto& nullAnimation : nullAnimations->children)
null_animation_draw(null_animation_draw, nullAnimation);
}
};
@@ -929,7 +967,7 @@ namespace anm2ed::imgui
render(animation, frameTime, {}, 0.0f, layeredOnions, settings.onionskinMode == (int)OnionskinMode::INDEX);
if (auto overlayAnimation = anm2.animation_get(overlayIndex))
if (auto overlayAnimation = anm2.element_get(ElementType::ANIMATION, overlayIndex))
render(overlayAnimation, frameTime, {}, 1.0f - math::uint8_to_float(overlayTransparency), layeredOnions,
settings.onionskinMode == (int)OnionskinMode::INDEX);
}
@@ -945,10 +983,10 @@ namespace anm2ed::imgui
isPreviewHovered = ImGui::IsItemHovered();
if (animation && animation->triggers.isVisible && !isOnlyShowLayers && !manager.isRecording)
auto triggers = animation ? animation_item_get(*animation, ItemType::TRIGGER) : nullptr;
if (animation && triggers && triggers->isVisible && !isOnlyShowLayers && !manager.isRecording)
{
if (auto trigger = animation->triggers.frame_generate(frameTime, anm2::TRIGGER);
trigger.isVisible && trigger.eventID > -1)
if (auto trigger = frame_generate(*triggers, frameTime); trigger.isVisible && trigger.eventId > -1)
{
auto clipMin = ImGui::GetItemRectMin();
auto clipMax = ImGui::GetItemRectMax();
@@ -958,9 +996,9 @@ namespace anm2ed::imgui
drawList->PushClipRect(clipMin, clipMax);
ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE_LARGE);
auto triggerTextColor = isLightTheme ? TRIGGER_TEXT_COLOR_LIGHT : TRIGGER_TEXT_COLOR_DARK;
if (anm2.content.events.contains(trigger.eventID))
drawList->AddText(textPos, ImGui::GetColorU32(triggerTextColor),
anm2.content.events.at(trigger.eventID).name.c_str());
auto events = anm2.element_get(ElementType::EVENTS);
auto event = events ? element_child_id_get(*events, ElementType::EVENT_ELEMENT, trigger.eventId) : nullptr;
if (event) drawList->AddText(textPos, ImGui::GetColorU32(triggerTextColor), event->name.c_str());
ImGui::PopFont();
drawList->PopClipRect();
}
@@ -999,20 +1037,23 @@ namespace anm2ed::imgui
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 isZoomIn = isFocused && shortcut(manager.chords[SHORTCUT_ZOOM_IN], shortcut::GLOBAL);
auto isZoomOut = isFocused && shortcut(manager.chords[SHORTCUT_ZOOM_OUT], shortcut::GLOBAL);
auto isMod = ImGui::IsKeyDown(ImGuiMod_Shift);
auto frame = document.frame_get();
auto item = document.item_get();
auto frame =
anm2.element_get(reference.animationIndex, referenceItemType, reference.frameIndex, reference.itemID);
auto item = anm2.element_get(reference.animationIndex, referenceItemType, reference.itemID);
auto useTool = tool;
auto step = isMod ? STEP_FAST : STEP;
mousePos = position_translate(zoom, pan, to_vec2(ImGui::GetMousePos()) - to_vec2(cursorScreenPos));
auto selectedNullIt = reference.itemType == anm2::NULL_ ? anm2.content.nulls.find(reference.itemID)
: anm2.content.nulls.end();
bool isSelectedNullRect = selectedNullIt != anm2.content.nulls.end() && selectedNullIt->second.isShowRect;
auto null_rect_top_left = [](const anm2::Frame& frame) { return frame.position - (frame.scale * 0.5f); };
auto nulls = anm2.element_get(ElementType::NULLS);
auto selectedNull = referenceItemType == ItemType::NULL_ && nulls
? element_child_id_get(*nulls, ElementType::NULL_ELEMENT, reference.itemID)
: nullptr;
bool isSelectedNullRect = selectedNull && selectedNull->isShowRect;
auto null_rect_top_left = [](const Element& frame) { return frame.position - (frame.scale * 0.5f); };
if (isMouseMiddleDown) useTool = tool::PAN;
if (tool == tool::MOVE && isMouseRightDown) useTool = tool::SCALE;
@@ -1033,8 +1074,31 @@ namespace anm2ed::imgui
auto isToolDuring = isToolMouseDown || isKeyDown;
auto isToolEnd = isToolMouseReleased || isKeyReleased;
auto frame_change_apply = [&](anm2::FrameChange frameChange, anm2::ChangeType changeType = anm2::ADJUST)
{ item->frames_change(frameChange, reference.itemType, changeType, frames); };
auto frame_snapshot = [&](auto message)
{
manager.command_push({manager.selected,
[message](Manager&, Document& document) { document.snapshot(localize.get(message)); }});
};
auto frame_change_apply = [&](FrameChange frameChange, ChangeType changeType = ChangeType::ADJUST)
{
auto queuedReference = reference;
auto queuedReferenceItemType = referenceItemType;
auto queuedFrames = frames;
manager.command_push({manager.selected,
[=](Manager&, Document& document)
{
auto item = document.anm2.element_get(queuedReference.animationIndex,
queuedReferenceItemType,
queuedReference.itemID);
if (!item) return;
frames_change(*item, frameChange, queuedReferenceItemType, changeType, queuedFrames);
}});
};
auto frames_changed = [&]()
{
manager.command_push(
{manager.selected, [](Manager&, Document& document) { document.anm2_change(Document::FRAMES); }});
};
auto null_rect_change = [&](vec2 topLeft, vec2 rectSize)
{
topLeft = vec2(ivec2(topLeft));
@@ -1064,7 +1128,7 @@ namespace anm2ed::imgui
if (!item || !frame || frames.empty()) break;
if (isToolBegin)
{
document.snapshot(localize.get(EDIT_FRAME_POSITION));
frame_snapshot(EDIT_FRAME_POSITION);
if (isToolMouseClicked)
{
auto origin = isSelectedNullRect ? null_rect_top_left(*frame) : frame->position;
@@ -1082,13 +1146,13 @@ namespace anm2ed::imgui
frame_change_apply({.positionX = (int)position.x, .positionY = (int)position.y});
}
if (isLeftPressed) frame_change_apply({.positionX = step}, anm2::SUBTRACT);
if (isRightPressed) frame_change_apply({.positionX = step}, anm2::ADD);
if (isUpPressed) frame_change_apply({.positionY = step}, anm2::SUBTRACT);
if (isDownPressed) frame_change_apply({.positionY = step}, anm2::ADD);
if (isLeftPressed) frame_change_apply({.positionX = step}, ChangeType::SUBTRACT);
if (isRightPressed) frame_change_apply({.positionX = step}, ChangeType::ADD);
if (isUpPressed) frame_change_apply({.positionY = step}, ChangeType::SUBTRACT);
if (isDownPressed) frame_change_apply({.positionY = step}, ChangeType::ADD);
if (isToolMouseReleased) isMoveDragging = false;
if (isToolEnd) document.change(Document::FRAMES);
if (isToolEnd) frames_changed();
if (isToolDuring)
{
if (ImGui::BeginTooltip())
@@ -1104,7 +1168,7 @@ namespace anm2ed::imgui
if (!item || !frame || frames.empty()) break;
if (isToolBegin)
{
document.snapshot(localize.get(EDIT_FRAME_SCALE));
frame_snapshot(EDIT_FRAME_SCALE);
if (isToolMouseClicked && isSelectedNullRect) nullRectScaleAnchor = null_rect_top_left(*frame);
}
if (isToolMouseDown)
@@ -1134,21 +1198,17 @@ namespace anm2ed::imgui
if (isSelectedNullRect)
{
if (isLeftPressed)
frame_change_apply({.positionX = step * 0.5f, .scaleX = step}, anm2::SUBTRACT);
if (isRightPressed)
frame_change_apply({.positionX = step * 0.5f, .scaleX = step}, anm2::ADD);
if (isUpPressed)
frame_change_apply({.positionY = step * 0.5f, .scaleY = step}, anm2::SUBTRACT);
if (isDownPressed)
frame_change_apply({.positionY = step * 0.5f, .scaleY = step}, anm2::ADD);
if (isLeftPressed) frame_change_apply({.positionX = step * 0.5f, .scaleX = step}, ChangeType::SUBTRACT);
if (isRightPressed) frame_change_apply({.positionX = step * 0.5f, .scaleX = step}, ChangeType::ADD);
if (isUpPressed) frame_change_apply({.positionY = step * 0.5f, .scaleY = step}, ChangeType::SUBTRACT);
if (isDownPressed) frame_change_apply({.positionY = step * 0.5f, .scaleY = step}, ChangeType::ADD);
}
else
{
if (isLeftPressed) frame_change_apply({.scaleX = step}, anm2::SUBTRACT);
if (isRightPressed) frame_change_apply({.scaleX = step}, anm2::ADD);
if (isUpPressed) frame_change_apply({.scaleY = step}, anm2::SUBTRACT);
if (isDownPressed) frame_change_apply({.scaleY = step}, anm2::ADD);
if (isLeftPressed) frame_change_apply({.scaleX = step}, ChangeType::SUBTRACT);
if (isRightPressed) frame_change_apply({.scaleX = step}, ChangeType::ADD);
if (isUpPressed) frame_change_apply({.scaleY = step}, ChangeType::SUBTRACT);
if (isDownPressed) frame_change_apply({.scaleY = step}, ChangeType::ADD);
}
if (isToolDuring)
@@ -1173,15 +1233,15 @@ namespace anm2ed::imgui
maxPoint = vec2(ivec2(maxPoint));
null_rect_change(minPoint, maxPoint - minPoint);
}
document.change(Document::FRAMES);
frames_changed();
}
break;
case tool::ROTATE:
if (!item || !frame || frames.empty()) break;
if (isToolBegin) document.snapshot(localize.get(EDIT_FRAME_ROTATION));
if (isToolMouseDown) frame_change_apply({.rotation = (int)mouseDelta.x}, anm2::ADD);
if (isLeftPressed || isDownPressed) frame_change_apply({.rotation = step}, anm2::SUBTRACT);
if (isUpPressed || isRightPressed) frame_change_apply({.rotation = step}, anm2::ADD);
if (isToolBegin) frame_snapshot(EDIT_FRAME_ROTATION);
if (isToolMouseDown) frame_change_apply({.rotation = (int)mouseDelta.x}, ChangeType::ADD);
if (isLeftPressed || isDownPressed) frame_change_apply({.rotation = step}, ChangeType::SUBTRACT);
if (isUpPressed || isRightPressed) frame_change_apply({.rotation = step}, ChangeType::ADD);
if (isToolDuring)
{
@@ -1193,7 +1253,7 @@ namespace anm2ed::imgui
}
}
if (isToolEnd) document.change(Document::FRAMES);
if (isToolEnd) frames_changed();
break;
default:
break;
@@ -1228,27 +1288,17 @@ namespace anm2ed::imgui
}
}
if (tool == tool::PAN && ImGui::BeginPopupContextWindow("##Animation Preview Context Menu", ImGuiMouseButton_Right))
if (tool == tool::PAN)
{
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, animation)) 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();
Actions actions{};
actions_undo_redo_add(actions, manager, document);
actions.separator();
actions.add(ACTION_CENTER_VIEW, []() { return true; }, center_view);
actions.add(ACTION_FIT_VIEW, [&]() { return animation; }, fit_view);
actions.separator();
actions.add(ACTION_ZOOM_IN, []() { return true; }, zoom_in);
actions.add(ACTION_ZOOM_OUT, []() { return true; }, zoom_out);
actions_context_window_draw("##Animation Preview Context Menu", actions, settings);
}
manager.progressPopup.trigger();
+2
View File
@@ -15,6 +15,7 @@ namespace anm2ed::imgui
MIX_Mixer* mixer = MIX_CreateMixerDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, nullptr);
AudioStream audioStream = AudioStream(mixer);
bool wasPlaybackPlaying{};
bool isFocused{};
bool isPreviewHovered{};
bool isSizeTrySet{true};
Settings savedSettings{};
@@ -38,6 +39,7 @@ namespace anm2ed::imgui
public:
AnimationPreview();
bool is_focused_get() const;
void tick(Manager&, Settings&, float);
void update(Manager&, Settings&, Resources&);
};
-501
View File
@@ -1,501 +0,0 @@
#include "animations.hpp"
#include <format>
#include <ranges>
#include "log.hpp"
#include "strings.hpp"
#include "toast.hpp"
#include "vector_.hpp"
using namespace anm2ed::util;
using namespace anm2ed::resource;
using namespace anm2ed::types;
namespace anm2ed::imgui
{
void Animations::update(Manager& manager, Settings& settings, Resources& resources, Clipboard& clipboard)
{
auto& document = *manager.get();
auto& anm2 = document.anm2;
auto& reference = document.reference;
auto& selection = document.animation.selection;
auto& mergeSelection = document.merge.selection;
auto& mergeReference = document.merge.reference;
auto& overlayIndex = document.overlayIndex;
auto rename_format_get = [&](int index)
{ return std::format("###Document #{} Animation #{}", manager.selected, index); };
auto rename = [&]()
{
if (!selection.empty()) renameQueued = *selection.begin();
};
auto add = [&]()
{
auto behavior = [&]()
{
anm2::Animation animation;
animation.name = localize.get(TEXT_NEW_ANIMATION);
if (anm2::Animation* referenceAnimation = document.animation_get())
{
for (auto [id, layerAnimation] : referenceAnimation->layerAnimations)
animation.layerAnimations[id] = anm2::Item();
animation.layerOrder = referenceAnimation->layerOrder;
for (auto [id, nullAnimation] : referenceAnimation->nullAnimations)
animation.nullAnimations[id] = anm2::Item();
}
animation.rootAnimation.frames.emplace_back(anm2::Frame());
auto index = (int)anm2.animations.items.size();
if (!selection.empty())
{
index = *selection.rbegin() + 1;
index = std::min(index, (int)anm2.animations.items.size());
}
if (anm2.animations.items.empty()) anm2.animations.defaultAnimation = animation.name;
anm2.animations.items.insert(anm2.animations.items.begin() + index, animation);
selection = {index};
reference = {index};
newAnimationSelectedIndex = index;
};
DOCUMENT_EDIT(document, localize.get(EDIT_ADD_ANIMATION), Document::ANIMATIONS, behavior());
};
auto remove = [&]()
{
auto behavior = [&]()
{
if (!selection.empty())
{
for (auto it = selection.rbegin(); it != selection.rend(); ++it)
{
auto i = *it;
if (overlayIndex == i) overlayIndex = -1;
if (reference.animationIndex == i) reference.animationIndex = -1;
anm2.animations.items.erase(anm2.animations.items.begin() + i);
}
selection.clear();
}
};
DOCUMENT_EDIT(document, localize.get(EDIT_REMOVE_ANIMATIONS), Document::ANIMATIONS, behavior());
};
auto duplicate = [&]()
{
auto behavior = [&]()
{
auto duplicated = selection;
auto end = std::ranges::max(duplicated);
for (auto& id : duplicated)
{
anm2.animations.items.insert(anm2.animations.items.begin() + end, anm2.animations.items[id]);
selection.insert(++end);
selection.erase(id);
}
};
DOCUMENT_EDIT(document, localize.get(EDIT_REMOVE_ANIMATIONS), Document::ANIMATIONS, behavior());
};
auto merge = [&]()
{
auto behavior = [&]()
{
if (mergeSelection.contains(overlayIndex)) overlayIndex = -1;
auto merged = anm2.animations_merge(mergeReference, mergeSelection, (merge::Type)settings.mergeType,
settings.mergeIsDeleteAnimationsAfter);
selection = {merged};
reference = {merged};
};
DOCUMENT_EDIT(document, localize.get(EDIT_MERGE_ANIMATIONS), Document::ANIMATIONS, behavior());
};
auto merge_popup_open = [&]()
{
mergePopup.open();
mergeSelection.clear();
mergeReference = *selection.begin();
};
auto merge_quick = [&]()
{
auto behavior = [&]()
{
int merged{};
if (selection.contains(overlayIndex)) overlayIndex = -1;
if (selection.size() > 1)
merged = anm2.animations_merge(*selection.begin(), selection);
else if (selection.size() == 1 && *selection.begin() != (int)anm2.animations.items.size() - 1)
{
auto start = *selection.begin();
auto next = *selection.begin() + 1;
std::set<int> animationSet{};
animationSet.insert(start);
animationSet.insert(next);
merged = anm2.animations_merge(start, animationSet);
}
else
return;
selection = {merged};
reference = {merged};
};
DOCUMENT_EDIT(document, localize.get(EDIT_MERGE_ANIMATIONS), Document::ANIMATIONS, behavior());
};
auto default_set = [&]()
{
DOCUMENT_EDIT(document, localize.get(EDIT_DEFAULT_ANIMATION), Document::ANIMATIONS,
anm2.animations.defaultAnimation = anm2.animations.items[*selection.begin()].name);
};
auto copy = [&]()
{
if (selection.empty()) return;
std::string clipboardText{};
for (auto& i : selection)
clipboardText += anm2.animations.items[i].to_string();
clipboard.set(clipboardText);
};
auto cut = [&]()
{
copy();
DOCUMENT_EDIT(document, localize.get(EDIT_CUT_ANIMATIONS), Document::ANIMATIONS, remove());
};
auto paste = [&]()
{
if (clipboard.is_empty()) return;
auto behavior = [&]()
{
auto clipboardText = clipboard.get();
auto start = selection.empty() ? anm2.animations.items.size() : *selection.rbegin() + 1;
std::set<int> indices{};
std::string errorString{};
if (anm2.animations_deserialize(clipboardText, start, indices, &errorString))
{
if (!indices.empty())
{
auto index = *indices.rbegin();
selection = {index};
reference = {index};
newAnimationSelectedIndex = index;
}
}
else
{
toasts.push(
std::vformat(localize.get(TOAST_DESERIALIZE_ANIMATIONS_FAILED), std::make_format_args(errorString)));
logger.error(std::vformat(localize.get(TOAST_DESERIALIZE_ANIMATIONS_FAILED, anm2ed::ENGLISH),
std::make_format_args(errorString)));
}
};
DOCUMENT_EDIT(document, localize.get(EDIT_PASTE_ANIMATIONS), Document::ANIMATIONS, behavior());
};
if (ImGui::Begin(localize.get(LABEL_ANIMATIONS_WINDOW), &settings.windowIsAnimations))
{
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) && ImGui::IsKeyPressed(ImGuiKey_Escape))
reference = {};
auto childSize = size_without_footer_get();
if (ImGui::BeginChild("##Animations Child", childSize, ImGuiChildFlags_Borders))
{
selection.start(anm2.animations.items.size());
for (auto [i, animation] : std::views::enumerate(anm2.animations.items))
{
ImGui::PushID((int)i);
auto isDefault = anm2.animations.defaultAnimation == animation.name;
auto isReferenced = reference.animationIndex == (int)i;
auto isNewAnimation = newAnimationSelectedIndex == (int)i;
auto font = isDefault && isReferenced ? font::BOLD_ITALICS
: isDefault ? font::BOLD
: isReferenced ? font::ITALICS
: font::REGULAR;
ImGui::PushFont(resources.fonts[font].get(), font::SIZE);
ImGui::SetNextItemSelectionUserData((int)i);
if (isNewAnimation || renameQueued == i)
{
renameState = RENAME_FORCE_EDIT;
renameQueued = -1;
}
if (selectable_input_text(animation.name, rename_format_get(i), animation.name, selection.contains((int)i),
ImGuiSelectableFlags_None, renameState))
{
reference = {(int)i};
document.frames.clear();
if (renameState == RENAME_BEGIN)
document.snapshot(localize.get(SNAPSHOT_RENAME_ANIMATION));
else if (renameState == RENAME_FINISHED)
{
if (anm2.animations.items.size() == 1) anm2.animations.defaultAnimation = animation.name;
document.change(Document::ANIMATIONS);
}
}
if (isNewAnimation)
{
isUpdateScroll = true;
newAnimationSelectedIndex = -1;
}
ImGui::PopFont();
if (isUpdateScroll && isReferenced)
{
ImGui::SetScrollHereY(0.5f);
isUpdateScroll = false;
}
if (ImGui::BeginItemTooltip())
{
ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE);
ImGui::TextUnformatted(animation.name.c_str());
ImGui::PopFont();
if (isDefault)
{
ImGui::PushFont(resources.fonts[font::ITALICS].get(), font::SIZE);
ImGui::TextUnformatted(localize.get(BASIC_DEFAULT));
ImGui::PopFont();
}
ImGui::TextUnformatted(
std::vformat(localize.get(FORMAT_LENGTH), std::make_format_args(animation.frameNum)).c_str());
auto loopLabel = animation.isLoop ? localize.get(BASIC_YES) : localize.get(BASIC_NO);
ImGui::TextUnformatted(std::vformat(localize.get(FORMAT_LOOP), std::make_format_args(loopLabel)).c_str());
ImGui::EndTooltip();
}
if (ImGui::BeginDragDropSource())
{
static std::vector<int> dragDropSelection{};
dragDropSelection.assign(selection.begin(), selection.end());
ImGui::SetDragDropPayload("Animation Drag Drop", dragDropSelection.data(),
dragDropSelection.size() * sizeof(int));
for (auto& i : dragDropSelection)
ImGui::Text("%s", anm2.animations.items[(int)i].name.c_str());
ImGui::EndDragDropSource();
}
if (ImGui::BeginDragDropTarget())
{
if (auto payload = ImGui::AcceptDragDropPayload("Animation Drag Drop"))
{
auto payloadIndices = (int*)(payload->Data);
auto payloadCount = payload->DataSize / sizeof(int);
std::vector<int> indices(payloadIndices, payloadIndices + payloadCount);
std::sort(indices.begin(), indices.end());
DOCUMENT_EDIT(document, localize.get(EDIT_MOVE_ANIMATIONS), Document::ANIMATIONS,
selection = vector::move_indices(anm2.animations.items, indices, i));
}
ImGui::EndDragDropTarget();
}
ImGui::PopID();
}
selection.finish();
if (shortcut(manager.chords[SHORTCUT_RENAME], shortcut::FOCUSED)) rename();
if (shortcut(manager.chords[SHORTCUT_MERGE], shortcut::FOCUSED)) merge_quick();
if (shortcut(manager.chords[SHORTCUT_CUT], shortcut::FOCUSED)) cut();
if (shortcut(manager.chords[SHORTCUT_COPY], shortcut::FOCUSED)) copy();
if (shortcut(manager.chords[SHORTCUT_PASTE], shortcut::FOCUSED)) paste();
if (ImGui::BeginPopupContextWindow("##Context Menu", ImGuiPopupFlags_MouseButtonRight))
{
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(BASIC_RENAME), settings.shortcutRename.c_str(), false,
selection.size() == 1))
rename();
if (ImGui::MenuItem(localize.get(SHORTCUT_STRING_ADD), settings.shortcutAdd.c_str())) add();
if (ImGui::MenuItem(localize.get(SHORTCUT_STRING_DUPLICATE), settings.shortcutDuplicate.c_str())) duplicate();
if (ImGui::MenuItem(localize.get(SHORTCUT_STRING_MERGE), settings.shortcutMerge.c_str())) merge_quick();
if (ImGui::MenuItem(localize.get(SHORTCUT_STRING_REMOVE), settings.shortcutRemove.c_str())) remove();
if (ImGui::MenuItem(localize.get(SHORTCUT_STRING_DEFAULT), settings.shortcutDefault.c_str(), false,
selection.size() == 1))
default_set();
ImGui::Separator();
if (ImGui::MenuItem(localize.get(BASIC_CUT), settings.shortcutCut.c_str(), false, !selection.empty())) cut();
if (ImGui::MenuItem(localize.get(BASIC_COPY), settings.shortcutCopy.c_str(), false, !selection.empty()))
copy();
if (ImGui::MenuItem(localize.get(BASIC_PASTE), settings.shortcutPaste.c_str(), false, !clipboard.is_empty()))
paste();
ImGui::EndPopup();
}
}
ImGui::EndChild();
auto widgetSize = widget_size_with_row_get(5);
shortcut(manager.chords[SHORTCUT_ADD]);
if (ImGui::Button(localize.get(BASIC_ADD), widgetSize)) add();
set_item_tooltip_shortcut(localize.get(TOOLTIP_ADD_ANIMATION), settings.shortcutAdd);
ImGui::SameLine();
ImGui::BeginDisabled(selection.empty());
shortcut(manager.chords[SHORTCUT_DUPLICATE]);
if (ImGui::Button(localize.get(BASIC_DUPLICATE), widgetSize)) duplicate();
ImGui::EndDisabled();
set_item_tooltip_shortcut(localize.get(TOOLTIP_DUPLICATE_ANIMATION), settings.shortcutDuplicate);
ImGui::SameLine();
ImGui::BeginDisabled(selection.size() != 1);
if (ImGui::Button(localize.get(LABEL_MERGE), widgetSize)) merge_popup_open();
ImGui::EndDisabled();
set_item_tooltip_shortcut(localize.get(TOOLTIP_OPEN_MERGE_POPUP), settings.shortcutMerge);
ImGui::SameLine();
ImGui::BeginDisabled(selection.empty());
shortcut(manager.chords[SHORTCUT_REMOVE]);
if (ImGui::Button(localize.get(BASIC_REMOVE), widgetSize)) remove();
ImGui::EndDisabled();
set_item_tooltip_shortcut(localize.get(TOOLTIP_REMOVE_ANIMATION), settings.shortcutRemove);
ImGui::SameLine();
ImGui::BeginDisabled(selection.size() != 1);
shortcut(manager.chords[SHORTCUT_DEFAULT]);
if (ImGui::Button(localize.get(BASIC_DEFAULT), widgetSize)) default_set();
ImGui::EndDisabled();
set_item_tooltip_shortcut(localize.get(TOOLTIP_SET_DEFAULT_ANIMATION), settings.shortcutDefault);
mergePopup.trigger();
if (ImGui::BeginPopupModal(mergePopup.label(), &mergePopup.isOpen, ImGuiWindowFlags_NoResize))
{
auto close = [&]()
{
mergeSelection.clear();
mergePopup.close();
};
auto& type = settings.mergeType;
auto& isDeleteAnimationsAfter = settings.mergeIsDeleteAnimationsAfter;
auto footerSize = footer_size_get();
auto optionsSize = child_size_get(2);
auto deleteAfterSize = child_size_get();
auto animationsSize =
ImVec2(0, ImGui::GetContentRegionAvail().y -
(optionsSize.y + deleteAfterSize.y + footerSize.y + ImGui::GetStyle().ItemSpacing.y * 3));
if (ImGui::BeginChild(localize.get(LABEL_ANIMATIONS_CHILD), animationsSize, ImGuiChildFlags_Borders))
{
mergeSelection.start(anm2.animations.items.size());
for (int i = 0; i < (int)anm2.animations.items.size(); i++)
{
if (i == mergeReference) continue;
auto& animation = anm2.animations.items[i];
ImGui::PushID(i);
ImGui::SetNextItemSelectionUserData(i);
ImGui::Selectable(animation.name.c_str(), mergeSelection.contains(i));
ImGui::PopID();
}
mergeSelection.finish();
}
ImGui::EndChild();
if (ImGui::BeginChild("##Merge Options", optionsSize, ImGuiChildFlags_Borders))
{
auto size = ImVec2(optionsSize.x * 0.5f, optionsSize.y - ImGui::GetStyle().WindowPadding.y * 2);
if (ImGui::BeginChild("##Merge Options 1", size))
{
ImGui::RadioButton(localize.get(LABEL_APPEND_FRAMES), &type, merge::APPEND);
ImGui::RadioButton(localize.get(LABEL_PREPEND_FRAMES), &type, merge::PREPEND);
}
ImGui::EndChild();
ImGui::SameLine();
if (ImGui::BeginChild("##Merge Options 2", size))
{
ImGui::RadioButton(localize.get(LABEL_REPLACE_FRAMES), &type, merge::REPLACE);
ImGui::RadioButton(localize.get(LABEL_IGNORE_FRAMES), &type, merge::IGNORE);
}
ImGui::EndChild();
}
ImGui::EndChild();
if (ImGui::BeginChild("##Merge Delete After", deleteAfterSize, ImGuiChildFlags_Borders))
ImGui::Checkbox(localize.get(LABEL_DELETE_ANIMATIONS_AFTER), &isDeleteAnimationsAfter);
ImGui::EndChild();
auto widgetSize = widget_size_with_row_get(2);
ImGui::BeginDisabled(mergeSelection.empty());
if (ImGui::Button(localize.get(LABEL_MERGE), widgetSize))
{
merge();
close();
}
ImGui::EndDisabled();
ImGui::SameLine();
if (ImGui::Button(localize.get(LABEL_CLOSE), widgetSize)) close();
ImGui::EndPopup();
}
}
ImGui::End();
auto isNextAnimation = shortcut(manager.chords[SHORTCUT_NEXT_ANIMATION], shortcut::GLOBAL);
auto isPreviousAnimation = shortcut(manager.chords[SHORTCUT_PREVIOUS_ANIMATION], shortcut::GLOBAL);
if ((isPreviousAnimation || isNextAnimation) && !anm2.animations.items.empty())
{
if (isPreviousAnimation)
reference.animationIndex = glm::clamp(--reference.animationIndex, 0, (int)anm2.animations.items.size() - 1);
if (isNextAnimation)
reference.animationIndex = glm::clamp(++reference.animationIndex, 0, (int)anm2.animations.items.size() - 1);
selection = {reference.animationIndex};
isUpdateScroll = true;
}
}
}
-23
View File
@@ -1,23 +0,0 @@
#pragma once
#include "clipboard.hpp"
#include "manager.hpp"
#include "resources.hpp"
#include "settings.hpp"
#include "strings.hpp"
namespace anm2ed::imgui
{
class Animations
{
PopupHelper mergePopup{PopupHelper(LABEL_ANIMATIONS_MERGE_POPUP)};
int newAnimationSelectedIndex{-1};
int renameQueued{-1};
bool isInContextMenu{};
bool isUpdateScroll{};
RenameState renameState{RENAME_SELECTABLE};
public:
void update(Manager&, Settings&, Resources&, Clipboard&);
};
}
-207
View File
@@ -1,207 +0,0 @@
#include "events.hpp"
#include <format>
#include <ranges>
#include "log.hpp"
#include "map_.hpp"
#include "strings.hpp"
#include "toast.hpp"
using namespace anm2ed::util;
using namespace anm2ed::resource;
using namespace anm2ed::types;
namespace anm2ed::imgui
{
void Events::update(Manager& manager, Settings& settings, Resources& resources, Clipboard& clipboard)
{
auto& document = *manager.get();
auto& anm2 = document.anm2;
auto& reference = document.event.reference;
auto& selection = document.event.selection;
auto rename_format_get = [&](int id) { return std::format("###Document #{} Event #{}", manager.selected, id); };
auto rename = [&]()
{
if (!selection.empty()) renameQueued = *selection.begin();
};
auto add = [&]()
{
auto behavior = [&]()
{
auto id = map::next_id_get(anm2.content.events);
anm2::Event event{};
event.name = localize.get(TEXT_NEW_EVENT);
anm2.content.events[id] = event;
selection = {id};
reference = {id};
newEventId = id;
};
DOCUMENT_EDIT(document, localize.get(EDIT_ADD_EVENT), Document::EVENTS, behavior());
};
auto remove_unused = [&]()
{
auto unused = anm2.events_unused();
if (unused.empty()) return;
auto behavior = [&]()
{
for (auto& id : unused)
{
for (auto& animation : anm2.animations.items)
for (auto& trigger : animation.triggers.frames)
if (trigger.eventID == id) trigger.eventID = -1;
anm2.content.events.erase(id);
}
};
DOCUMENT_EDIT(document, localize.get(EDIT_REMOVE_UNUSED_EVENTS), Document::EVENTS, behavior());
};
auto copy = [&]()
{
if (selection.empty()) return;
std::string clipboardText{};
for (auto& id : selection)
clipboardText += anm2.content.events[id].to_string(id);
clipboard.set(clipboardText);
};
auto paste = [&]()
{
if (clipboard.is_empty()) return;
auto behavior = [&]()
{
auto maxEventIdBefore = anm2.content.events.empty() ? -1 : anm2.content.events.rbegin()->first;
std::string errorString{};
document.snapshot(localize.get(EDIT_PASTE_EVENTS));
if (anm2.events_deserialize(clipboard.get(), merge::APPEND, &errorString))
{
if (!anm2.content.events.empty())
{
auto maxEventIdAfter = anm2.content.events.rbegin()->first;
if (maxEventIdAfter > maxEventIdBefore)
{
newEventId = maxEventIdAfter;
selection = {maxEventIdAfter};
reference = maxEventIdAfter;
}
}
document.change(Document::EVENTS);
}
else
{
toasts.push(std::vformat(localize.get(TOAST_DESERIALIZE_EVENTS_FAILED), std::make_format_args(errorString)));
logger.error(std::vformat(localize.get(TOAST_DESERIALIZE_EVENTS_FAILED, anm2ed::ENGLISH),
std::make_format_args(errorString)));
}
};
DOCUMENT_EDIT(document, localize.get(EDIT_PASTE_EVENTS), Document::EVENTS, behavior());
};
if (ImGui::Begin(localize.get(LABEL_EVENTS_WINDOW), &settings.windowIsEvents))
{
auto childSize = size_without_footer_get();
if (ImGui::BeginChild("##Events Child", childSize, true))
{
selection.start(anm2.content.events.size());
for (auto& [id, event] : anm2.content.events)
{
auto isNewEvent = (newEventId == id);
ImGui::PushID(id);
ImGui::SetNextItemSelectionUserData(id);
if (isNewEvent || renameQueued == id)
{
renameState = RENAME_FORCE_EDIT;
renameQueued = -1;
}
if (selectable_input_text(event.name, rename_format_get(id), event.name, selection.contains(id),
ImGuiSelectableFlags_None, renameState))
{
if (renameState == RENAME_BEGIN)
document.snapshot(localize.get(EDIT_RENAME_EVENT));
else if (renameState == RENAME_FINISHED)
document.change(Document::EVENTS);
}
if (isNewEvent)
{
ImGui::SetScrollHereY(0.5f);
newEventId = -1;
}
if (ImGui::BeginItemTooltip())
{
ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE);
ImGui::TextUnformatted(event.name.c_str());
ImGui::PopFont();
ImGui::TextUnformatted(std::vformat(localize.get(FORMAT_ID), std::make_format_args(id)).c_str());
ImGui::EndTooltip();
}
ImGui::PopID();
}
selection.finish();
if (shortcut(manager.chords[SHORTCUT_RENAME], shortcut::FOCUSED)) rename();
if (shortcut(manager.chords[SHORTCUT_ADD], shortcut::FOCUSED)) add();
if (shortcut(manager.chords[SHORTCUT_REMOVE], shortcut::FOCUSED)) remove_unused();
if (shortcut(manager.chords[SHORTCUT_COPY], shortcut::FOCUSED)) copy();
if (shortcut(manager.chords[SHORTCUT_PASTE], shortcut::FOCUSED)) paste();
if (ImGui::BeginPopupContextWindow("##Context Menu", ImGuiPopupFlags_MouseButtonRight))
{
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(BASIC_RENAME), settings.shortcutRename.c_str(), false,
selection.size() == 1))
rename();
if (ImGui::MenuItem(localize.get(SHORTCUT_STRING_ADD), settings.shortcutAdd.c_str())) add();
if (ImGui::MenuItem(localize.get(BASIC_REMOVE_UNUSED), settings.shortcutRemove.c_str())) remove_unused();
ImGui::Separator();
if (ImGui::MenuItem(localize.get(BASIC_COPY), settings.shortcutCopy.c_str(), false, !selection.empty()))
copy();
if (ImGui::MenuItem(localize.get(BASIC_PASTE), settings.shortcutPaste.c_str(), false, !clipboard.is_empty()))
paste();
ImGui::EndPopup();
}
}
ImGui::EndChild();
auto widgetSize = widget_size_with_row_get(2);
shortcut(manager.chords[SHORTCUT_ADD]);
if (ImGui::Button(localize.get(BASIC_ADD), widgetSize)) add();
set_item_tooltip_shortcut(localize.get(TOOLTIP_ADD_EVENT), settings.shortcutAdd);
ImGui::SameLine();
shortcut(manager.chords[SHORTCUT_REMOVE]);
if (ImGui::Button(localize.get(BASIC_REMOVE_UNUSED), widgetSize)) remove_unused();
set_item_tooltip_shortcut(localize.get(TOOLTIP_REMOVE_UNUSED_EVENTS), settings.shortcutRemove);
}
ImGui::End();
}
}
-19
View File
@@ -1,19 +0,0 @@
#pragma once
#include "clipboard.hpp"
#include "manager.hpp"
#include "resources.hpp"
#include "settings.hpp"
namespace anm2ed::imgui
{
class Events
{
int newEventId{-1};
int renameQueued{-1};
RenameState renameState{RENAME_SELECTABLE};
public:
void update(Manager&, Settings&, Resources&, Clipboard&);
};
}
+218 -114
View File
@@ -1,13 +1,14 @@
#include "frame_properties.hpp"
#include <glm/gtc/type_ptr.hpp>
#include <limits>
#include <ranges>
#include <string>
#include <vector>
#include "math_.hpp"
#include "math.hpp"
#include "strings.hpp"
#include "types.hpp"
#include "util/imgui/imgui.hpp"
using namespace anm2ed::util::math;
using namespace anm2ed::types;
@@ -17,11 +18,70 @@ namespace anm2ed::imgui
{
void FrameProperties::update(Manager& manager, Settings& settings)
{
if (ImGui::Begin(localize.get(LABEL_FRAME_PROPERTIES_WINDOW), &settings.windowIsFrameProperties))
auto& document = *manager.get();
auto frameSelectionCount =
document.frames.references.empty() ? document.frames.selection.size() : document.frames.references.size();
auto isSingleFrameSelection = frameSelectionCount == 1;
auto isMultiFrameSelection = frameSelectionCount > 1;
auto isSingleFrameBatchMode =
isSingleFrameSelection && document.reference.itemType != TRIGGER && isBatchMode;
auto isBatchFrameProperties = isMultiFrameSelection || isSingleFrameBatchMode;
auto windowLabel = std::string(localize.get(isBatchFrameProperties ? LABEL_CHANGE_ALL_FRAME_PROPERTIES
: LABEL_FRAME_PROPERTIES_WINDOW));
if (isBatchFrameProperties) windowLabel += "###Frame Properties";
if (ImGui::Begin(windowLabel.c_str(), &settings.windowIsFrameProperties))
{
auto& document = *manager.get();
auto& frames = document.frames.selection;
auto& type = document.reference.itemType;
auto& anm2 = document.anm2;
auto& reference = document.reference;
auto& type = reference.itemType;
auto itemType = static_cast<ItemType>(type);
auto frame = anm2.element_get(reference.animationIndex, itemType, reference.frameIndex, reference.itemID);
auto frame_edit = [&](auto message, auto behavior)
{
auto queuedReference = reference;
manager.command_push({manager.selected,
[=](Manager&, Document& document) mutable
{
auto itemType = static_cast<ItemType>(queuedReference.itemType);
auto frame = document.anm2.element_get(queuedReference.animationIndex, itemType,
queuedReference.frameIndex,
queuedReference.itemID);
auto item =
document.anm2.element_get(queuedReference.animationIndex, itemType,
queuedReference.itemID);
if (!frame) return;
document.snapshot(localize.get(message));
behavior(document, *frame, item, queuedReference);
document.anm2_change(Document::FRAMES);
}});
};
auto persistent_edit = [&](auto state, auto message, auto behavior)
{
if (state == edit::NONE) return;
auto queuedReference = reference;
manager.command_push({manager.selected,
[=](Manager&, Document& document) mutable
{
auto itemType = static_cast<ItemType>(queuedReference.itemType);
auto frame = document.anm2.element_get(queuedReference.animationIndex, itemType,
queuedReference.frameIndex,
queuedReference.itemID);
auto item =
document.anm2.element_get(queuedReference.animationIndex, itemType,
queuedReference.itemID);
if (!frame) return;
if (state == edit::START) document.snapshot(localize.get(message));
behavior(document, *frame, item, queuedReference);
if (state == edit::END) document.anm2_change(Document::FRAMES);
}});
};
auto regionLabelsString = std::vector<std::string>{localize.get(BASIC_NONE)};
auto regionLabels = std::vector<const char*>{regionLabelsString[0].c_str()};
auto regionIds = std::vector<int>{-1};
@@ -33,17 +93,14 @@ namespace anm2ed::imgui
interpolationLabelsString[2].c_str(), interpolationLabelsString[3].c_str(),
interpolationLabelsString[4].c_str()};
auto interpolationValues =
std::vector<int>{anm2::Frame::Interpolation::NONE, anm2::Frame::Interpolation::LINEAR,
anm2::Frame::Interpolation::EASE_IN, anm2::Frame::Interpolation::EASE_OUT,
anm2::Frame::Interpolation::EASE_IN_OUT};
std::vector<int>{(int)Interpolation::NONE, (int)Interpolation::LINEAR, (int)Interpolation::EASE_IN,
(int)Interpolation::EASE_OUT, (int)Interpolation::EASE_IN_OUT};
if (type == anm2::LAYER && document.reference.itemID != -1)
if (type == LAYER && reference.itemID != -1)
{
if (auto layerIt = document.anm2.content.layers.find(document.reference.itemID);
layerIt != document.anm2.content.layers.end())
if (auto layer = anm2.element_get(ElementType::LAYER_ELEMENT, reference.itemID))
{
auto spritesheetID = layerIt->second.spritesheetID;
auto regionIt = document.regionBySpritesheet.find(spritesheetID);
auto regionIt = document.regionBySpritesheet.find(layer->spritesheetId);
if (regionIt != document.regionBySpritesheet.end() && !regionIt->second.ids.empty() &&
!regionIt->second.labels.empty())
{
@@ -53,37 +110,70 @@ namespace anm2ed::imgui
}
}
if (frames.size() <= 1)
auto mode_selector_draw = [&]()
{
auto frame = document.frame_get();
auto useFrame = frame ? *frame : anm2::Frame();
auto displayFrame = frame && type == anm2::LAYER && document.reference.itemID != -1
? document.anm2.frame_effective(document.reference.itemID, *frame)
if (!isSingleFrameSelection || type == TRIGGER) return;
ImGui::SeparatorText(localize.get(BASIC_MODE));
int mode = isBatchMode ? 1 : 0;
ImGui::RadioButton(localize.get(BASIC_SINGLE), &mode, 0);
ImGui::SameLine();
ImGui::RadioButton(localize.get(BASIC_BATCH), &mode, 1);
isBatchMode = mode == 1;
};
if (isSingleFrameBatchMode)
{
changeAllFrameProperties.update(manager, document, settings);
mode_selector_draw();
}
else if (!isMultiFrameSelection)
{
auto useFrame = frame ? *frame : Element();
auto displayFrame = frame && type == LAYER && reference.itemID != -1
? anm2.frame_effective(reference.itemID, *frame)
: useFrame;
ImGui::BeginDisabled(!frame);
{
if (type == anm2::TRIGGER)
if (type == TRIGGER)
{
if (combo_id_mapped(localize.get(BASIC_EVENT), frame ? &useFrame.eventID : &dummy_value_negative<int>(),
if (combo_id_mapped(localize.get(BASIC_EVENT), frame ? &useFrame.eventId : &dummy_value_negative<int>(),
document.event.ids, document.event.labels) &&
frame)
DOCUMENT_EDIT(document, localize.get(EDIT_TRIGGER_EVENT), Document::FRAMES,
frame->eventID = useFrame.eventID);
{
auto eventId = useFrame.eventId;
frame_edit(EDIT_TRIGGER_EVENT,
[eventId](Document&, Element& frame, Element*, const Reference&) { frame.eventId = eventId; });
}
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_TRIGGER_EVENT));
if (input_int_range(localize.get(BASIC_AT_FRAME), frame ? useFrame.atFrame : dummy_value<int>(), 0,
std::numeric_limits<int>::max(), STEP, STEP_FAST,
!frame ? ImGuiInputTextFlags_DisplayEmptyRefVal : 0) &&
frame)
DOCUMENT_EDIT(document, localize.get(EDIT_TRIGGER_AT_FRAME), Document::FRAMES,
frame->atFrame = useFrame.atFrame);
{
auto atFrame = useFrame.atFrame;
frame_edit(EDIT_TRIGGER_AT_FRAME,
[atFrame](Document& document, Element& frame, Element* item, const Reference&)
{
frame.atFrame = atFrame;
if (!item) return;
frames_sort_by_at_frame(*item);
document.reference.frameIndex = frame_index_from_at_frame_get(*item, atFrame);
document.frames.selection = {document.reference.frameIndex};
document.frames.references = {document.reference};
});
}
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_TRIGGER_AT_FRAME));
if (ImGui::Checkbox(localize.get(BASIC_VISIBLE), frame ? &useFrame.isVisible : &dummy_value<bool>()) &&
frame)
DOCUMENT_EDIT(document, localize.get(EDIT_TRIGGER_VISIBILITY), Document::FRAMES,
frame->isVisible = useFrame.isVisible);
{
auto isVisible = useFrame.isVisible;
frame_edit(EDIT_TRIGGER_VISIBILITY,
[isVisible](Document&, Element& frame, Element*, const Reference&)
{ frame.isVisible = isVisible; });
}
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_TRIGGER_VISIBILITY));
ImGui::SeparatorText(localize.get(LABEL_SOUNDS));
@@ -92,16 +182,23 @@ namespace anm2ed::imgui
if (ImGui::BeginChild("##Sounds Child", childSize, ImGuiChildFlags_Borders))
{
if (!useFrame.soundIDs.empty())
if (!useFrame.soundIds.empty())
{
for (auto [i, id] : std::views::enumerate(useFrame.soundIDs))
for (auto [i, id] : std::views::enumerate(useFrame.soundIds))
{
ImGui::PushID(i);
if (combo_id_mapped("##Sound", frame ? &id : &dummy_value_negative<int>(), document.sound.ids,
document.sound.labels) &&
frame)
DOCUMENT_EDIT(document, localize.get(EDIT_TRIGGER_SOUND), Document::FRAMES,
frame->soundIDs[i] = id);
{
auto soundIndex = (std::size_t)i;
auto soundId = id;
frame_edit(EDIT_TRIGGER_SOUND,
[soundIndex, soundId](Document&, Element& frame, Element*, const Reference&)
{
if (soundIndex < frame.soundIds.size()) frame.soundIds[soundIndex] = soundId;
});
}
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_TRIGGER_SOUND));
ImGui::PopID();
}
@@ -112,125 +209,127 @@ namespace anm2ed::imgui
auto widgetSize = imgui::widget_size_with_row_get(2);
if (ImGui::Button(localize.get(BASIC_ADD), widgetSize) && frame)
DOCUMENT_EDIT(document, localize.get(EDIT_ADD_TRIGGER_SOUND), Document::FRAMES,
frame->soundIDs.push_back(-1));
frame_edit(EDIT_ADD_TRIGGER_SOUND,
[](Document&, Element& frame, Element*, const Reference&) { frame.soundIds.push_back(-1); });
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_ADD_TRIGGER_SOUND));
ImGui::SameLine();
ImGui::BeginDisabled(useFrame.soundIDs.empty());
ImGui::BeginDisabled(useFrame.soundIds.empty());
if (ImGui::Button(localize.get(BASIC_REMOVE), widgetSize) && frame)
DOCUMENT_EDIT(document, localize.get(EDIT_REMOVE_TRIGGER_SOUND), Document::FRAMES,
frame->soundIDs.pop_back());
frame_edit(EDIT_REMOVE_TRIGGER_SOUND,
[](Document&, Element& frame, Element*, const Reference&)
{
if (!frame.soundIds.empty()) frame.soundIds.pop_back();
});
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_REMOVE_TRIGGER_SOUND));
ImGui::EndDisabled();
}
else
{
bool isRegionSet = frame && displayFrame.regionID != -1 && displayFrame.crop == frame->crop &&
bool isRegionSet = frame && displayFrame.regionId != -1 && displayFrame.crop == frame->crop &&
displayFrame.size == frame->size && displayFrame.pivot == frame->pivot;
ImGui::BeginDisabled(type == anm2::ROOT || type == anm2::NULL_ || isRegionSet);
ImGui::BeginDisabled(type == ROOT || type == NULL_ || isRegionSet);
{
auto cropDisplay = frame ? displayFrame.crop : vec2();
auto cropEdit =
drag_float2_persistent(localize.get(BASIC_CROP), frame ? &cropDisplay : &dummy_value<vec2>(),
DRAG_SPEED, 0.0f, 0.0f, frame ? vec2_format_get(displayFrame.crop) : "");
if (cropEdit == edit::START)
document.snapshot(localize.get(EDIT_FRAME_CROP));
if (frame && cropEdit != edit::NONE) frame->crop = cropDisplay;
if (cropEdit == edit::END)
document.change(Document::FRAMES);
persistent_edit(cropEdit, EDIT_FRAME_CROP,
[cropDisplay](Document&, Element& frame, Element*, const Reference&)
{ frame.crop = cropDisplay; });
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_CROP));
auto sizeDisplay = frame ? displayFrame.size : vec2();
auto sizeEdit =
drag_float2_persistent(localize.get(BASIC_SIZE), frame ? &sizeDisplay : &dummy_value<vec2>(),
DRAG_SPEED, 0.0f, 0.0f, frame ? vec2_format_get(displayFrame.size) : "");
if (sizeEdit == edit::START)
document.snapshot(localize.get(EDIT_FRAME_SIZE));
if (frame && sizeEdit != edit::NONE) frame->size = sizeDisplay;
if (sizeEdit == edit::END)
document.change(Document::FRAMES);
persistent_edit(sizeEdit, EDIT_FRAME_SIZE,
[sizeDisplay](Document&, Element& frame, Element*, const Reference&)
{ frame.size = sizeDisplay; });
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_SIZE));
}
ImGui::EndDisabled();
auto positionEdit =
drag_float2_persistent(localize.get(BASIC_POSITION), frame ? &frame->position : &dummy_value<vec2>(),
drag_float2_persistent(localize.get(BASIC_POSITION),
frame ? &useFrame.position : &dummy_value<vec2>(),
DRAG_SPEED, 0.0f, 0.0f, frame ? vec2_format_get(frame->position) : "");
if (positionEdit == edit::START)
document.snapshot(localize.get(EDIT_FRAME_POSITION));
else if (positionEdit == edit::END)
document.change(Document::FRAMES);
persistent_edit(positionEdit, EDIT_FRAME_POSITION,
[position = useFrame.position](Document&, Element& frame, Element*, const Reference&)
{ frame.position = position; });
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_POSITION));
ImGui::BeginDisabled(type == anm2::ROOT || type == anm2::NULL_ || isRegionSet);
ImGui::BeginDisabled(type == ROOT || type == NULL_ || isRegionSet);
{
auto pivotDisplay = frame ? displayFrame.pivot : vec2();
auto pivotEdit =
drag_float2_persistent(localize.get(BASIC_PIVOT), frame ? &pivotDisplay : &dummy_value<vec2>(),
DRAG_SPEED, 0.0f, 0.0f, frame ? vec2_format_get(displayFrame.pivot) : "");
if (pivotEdit == edit::START)
document.snapshot(localize.get(EDIT_FRAME_PIVOT));
if (frame && pivotEdit != edit::NONE) frame->pivot = pivotDisplay;
if (pivotEdit == edit::END)
document.change(Document::FRAMES);
persistent_edit(pivotEdit, EDIT_FRAME_PIVOT,
[pivotDisplay](Document&, Element& frame, Element*, const Reference&)
{ frame.pivot = pivotDisplay; });
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_PIVOT));
}
ImGui::EndDisabled();
auto scaleEdit =
drag_float2_persistent(localize.get(BASIC_SCALE), frame ? &frame->scale : &dummy_value<vec2>(),
drag_float2_persistent(localize.get(BASIC_SCALE), frame ? &useFrame.scale : &dummy_value<vec2>(),
DRAG_SPEED, 0.0f, 0.0f, frame ? vec2_format_get(frame->scale) : "");
persistent_edit(scaleEdit, EDIT_FRAME_SCALE,
[scale = useFrame.scale](Document&, Element& frame, Element*, const Reference&)
{ frame.scale = scale; });
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_SCALE));
if (scaleEdit == edit::START)
document.snapshot(localize.get(EDIT_FRAME_SCALE));
else if (scaleEdit == edit::END)
document.change(Document::FRAMES);
auto rotationEdit =
drag_float_persistent(localize.get(BASIC_ROTATION), frame ? &frame->rotation : &dummy_value<float>(),
drag_float_persistent(localize.get(BASIC_ROTATION), frame ? &useFrame.rotation : &dummy_value<float>(),
DRAG_SPEED, 0.0f, 0.0f, frame ? float_format_get(frame->rotation) : "");
persistent_edit(rotationEdit, EDIT_FRAME_ROTATION,
[rotation = useFrame.rotation](Document&, Element& frame, Element*, const Reference&)
{ frame.rotation = rotation; });
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_ROTATION));
if (rotationEdit == edit::START)
document.snapshot(localize.get(EDIT_FRAME_ROTATION));
else if (rotationEdit == edit::END)
document.change(Document::FRAMES);
if (input_int_range(localize.get(BASIC_DURATION), frame ? useFrame.duration : dummy_value<int>(),
frame ? anm2::FRAME_DURATION_MIN : 0, anm2::FRAME_DURATION_MAX, STEP, STEP_FAST,
frame ? FRAME_DURATION_MIN : 0, FRAME_DURATION_MAX, STEP, STEP_FAST,
!frame ? ImGuiInputTextFlags_DisplayEmptyRefVal : 0) &&
frame)
DOCUMENT_EDIT(document, localize.get(EDIT_FRAME_DURATION), Document::FRAMES,
frame->duration = useFrame.duration);
{
auto duration = useFrame.duration;
frame_edit(EDIT_FRAME_DURATION,
[duration](Document&, Element& frame, Element*, const Reference&) { frame.duration = duration; });
}
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_DURATION));
auto tintEdit =
color_edit4_persistent(localize.get(BASIC_TINT), frame ? &frame->tint : &dummy_value<vec4>());
color_edit4_persistent(localize.get(BASIC_TINT), frame ? &useFrame.tint : &dummy_value<vec4>());
persistent_edit(tintEdit, EDIT_FRAME_TINT,
[tint = useFrame.tint](Document&, Element& frame, Element*, const Reference&)
{ frame.tint = tint; });
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_TINT));
if (tintEdit == edit::START)
document.snapshot(localize.get(EDIT_FRAME_TINT));
else if (tintEdit == edit::END)
document.change(Document::FRAMES);
auto colorOffsetEdit = color_edit3_persistent(localize.get(BASIC_COLOR_OFFSET),
frame ? &frame->colorOffset : &dummy_value<vec3>());
frame ? &useFrame.colorOffset : &dummy_value<vec3>());
persistent_edit(colorOffsetEdit, EDIT_FRAME_COLOR_OFFSET,
[colorOffset = useFrame.colorOffset](Document&, Element& frame, Element*, const Reference&)
{ frame.colorOffset = colorOffset; });
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_COLOR_OFFSET));
if (colorOffsetEdit == edit::START)
document.snapshot(localize.get(EDIT_FRAME_COLOR_OFFSET));
else if (colorOffsetEdit == edit::END)
document.change(Document::FRAMES);
ImGui::BeginDisabled(type != anm2::LAYER);
if (combo_id_mapped(localize.get(BASIC_REGION), frame ? &useFrame.regionID : &dummy_value_negative<int>(),
regionIds, regionLabels) &&
ImGui::BeginDisabled(type != LAYER);
if (combo_id_mapped(localize.get(BASIC_REGION), frame ? &useFrame.regionId : &dummy_value_negative<int>(),
regionIds, regionLabels) &&
frame)
DOCUMENT_EDIT(document, localize.get(EDIT_SET_REGION_PROPERTIES), Document::FRAMES,
frame->regionID = useFrame.regionID;
auto effectiveFrame = document.anm2.frame_effective(document.reference.itemID, *frame);
frame->crop = effectiveFrame.crop;
frame->size = effectiveFrame.size;
frame->pivot = effectiveFrame.pivot);
{
auto regionId = useFrame.regionId;
frame_edit(EDIT_SET_REGION_PROPERTIES,
[regionId](Document& document, Element& frame, Element*, const Reference& reference)
{
frame.regionId = regionId;
auto effectiveFrame = document.anm2.frame_effective(reference.itemID, frame);
frame.crop = effectiveFrame.crop;
frame.size = effectiveFrame.size;
frame.pivot = effectiveFrame.pivot;
});
}
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_REGION));
ImGui::EndDisabled();
@@ -238,14 +337,19 @@ namespace anm2ed::imgui
if (combo_id_mapped(localize.get(BASIC_INTERPOLATED), &interpolationValue, interpolationValues,
interpolationLabels) &&
frame)
DOCUMENT_EDIT(document, localize.get(EDIT_FRAME_INTERPOLATION), Document::FRAMES,
frame->interpolation = static_cast<anm2::Frame::Interpolation>(interpolationValue));
frame_edit(EDIT_FRAME_INTERPOLATION,
[interpolationValue](Document&, Element& frame, Element*, const Reference&)
{ frame.interpolation = static_cast<Interpolation>(interpolationValue); });
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_FRAME_INTERPOLATION));
if (ImGui::Checkbox(localize.get(BASIC_VISIBLE), frame ? &useFrame.isVisible : &dummy_value<bool>()) &&
frame)
DOCUMENT_EDIT(document, localize.get(EDIT_FRAME_VISIBILITY), Document::FRAMES,
frame->isVisible = useFrame.isVisible);
{
auto isVisible = useFrame.isVisible;
frame_edit(EDIT_FRAME_VISIBILITY,
[isVisible](Document&, Element& frame, Element*, const Reference&)
{ frame.isVisible = isVisible; });
}
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_FRAME_VISIBILITY));
auto widgetSize = widget_size_with_row_get(2);
@@ -253,40 +357,40 @@ namespace anm2ed::imgui
if (ImGui::Button(localize.get(LABEL_FLIP_X), widgetSize) && frame)
{
if (ImGui::IsKeyDown(ImGuiMod_Ctrl))
{
DOCUMENT_EDIT(document, localize.get(EDIT_FRAME_FLIP_X), Document::FRAMES,
frame->scale.x = -frame->scale.x;
frame->position.x = -frame->position.x);
}
frame_edit(EDIT_FRAME_FLIP_X,
[](Document&, Element& frame, Element*, const Reference&)
{
frame.scale.x = -frame.scale.x;
frame.position.x = -frame.position.x;
});
else
{
DOCUMENT_EDIT(document, localize.get(EDIT_FRAME_FLIP_X), Document::FRAMES,
frame->scale.x = -frame->scale.x);
}
frame_edit(EDIT_FRAME_FLIP_X,
[](Document&, Element& frame, Element*, const Reference&) { frame.scale.x = -frame.scale.x; });
}
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_FLIP_X));
ImGui::SameLine();
if (ImGui::Button(localize.get(LABEL_FLIP_Y), widgetSize) && frame)
{
if (ImGui::IsKeyDown(ImGuiMod_Ctrl))
{
DOCUMENT_EDIT(document, localize.get(EDIT_FRAME_FLIP_Y), Document::FRAMES,
frame->scale.y = -frame->scale.y;
frame->position.y = -frame->position.y);
}
frame_edit(EDIT_FRAME_FLIP_Y,
[](Document&, Element& frame, Element*, const Reference&)
{
frame.scale.y = -frame.scale.y;
frame.position.y = -frame.position.y;
});
else
{
DOCUMENT_EDIT(document, localize.get(EDIT_FRAME_FLIP_Y), Document::FRAMES,
frame->scale.y = -frame->scale.y);
}
frame_edit(EDIT_FRAME_FLIP_Y,
[](Document&, Element& frame, Element*, const Reference&) { frame.scale.y = -frame.scale.y; });
}
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_FLIP_Y));
}
}
ImGui::EndDisabled();
mode_selector_draw();
}
else
changeAllFrameProperties.update(document, settings);
changeAllFrameProperties.update(manager, document, settings);
}
ImGui::End();
+1
View File
@@ -10,6 +10,7 @@ namespace anm2ed::imgui
class FrameProperties
{
wizard::ChangeAllFrameProperties changeAllFrameProperties{};
bool isBatchMode{};
public:
void update(Manager&, Settings&);
-235
View File
@@ -1,235 +0,0 @@
#include "layers.hpp"
#include <format>
#include <ranges>
#include "log.hpp"
#include "map_.hpp"
#include "strings.hpp"
#include "toast.hpp"
using namespace anm2ed::util;
using namespace anm2ed::resource;
using namespace anm2ed::types;
namespace anm2ed::imgui
{
void Layers::update(Manager& manager, Settings& settings, Resources& resources, Clipboard& clipboard)
{
auto& document = *manager.get();
auto& anm2 = document.anm2;
auto& reference = document.layer.reference;
auto& selection = document.layer.selection;
auto& propertiesPopup = manager.layerPropertiesPopup;
auto add = [&]() { manager.layer_properties_open(); };
auto remove_unused = [&]()
{
auto unused = anm2.layers_unused();
if (unused.empty()) return;
auto behavior = [&]()
{
for (auto& id : unused)
anm2.content.layers.erase(id);
};
DOCUMENT_EDIT(document, localize.get(EDIT_REMOVE_UNUSED_LAYERS), Document::LAYERS, behavior());
};
auto copy = [&]()
{
if (selection.empty()) return;
std::string clipboardText{};
for (auto& id : selection)
clipboardText += anm2.content.layers[id].to_string(id);
clipboard.set(clipboardText);
};
auto paste = [&]()
{
if (clipboard.is_empty()) return;
auto behavior = [&]()
{
auto maxLayerIdBefore = anm2.content.layers.empty() ? -1 : anm2.content.layers.rbegin()->first;
std::string errorString{};
document.snapshot(localize.get(EDIT_PASTE_LAYERS));
if (anm2.layers_deserialize(clipboard.get(), merge::APPEND, &errorString))
{
if (!anm2.content.layers.empty())
{
auto maxLayerIdAfter = anm2.content.layers.rbegin()->first;
if (maxLayerIdAfter > maxLayerIdBefore)
{
newLayerId = maxLayerIdAfter;
selection = {maxLayerIdAfter};
reference = maxLayerIdAfter;
}
}
document.change(Document::NULLS);
}
else
{
toasts.push(std::vformat(localize.get(TOAST_DESERIALIZE_LAYERS_FAILED), std::make_format_args(errorString)));
logger.error(std::vformat(localize.get(TOAST_DESERIALIZE_LAYERS_FAILED, anm2ed::ENGLISH),
std::make_format_args(errorString)));
}
};
DOCUMENT_EDIT(document, localize.get(EDIT_PASTE_LAYERS), Document::LAYERS, behavior());
};
auto properties = [&](int id) { manager.layer_properties_open(id); };
if (ImGui::Begin(localize.get(LABEL_LAYERS_WINDOW), &settings.windowIsLayers))
{
auto childSize = size_without_footer_get();
if (ImGui::BeginChild("##Layers Child", childSize, true))
{
selection.start(anm2.content.layers.size());
for (auto& [id, layer] : anm2.content.layers)
{
auto isSelected = selection.contains(id);
ImGui::PushID(id);
ImGui::SetNextItemSelectionUserData(id);
ImGui::Selectable(
std::vformat(localize.get(FORMAT_LAYER), std::make_format_args(id, layer.name, layer.spritesheetID))
.c_str(),
isSelected);
if (newLayerId == id)
{
ImGui::SetScrollHereY(0.5f);
newLayerId = -1;
}
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) properties(id);
if (ImGui::BeginItemTooltip())
{
ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE);
ImGui::TextUnformatted(layer.name.c_str());
ImGui::PopFont();
ImGui::TextUnformatted(std::vformat(localize.get(FORMAT_ID), std::make_format_args(id)).c_str());
ImGui::TextUnformatted(
std::vformat(localize.get(FORMAT_SPRITESHEET_ID), std::make_format_args(layer.spritesheetID)).c_str());
ImGui::EndTooltip();
}
ImGui::PopID();
}
selection.finish();
if (shortcut(manager.chords[SHORTCUT_ADD], shortcut::FOCUSED)) add();
if (shortcut(manager.chords[SHORTCUT_REMOVE], shortcut::FOCUSED)) remove_unused();
if (shortcut(manager.chords[SHORTCUT_COPY], shortcut::FOCUSED)) copy();
if (shortcut(manager.chords[SHORTCUT_PASTE], shortcut::FOCUSED)) paste();
if (ImGui::BeginPopupContextWindow("##Context Menu", ImGuiPopupFlags_MouseButtonRight))
{
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(BASIC_PROPERTIES), nullptr, false, selection.size() == 1))
properties(*selection.begin());
if (ImGui::MenuItem(localize.get(BASIC_ADD), settings.shortcutAdd.c_str())) add();
if (ImGui::MenuItem(localize.get(BASIC_REMOVE_UNUSED), settings.shortcutRemove.c_str())) remove_unused();
ImGui::Separator();
if (ImGui::MenuItem(localize.get(BASIC_COPY), settings.shortcutCopy.c_str(), false, !selection.empty()))
copy();
if (ImGui::MenuItem(localize.get(BASIC_PASTE), settings.shortcutPaste.c_str(), false, !clipboard.is_empty()))
paste();
ImGui::EndPopup();
}
}
ImGui::EndChild();
auto widgetSize = widget_size_with_row_get(2);
shortcut(manager.chords[SHORTCUT_ADD]);
if (ImGui::Button(localize.get(BASIC_ADD), widgetSize)) add();
set_item_tooltip_shortcut(localize.get(TOOLTIP_ADD_LAYER), settings.shortcutAdd);
ImGui::SameLine();
shortcut(manager.chords[SHORTCUT_REMOVE]);
if (ImGui::Button(localize.get(BASIC_REMOVE_UNUSED), widgetSize)) remove_unused();
set_item_tooltip_shortcut(localize.get(TOOLTIP_REMOVE_UNUSED_LAYERS), settings.shortcutRemove);
}
ImGui::End();
manager.layer_properties_trigger();
if (ImGui::BeginPopupModal(propertiesPopup.label(), &propertiesPopup.isOpen, ImGuiWindowFlags_NoResize))
{
auto childSize = child_size_get(2);
auto& layer = manager.editLayer;
if (ImGui::BeginChild("##Child", childSize, ImGuiChildFlags_Borders))
{
if (propertiesPopup.isJustOpened) ImGui::SetKeyboardFocusHere();
input_text_string(localize.get(BASIC_NAME), &layer.name);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_ITEM_NAME));
combo_id_mapped(localize.get(LABEL_SPRITESHEET), &layer.spritesheetID, document.spritesheet.ids,
document.spritesheet.labels);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_LAYER_SPRITESHEET));
}
ImGui::EndChild();
auto widgetSize = widget_size_with_row_get(2);
shortcut(manager.chords[SHORTCUT_CONFIRM]);
if (ImGui::Button(reference == -1 ? localize.get(BASIC_ADD) : localize.get(BASIC_CONFIRM), widgetSize))
{
if (reference == -1)
{
auto layer_add = [&]()
{
auto id = map::next_id_get(anm2.content.layers);
anm2.content.layers[id] = layer;
selection = {id};
newLayerId = id;
};
DOCUMENT_EDIT(document, localize.get(EDIT_ADD_LAYER), Document::LAYERS, layer_add());
}
else
{
auto layer_set = [&]()
{
anm2.content.layers[reference] = layer;
selection = {reference};
};
DOCUMENT_EDIT(document, localize.get(EDIT_SET_LAYER_PROPERTIES), Document::LAYERS, layer_set());
}
manager.layer_properties_close();
}
ImGui::SameLine();
shortcut(manager.chords[SHORTCUT_CANCEL]);
if (ImGui::Button(localize.get(BASIC_CANCEL), widgetSize)) manager.layer_properties_close();
manager.layer_properties_end();
ImGui::EndPopup();
}
}
}
-17
View File
@@ -1,17 +0,0 @@
#pragma once
#include "clipboard.hpp"
#include "manager.hpp"
#include "resources.hpp"
#include "settings.hpp"
namespace anm2ed::imgui
{
class Layers
{
int newLayerId{-1};
public:
void update(Manager&, Settings&, Resources&, Clipboard&);
};
}
-233
View File
@@ -1,233 +0,0 @@
#include "nulls.hpp"
#include <format>
#include <ranges>
#include "log.hpp"
#include "map_.hpp"
#include "strings.hpp"
#include "toast.hpp"
using namespace anm2ed::resource;
using namespace anm2ed::util;
using namespace anm2ed::types;
namespace anm2ed::imgui
{
void Nulls::update(Manager& manager, Settings& settings, Resources& resources, Clipboard& clipboard)
{
auto& document = *manager.get();
auto& anm2 = document.anm2;
auto& reference = document.null.reference;
auto& selection = document.null.selection;
auto& propertiesPopup = manager.nullPropertiesPopup;
auto add = [&]() { manager.null_properties_open(); };
auto remove_unused = [&]()
{
auto unused = anm2.nulls_unused();
if (unused.empty()) return;
auto behavior = [&]()
{
for (auto& id : unused)
anm2.content.nulls.erase(id);
};
DOCUMENT_EDIT(document, localize.get(EDIT_REMOVE_UNUSED_NULLS), Document::NULLS, behavior());
};
auto copy = [&]()
{
if (selection.empty()) return;
std::string clipboardText{};
for (auto& id : selection)
clipboardText += anm2.content.nulls[id].to_string(id);
clipboard.set(clipboardText);
};
auto paste = [&]()
{
if (clipboard.is_empty()) return;
auto behavior = [&]()
{
auto maxNullIdBefore = anm2.content.nulls.empty() ? -1 : anm2.content.nulls.rbegin()->first;
std::string errorString{};
document.snapshot(localize.get(EDIT_PASTE_NULLS));
if (anm2.nulls_deserialize(clipboard.get(), merge::APPEND, &errorString))
{
if (!anm2.content.nulls.empty())
{
auto maxNullIdAfter = anm2.content.nulls.rbegin()->first;
if (maxNullIdAfter > maxNullIdBefore)
{
newNullId = maxNullIdAfter;
selection = {maxNullIdAfter};
reference = maxNullIdAfter;
}
}
document.change(Document::NULLS);
}
else
{
toasts.push(std::vformat(localize.get(TOAST_DESERIALIZE_NULLS_FAILED), std::make_format_args(errorString)));
logger.error(std::vformat(localize.get(TOAST_DESERIALIZE_NULLS_FAILED, anm2ed::ENGLISH),
std::make_format_args(errorString)));
}
};
DOCUMENT_EDIT(document, localize.get(EDIT_PASTE_NULLS), Document::NULLS, behavior());
};
auto properties = [&](int id) { manager.null_properties_open(id); };
if (ImGui::Begin(localize.get(LABEL_NULLS_WINDOW), &settings.windowIsNulls))
{
auto childSize = size_without_footer_get();
if (ImGui::BeginChild("##Nulls Child", childSize, true))
{
selection.start(anm2.content.nulls.size());
for (auto& [id, null] : anm2.content.nulls)
{
auto isSelected = selection.contains(id);
auto isReferenced = reference == id;
ImGui::PushID(id);
ImGui::SetNextItemSelectionUserData(id);
if (isReferenced) ImGui::PushFont(resources.fonts[font::ITALICS].get(), font::SIZE);
ImGui::Selectable(std::vformat(localize.get(FORMAT_NULL), std::make_format_args(id, null.name)).c_str(),
isSelected);
if (newNullId == id)
{
ImGui::SetScrollHereY(0.5f);
newNullId = -1;
}
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) properties(id);
if (isReferenced) ImGui::PopFont();
if (ImGui::BeginItemTooltip())
{
ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE);
ImGui::TextUnformatted(null.name.c_str());
ImGui::PopFont();
ImGui::TextUnformatted(std::vformat(localize.get(FORMAT_ID), std::make_format_args(id)).c_str());
ImGui::EndTooltip();
}
ImGui::PopID();
}
selection.finish();
if (shortcut(manager.chords[SHORTCUT_ADD], shortcut::FOCUSED)) add();
if (shortcut(manager.chords[SHORTCUT_REMOVE], shortcut::FOCUSED)) remove_unused();
if (shortcut(manager.chords[SHORTCUT_COPY], shortcut::FOCUSED)) copy();
if (shortcut(manager.chords[SHORTCUT_PASTE], shortcut::FOCUSED)) paste();
if (ImGui::BeginPopupContextWindow("##Context Menu", ImGuiPopupFlags_MouseButtonRight))
{
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(BASIC_PROPERTIES), nullptr, false, selection.size() == 1))
properties(*selection.begin());
if (ImGui::MenuItem(localize.get(BASIC_ADD), settings.shortcutAdd.c_str())) add();
if (ImGui::MenuItem(localize.get(BASIC_REMOVE_UNUSED), settings.shortcutRemove.c_str())) remove_unused();
ImGui::Separator();
if (ImGui::MenuItem(localize.get(BASIC_COPY), settings.shortcutCopy.c_str(), false, !selection.empty()))
copy();
if (ImGui::MenuItem(localize.get(BASIC_PASTE), settings.shortcutPaste.c_str(), false, !clipboard.is_empty()))
paste();
ImGui::EndPopup();
}
}
ImGui::EndChild();
auto widgetSize = widget_size_with_row_get(2);
shortcut(manager.chords[SHORTCUT_ADD]);
if (ImGui::Button(localize.get(BASIC_ADD), widgetSize)) add();
set_item_tooltip_shortcut(localize.get(TOOLTIP_ADD_NULL), settings.shortcutAdd);
ImGui::SameLine();
shortcut(manager.chords[SHORTCUT_REMOVE]);
if (ImGui::Button(localize.get(BASIC_REMOVE_UNUSED), widgetSize)) remove_unused();
set_item_tooltip_shortcut(localize.get(TOOLTIP_REMOVE_UNUSED_NULLS), settings.shortcutRemove);
}
ImGui::End();
manager.null_properties_trigger();
if (ImGui::BeginPopupModal(propertiesPopup.label(), &propertiesPopup.isOpen, ImGuiWindowFlags_NoResize))
{
auto childSize = child_size_get(2);
auto& null = manager.editNull;
if (ImGui::BeginChild("##Child", childSize, ImGuiChildFlags_Borders))
{
if (propertiesPopup.isJustOpened) ImGui::SetKeyboardFocusHere();
input_text_string(localize.get(BASIC_NAME), &null.name);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_NULL_NAME));
ImGui::Checkbox(localize.get(LABEL_RECT), &null.isShowRect);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_NULL_RECT));
}
ImGui::EndChild();
auto widgetSize = widget_size_with_row_get(2);
shortcut(manager.chords[SHORTCUT_CONFIRM]);
if (ImGui::Button(reference == -1 ? localize.get(BASIC_ADD) : localize.get(BASIC_CONFIRM), widgetSize))
{
if (reference == -1)
{
auto null_add = [&]()
{
auto id = map::next_id_get(anm2.content.nulls);
anm2.content.nulls[id] = null;
selection = {id};
newNullId = id;
};
DOCUMENT_EDIT(document, localize.get(EDIT_ADD_NULL), Document::NULLS, null_add());
}
else
{
auto null_set = [&]()
{
anm2.content.nulls[reference] = null;
selection = {reference};
};
DOCUMENT_EDIT(document, localize.get(EDIT_SET_NULL_PROPERTIES), Document::NULLS, null_set());
}
manager.null_properties_close();
}
ImGui::SameLine();
shortcut(manager.chords[SHORTCUT_CANCEL]);
if (ImGui::Button(localize.get(BASIC_CANCEL), widgetSize)) manager.null_properties_close();
ImGui::EndPopup();
}
manager.null_properties_end();
}
}
-17
View File
@@ -1,17 +0,0 @@
#pragma once
#include "clipboard.hpp"
#include "manager.hpp"
#include "resources.hpp"
#include "settings.hpp"
namespace anm2ed::imgui
{
class Nulls
{
int newNullId{-1};
public:
void update(Manager&, Settings&, Resources&, Clipboard&);
};
}
+1 -1
View File
@@ -2,7 +2,7 @@
#include <glm/gtc/type_ptr.hpp>
#include "imgui_.hpp"
#include "util/imgui/imgui.hpp"
#include "strings.hpp"
using namespace anm2ed::types;
-605
View File
@@ -1,605 +0,0 @@
#include "regions.hpp"
#include <algorithm>
#include <ranges>
#include <format>
#include "document.hpp"
#include "log.hpp"
#include "map_.hpp"
#include "math_.hpp"
#include "strings.hpp"
#include "toast.hpp"
#include "vector_.hpp"
#include "../../util/map_.hpp"
using namespace anm2ed::types;
using namespace anm2ed::resource;
using namespace anm2ed::util;
using namespace glm;
namespace anm2ed::imgui
{
void Regions::update(Manager& manager, Settings& settings, Resources& resources, Clipboard& clipboard)
{
auto& document = *manager.get();
auto& anm2 = document.anm2;
auto& selection = document.region.selection;
auto& frame = document.frames;
auto& spritesheetReference = document.spritesheet.reference;
auto& reference = document.region.reference;
auto style = ImGui::GetStyle();
auto spritesheet = map::find(anm2.content.spritesheets, spritesheetReference);
if (manager.isMakeRegionRequested)
{
spritesheetReference = manager.makeRegionSpritesheetId;
reference = -1;
editRegion = manager.makeRegion;
isPreserveEditRegionOnOpen = true;
propertiesPopup.open();
manager.isMakeRegionRequested = false;
spritesheet = map::find(anm2.content.spritesheets, spritesheetReference);
}
auto remove_unused = [&]()
{
if (!spritesheet) return;
auto unused = anm2.regions_unused(*spritesheet);
if (unused.empty()) return;
auto behavior = [&]()
{
for (auto& id : unused)
{
for (auto& animation : anm2.animations.items)
for (auto& layerAnimation : animation.layerAnimations | std::views::values)
for (auto& frame : layerAnimation.frames)
if (frame.regionID == id) frame.regionID = -1;
spritesheet->regions.erase(id);
auto& order = spritesheet->regionOrder;
order.erase(std::remove(order.begin(), order.end(), id), order.end());
}
};
DOCUMENT_EDIT(document, localize.get(EDIT_REMOVE_UNUSED_REGIONS), Document::SPRITESHEETS, behavior());
};
auto trim = [&]()
{
if (!spritesheet || selection.empty()) return;
auto behavior = [&]()
{
if (anm2.regions_trim(spritesheetReference, selection))
{
if (reference != -1 && !selection.contains(reference)) reference = *selection.begin();
document.reference = {document.reference.animationIndex};
frame.reference = -1;
frame.selection.clear();
}
};
DOCUMENT_EDIT(document, localize.get(EDIT_TRIM_REGIONS), Document::SPRITESHEETS, behavior());
};
auto copy = [&]()
{
if (!spritesheet || selection.empty()) return;
std::string clipboardText{};
for (auto& id : selection)
clipboardText += spritesheet->region_to_string(id);
clipboard.set(clipboardText);
};
auto paste = [&]()
{
if (!spritesheet || clipboard.is_empty()) return;
auto behavior = [&]()
{
auto maxRegionIdBefore = spritesheet->regions.empty() ? -1 : spritesheet->regions.rbegin()->first;
std::string errorString{};
document.snapshot(localize.get(EDIT_PASTE_REGIONS));
if (spritesheet->regions_deserialize(clipboard.get(), merge::APPEND, &errorString))
{
if (!spritesheet->regions.empty())
{
auto maxRegionIdAfter = spritesheet->regions.rbegin()->first;
if (maxRegionIdAfter > maxRegionIdBefore)
{
newRegionId = maxRegionIdAfter;
selection = {maxRegionIdAfter};
reference = maxRegionIdAfter;
}
}
document.change(Document::SPRITESHEETS);
}
else
{
toasts.push(std::vformat(localize.get(TOAST_DESERIALIZE_REGIONS_FAILED), std::make_format_args(errorString)));
logger.error(std::vformat(localize.get(TOAST_DESERIALIZE_REGIONS_FAILED, anm2ed::ENGLISH),
std::make_format_args(errorString)));
}
};
DOCUMENT_EDIT(document, localize.get(EDIT_PASTE_REGIONS), Document::SPRITESHEETS, behavior());
};
auto add_open = [&]()
{
reference = -1;
editRegion = anm2::Spritesheet::Region{};
propertiesPopup.open();
};
auto properties_open = [&](int id)
{
if (!spritesheet || !spritesheet->regions.contains(id)) return;
reference = id;
propertiesPopup.open();
};
auto context_menu = [&]()
{
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, style.WindowPadding);
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing);
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenOverlappedByWindow) &&
ImGui::IsMouseClicked(ImGuiMouseButton_Right))
ImGui::OpenPopup("##Region Context Menu");
if (ImGui::BeginPopup("##Region Context Menu"))
{
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(BASIC_PROPERTIES), nullptr, false, selection.size() == 1))
properties_open(*selection.begin());
if (ImGui::MenuItem(localize.get(BASIC_ADD), settings.shortcutAdd.c_str())) add_open();
if (ImGui::MenuItem(localize.get(BASIC_REMOVE_UNUSED), settings.shortcutRemove.c_str())) remove_unused();
if (ImGui::MenuItem(localize.get(BASIC_TRIM), nullptr, false, !selection.empty())) trim();
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_TRIM_REGIONS));
ImGui::Separator();
if (ImGui::MenuItem(localize.get(BASIC_COPY), settings.shortcutCopy.c_str(), false, !selection.empty())) copy();
if (ImGui::MenuItem(localize.get(BASIC_PASTE), settings.shortcutPaste.c_str(), false, !clipboard.is_empty()))
paste();
ImGui::EndPopup();
}
ImGui::PopStyleVar(2);
};
if (ImGui::Begin(localize.get(LABEL_REGIONS_WINDOW), &settings.windowIsRegions))
{
if (!spritesheet)
{
ImGui::TextUnformatted(localize.get(TEXT_SELECT_SPRITESHEET));
ImGui::End();
return;
}
auto childSize = size_without_footer_get();
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2());
if (ImGui::BeginChild("##Regions Child", childSize, ImGuiChildFlags_Borders))
{
auto regionChildSize = ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetTextLineHeightWithSpacing() * 2);
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2());
auto rebuild_order = [&]()
{
spritesheet->regionOrder.clear();
spritesheet->regionOrder.reserve(spritesheet->regions.size());
for (auto id : spritesheet->regions | std::views::keys)
spritesheet->regionOrder.push_back(id);
};
if (spritesheet->regionOrder.size() != spritesheet->regions.size())
rebuild_order();
else
{
bool isOrderValid = true;
for (auto id : spritesheet->regionOrder)
if (!spritesheet->regions.contains(id))
{
isOrderValid = false;
break;
}
if (!isOrderValid) rebuild_order();
}
selection.set_index_map(&spritesheet->regionOrder);
selection.start(spritesheet->regionOrder.size());
if (ImGui::Shortcut(ImGuiMod_Ctrl | ImGuiKey_A, ImGuiInputFlags_RouteFocused))
{
selection.clear();
for (auto& id : spritesheet->regionOrder)
selection.insert(id);
}
if (ImGui::Shortcut(ImGuiKey_Escape, ImGuiInputFlags_RouteFocused)) selection.clear();
auto scroll_to_item = [&](float itemHeight, bool isTarget)
{
if (!isTarget) return;
auto windowHeight = ImGui::GetWindowHeight();
auto targetTop = ImGui::GetCursorPosY();
auto targetBottom = targetTop + itemHeight;
auto visibleTop = ImGui::GetScrollY();
auto visibleBottom = visibleTop + windowHeight;
if (targetTop < visibleTop)
ImGui::SetScrollY(targetTop);
else if (targetBottom > visibleBottom)
ImGui::SetScrollY(targetBottom - windowHeight);
};
int scrollTargetId = -1;
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) &&
(ImGui::IsKeyPressed(ImGuiKey_UpArrow, true) || ImGui::IsKeyPressed(ImGuiKey_DownArrow, true)))
{
auto& order = spritesheet->regionOrder;
if (!order.empty())
{
int delta = ImGui::IsKeyPressed(ImGuiKey_UpArrow, true) ? -1 : 1;
int current = reference;
if (current == -1 && !selection.empty()) current = *selection.begin();
auto it = std::find(order.begin(), order.end(), current);
int index = it == order.end() ? 0 : (int)std::distance(order.begin(), it);
index = std::clamp(index + delta, 0, (int)order.size() - 1);
int nextId = order[index];
selection = {nextId};
reference = nextId;
document.reference = {document.reference.animationIndex};
frame.reference = -1;
frame.selection.clear();
scrollTargetId = nextId;
}
}
bool isValid = spritesheet->is_valid();
auto& texture = isValid ? spritesheet->texture : resources.icons[icon::NONE];
auto tintColor = !isValid ? ImVec4(1.0f, 0.25f, 0.25f, 1.0f) : ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
static std::vector<int> dragDropSelection{};
bool isRegionDragDropSourceSubmitted = false;
for (int i = 0; i < (int)spritesheet->regionOrder.size(); i++)
{
int id = spritesheet->regionOrder[i];
auto regionIt = spritesheet->regions.find(id);
if (regionIt == spritesheet->regions.end()) continue;
auto& region = regionIt->second;
auto isNewRegion = newRegionId == id;
auto nameCStr = region.name.c_str();
auto isSelected = selection.contains(id);
auto isReferenced = id == reference;
ImGui::PushID(id);
scroll_to_item(regionChildSize.y, scrollTargetId == id);
if (ImGui::BeginChild("##Region Child", regionChildSize, ImGuiChildFlags_Borders))
{
auto cursorPos = ImGui::GetCursorPos();
ImGui::SetNextItemSelectionUserData(i);
ImGui::SetNextItemStorageID(id);
if (ImGui::Selectable("##Region Selectable", isSelected, 0, regionChildSize))
{
reference = id;
document.reference = {document.reference.animationIndex};
frame.reference = -1;
frame.selection.clear();
}
if (scrollTargetId == id) ImGui::SetItemDefaultFocus();
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) propertiesPopup.open();
auto viewport = ImGui::GetMainViewport();
auto maxPreviewSize = to_vec2(viewport->Size) * 0.5f;
vec2 regionSize = glm::max(region.size, vec2(1.0f));
vec2 previewSize = regionSize;
if (previewSize.x > maxPreviewSize.x || previewSize.y > maxPreviewSize.y)
{
auto scale = glm::min(maxPreviewSize.x / previewSize.x, maxPreviewSize.y / previewSize.y);
previewSize *= scale;
}
vec2 uvMin{};
vec2 uvMax{1.0f, 1.0f};
if (isValid)
{
uvMin = region.crop / vec2(texture.size);
uvMax = (region.crop + region.size) / vec2(texture.size);
}
auto textWidth = ImGui::CalcTextSize(nameCStr).x;
auto tooltipPadding = style.WindowPadding.x * 4.0f;
auto minWidth = previewSize.x + style.ItemSpacing.x + textWidth + tooltipPadding;
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, style.WindowPadding);
if (ImGui::IsItemHovered(ImGuiHoveredFlags_ForTooltip) && !ImGui::IsMouseDragging(ImGuiMouseButton_Left))
{
ImGui::SetNextWindowSize(ImVec2(minWidth, 0), ImGuiCond_Appearing);
if (ImGui::BeginItemTooltip())
{
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2());
auto childFlags = ImGuiChildFlags_Borders | ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY;
auto noScrollFlags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse;
if (ImGui::BeginChild("##Region Tooltip Image Child", to_imvec2(previewSize), childFlags,
noScrollFlags))
ImGui::ImageWithBg(texture.id, to_imvec2(previewSize), to_imvec2(uvMin), to_imvec2(uvMax), ImVec4(),
tintColor);
ImGui::EndChild();
ImGui::PopStyleVar();
ImGui::SameLine();
auto infoChildFlags = ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY;
if (ImGui::BeginChild("##Region Info Tooltip Child", ImVec2(), infoChildFlags, noScrollFlags))
{
ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE);
ImGui::TextUnformatted(nameCStr);
ImGui::PopFont();
ImGui::TextUnformatted(std::vformat(localize.get(FORMAT_ID), std::make_format_args(id)).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::EndChild();
ImGui::EndTooltip();
}
}
ImGui::PopStyleVar(2);
if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceNoPreviewTooltip))
{
isRegionDragDropSourceSubmitted = true;
if (selection.contains(id))
dragDropSelection.assign(selection.begin(), selection.end());
else
dragDropSelection = {id};
ImGui::SetDragDropPayload("Region Drag Drop", dragDropSelection.data(),
dragDropSelection.size() * sizeof(int));
ImGui::EndDragDropSource();
}
if (ImGui::BeginDragDropTarget())
{
if (auto payload = ImGui::AcceptDragDropPayload("Region Drag Drop"))
{
auto payloadIds = (int*)(payload->Data);
int payloadCount = (int)(payload->DataSize / sizeof(int));
std::vector<int> indices{};
indices.reserve(payloadCount);
for (int payloadIndex = 0; payloadIndex < payloadCount; payloadIndex++)
{
int payloadId = payloadIds[payloadIndex];
int index = vector::find_index(spritesheet->regionOrder, payloadId);
if (index != -1) indices.push_back(index);
}
if (!indices.empty())
{
std::sort(indices.begin(), indices.end());
DOCUMENT_EDIT(document, localize.get(EDIT_MOVE_REGIONS), Document::SPRITESHEETS,
vector::move_indices(spritesheet->regionOrder, indices, i));
}
}
ImGui::EndDragDropTarget();
}
auto imageSize = to_imvec2(vec2(regionChildSize.y));
auto aspectRatio = region.size.y != 0.0f ? (float)region.size.x / region.size.y : 1.0f;
if (imageSize.x / imageSize.y > aspectRatio)
imageSize.x = imageSize.y * aspectRatio;
else
imageSize.y = imageSize.x / aspectRatio;
ImGui::SetCursorPos(cursorPos);
ImGui::ImageWithBg(texture.id, imageSize, to_imvec2(uvMin), to_imvec2(uvMax), ImVec4(), tintColor);
ImGui::SetCursorPos(ImVec2(regionChildSize.y + style.ItemSpacing.x,
regionChildSize.y - regionChildSize.y / 2 - ImGui::GetTextLineHeight() / 2));
if (isReferenced) ImGui::PushFont(resources.fonts[font::ITALICS].get(), font::SIZE);
ImGui::TextUnformatted(nameCStr);
if (isReferenced) ImGui::PopFont();
}
ImGui::EndChild();
if (isNewRegion)
{
ImGui::SetScrollHereY(0.5f);
newRegionId = -1;
}
ImGui::PopID();
}
auto regionDragPayload = ImGui::GetDragDropPayload();
bool isRegionDragActive = regionDragPayload && regionDragPayload->IsDataType("Region Drag Drop");
if (isRegionDragActive && !isRegionDragDropSourceSubmitted && !dragDropSelection.empty() &&
ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceExtern | ImGuiDragDropFlags_SourceNoPreviewTooltip))
{
ImGui::SetDragDropPayload("Region Drag Drop", dragDropSelection.data(),
dragDropSelection.size() * sizeof(int));
ImGui::EndDragDropSource();
}
if (isRegionDragActive && !dragDropSelection.empty())
{
auto mousePos = ImGui::GetIO().MousePos;
auto tooltipOffset = ImVec2(16.0f, 16.0f);
ImGui::SetNextWindowPos(ImVec2(mousePos.x + tooltipOffset.x, mousePos.y + tooltipOffset.y));
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, style.WindowPadding);
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing);
if (ImGui::BeginTooltip())
{
for (auto regionId : dragDropSelection)
{
auto dragIt = spritesheet->regions.find(regionId);
if (dragIt == spritesheet->regions.end()) continue;
ImGui::TextUnformatted(dragIt->second.name.c_str());
}
ImGui::EndTooltip();
}
ImGui::PopStyleVar(2);
}
ImGui::PopStyleVar();
selection.finish();
if (shortcut(manager.chords[SHORTCUT_ADD], shortcut::FOCUSED)) add_open();
if (shortcut(manager.chords[SHORTCUT_REMOVE], shortcut::FOCUSED)) remove_unused();
if (shortcut(manager.chords[SHORTCUT_COPY], shortcut::FOCUSED)) copy();
if (shortcut(manager.chords[SHORTCUT_PASTE], shortcut::FOCUSED)) paste();
}
ImGui::EndChild();
ImGui::PopStyleVar();
context_menu();
auto rowOneWidgetSize = widget_size_with_row_get(2);
shortcut(manager.chords[SHORTCUT_ADD]);
if (ImGui::Button(localize.get(BASIC_ADD), rowOneWidgetSize)) add_open();
set_item_tooltip_shortcut(localize.get(TOOLTIP_ADD_REGION), settings.shortcutAdd);
ImGui::SameLine();
shortcut(manager.chords[SHORTCUT_REMOVE]);
if (ImGui::Button(localize.get(BASIC_REMOVE_UNUSED), rowOneWidgetSize)) remove_unused();
set_item_tooltip_shortcut(localize.get(TOOLTIP_REMOVE_UNUSED_REGIONS), settings.shortcutAdd);
}
ImGui::End();
propertiesPopup.trigger();
if (ImGui::BeginPopupModal(propertiesPopup.label(), &propertiesPopup.isOpen, ImGuiWindowFlags_NoResize))
{
if (!spritesheet || (reference != -1 && !spritesheet->regions.contains(reference)))
{
propertiesPopup.close();
ImGui::EndPopup();
return;
}
auto childSize = child_size_get(5);
auto& region = reference == -1 ? editRegion : spritesheet->regions.at(reference);
if (propertiesPopup.isJustOpened)
{
if (!isPreserveEditRegionOnOpen)
editRegion = anm2::Spritesheet::Region{};
isPreserveEditRegionOnOpen = false;
}
if (ImGui::BeginChild("##Child", childSize, ImGuiChildFlags_Borders))
{
const char* originOptions[] = {localize.get(LABEL_REGION_ORIGIN_TOP_LEFT),
localize.get(LABEL_REGION_ORIGIN_CENTER),
localize.get(LABEL_REGION_ORIGIN_CUSTOM)};
if (propertiesPopup.isJustOpened) ImGui::SetKeyboardFocusHere();
input_text_string(localize.get(BASIC_NAME), &region.name);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_ITEM_NAME));
ImGui::DragFloat2(localize.get(BASIC_CROP), value_ptr(region.crop), DRAG_SPEED, 0.0f, 0.0f,
math::vec2_format_get(region.crop));
ImGui::DragFloat2(localize.get(BASIC_SIZE), value_ptr(region.size), DRAG_SPEED, 0.0f, 0.0f,
math::vec2_format_get(region.size));
ImGui::BeginDisabled(region.origin != anm2::Spritesheet::Region::CUSTOM);
ImGui::DragFloat2(localize.get(BASIC_PIVOT), value_ptr(region.pivot), DRAG_SPEED, 0.0f, 0.0f,
math::vec2_format_get(region.pivot));
ImGui::EndDisabled();
if (ImGui::Combo(localize.get(LABEL_REGION_PROPERTIES_ORIGIN), (int*)&region.origin, originOptions,
IM_ARRAYSIZE(originOptions)))
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_REGION_PROPERTIES_ORIGIN));
if (region.origin == anm2::Spritesheet::Region::TOP_LEFT)
region.pivot = {};
else if (region.origin == anm2::Spritesheet::Region::ORIGIN_CENTER)
region.pivot = {(int)(region.size.x / 2.0f), (int)(region.size.y / 2.0f)};
}
ImGui::EndChild();
auto widgetSize = widget_size_with_row_get(2);
shortcut(manager.chords[SHORTCUT_CONFIRM]);
if (ImGui::Button(reference == -1 ? localize.get(BASIC_ADD) : localize.get(BASIC_CONFIRM), widgetSize))
{
if (reference == -1)
{
auto add = [&]()
{
auto id = map::next_id_get(spritesheet->regions);
spritesheet->regions[id] = region;
spritesheet->regionOrder.push_back(id);
selection = {id};
newRegionId = id;
};
DOCUMENT_EDIT(document, localize.get(EDIT_ADD_REGION), Document::SPRITESHEETS, add());
}
else
{
auto set = [&]()
{
spritesheet->regions.at(reference) = region;
selection = {reference};
};
DOCUMENT_EDIT(document, localize.get(EDIT_SET_REGION_PROPERTIES), Document::SPRITESHEETS, set());
}
frame.reference = -1;
frame.selection.clear();
propertiesPopup.close();
}
ImGui::SameLine();
shortcut(manager.chords[SHORTCUT_CANCEL]);
if (ImGui::Button(localize.get(BASIC_CANCEL), widgetSize)) propertiesPopup.close();
ImGui::EndPopup();
}
propertiesPopup.end();
}
}
-20
View File
@@ -1,20 +0,0 @@
#pragma once
#include "clipboard.hpp"
#include "manager.hpp"
#include "resources.hpp"
#include "settings.hpp"
namespace anm2ed::imgui
{
class Regions
{
public:
anm2::Spritesheet::Region editRegion{};
int newRegionId{-1};
bool isPreserveEditRegionOnOpen{};
imgui::PopupHelper propertiesPopup{imgui::PopupHelper(LABEL_REGION_PROPERTIES, imgui::POPUP_SMALL_NO_HEIGHT)};
void update(Manager&, Settings&, Resources&, Clipboard&);
};
}
-391
View File
@@ -1,391 +0,0 @@
#include "sounds.hpp"
#include <algorithm>
#include <format>
#include <ranges>
#include <vector>
#include "log.hpp"
#include "path_.hpp"
#include "strings.hpp"
#include "toast.hpp"
using namespace anm2ed::util;
using namespace anm2ed::types;
using namespace anm2ed::resource;
using namespace glm;
namespace anm2ed::imgui
{
void Sounds::update(Manager& manager, Settings& settings, Resources& resources, Dialog& dialog, Clipboard& clipboard)
{
auto& document = *manager.get();
auto& anm2 = document.anm2;
auto& reference = document.sound.reference;
auto& selection = document.sound.selection;
auto style = ImGui::GetStyle();
auto add_open = [&]() { dialog.file_open(Dialog::SOUND_OPEN); };
auto replace_open = [&]() { dialog.file_open(Dialog::SOUND_REPLACE); };
auto play = [&](anm2::Sound& sound) { sound.play(); };
auto add = [&](const std::filesystem::path& path)
{
auto behavior = [&]()
{
int id{};
auto pathString = path::to_utf8(path);
if (anm2.sound_add(document.directory_get(), path, id))
{
selection = {id};
newSoundId = id;
toasts.push(std::vformat(localize.get(TOAST_SOUND_INITIALIZED), std::make_format_args(id, pathString)));
logger.info(std::vformat(localize.get(TOAST_SOUND_INITIALIZED, anm2ed::ENGLISH),
std::make_format_args(id, pathString)));
}
else
{
toasts.push(std::vformat(localize.get(TOAST_SOUND_INITIALIZE_FAILED), std::make_format_args(pathString)));
logger.error(std::vformat(localize.get(TOAST_SOUND_INITIALIZE_FAILED, anm2ed::ENGLISH),
std::make_format_args(pathString)));
}
};
DOCUMENT_EDIT(document, localize.get(EDIT_ADD_SOUND), Document::SOUNDS, behavior());
};
auto remove_unused = [&]()
{
auto unused = anm2.sounds_unused();
if (unused.empty()) return;
auto behavior = [&]()
{
for (auto& id : unused)
anm2.content.sounds.erase(id);
};
DOCUMENT_EDIT(document, localize.get(EDIT_REMOVE_UNUSED_SOUNDS), Document::SOUNDS, behavior());
};
auto reload = [&]()
{
auto behavior = [&]()
{
for (auto& id : selection)
{
anm2::Sound& sound = anm2.content.sounds[id];
sound.reload(document.directory_get());
auto pathString = path::to_utf8(sound.path);
toasts.push(std::vformat(localize.get(TOAST_RELOAD_SOUND), std::make_format_args(id, pathString)));
logger.info(
std::vformat(localize.get(TOAST_RELOAD_SOUND, anm2ed::ENGLISH), std::make_format_args(id, pathString)));
}
};
DOCUMENT_EDIT(document, localize.get(EDIT_RELOAD_SOUNDS), Document::SOUNDS, behavior());
};
auto replace = [&](const std::filesystem::path& path)
{
if (selection.size() != 1 || path.empty()) return;
auto behavior = [&]()
{
auto& id = *selection.begin();
anm2::Sound& sound = anm2.content.sounds[id];
sound = anm2::Sound(document.directory_get(), path);
auto pathString = path::to_utf8(sound.path);
toasts.push(std::vformat(localize.get(TOAST_REPLACE_SOUND), std::make_format_args(id, pathString)));
logger.info(
std::vformat(localize.get(TOAST_REPLACE_SOUND, anm2ed::ENGLISH), std::make_format_args(id, pathString)));
};
DOCUMENT_EDIT(document, localize.get(EDIT_REPLACE_SOUND), Document::SOUNDS, behavior());
};
auto open_directory = [&](anm2::Sound& sound)
{
if (sound.path.empty()) return;
std::error_code ec{};
auto absolutePath = std::filesystem::weakly_canonical(document.directory_get() / sound.path, ec);
if (ec) absolutePath = document.directory_get() / sound.path;
auto target = std::filesystem::is_directory(absolutePath) ? absolutePath
: std::filesystem::is_directory(absolutePath.parent_path()) ? absolutePath.parent_path()
: document.directory_get();
dialog.file_explorer_open(target);
};
auto copy = [&]()
{
if (selection.empty()) return;
std::string clipboardText{};
for (auto& id : selection)
clipboardText += anm2.content.sounds[id].to_string(id);
clipboard.set(clipboardText);
};
auto paste = [&]()
{
if (clipboard.is_empty()) return;
auto behavior = [&]()
{
auto maxSoundIdBefore = anm2.content.sounds.empty() ? -1 : anm2.content.sounds.rbegin()->first;
std::string errorString{};
document.snapshot(localize.get(TOAST_SOUNDS_PASTE));
if (anm2.sounds_deserialize(clipboard.get(), document.directory_get(), merge::APPEND, &errorString))
{
if (!anm2.content.sounds.empty())
{
auto maxSoundIdAfter = anm2.content.sounds.rbegin()->first;
if (maxSoundIdAfter > maxSoundIdBefore)
{
newSoundId = maxSoundIdAfter;
selection = {maxSoundIdAfter};
reference = maxSoundIdAfter;
}
}
document.change(Document::SOUNDS);
}
else
{
toasts.push(std::vformat(localize.get(TOAST_SOUNDS_DESERIALIZE_ERROR), std::make_format_args(errorString)));
logger.error(std::vformat(localize.get(TOAST_SOUNDS_DESERIALIZE_ERROR, anm2ed::ENGLISH),
std::make_format_args(errorString)));
}
};
DOCUMENT_EDIT(document, localize.get(EDIT_PASTE_SOUNDS), Document::SOUNDS, behavior());
};
auto context_menu = [&]()
{
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, style.WindowPadding);
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing);
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenOverlappedByWindow) &&
ImGui::IsMouseClicked(ImGuiMouseButton_Right))
ImGui::OpenPopup("##Sound Context Menu");
if (ImGui::BeginPopup("##Sound Context Menu"))
{
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_PLAY), nullptr, false, selection.size() == 1))
play(anm2.content.sounds[*selection.begin()]);
if (ImGui::MenuItem(localize.get(BASIC_OPEN_DIRECTORY), nullptr, false, selection.size() == 1))
open_directory(anm2.content.sounds[*selection.begin()]);
if (ImGui::MenuItem(localize.get(BASIC_ADD), settings.shortcutAdd.c_str())) add_open();
if (ImGui::MenuItem(localize.get(BASIC_REMOVE_UNUSED), settings.shortcutRemove.c_str())) remove_unused();
if (ImGui::MenuItem(localize.get(BASIC_RELOAD), nullptr, false, !selection.empty())) reload();
if (ImGui::MenuItem(localize.get(BASIC_REPLACE), nullptr, false, selection.size() == 1)) replace_open();
ImGui::Separator();
if (ImGui::MenuItem(localize.get(BASIC_COPY), settings.shortcutCopy.c_str(), false, !selection.empty())) copy();
if (ImGui::MenuItem(localize.get(BASIC_PASTE), settings.shortcutPaste.c_str(), false, !clipboard.is_empty()))
paste();
ImGui::EndPopup();
}
ImGui::PopStyleVar(2);
};
if (ImGui::Begin(localize.get(LABEL_SOUNDS_WINDOW), &settings.windowIsSounds))
{
auto childSize = imgui::size_without_footer_get();
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2());
if (ImGui::BeginChild("##Sounds Child", childSize, ImGuiChildFlags_Borders))
{
auto soundChildSize = ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetTextLineHeightWithSpacing() * 2);
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2());
selection.start(anm2.content.sounds.size());
if (ImGui::Shortcut(ImGuiMod_Ctrl | ImGuiKey_A, ImGuiInputFlags_RouteFocused))
{
selection.clear();
for (auto& id : anm2.content.sounds | std::views::keys)
selection.insert(id);
}
if (ImGui::Shortcut(ImGuiKey_Escape, ImGuiInputFlags_RouteFocused)) selection.clear();
auto scroll_to_item = [&](float itemHeight, bool isTarget)
{
if (!isTarget) return;
auto windowHeight = ImGui::GetWindowHeight();
auto targetTop = ImGui::GetCursorPosY();
auto targetBottom = targetTop + itemHeight;
auto visibleTop = ImGui::GetScrollY();
auto visibleBottom = visibleTop + windowHeight;
if (targetTop < visibleTop)
ImGui::SetScrollY(targetTop);
else if (targetBottom > visibleBottom)
ImGui::SetScrollY(targetBottom - windowHeight);
};
int scrollTargetId = -1;
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) &&
(ImGui::IsKeyPressed(ImGuiKey_UpArrow, true) || ImGui::IsKeyPressed(ImGuiKey_DownArrow, true)))
{
std::vector<int> ids{};
ids.reserve(anm2.content.sounds.size());
for (auto& [id, sound] : anm2.content.sounds)
ids.push_back(id);
if (!ids.empty())
{
int delta = ImGui::IsKeyPressed(ImGuiKey_UpArrow, true) ? -1 : 1;
int current = reference;
if (current == -1 && !selection.empty()) current = *selection.begin();
auto it = std::find(ids.begin(), ids.end(), current);
int index = it == ids.end() ? 0 : (int)std::distance(ids.begin(), it);
index = std::clamp(index + delta, 0, (int)ids.size() - 1);
int nextId = ids[index];
selection = {nextId};
reference = nextId;
scrollTargetId = nextId;
}
}
for (auto& [id, sound] : anm2.content.sounds)
{
auto isNewSound = newSoundId == id;
ImGui::PushID(id);
scroll_to_item(soundChildSize.y, scrollTargetId == id);
if (ImGui::BeginChild("##Sound Child", soundChildSize, ImGuiChildFlags_Borders))
{
auto isSelected = selection.contains(id);
auto cursorPos = ImGui::GetCursorPos();
bool isValid = sound.is_valid();
auto& soundIcon = isValid ? resources.icons[icon::SOUND] : resources.icons[icon::NONE];
auto tintColor = !isValid ? ImVec4(1.0f, 0.25f, 0.25f, 1.0f) : ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
auto pathString = path::to_utf8(sound.path);
ImGui::SetNextItemSelectionUserData(id);
ImGui::SetNextItemStorageID(id);
if (ImGui::Selectable("##Sound Selectable", isSelected, 0, soundChildSize))
{
reference = id;
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) play(sound);
}
if (scrollTargetId == id) ImGui::SetItemDefaultFocus();
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) open_directory(sound);
auto textWidth = ImGui::CalcTextSize(pathString.c_str()).x;
auto tooltipPadding = style.WindowPadding.x * 4.0f;
auto minWidth = textWidth + style.ItemSpacing.x + tooltipPadding;
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, style.WindowPadding);
ImGui::SetNextWindowSize(ImVec2(minWidth, 0), ImGuiCond_Appearing);
if (ImGui::BeginItemTooltip())
{
ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE);
ImGui::TextUnformatted(pathString.c_str());
ImGui::PopFont();
ImGui::Text("%s: %d", localize.get(BASIC_ID), id);
if (!isValid)
{
ImGui::Spacing();
ImGui::TextWrapped("%s", localize.get(TOOLTIP_SOUND_INVALID));
}
else
{
ImGui::Text("%s", localize.get(TEXT_SOUND_PLAY));
ImGui::Text("%s", localize.get(TEXT_OPEN_DIRECTORY));
}
ImGui::EndTooltip();
}
ImGui::PopStyleVar(2);
ImGui::SetCursorPos(cursorPos);
auto imageSize = to_imvec2(vec2(soundChildSize.y));
ImGui::ImageWithBg(soundIcon.id, imageSize, ImVec2(), ImVec2(1, 1), ImVec4(), tintColor);
ImGui::SetCursorPos(ImVec2(soundChildSize.y + style.ItemSpacing.x,
soundChildSize.y - soundChildSize.y / 2 - ImGui::GetTextLineHeight() / 2));
ImGui::TextUnformatted(
std::vformat(localize.get(FORMAT_SOUND), std::make_format_args(id, pathString)).c_str());
}
ImGui::EndChild();
if (isNewSound)
{
ImGui::SetScrollHereY(0.5f);
newSoundId = -1;
}
ImGui::PopID();
}
ImGui::PopStyleVar();
selection.finish();
}
ImGui::EndChild();
ImGui::PopStyleVar();
context_menu();
auto widgetSize = imgui::widget_size_with_row_get(4);
imgui::shortcut(manager.chords[SHORTCUT_ADD]);
if (ImGui::Button(localize.get(BASIC_ADD), widgetSize)) add_open();
imgui::set_item_tooltip_shortcut(localize.get(TOOLTIP_SOUND_ADD), settings.shortcutAdd);
if (dialog.is_selected(Dialog::SOUND_OPEN))
{
add(dialog.path);
dialog.reset();
}
ImGui::SameLine();
imgui::shortcut(manager.chords[SHORTCUT_REMOVE]);
if (ImGui::Button(localize.get(BASIC_REMOVE_UNUSED), widgetSize)) remove_unused();
imgui::set_item_tooltip_shortcut(localize.get(TOOLTIP_REMOVE_UNUSED_SOUNDS), settings.shortcutRemove);
ImGui::SameLine();
ImGui::BeginDisabled(selection.empty());
if (ImGui::Button(localize.get(BASIC_RELOAD), widgetSize)) reload();
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_RELOAD_SOUNDS));
ImGui::EndDisabled();
ImGui::SameLine();
ImGui::BeginDisabled(selection.size() != 1);
if (ImGui::Button(localize.get(BASIC_REPLACE), widgetSize)) replace_open();
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_REPLACE_SOUND));
ImGui::EndDisabled();
if (dialog.is_selected(Dialog::SOUND_REPLACE))
{
replace(dialog.path);
dialog.reset();
}
if (imgui::shortcut(manager.chords[SHORTCUT_ADD], shortcut::FOCUSED)) add_open();
if (imgui::shortcut(manager.chords[SHORTCUT_REMOVE], shortcut::FOCUSED)) remove_unused();
if (imgui::shortcut(manager.chords[SHORTCUT_COPY], shortcut::FOCUSED)) copy();
if (imgui::shortcut(manager.chords[SHORTCUT_PASTE], shortcut::FOCUSED)) paste();
}
ImGui::End();
}
}
-18
View File
@@ -1,18 +0,0 @@
#pragma once
#include "clipboard.hpp"
#include "dialog.hpp"
#include "manager.hpp"
#include "resources.hpp"
#include "settings.hpp"
namespace anm2ed::imgui
{
class Sounds
{
int newSoundId{-1};
public:
void update(Manager&, Settings&, Resources&, Dialog&, Clipboard&);
};
}
+335 -203
View File
@@ -2,11 +2,13 @@
#include <cmath>
#include <format>
#include <set>
#include <utility>
#include "imgui_.hpp"
#include "actions.hpp"
#include "util/imgui/imgui.hpp"
#include "imgui_internal.h"
#include "math_.hpp"
#include "math.hpp"
#include "strings.hpp"
#include "tool.hpp"
#include "types.hpp"
@@ -20,12 +22,17 @@ namespace anm2ed::imgui
{
SpritesheetEditor::SpritesheetEditor() : Canvas(vec2()) {}
bool SpritesheetEditor::is_focused_get() const { return isFocused; }
void SpritesheetEditor::update(Manager& manager, Settings& settings, Resources& resources)
{
isFocused = false;
auto& document = *manager.get();
auto& anm2 = document.anm2;
auto& reference = document.reference;
auto& referenceSpritesheet = document.spritesheet.reference;
auto referenceItemType = static_cast<ItemType>(reference.itemType);
auto& pan = document.editorPan;
auto& zoom = document.editorZoom;
auto& backgroundColor = settings.editorBackgroundColor;
@@ -38,7 +45,8 @@ namespace anm2ed::imgui
auto& zoomStep = settings.inputZoomStep;
auto& isBorder = settings.editorIsBorder;
auto& isTransparent = settings.editorIsTransparent;
auto spritesheet = document.spritesheet_get();
auto spritesheet = anm2.element_get(ElementType::SPRITESHEET, referenceSpritesheet);
auto texture = document.texture_get(referenceSpritesheet);
auto& tool = settings.tool;
auto& shaderGrid = resources.shaders[shader::GRID];
auto& shaderTexture = resources.shaders[shader::TEXTURE];
@@ -79,8 +87,8 @@ namespace anm2ed::imgui
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});
if (texture && texture->is_valid())
set_to_rect(zoom, pan, {0, 0, (float)texture->size.x, (float)texture->size.y});
};
auto zoom_adjust = [&](float delta)
@@ -94,8 +102,12 @@ namespace anm2ed::imgui
auto zoom_in = [&]() { zoom_adjust(zoomStep); };
auto zoom_out = [&]() { zoom_adjust(-zoomStep); };
auto region_get = [&](int id)
{ return spritesheet ? element_child_id_get(*spritesheet, ElementType::REGION, id) : nullptr; };
if (ImGui::Begin(localize.get(LABEL_SPRITESHEET_EDITOR_WINDOW), &settings.windowIsSpritesheetEditor))
{
isFocused = ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows);
auto childSize = ImVec2(imgui::row_widget_width_get(3),
(ImGui::GetTextLineHeightWithSpacing() * 4) + (ImGui::GetStyle().WindowPadding.y * 2));
@@ -196,18 +208,17 @@ namespace anm2ed::imgui
viewport_set();
clear(isTransparent ? vec4(0) : vec4(backgroundColor, 1.0f));
auto frame = document.frame_get();
auto item = document.item_get();
auto frame =
anm2.element_get(reference.animationIndex, referenceItemType, reference.frameIndex, reference.itemID);
if (spritesheet && spritesheet->texture.is_valid())
if (spritesheet && texture && texture->is_valid())
{
auto& texture = spritesheet->texture;
auto transform = transform_get(zoom, pan);
auto spritesheetModel = math::quad_model_get(texture.size);
auto spritesheetModel = math::quad_model_get(texture->size);
auto spritesheetTransform = transform * spritesheetModel;
texture_render(shaderTexture, texture.id, spritesheetTransform);
texture_render(shaderTexture, texture->id, spritesheetTransform);
if (isGrid) grid_render(shaderGrid, zoom, pan, gridSize, gridOffset, gridColor);
@@ -217,32 +228,29 @@ namespace anm2ed::imgui
if (hoveredRegionId != -1)
{
auto regionIt = spritesheet->regions.find(hoveredRegionId);
if (regionIt != spritesheet->regions.end())
if (auto region = region_get(hoveredRegionId))
{
auto& region = regionIt->second;
auto cropModel = math::quad_model_get(region.size, region.crop);
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));
}
}
auto layerIt = anm2.content.layers.find(reference.itemID);
auto layer = anm2.element_get(ElementType::LAYER_ELEMENT, reference.itemID);
bool isReferenceLayerOnSpritesheet =
frame && reference.itemID > -1 && layerIt != anm2.content.layers.end() &&
layerIt->second.spritesheetID == referenceSpritesheet;
frame && reference.itemID > -1 && layer && layer->spritesheetId == referenceSpritesheet;
int highlightedRegionId = -1;
if (isReferenceLayerOnSpritesheet && frame->regionID != -1 && spritesheet->regions.contains(frame->regionID))
if (isReferenceLayerOnSpritesheet && frame->regionId != -1 && region_get(frame->regionId))
{
highlightedRegionId = frame->regionID;
highlightedRegionId = frame->regionId;
}
else if (regionReference != -1 && spritesheet->regions.contains(regionReference))
else if (regionReference != -1 && region_get(regionReference))
{
highlightedRegionId = regionReference;
}
auto draw_region_rect = [&](anm2::Spritesheet::Region& region, vec4 regionColor)
auto draw_region_rect = [&](Element& region, vec4 regionColor)
{
auto cropModel = math::quad_model_get(region.size, region.crop);
auto cropTransform = transform * cropModel;
@@ -250,8 +258,10 @@ namespace anm2ed::imgui
BORDER_DASH_OFFSET);
};
for (auto& [id, region] : spritesheet->regions)
for (auto& region : spritesheet->children)
{
if (region.type != ElementType::REGION) continue;
auto id = region.id;
if (id == highlightedRegionId) continue;
draw_region_rect(region, color::WHITE);
@@ -262,26 +272,25 @@ namespace anm2ed::imgui
if (highlightedRegionId != -1)
{
auto regionIt = spritesheet->regions.find(highlightedRegionId);
if (regionIt != spritesheet->regions.end())
if (auto region = region_get(highlightedRegionId))
{
auto& region = regionIt->second;
draw_region_rect(region, color::RED);
draw_region_rect(*region, color::RED);
auto pivotTransform =
transform * math::quad_model_get(PIVOT_SIZE, region.crop + region.pivot, PIVOT_SIZE * 0.5f);
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 = isReferenceLayerOnSpritesheet;
if (isFrameOnSpritesheet && frame->regionID == -1)
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);
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);
}
}
@@ -293,7 +302,7 @@ namespace anm2ed::imgui
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));
ImGui::Image(this->texture, to_imvec2(size));
if (ImGui::IsItemHovered())
{
@@ -330,8 +339,8 @@ namespace anm2ed::imgui
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 isZoomIn = isFocused && shortcut(manager.chords[SHORTCUT_ZOOM_IN], shortcut::GLOBAL);
auto isZoomOut = isFocused && shortcut(manager.chords[SHORTCUT_ZOOM_OUT], shortcut::GLOBAL);
auto isBegin = isMouseClicked || isKeyJustPressed;
auto isDuring = isMouseDown || isKeyDown;
@@ -339,11 +348,12 @@ namespace anm2ed::imgui
auto isMod = ImGui::IsKeyDown(ImGuiMod_Shift);
auto frame = document.frame_get();
auto layerIt = anm2.content.layers.find(reference.itemID);
auto frame =
anm2.element_get(reference.animationIndex, referenceItemType, reference.frameIndex, reference.itemID);
auto item = anm2.element_get(reference.animationIndex, referenceItemType, reference.itemID);
auto layer = anm2.element_get(ElementType::LAYER_ELEMENT, reference.itemID);
bool isReferenceLayerOnSpritesheet =
frame && reference.itemID > -1 && layerIt != anm2.content.layers.end() &&
layerIt->second.spritesheetID == referenceSpritesheet;
frame && reference.itemID > -1 && layer && layer->spritesheetId == referenceSpritesheet;
auto useTool = tool;
auto step = isMod ? STEP_FAST : STEP;
auto stepX = isGridSnap ? step * gridSize.x : step;
@@ -373,31 +383,212 @@ namespace anm2ed::imgui
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 clamp_vec2_to_int = [](const vec2& value) { return vec2(ivec2(value)); };
auto snapshot_push = [&](StringType messageType)
{
auto message = std::string(localize.get(messageType));
manager.command_push(
{manager.selected, [message](Manager&, Document& document) { document.snapshot(message); }});
};
auto document_change_push = [&](Document::ChangeType changeType)
{
manager.command_push({manager.selected,
[changeType](Manager&, Document& document) { document.anm2_change(changeType); }});
};
auto frame_change_apply = [&](FrameChange frameChange, ChangeType changeType = ChangeType::ADJUST)
{
auto queuedReference = reference;
auto queuedReferenceItemType = referenceItemType;
std::set<int> queuedFrames(frames.begin(), frames.end());
manager.command_push({manager.selected,
[=](Manager&, Document& document)
{
auto item = document.anm2.element_get(queuedReference.animationIndex,
queuedReferenceItemType,
queuedReference.itemID);
if (!item) return;
frames_change(*item, frameChange, queuedReferenceItemType, changeType, queuedFrames);
}});
};
auto frame_change_from_current_apply = [&](auto frameChangeGet, ChangeType changeType = ChangeType::ADJUST)
{
auto queuedReference = reference;
auto queuedReferenceItemType = referenceItemType;
std::set<int> queuedFrames(frames.begin(), frames.end());
manager.command_push({manager.selected,
[=](Manager&, Document& document)
{
auto item = document.anm2.element_get(queuedReference.animationIndex,
queuedReferenceItemType,
queuedReference.itemID);
auto frame = document.anm2.element_get(queuedReference.animationIndex,
queuedReferenceItemType,
queuedReference.frameIndex,
queuedReference.itemID);
if (!item || !frame) return;
frames_change(*item, frameChangeGet(*frame), queuedReferenceItemType, changeType,
queuedFrames);
}});
};
auto frame_crop_normalize_apply = [&](bool isSnap, ivec2 snapGridSize, ivec2 snapGridOffset)
{
frame_change_from_current_apply(
[=](const Element& frame)
{
auto minPoint = glm::min(frame.crop, frame.crop + frame.size);
auto maxPoint = glm::max(frame.crop, frame.crop + frame.size);
if (isSnap)
{
if (snapGridSize.x != 0)
{
auto offsetX = (float)snapGridOffset.x;
auto sizeX = (float)snapGridSize.x;
minPoint.x = std::floor((minPoint.x - offsetX) / sizeX) * sizeX + offsetX;
maxPoint.x = std::ceil((maxPoint.x - offsetX) / sizeX) * sizeX + offsetX;
}
if (snapGridSize.y != 0)
{
auto offsetY = (float)snapGridOffset.y;
auto sizeY = (float)snapGridSize.y;
minPoint.y = std::floor((minPoint.y - offsetY) / sizeY) * sizeY + offsetY;
maxPoint.y = std::ceil((maxPoint.y - offsetY) / sizeY) * sizeY + offsetY;
}
}
return FrameChange{.cropX = minPoint.x,
.cropY = minPoint.y,
.sizeX = maxPoint.x - minPoint.x,
.sizeY = maxPoint.y - minPoint.y};
});
};
auto region_update = [&](int id, auto update)
{
auto queuedSpritesheet = referenceSpritesheet;
manager.command_push({manager.selected,
[=](Manager&, Document& document)
{
auto spritesheet = document.anm2.element_get(ElementType::SPRITESHEET,
queuedSpritesheet);
if (!spritesheet) return;
auto region = element_child_id_get(*spritesheet, ElementType::REGION, id);
if (!region) return;
update(*region);
}});
};
auto region_update_all = [&](auto update)
{
auto queuedSpritesheet = referenceSpritesheet;
std::set<int> queuedSelection(regionSelection.begin(), regionSelection.end());
manager.command_push({manager.selected,
[=](Manager&, Document& document)
{
auto spritesheet = document.anm2.element_get(ElementType::SPRITESHEET,
queuedSpritesheet);
if (!spritesheet) return;
for (auto id : queuedSelection)
{
auto region = element_child_id_get(*spritesheet, ElementType::REGION, id);
if (!region) continue;
update(*region);
}
}});
};
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 queuedCrop = clamp_vec2_to_int(crop);
auto queuedSize = clamp_vec2_to_int(size);
region_update_all(
[=](Element& region)
{
region.crop = queuedCrop;
region.size = queuedSize;
});
};
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);
}
region_update_all(
[=](Element& region)
{
region.crop = clamp_vec2_to_int(region.crop + delta);
region.size = clamp_vec2_to_int(region.size);
});
};
auto region_crop_normalize_all = [&](bool isSnap, ivec2 snapGridSize, ivec2 snapGridOffset)
{
region_update_all(
[=](Element& region)
{
auto minPoint = glm::min(region.crop, region.crop + region.size);
auto maxPoint = glm::max(region.crop, region.crop + region.size);
if (isSnap)
{
if (snapGridSize.x != 0)
{
auto offsetX = (float)snapGridOffset.x;
auto sizeX = (float)snapGridSize.x;
minPoint.x = std::floor((minPoint.x - offsetX) / sizeX) * sizeX + offsetX;
maxPoint.x = std::ceil((maxPoint.x - offsetX) / sizeX) * sizeX + offsetX;
}
if (snapGridSize.y != 0)
{
auto offsetY = (float)snapGridOffset.y;
auto sizeY = (float)snapGridSize.y;
minPoint.y = std::floor((minPoint.y - offsetY) / sizeY) * sizeY + offsetY;
maxPoint.y = std::ceil((maxPoint.y - offsetY) / sizeY) * sizeY + offsetY;
}
}
region.crop = clamp_vec2_to_int(minPoint);
region.size = clamp_vec2_to_int(maxPoint - minPoint);
});
};
auto texture_line_apply = [&](ivec2 start, ivec2 end, vec4 color)
{
auto queuedSpritesheet = referenceSpritesheet;
manager.command_push({manager.selected,
[=](Manager&, Document& document)
{
auto texture = document.texture_get(queuedSpritesheet);
if (!texture) return;
texture->pixel_line(start, end, color);
}});
};
auto texture_change_push = [&]()
{
auto queuedSpritesheet = referenceSpritesheet;
manager.command_push({manager.selected,
[=](Manager&, Document& document) { document.texture_change(queuedSpritesheet); }});
};
auto region_selection_set = [&](int id)
{
manager.command_push({manager.selected,
[=](Manager&, Document& document)
{
document.region.reference = id;
document.region.selection = {id};
}});
};
auto region_pivot_set = [&](int id, vec2 pivot)
{
auto queuedPivot = clamp_vec2_to_int(pivot);
region_update(id,
[=](Element& region)
{
region.origin = Origin::CUSTOM;
region.pivot = queuedPivot;
});
};
auto region_pivot_offset = [&](int id, vec2 delta)
{
region_update(id,
[=](Element& region)
{
region.origin = Origin::CUSTOM;
region.pivot = clamp_vec2_to_int(region.pivot + delta);
});
};
if (isMouseMiddleDown) useTool = tool::PAN;
@@ -408,16 +599,17 @@ namespace anm2ed::imgui
hoveredRegionId = -1;
if (useTool == tool::PAN && spritesheet && spritesheet->texture.is_valid() && isMouseOverCanvas)
if (useTool == tool::PAN && spritesheet && texture && texture->is_valid() && isMouseOverCanvas)
{
for (auto& [id, region] : spritesheet->regions)
for (auto& region : spritesheet->children)
{
if (region.type != ElementType::REGION) continue;
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;
hoveredRegionId = region.id;
break;
}
}
@@ -428,12 +620,12 @@ namespace anm2ed::imgui
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 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());
bool isSpritesheetAvailable = !isSpritesheetRequired || (texture && texture->is_valid());
auto cursor = (isAreaAllowed && isFrameAvailable && isSpritesheetAvailable) ? toolInfo.cursor
: ImGuiMouseCursor_NotAllowed;
ImGui::SetMouseCursor(cursor);
@@ -444,29 +636,23 @@ namespace anm2ed::imgui
case tool::PAN:
if (isMouseLeftClicked && hoveredRegionId != -1)
{
regionReference = hoveredRegionId;
regionSelection = {hoveredRegionId};
region_selection_set(hoveredRegionId);
if (isReferenceLayerOnSpritesheet)
{
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);
});
snapshot_push(EDIT_FRAME_REGION);
FrameChange change{};
change.regionId = hoveredRegionId;
if (auto region = region_get(hoveredRegionId))
{
change.cropX = region->crop.x;
change.cropY = region->crop.y;
change.sizeX = region->size.x;
change.sizeY = region->size.y;
change.pivotX = region->pivot.x;
change.pivotY = region->pivot.y;
}
frame_change_apply(change);
document_change_push(Document::FRAMES);
}
}
if (isMouseDown || isMouseMiddleDown) pan += mouseDelta;
@@ -476,48 +662,48 @@ namespace anm2ed::imgui
if (!frame && regionReference != -1)
{
if (!spritesheet) break;
auto regionIt = spritesheet->regions.find(regionReference);
if (regionIt == spritesheet->regions.end()) break;
auto region = region_get(regionReference);
if (!region) 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 (isBegin) snapshot_push(EDIT_REGION_MOVE);
if (isMouseDown) region_pivot_set(regionReference, ivec2(mousePos) - ivec2(region->crop));
if (isLeftPressed) region_pivot_offset(regionReference, vec2(-step, 0));
if (isRightPressed) region_pivot_offset(regionReference, vec2(step, 0));
if (isUpPressed) region_pivot_offset(regionReference, vec2(0, -step));
if (isDownPressed) region_pivot_offset(regionReference, vec2(0, step));
if (isDuring)
{
if (ImGui::BeginTooltip())
{
ImGui::TextUnformatted(
std::vformat(localize.get(FORMAT_PIVOT), std::make_format_args(region.pivot.x, region.pivot.y))
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);
if (isEnd) document_change_push(Document::SPRITESHEETS);
break;
}
if (!item || frames.empty()) break;
if (isBegin) document.snapshot(localize.get(EDIT_FRAME_PIVOT));
if (isBegin) snapshot_push(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)});
auto frameCrop = ivec2(frame->crop);
frame_change_apply({.pivotX = (int)(mousePos.x - frameCrop.x),
.pivotY = (int)(mousePos.y - frameCrop.y),
.cropX = frameCrop.x,
.cropY = frameCrop.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);
if (isLeftPressed) frame_change_apply({.pivotX = step}, ChangeType::SUBTRACT);
if (isRightPressed) frame_change_apply({.pivotX = step}, ChangeType::ADD);
if (isUpPressed) frame_change_apply({.pivotY = step}, ChangeType::SUBTRACT);
if (isDownPressed) frame_change_apply({.pivotY = step}, ChangeType::ADD);
frame_change_apply({.pivotX = (int)frame->pivot.x, .pivotY = (int)frame->pivot.y});
frame_change_from_current_apply(
[](const Element& frame) { return FrameChange{.pivotX = (int)frame.pivot.x, .pivotY = (int)frame.pivot.y}; });
if (isDuring)
{
@@ -530,14 +716,14 @@ namespace anm2ed::imgui
}
}
if (isEnd) document.change(Document::FRAMES);
if (isEnd) document_change_push(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 (isBegin) snapshot_push(EDIT_REGION_CROP);
if (isMouseClicked)
{
@@ -557,49 +743,29 @@ namespace anm2ed::imgui
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);
}
}
}
region_crop_normalize_all(isGridSnap, gridSize, gridOffset);
if (ImGui::BeginTooltip())
{
auto it = spritesheet->regions.find(*regionSelection.begin());
if (it != spritesheet->regions.end())
if (auto region = region_get(*regionSelection.begin()))
{
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::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());
}
ImGui::EndTooltip();
}
}
if (isEnd) document.change(Document::SPRITESHEETS);
if (isEnd) document_change_push(Document::SPRITESHEETS);
break;
}
if (!item) break;
if (isBegin) document.snapshot(localize.get(EDIT_FRAME_CROP));
if (isBegin) snapshot_push(EDIT_FRAME_CROP);
if (isMouseClicked)
{
@@ -614,10 +780,10 @@ namespace anm2ed::imgui
.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);
if (isLeftPressed) frame_change_apply({.cropX = stepX}, ChangeType::SUBTRACT);
if (isRightPressed) frame_change_apply({.cropX = stepX}, ChangeType::ADD);
if (isUpPressed) frame_change_apply({.cropY = stepY}, ChangeType::SUBTRACT);
if (isDownPressed) frame_change_apply({.cropY = stepY}, ChangeType::ADD);
frame_change_apply(
{.cropX = frame->crop.x, .cropY = frame->crop.y, .sizeX = frame->size.x, .sizeY = frame->size.y});
@@ -626,23 +792,7 @@ namespace anm2ed::imgui
{
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});
}
frame_crop_normalize_apply(isGridSnap, gridSize, gridOffset);
}
if (ImGui::BeginTooltip())
{
@@ -655,27 +805,24 @@ namespace anm2ed::imgui
ImGui::EndTooltip();
}
}
if (isEnd) document.change(Document::FRAMES);
if (isEnd) document_change_push(Document::FRAMES);
break;
case tool::DRAW:
case tool::ERASE:
{
if (!spritesheet) break;
if (!texture) 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);
}
snapshot_push(useTool == tool::DRAW ? EDIT_DRAW : EDIT_ERASE);
if (isMouseDown) texture_line_apply(ivec2(previousMousePos), ivec2(mousePos), color);
if (isMouseReleased) texture_change_push();
break;
}
case tool::COLOR_PICKER:
{
if (spritesheet && isDuring)
if (texture && isDuring)
{
toolColor = spritesheet->texture.pixel_read(mousePos);
toolColor = texture->pixel_read(mousePos);
if (ImGui::BeginTooltip())
{
ImGui::ColorButton("##Color Picker Button", to_imvec4(toolColor));
@@ -696,31 +843,31 @@ namespace anm2ed::imgui
if (tool == tool::PAN && hoveredRegionId != -1 && spritesheet)
{
auto regionIt = spritesheet->regions.find(hoveredRegionId);
if (regionIt != spritesheet->regions.end())
if (auto region = region_get(hoveredRegionId))
{
if (ImGui::BeginTooltip())
{
auto& region = regionIt->second;
ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE);
ImGui::TextUnformatted(region.name.c_str());
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());
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)
std::vformat(localize.get(FORMAT_SIZE), std::make_format_args(region->size.x, region->size.y))
.c_str());
if (region->origin == Origin::CUSTOM)
{
ImGui::TextUnformatted(
std::vformat(localize.get(FORMAT_PIVOT), std::make_format_args(region.pivot.x, region.pivot.y))
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;
if (region->origin == Origin::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());
@@ -766,8 +913,7 @@ namespace anm2ed::imgui
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;
if (texture && mouseWheel == 0) focus = texture->size / 2;
auto previousZoom = zoom;
zoom_set(zoom, pan, focus, (mouseWheel > 0 || isZoomIn) ? zoomStep : -zoomStep);
@@ -776,31 +922,17 @@ namespace anm2ed::imgui
}
}
if (tool == tool::PAN &&
ImGui::BeginPopupContextWindow("##Spritesheet Editor Context Menu", ImGuiMouseButton_Right))
if (tool == tool::PAN)
{
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();
Actions actions{};
actions_undo_redo_add(actions, manager, document);
actions.separator();
actions.add(ACTION_CENTER_VIEW, []() { return true; }, center_view);
actions.add(ACTION_FIT_VIEW, [&]() { return texture && texture->is_valid(); }, fit_view);
actions.separator();
actions.add(ACTION_ZOOM_IN, []() { return true; }, zoom_in);
actions.add(ACTION_ZOOM_OUT, []() { return true; }, zoom_out);
actions_context_window_draw("##Spritesheet Editor Context Menu", actions, settings);
}
if (!document.isSpritesheetEditorSet)
+2
View File
@@ -17,10 +17,12 @@ namespace anm2ed::imgui
float checkerSyncZoom{};
bool isCheckerPanInitialized{};
bool hasPendingZoomPanAdjust{};
bool isFocused{};
int hoveredRegionId{-1};
public:
SpritesheetEditor();
bool is_focused_get() const;
void update(Manager&, Settings&, Resources&);
};
}
-709
View File
@@ -1,709 +0,0 @@
#include "spritesheets.hpp"
#include <algorithm>
#include <ranges>
#include <vector>
#include <filesystem>
#include <format>
#include <functional>
#include "document.hpp"
#include "log.hpp"
#include "path_.hpp"
#include "strings.hpp"
#include "toast.hpp"
#include "working_directory.hpp"
using namespace anm2ed::types;
using namespace anm2ed::resource;
using namespace anm2ed::util;
using namespace glm;
namespace anm2ed::imgui
{
static constexpr auto PADDING_MAX = 100;
void Spritesheets::update(Manager& manager, Settings& settings, Resources& resources, Dialog& dialog,
Clipboard& clipboard)
{
auto& document = *manager.get();
auto& anm2 = document.anm2;
auto& selection = document.spritesheet.selection;
auto& reference = document.spritesheet.reference;
auto& region = document.region;
auto style = ImGui::GetStyle();
std::function<void()> pack{};
auto add_open = [&]() { dialog.file_open(Dialog::SPRITESHEET_OPEN); };
auto replace_open = [&]() { dialog.file_open(Dialog::SPRITESHEET_REPLACE); };
auto set_file_path_open = [&]() { dialog.file_save(Dialog::SPRITESHEET_PATH_SET); };
auto merge_open = [&]()
{
if (selection.size() <= 1) return;
mergeSelection = selection;
mergePopup.open();
};
auto pack_open = [&]()
{
if (selection.size() != 1) return;
auto id = *selection.begin();
if (!anm2.content.spritesheets.contains(id)) return;
if (anm2.content.spritesheets.at(id).regions.empty()) return;
packId = id;
packPopup.open();
};
auto add = [&](const std::filesystem::path& path)
{
if (path.empty()) return;
document.spritesheet_add(path);
newSpritesheetId = document.spritesheet.reference;
};
auto remove_unused = [&]()
{
auto unused = anm2.spritesheets_unused();
if (unused.empty()) return;
auto behavior = [&]()
{
for (auto& id : unused)
{
anm2::Spritesheet& spritesheet = anm2.content.spritesheets[id];
auto pathString = path::to_utf8(spritesheet.path);
toasts.push(std::vformat(localize.get(TOAST_REMOVE_SPRITESHEET), std::make_format_args(id, pathString)));
logger.info(std::vformat(localize.get(TOAST_REMOVE_SPRITESHEET, anm2ed::ENGLISH),
std::make_format_args(id, pathString)));
anm2.content.spritesheets.erase(id);
}
};
DOCUMENT_EDIT(document, localize.get(EDIT_REMOVE_UNUSED_SPRITESHEETS), Document::ALL, behavior());
};
auto reload = [&]()
{
if (selection.empty()) return;
auto behavior = [&]()
{
for (auto& id : selection)
{
anm2::Spritesheet& spritesheet = anm2.content.spritesheets[id];
spritesheet.reload(document.directory_get());
document.spritesheet_hash_set_saved(id);
auto pathString = path::to_utf8(spritesheet.path);
toasts.push(std::vformat(localize.get(TOAST_RELOAD_SPRITESHEET), std::make_format_args(id, pathString)));
logger.info(std::vformat(localize.get(TOAST_RELOAD_SPRITESHEET, anm2ed::ENGLISH),
std::make_format_args(id, pathString)));
}
};
DOCUMENT_EDIT(document, localize.get(EDIT_RELOAD_SPRITESHEETS), Document::SPRITESHEETS, behavior());
};
auto replace = [&](const std::filesystem::path& path)
{
if (selection.size() != 1 || path.empty()) return;
auto behavior = [&]()
{
auto& id = *selection.begin();
anm2::Spritesheet& spritesheet = anm2.content.spritesheets[id];
spritesheet.reload(document.directory_get(), path);
document.spritesheet_hash_set_saved(id);
auto pathString = path::to_utf8(spritesheet.path);
toasts.push(std::vformat(localize.get(TOAST_REPLACE_SPRITESHEET), std::make_format_args(id, pathString)));
logger.info(std::vformat(localize.get(TOAST_REPLACE_SPRITESHEET, anm2ed::ENGLISH),
std::make_format_args(id, pathString)));
};
DOCUMENT_EDIT(document, localize.get(EDIT_REPLACE_SPRITESHEET), Document::SPRITESHEETS, behavior());
};
auto set_file_path = [&](const std::filesystem::path& path)
{
if (selection.size() != 1 || path.empty()) return;
auto behavior = [&]()
{
auto id = *selection.begin();
if (!anm2.content.spritesheets.contains(id)) return;
WorkingDirectory workingDirectory(document.directory_get());
anm2.content.spritesheets[id].path = path::make_relative(path);
};
DOCUMENT_EDIT(document, localize.get(EDIT_SET_SPRITESHEET_FILE_PATH), Document::SPRITESHEETS, behavior());
};
auto save = [&](const std::set<int>& ids)
{
if (ids.empty()) return;
for (auto& id : ids)
{
if (!anm2.content.spritesheets.contains(id)) continue;
anm2::Spritesheet& spritesheet = anm2.content.spritesheets[id];
auto pathString = path::to_utf8(spritesheet.path);
if (spritesheet.save(document.directory_get()))
{
document.spritesheet_hash_set_saved(id);
toasts.push(std::vformat(localize.get(TOAST_SAVE_SPRITESHEET), std::make_format_args(id, pathString)));
logger.info(std::vformat(localize.get(TOAST_SAVE_SPRITESHEET, anm2ed::ENGLISH),
std::make_format_args(id, pathString)));
}
else
{
toasts.push(std::vformat(localize.get(TOAST_SAVE_SPRITESHEET_FAILED), std::make_format_args(id, pathString)));
logger.error(std::vformat(localize.get(TOAST_SAVE_SPRITESHEET_FAILED, anm2ed::ENGLISH),
std::make_format_args(id, pathString)));
}
}
};
auto save_open = [&]()
{
if (selection.empty()) return;
if (settings.fileIsWarnOverwrite)
{
saveSelection = selection;
overwritePopup.open();
}
else
{
save(selection);
}
};
auto merge = [&]()
{
if (mergeSelection.size() <= 1) return;
auto behavior = [&]()
{
auto baseID = *mergeSelection.begin();
if (anm2.spritesheets_merge(mergeSelection, (anm2::SpritesheetMergeOrigin)settings.mergeSpritesheetsOrigin,
settings.mergeSpritesheetsIsMakeRegions,
settings.mergeSpritesheetsIsMakePrimaryRegion,
(origin::Type)settings.mergeSpritesheetsRegionOrigin))
{
selection = {baseID};
reference = baseID;
region.reference = -1;
region.selection.clear();
toasts.push(localize.get(TOAST_MERGE_SPRITESHEETS));
logger.info(localize.get(TOAST_MERGE_SPRITESHEETS, anm2ed::ENGLISH));
}
else
{
toasts.push(localize.get(TOAST_MERGE_SPRITESHEETS_FAILED));
logger.error(localize.get(TOAST_MERGE_SPRITESHEETS_FAILED, anm2ed::ENGLISH));
}
};
DOCUMENT_EDIT(document, localize.get(EDIT_MERGE_SPRITESHEETS), Document::ALL, behavior());
};
pack = [&]()
{
int id = packId != -1 ? packId : (selection.size() == 1 ? *selection.begin() : -1);
if (id == -1) return;
if (!anm2.content.spritesheets.contains(id)) return;
if (anm2.content.spritesheets.at(id).regions.empty()) return;
auto behavior = [&]()
{
auto padding = std::max(0, settings.packPadding);
if (anm2.spritesheet_pack(id, padding))
{
toasts.push(localize.get(TOAST_PACK_SPRITESHEET));
logger.info(localize.get(TOAST_PACK_SPRITESHEET, anm2ed::ENGLISH));
}
else
{
toasts.push(localize.get(TOAST_PACK_SPRITESHEET_FAILED));
logger.error(localize.get(TOAST_PACK_SPRITESHEET_FAILED, anm2ed::ENGLISH));
}
};
DOCUMENT_EDIT(document, localize.get(EDIT_PACK_SPRITESHEET), Document::SPRITESHEETS, behavior());
};
auto open_directory = [&](anm2::Spritesheet& spritesheet)
{
if (spritesheet.path.empty()) return;
std::error_code ec{};
auto absolutePath = std::filesystem::weakly_canonical(document.directory_get() / spritesheet.path, ec);
if (ec) absolutePath = document.directory_get() / spritesheet.path;
auto target = std::filesystem::is_directory(absolutePath) ? absolutePath
: std::filesystem::is_directory(absolutePath.parent_path()) ? absolutePath.parent_path()
: document.directory_get();
dialog.file_explorer_open(target);
};
auto copy = [&]()
{
if (selection.empty()) return;
std::string clipboardText{};
for (auto& id : selection)
clipboardText += anm2.content.spritesheets[id].to_string(id);
clipboard.set(clipboardText);
};
auto paste = [&]()
{
if (clipboard.is_empty()) return;
auto behavior = [&]()
{
auto maxSpritesheetIdBefore =
anm2.content.spritesheets.empty() ? -1 : anm2.content.spritesheets.rbegin()->first;
std::string errorString{};
document.snapshot(localize.get(EDIT_PASTE_SPRITESHEETS));
if (anm2.spritesheets_deserialize(clipboard.get(), document.directory_get(), merge::APPEND, &errorString))
{
if (!anm2.content.spritesheets.empty())
{
auto maxSpritesheetIdAfter = anm2.content.spritesheets.rbegin()->first;
if (maxSpritesheetIdAfter > maxSpritesheetIdBefore)
{
newSpritesheetId = maxSpritesheetIdAfter;
selection = {maxSpritesheetIdAfter};
reference = maxSpritesheetIdAfter;
region.reference = -1;
region.selection.clear();
}
}
document.change(Document::SPRITESHEETS);
}
else
{
toasts.push(
std::vformat(localize.get(TOAST_DESERIALIZE_SPRITESHEETS_FAILED), std::make_format_args(errorString)));
logger.error(std::vformat(localize.get(TOAST_DESERIALIZE_SPRITESHEETS_FAILED, anm2ed::ENGLISH),
std::make_format_args(errorString)));
};
};
DOCUMENT_EDIT(document, localize.get(EDIT_PASTE_SPRITESHEETS), Document::SPRITESHEETS, behavior());
};
auto context_menu = [&]()
{
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, style.WindowPadding);
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing);
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenOverlappedByWindow) &&
ImGui::IsMouseClicked(ImGuiMouseButton_Right))
ImGui::OpenPopup("##Spritesheet Context Menu");
if (ImGui::BeginPopup("##Spritesheet Context Menu"))
{
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(BASIC_OPEN_DIRECTORY), nullptr, false, selection.size() == 1))
open_directory(anm2.content.spritesheets[*selection.begin()]);
if (ImGui::MenuItem(localize.get(BASIC_SET_FILE_PATH), nullptr, false, selection.size() == 1))
set_file_path_open();
if (ImGui::MenuItem(localize.get(BASIC_ADD), settings.shortcutAdd.c_str())) add_open();
if (ImGui::MenuItem(localize.get(BASIC_REMOVE_UNUSED), settings.shortcutRemove.c_str())) remove_unused();
bool isPackable = selection.size() == 1 && anm2.content.spritesheets.contains(*selection.begin()) &&
!anm2.content.spritesheets.at(*selection.begin()).regions.empty();
if (ImGui::MenuItem(localize.get(BASIC_RELOAD), nullptr, false, !selection.empty())) reload();
if (ImGui::MenuItem(localize.get(BASIC_REPLACE), nullptr, false, selection.size() == 1)) replace_open();
if (ImGui::MenuItem(localize.get(BASIC_MERGE), settings.shortcutMerge.c_str(), false, selection.size() > 1))
merge_open();
if (ImGui::MenuItem(localize.get(BASIC_PACK), nullptr, false, isPackable)) pack_open();
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled))
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_PACK_SPRITESHEET));
if (ImGui::MenuItem(localize.get(BASIC_SAVE), nullptr, false, !selection.empty())) save_open();
ImGui::Separator();
if (ImGui::MenuItem(localize.get(BASIC_COPY), settings.shortcutCopy.c_str(), false, !selection.empty())) copy();
if (ImGui::MenuItem(localize.get(BASIC_PASTE), settings.shortcutPaste.c_str(), false, !clipboard.is_empty()))
paste();
ImGui::EndPopup();
}
ImGui::PopStyleVar(2);
};
if (ImGui::Begin(localize.get(LABEL_SPRITESHEETS_WINDOW), &settings.windowIsSpritesheets))
{
auto childSize = size_without_footer_get(2);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2());
if (ImGui::BeginChild("##Spritesheets Child", childSize, ImGuiChildFlags_Borders))
{
auto spritesheetChildSize = ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetTextLineHeightWithSpacing() * 4);
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2());
selection.start(anm2.content.spritesheets.size());
if (ImGui::Shortcut(ImGuiMod_Ctrl | ImGuiKey_A, ImGuiInputFlags_RouteFocused))
{
selection.clear();
for (auto& id : anm2.content.spritesheets | std::views::keys)
selection.insert(id);
}
if (ImGui::Shortcut(ImGuiKey_Escape, ImGuiInputFlags_RouteFocused)) selection.clear();
auto scroll_to_item = [&](float itemHeight, bool isTarget)
{
if (!isTarget) return;
auto windowHeight = ImGui::GetWindowHeight();
auto targetTop = ImGui::GetCursorPosY();
auto targetBottom = targetTop + itemHeight;
auto visibleTop = ImGui::GetScrollY();
auto visibleBottom = visibleTop + windowHeight;
if (targetTop < visibleTop)
ImGui::SetScrollY(targetTop);
else if (targetBottom > visibleBottom)
ImGui::SetScrollY(targetBottom - windowHeight);
};
int scrollTargetId = -1;
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) &&
(ImGui::IsKeyPressed(ImGuiKey_UpArrow, true) || ImGui::IsKeyPressed(ImGuiKey_DownArrow, true)))
{
std::vector<int> ids{};
ids.reserve(anm2.content.spritesheets.size());
for (auto& [id, sheet] : anm2.content.spritesheets)
ids.push_back(id);
if (!ids.empty())
{
int delta = ImGui::IsKeyPressed(ImGuiKey_UpArrow, true) ? -1 : 1;
int current = reference;
if (current == -1 && !selection.empty()) current = *selection.begin();
auto it = std::find(ids.begin(), ids.end(), current);
int index = it == ids.end() ? 0 : (int)std::distance(ids.begin(), it);
index = std::clamp(index + delta, 0, (int)ids.size() - 1);
int nextId = ids[index];
selection = {nextId};
reference = nextId;
region.reference = -1;
region.selection.clear();
scrollTargetId = nextId;
}
}
for (auto& [id, spritesheet] : anm2.content.spritesheets)
{
auto isNewSpritesheet = newSpritesheetId == id;
ImGui::PushID(id);
scroll_to_item(spritesheetChildSize.y, scrollTargetId == id);
if (ImGui::BeginChild("##Spritesheet Child", spritesheetChildSize, ImGuiChildFlags_Borders))
{
auto isSelected = selection.contains(id);
auto isReferenced = id == reference;
auto cursorPos = ImGui::GetCursorPos();
bool isValid = spritesheet.texture.is_valid();
auto& texture = isValid ? spritesheet.texture : resources.icons[icon::NONE];
auto tintColor = !isValid ? ImVec4(1.0f, 0.25f, 0.25f, 1.0f) : ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
auto pathString = path::to_utf8(spritesheet.path);
auto pathCStr = pathString.c_str();
ImGui::SetNextItemSelectionUserData(id);
ImGui::SetNextItemStorageID(id);
if (ImGui::Selectable("##Spritesheet Selectable", isSelected, 0, spritesheetChildSize))
{
reference = id;
region.reference = -1;
region.selection.clear();
}
if (scrollTargetId == id) ImGui::SetItemDefaultFocus();
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left))
open_directory(spritesheet);
auto viewport = ImGui::GetMainViewport();
auto maxPreviewSize = to_vec2(viewport->Size) * 0.5f;
vec2 textureSize = vec2(glm::max(texture.size.x, 1), glm::max(texture.size.y, 1));
if (textureSize.x > maxPreviewSize.x || textureSize.y > maxPreviewSize.y)
{
auto scale = glm::min(maxPreviewSize.x / textureSize.x, maxPreviewSize.y / textureSize.y);
textureSize *= scale;
}
auto textWidth = ImGui::CalcTextSize(pathCStr).x;
auto tooltipPadding = style.WindowPadding.x * 4.0f;
auto minWidth = textureSize.x + style.ItemSpacing.x + textWidth + tooltipPadding;
ImGui::SetNextWindowSize(ImVec2(minWidth, 0), ImGuiCond_Appearing);
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, style.WindowPadding);
if (ImGui::BeginItemTooltip())
{
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2());
auto childFlags = ImGuiChildFlags_Borders | ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY;
auto noScrollFlags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse;
if (ImGui::BeginChild("##Spritesheet Tooltip Image Child", to_imvec2(textureSize), childFlags,
noScrollFlags))
ImGui::ImageWithBg(texture.id, to_imvec2(textureSize), ImVec2(), ImVec2(1, 1), ImVec4(), tintColor);
ImGui::EndChild();
ImGui::PopStyleVar();
ImGui::SameLine();
auto infoChildFlags = ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY;
if (ImGui::BeginChild("##Spritesheet Info Tooltip Child", ImVec2(), infoChildFlags, noScrollFlags))
{
ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE);
ImGui::TextUnformatted(pathCStr);
ImGui::PopFont();
ImGui::TextUnformatted(std::vformat(localize.get(FORMAT_ID), std::make_format_args(id)).c_str());
if (!isValid)
ImGui::TextUnformatted(localize.get(TOOLTIP_SPRITESHEET_INVALID));
else
ImGui::TextUnformatted(std::vformat(localize.get(FORMAT_TEXTURE_SIZE),
std::make_format_args(texture.size.x, texture.size.y))
.c_str());
ImGui::TextUnformatted(localize.get(TEXT_OPEN_DIRECTORY));
}
ImGui::EndChild();
ImGui::EndTooltip();
}
ImGui::PopStyleVar(2);
auto imageSize = to_imvec2(vec2(spritesheetChildSize.y));
auto aspectRatio = (float)texture.size.x / texture.size.y;
if (imageSize.x / imageSize.y > aspectRatio)
imageSize.x = imageSize.y * aspectRatio;
else
imageSize.y = imageSize.x / aspectRatio;
ImGui::SetCursorPos(cursorPos);
ImGui::ImageWithBg(texture.id, imageSize, ImVec2(), ImVec2(1, 1), ImVec4(), tintColor);
ImGui::SetCursorPos(
ImVec2(spritesheetChildSize.y + style.ItemSpacing.x,
spritesheetChildSize.y - spritesheetChildSize.y / 2 - ImGui::GetTextLineHeight() / 2));
if (isReferenced) ImGui::PushFont(resources.fonts[font::ITALICS].get(), font::SIZE);
auto spritesheetLabel = std::vformat(localize.get(FORMAT_SPRITESHEET), std::make_format_args(id, pathCStr));
if (document.spritesheet_is_dirty(id))
spritesheetLabel =
std::vformat(localize.get(FORMAT_SPRITESHEET_NOT_SAVED), std::make_format_args(spritesheetLabel));
ImGui::TextUnformatted(spritesheetLabel.c_str());
if (isReferenced) ImGui::PopFont();
}
ImGui::EndChild();
if (isNewSpritesheet)
{
ImGui::SetScrollHereY(0.5f);
newSpritesheetId = -1;
}
ImGui::PopID();
}
ImGui::PopStyleVar();
selection.finish();
}
ImGui::EndChild();
ImGui::PopStyleVar();
context_menu();
auto rowOneWidgetSize = widget_size_with_row_get(3);
shortcut(manager.chords[SHORTCUT_ADD]);
if (ImGui::Button(localize.get(BASIC_ADD), rowOneWidgetSize)) add_open();
set_item_tooltip_shortcut(localize.get(TOOLTIP_ADD_SPRITESHEET), settings.shortcutAdd);
if (dialog.is_selected(Dialog::SPRITESHEET_OPEN))
{
add(dialog.path);
dialog.reset();
}
ImGui::SameLine();
ImGui::BeginDisabled(selection.empty());
if (ImGui::Button(localize.get(BASIC_RELOAD), rowOneWidgetSize)) reload();
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_RELOAD_SPRITESHEETS));
ImGui::EndDisabled();
ImGui::SameLine();
ImGui::BeginDisabled(selection.size() != 1);
if (ImGui::Button(localize.get(BASIC_REPLACE), rowOneWidgetSize)) replace_open();
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_REPLACE_SPRITESHEET));
ImGui::EndDisabled();
if (dialog.is_selected(Dialog::SPRITESHEET_REPLACE))
{
replace(dialog.path);
dialog.reset();
}
if (dialog.is_selected(Dialog::SPRITESHEET_PATH_SET))
{
set_file_path(dialog.path);
dialog.reset();
}
auto rowTwoWidgetSize = widget_size_with_row_get(2);
shortcut(manager.chords[SHORTCUT_REMOVE]);
if (ImGui::Button(localize.get(BASIC_REMOVE_UNUSED), rowTwoWidgetSize)) remove_unused();
set_item_tooltip_shortcut(localize.get(TOOLTIP_REMOVE_UNUSED_SPRITESHEETS), settings.shortcutRemove);
ImGui::SameLine();
ImGui::BeginDisabled(selection.empty());
if (ImGui::Button(localize.get(BASIC_SAVE), rowTwoWidgetSize)) save_open();
ImGui::EndDisabled();
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_SAVE_SPRITESHEETS));
if (imgui::shortcut(manager.chords[SHORTCUT_ADD], shortcut::FOCUSED)) add_open();
if (imgui::shortcut(manager.chords[SHORTCUT_REMOVE], shortcut::FOCUSED)) remove_unused();
if (imgui::shortcut(manager.chords[SHORTCUT_COPY], shortcut::FOCUSED)) copy();
if (imgui::shortcut(manager.chords[SHORTCUT_PASTE], shortcut::FOCUSED)) paste();
if (imgui::shortcut(manager.chords[SHORTCUT_MERGE], shortcut::FOCUSED) && selection.size() > 1) merge_open();
}
ImGui::End();
mergePopup.trigger();
if (ImGui::BeginPopupModal(mergePopup.label(), &mergePopup.isOpen, ImGuiWindowFlags_NoResize))
{
settings.mergeSpritesheetsRegionOrigin =
glm::clamp(settings.mergeSpritesheetsRegionOrigin, (int)origin::TOP_LEFT, (int)origin::ORIGIN_CENTER);
auto close = [&]()
{
mergeSelection.clear();
mergePopup.close();
};
auto optionsSize = child_size_get(6);
if (ImGui::BeginChild("##Merge Spritesheets Options", optionsSize, ImGuiChildFlags_Borders))
{
ImGui::SeparatorText(localize.get(LABEL_REGION_PROPERTIES_ORIGIN));
ImGui::RadioButton(localize.get(LABEL_MERGE_SPRITESHEETS_APPEND_BOTTOM), &settings.mergeSpritesheetsOrigin,
anm2::APPEND_BOTTOM);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_MERGE_SPRITESHEETS_BOTTOM_LEFT));
ImGui::SameLine();
ImGui::RadioButton(localize.get(LABEL_MERGE_SPRITESHEETS_APPEND_RIGHT), &settings.mergeSpritesheetsOrigin,
anm2::APPEND_RIGHT);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_MERGE_SPRITESHEETS_TOP_RIGHT));
ImGui::SeparatorText(localize.get(LABEL_OPTIONS));
ImGui::Checkbox(localize.get(LABEL_MERGE_MAKE_SPRITESHEET_REGIONS), &settings.mergeSpritesheetsIsMakeRegions);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_MERGE_MAKE_SPRITESHEET_REGIONS));
ImGui::BeginDisabled(!settings.mergeSpritesheetsIsMakeRegions);
ImGui::Checkbox(localize.get(LABEL_MERGE_MAKE_PRIMARY_SPRITESHEET_REGION),
&settings.mergeSpritesheetsIsMakePrimaryRegion);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_MERGE_MAKE_PRIMARY_SPRITESHEET_REGION));
const char* regionOriginOptions[] = {localize.get(LABEL_REGION_ORIGIN_TOP_LEFT),
localize.get(LABEL_REGION_ORIGIN_CENTER)};
ImGui::Combo(localize.get(LABEL_REGION_PROPERTIES_ORIGIN), &settings.mergeSpritesheetsRegionOrigin,
regionOriginOptions, IM_ARRAYSIZE(regionOriginOptions));
ImGui::EndDisabled();
}
ImGui::EndChild();
auto widgetSize = widget_size_with_row_get(2);
shortcut(manager.chords[SHORTCUT_CONFIRM]);
ImGui::BeginDisabled(mergeSelection.size() <= 1);
if (ImGui::Button(localize.get(BASIC_MERGE), widgetSize))
{
merge();
close();
}
ImGui::EndDisabled();
ImGui::SameLine();
shortcut(manager.chords[SHORTCUT_CANCEL]);
if (ImGui::Button(localize.get(BASIC_CANCEL), widgetSize)) close();
ImGui::EndPopup();
}
mergePopup.end();
packPopup.trigger();
if (ImGui::BeginPopupModal(packPopup.label(), &packPopup.isOpen, ImGuiWindowFlags_NoResize))
{
settings.packPadding = std::max(0, settings.packPadding);
auto close = [&]()
{
packId = -1;
packPopup.close();
};
auto optionsSize = child_size_get(1);
if (ImGui::BeginChild("##Pack Spritesheet Options", optionsSize, ImGuiChildFlags_Borders))
{
ImGui::DragInt(localize.get(LABEL_PACK_PADDING), &settings.packPadding, DRAG_SPEED, 0, PADDING_MAX);
}
ImGui::EndChild();
auto widgetSize = widget_size_with_row_get(2);
shortcut(manager.chords[SHORTCUT_CONFIRM]);
bool isPackable = packId != -1 && anm2.content.spritesheets.contains(packId) &&
!anm2.content.spritesheets.at(packId).regions.empty();
ImGui::BeginDisabled(!isPackable);
if (ImGui::Button(localize.get(BASIC_PACK), widgetSize))
{
if (pack) pack();
close();
}
ImGui::EndDisabled();
ImGui::SameLine();
shortcut(manager.chords[SHORTCUT_CANCEL]);
if (ImGui::Button(localize.get(BASIC_CANCEL), widgetSize)) close();
ImGui::EndPopup();
}
packPopup.end();
overwritePopup.trigger();
if (ImGui::BeginPopupModal(overwritePopup.label(), &overwritePopup.isOpen, ImGuiWindowFlags_NoResize))
{
ImGui::TextUnformatted(localize.get(LABEL_OVERWRITE_CONFIRMATION));
auto widgetSize = widget_size_with_row_get(2);
if (ImGui::Button(localize.get(BASIC_YES), widgetSize))
{
save(saveSelection);
saveSelection.clear();
overwritePopup.close();
}
ImGui::SameLine();
if (ImGui::Button(localize.get(BASIC_NO), widgetSize))
{
saveSelection.clear();
overwritePopup.close();
}
ImGui::EndPopup();
}
overwritePopup.end();
}
}
-24
View File
@@ -1,24 +0,0 @@
#pragma once
#include "clipboard.hpp"
#include "dialog.hpp"
#include "manager.hpp"
#include "resources.hpp"
#include "settings.hpp"
namespace anm2ed::imgui
{
class Spritesheets
{
int newSpritesheetId{-1};
PopupHelper mergePopup{PopupHelper(LABEL_SPRITESHEETS_MERGE_POPUP, imgui::POPUP_SMALL_NO_HEIGHT)};
PopupHelper packPopup{PopupHelper(LABEL_SPRITESHEETS_PACK_POPUP, imgui::POPUP_SMALL_NO_HEIGHT)};
PopupHelper overwritePopup{PopupHelper(LABEL_TASKBAR_OVERWRITE_FILE, imgui::POPUP_SMALL_NO_HEIGHT)};
std::set<int> mergeSelection{};
int packId{-1};
std::set<int> saveSelection{};
public:
void update(Manager&, Settings&, Resources&, Dialog&, Clipboard& clipboard);
};
}
File diff suppressed because it is too large Load Diff
+47 -4
View File
@@ -1,5 +1,7 @@
#pragma once
#include <set>
#include <string>
#include <vector>
#include "clipboard.hpp"
@@ -8,17 +10,41 @@
#include "resources.hpp"
#include "settings.hpp"
#include "strings.hpp"
#include "util/imgui/popup.hpp"
namespace anm2ed::imgui
{
struct TimelineGroupReference
{
int documentIndex{-1};
int animationIndex{-1};
int type{NONE};
int id{-1};
auto operator<=>(const TimelineGroupReference&) const = default;
};
struct TimelineRowReference
{
int documentIndex{-1};
int animationIndex{-1};
int type{NONE};
int id{-1};
int index{-1};
bool isGroup{};
auto operator<=>(const TimelineRowReference&) const = default;
};
struct FrameMoveDrag
{
anm2::Type type{anm2::NONE};
int type{NONE};
int itemID{-1};
int animationIndex{-1};
int frameIndex{-1};
int duration{1};
std::vector<int> indices{};
std::vector<Reference> references{};
bool isActive{};
};
@@ -30,8 +56,22 @@ namespace anm2ed::imgui
popup::ItemProperties itemProperties{};
PopupHelper bakePopup{PopupHelper(LABEL_TIMELINE_BAKE_POPUP, POPUP_SMALL_NO_HEIGHT)};
int hoveredTime{};
anm2::Frame* draggedFrame{};
anm2::Type draggedFrameType{};
bool isFrameBoxSelecting{};
bool isFrameBoxAdditive{};
ImVec2 frameBoxStart{};
ImVec2 frameBoxEnd{};
std::set<Reference> frameBoxSelection{};
std::set<TimelineGroupReference> groupReferences{};
TimelineRowReference rowSelectionAnchor{};
bool isRowSelectionAnchorSet{};
PopupHelper groupPropertiesPopup{PopupHelper(LABEL_GROUP_PROPERTIES, POPUP_SMALL_NO_HEIGHT)};
std::string groupName{};
int groupAnimationIndex{-1};
int groupType{NONE};
int groupId{-1};
Reference draggedFrameReference{};
bool isDraggedFrameActive{};
int draggedFrameType{};
int draggedFrameIndex{-1};
int draggedFrameStart{-1};
int draggedFrameStartDuration{-1};
@@ -42,7 +82,10 @@ namespace anm2ed::imgui
std::vector<int> frameSelectionSnapshot{};
std::vector<int> frameSelectionLocked{};
bool isFrameSelectionLocked{};
anm2::Reference frameSelectionSnapshotReference{};
Reference frameSelectionSnapshotReference{};
Reference frameSelectionAnchor{};
bool isFrameSelectionAnchorSet{};
std::vector<TimelineRowReference> rowDragReferences{};
glm::vec2 scroll{};
ImDrawList* pickerLineDrawList{};
ImGuiStyle style{};
+5 -2
View File
@@ -6,6 +6,7 @@
#include "strings.hpp"
#include "tool.hpp"
#include "types.hpp"
#include "util/imgui/imgui.hpp"
using namespace anm2ed::resource;
using namespace anm2ed::types;
@@ -30,10 +31,12 @@ namespace anm2ed::imgui
switch (type)
{
case tool::UNDO:
if (document.is_able_to_undo()) document.undo();
if (document.is_able_to_undo())
manager.command_push({manager.selected, [](Manager&, Document& document) { document.undo(); }});
break;
case tool::REDO:
if (document.is_able_to_redo()) document.redo();
if (document.is_able_to_redo())
manager.command_push({manager.selected, [](Manager&, Document& document) { document.redo(); }});
break;
case tool::COLOR:
colorEditPopup.open();
+5 -6
View File
@@ -3,7 +3,7 @@
#include <format>
#include <ranges>
#include "path_.hpp"
#include "path.hpp"
#include "strings.hpp"
using namespace anm2ed::util;
@@ -15,8 +15,7 @@ namespace anm2ed::imgui
{
auto viewport = ImGui::GetMainViewport();
auto windowHeight = viewport->Size.y - taskbar.height - documents.height;
if (windowHeight < 1.0f)
windowHeight = 1.0f;
if (windowHeight < 1.0f) windowHeight = 1.0f;
ImGui::SetNextWindowViewport(viewport->ID);
ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x, viewport->Pos.y + taskbar.height + documents.height));
@@ -37,10 +36,10 @@ namespace anm2ed::imgui
auto widgetSize = widget_size_with_row_get(2);
if (ImGui::Button(localize.get(BASIC_NEW), widgetSize))
dialog.file_save(Dialog::ANM2_NEW); // handled in taskbar.cpp
dialog.file_save(Dialog::ANM2_CREATE); // handled in taskbar.cpp
ImGui::SameLine();
if (ImGui::Button(localize.get(BASIC_OPEN), widgetSize))
dialog.file_open(Dialog::ANM2_OPEN); // handled in taskbar.cpp
dialog.file_open(Dialog::ANM2_OPEN, true); // handled in taskbar.cpp
if (ImGui::BeginChild("##Recent Files Child", {}, ImGuiChildFlags_Borders))
{
@@ -53,7 +52,7 @@ namespace anm2ed::imgui
if (ImGui::Selectable(label.c_str()))
{
manager.open(file);
manager.command_push({.runManager = [file](Manager& manager) { manager.open(file); }});
ImGui::PopID();
break;
}
File diff suppressed because it is too large Load Diff
+140
View File
@@ -0,0 +1,140 @@
#pragma once
#include <functional>
#include <set>
#include <string>
#include <vector>
#include "clipboard.hpp"
#include "dialog.hpp"
#include "util/imgui/imgui.hpp"
#include "manager.hpp"
#include "resources.hpp"
#include "settings.hpp"
#include "storage.hpp"
namespace anm2ed::imgui
{
enum WindowFlag
{
WINDOW_ADD = 1 << 0,
WINDOW_REMOVE = 1 << 1,
WINDOW_REMOVE_UNUSED = 1 << 2,
WINDOW_DUPLICATE = 1 << 3,
WINDOW_MERGE = 1 << 4,
WINDOW_DEFAULT = 1 << 5,
WINDOW_CUT = 1 << 6,
WINDOW_COPY = 1 << 7,
WINDOW_PASTE = 1 << 8,
WINDOW_RENAME = 1 << 9,
WINDOW_PROPERTIES = 1 << 10,
WINDOW_REFERENCE_ITALIC = 1 << 11
};
using WindowFlags = int;
constexpr bool window_flag_has(WindowFlags flags, WindowFlag flag) { return (flags & flag) != 0; }
struct Window
{
using StorageGet = std::function<Storage&(Document&)>;
using ElementGet = std::function<Element*(Anm2&, int)>;
using ElementKeyGet = std::function<int(const Element&, int)>;
using RowLabelGet = std::function<std::string(Document&, const Element&)>;
using RowFontGet = std::function<resource::font::Type(Document&, const Element&, int)>;
using RowSelect = std::function<void(Window&, Document&, int)>;
using RenameFinish = std::function<void(Document&, Element&, int, int)>;
using TooltipDraw = std::function<void(Document&, Resources&, const Element&)>;
using RowDragDropUpdate = std::function<bool(Window&, Manager&, Document&, const Element&, int)>;
using PropertiesOpen = std::function<void(Manager&, int)>;
using Command = std::function<void(Window&, Manager&, Settings&, Document&, Clipboard&)>;
using IsAvailable = std::function<bool(Document&)>;
using Update = std::function<void(Window&, Manager&, Settings&, Resources&, Clipboard&, Document&)>;
using RowsUpdate = std::function<void(Window&, Manager&, Settings&, Resources&, Clipboard&, Document&, ImVec2)>;
StringType title{};
bool Settings::* isOpen{};
Document::ChangeType changeType{};
ElementType containerType{ElementType::UNKNOWN};
ElementType elementType{ElementType::UNKNOWN};
const char* childLabel{"##Window Child"};
StringType addTooltip{};
StringType duplicateTooltip{};
StringType mergeTooltip{};
StringType removeTooltip{};
StringType removeUnusedTooltip{};
StringType defaultTooltip{};
StringType addEdit{};
StringType renameEdit{};
StringType pasteEdit{};
StringType removeUnusedEdit{};
StringType deserializeFailedToast{};
StringType unavailableText{STRING_UNDEFINED};
int newElementId{-1};
int scrollQueued{-1};
int renameQueued{-1};
int renameId{-1};
int editId{-1};
int footerRows{-1};
RenameState renameState{RENAME_SELECTABLE};
std::string renameText{};
PopupHelper popup{STRING_UNDEFINED};
PopupHelper popup2{STRING_UNDEFINED};
PopupHelper popup3{STRING_UNDEFINED};
std::set<int> selection{};
std::set<int> selection2{};
std::vector<int> dragSelection{};
std::vector<int> order{};
Element editElement{};
Dialog* dialog{};
WindowFlags flags{WINDOW_COPY | WINDOW_PASTE};
bool isChildPaddingZero{};
bool isPreserveEditElementOnOpen{};
ImVec2 tooltipWindowPadding{};
ImVec2 tooltipItemSpacing{};
StorageGet storage_get{};
ElementGet element_get{};
ElementKeyGet element_key_get{};
RowLabelGet row_label_get{};
RowFontGet row_font_get{};
RowSelect row_select{};
RenameFinish rename_finish{};
TooltipDraw tooltip_draw{};
RowDragDropUpdate row_drag_drop_update{};
PropertiesOpen properties_open{};
Command add{};
Command remove{};
Command duplicate{};
Command merge{};
Command merge_open{};
Command default_set{};
Command cut{};
Command copy{};
Command paste{};
Command reload{};
Command replace{};
Command save{};
Command pack{};
Command trim{};
Command properties{};
Command open{};
Command path_set{};
IsAvailable is_available{};
Update begin_update{};
RowsUpdate rows_update{};
Update context_update{};
Update footer_update{};
Update body_update{};
Update popup_update{};
Update post_update{};
};
Window animations_window_register();
Window regions_window_register();
Window sounds_window_register();
Window spritesheets_window_register();
Window layers_window_register();
Window nulls_window_register();
Window events_window_register();
void window_update(Window&, Manager&, Settings&, Resources&, Dialog&, Clipboard&);
}
+2 -1
View File
@@ -19,6 +19,7 @@ namespace anm2ed::imgui::wizard
{""},
{"Designer", font::BOLD},
{"Shweet"},
{"OpenAI Codex"},
{""},
{"Additional Help", font::BOLD},
{"im-tem"},
@@ -37,7 +38,7 @@ namespace anm2ed::imgui::wizard
{"Music", font::BOLD},
{"Soundbin"},
{"\"Digital Antidepressant\""},
{"https://soundbin.newgrounds.com/"},
{"https://www.newgrounds.com/audio/listen/1565433"},
{"License: CC0"},
{""},
{"Libraries", font::BOLD},
+218 -94
View File
@@ -1,31 +1,34 @@
#include "change_all_frame_properties.hpp"
#include <algorithm>
#include <map>
#include <set>
#include <string>
#include <vector>
#include "math_.hpp"
#include "math.hpp"
#include "util/imgui/imgui.hpp"
using namespace anm2ed::util::math;
using namespace glm;
namespace anm2ed::imgui::wizard
{
namespace
enum ChangeDestination
{
enum ChangeDestination
{
CHANGE_DESTINATION_FRAMES,
CHANGE_DESTINATION_ANIMATIONS,
};
}
CHANGE_DESTINATION_FRAMES,
CHANGE_DESTINATION_ANIMATIONS,
CHANGE_DESTINATION_ITEMS,
};
void ChangeAllFrameProperties::update(Document& document, Settings& settings, bool isFromWizard)
void ChangeAllFrameProperties::update(Manager& manager, Document& document, Settings& settings, bool isFromWizard)
{
isChanged = false;
auto& anm2 = document.anm2;
auto& frames = document.frames.selection;
auto& frameReferences = document.frames.references;
auto& itemReferences = document.items.references;
auto& animations = document.animation.selection;
auto& isCropX = settings.changeIsCropX;
auto& isCropY = settings.changeIsCropY;
@@ -71,19 +74,56 @@ namespace anm2ed::imgui::wizard
auto& isNulls = settings.changeIsNulls;
auto& itemType = document.reference.itemType;
auto is_frame_reference_changeable = [](const Reference& reference) { return reference.itemType != TRIGGER; };
auto is_item_reference_changeable = [](const Reference& reference) { return reference.itemType != TRIGGER; };
auto selected_frame_references_get = [&]()
{
std::set<Reference> result = frameReferences;
if (result.empty())
for (auto frameIndex : frames)
result.insert({document.reference.animationIndex, itemType, document.reference.itemID, frameIndex});
std::erase_if(result, [&](const Reference& reference) { return !is_frame_reference_changeable(reference); });
return result;
};
auto selected_item_references_get = [&]()
{
std::set<Reference> result = itemReferences;
if (result.empty() && itemType != NONE)
result.insert({document.reference.animationIndex, itemType, document.reference.itemID});
std::erase_if(result, [&](const Reference& reference) { return !is_item_reference_changeable(reference); });
return result;
};
auto selectedFrameReferences = selected_frame_references_get();
auto selectedItemReferences = selected_item_references_get();
bool isFramesDestination = !isFromWizard || destination == CHANGE_DESTINATION_FRAMES;
bool isSelectedFramesAvailable = !frames.empty() && itemType != anm2::TRIGGER;
bool isItemsDestination = isFromWizard && destination == CHANGE_DESTINATION_ITEMS;
bool isSelectedFramesAvailable = !selectedFrameReferences.empty();
bool isSelectedItemsAvailable = !selectedItemReferences.empty();
bool isSelectedAnimationsAvailable = !animations.empty();
if (isFromWizard)
{
if (destination == CHANGE_DESTINATION_FRAMES && !isSelectedFramesAvailable && isSelectedAnimationsAvailable)
destination = CHANGE_DESTINATION_ANIMATIONS;
if (destination == CHANGE_DESTINATION_FRAMES && !isSelectedFramesAvailable)
destination = isSelectedItemsAvailable ? CHANGE_DESTINATION_ITEMS : CHANGE_DESTINATION_ANIMATIONS;
if (destination == CHANGE_DESTINATION_ITEMS && !isSelectedItemsAvailable)
destination = isSelectedFramesAvailable ? CHANGE_DESTINATION_FRAMES : CHANGE_DESTINATION_ANIMATIONS;
if (destination == CHANGE_DESTINATION_ANIMATIONS && !isSelectedAnimationsAvailable && isSelectedItemsAvailable)
destination = CHANGE_DESTINATION_ITEMS;
if (destination == CHANGE_DESTINATION_ANIMATIONS && !isSelectedAnimationsAvailable && isSelectedFramesAvailable)
destination = CHANGE_DESTINATION_FRAMES;
isFramesDestination = destination == CHANGE_DESTINATION_FRAMES;
isItemsDestination = destination == CHANGE_DESTINATION_ITEMS;
}
bool isLayerPropertyAvailable = isFramesDestination ? itemType == anm2::LAYER : isLayers;
auto isLayerPropertyAvailable = isFramesDestination
? std::ranges::any_of(selectedFrameReferences,
[](const Reference& reference)
{ return reference.itemType == LAYER; })
: isItemsDestination
? std::ranges::any_of(selectedItemReferences,
[](const Reference& reference)
{ return reference.itemType == LAYER; })
: isLayers;
#define PROPERTIES_WIDGET(body, checkboxLabel, isEnabled) \
ImGui::Checkbox(checkboxLabel, &isEnabled); \
@@ -212,9 +252,8 @@ namespace anm2ed::imgui::wizard
auto duration_value = [&](const char* checkboxLabel, const char* valueLabel, bool& isEnabled, int& value)
{
PROPERTIES_WIDGET(
input_int_range(valueLabel, value, anm2::FRAME_DURATION_MIN, anm2::FRAME_DURATION_MAX, STEP, STEP_FAST),
checkboxLabel, isEnabled);
PROPERTIES_WIDGET(input_int_range(valueLabel, value, FRAME_DURATION_MIN, FRAME_DURATION_MAX, STEP, STEP_FAST),
checkboxLabel, isEnabled);
};
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImGui::GetStyle().ItemInnerSpacing);
@@ -248,26 +287,22 @@ namespace anm2ed::imgui::wizard
std::vector<int> fallbackIds{-1};
std::vector<std::string> fallbackLabelsString{localize.get(BASIC_NONE)};
std::vector<const char*> fallbackLabels{fallbackLabelsString[0].c_str()};
std::vector<int> interpolationIds{anm2::Frame::Interpolation::NONE, anm2::Frame::Interpolation::LINEAR,
anm2::Frame::Interpolation::EASE_IN, anm2::Frame::Interpolation::EASE_OUT,
anm2::Frame::Interpolation::EASE_IN_OUT};
std::vector<std::string> interpolationLabelsString{
localize.get(BASIC_NONE), localize.get(BASIC_LINEAR), localize.get(BASIC_EASE_IN),
localize.get(BASIC_EASE_OUT), localize.get(BASIC_EASE_IN_OUT)};
std::vector<const char*> interpolationLabels{interpolationLabelsString[0].c_str(),
interpolationLabelsString[1].c_str(),
interpolationLabelsString[2].c_str(),
interpolationLabelsString[3].c_str(),
interpolationLabelsString[4].c_str()};
std::vector<int> interpolationIds{(int)Interpolation::NONE, (int)Interpolation::LINEAR, (int)Interpolation::EASE_IN,
(int)Interpolation::EASE_OUT, (int)Interpolation::EASE_IN_OUT};
std::vector<std::string> interpolationLabelsString{localize.get(BASIC_NONE), localize.get(BASIC_LINEAR),
localize.get(BASIC_EASE_IN), localize.get(BASIC_EASE_OUT),
localize.get(BASIC_EASE_IN_OUT)};
std::vector<const char*> interpolationLabels{
interpolationLabelsString[0].c_str(), interpolationLabelsString[1].c_str(),
interpolationLabelsString[2].c_str(), interpolationLabelsString[3].c_str(),
interpolationLabelsString[4].c_str()};
const Storage* regionStorage = nullptr;
if (itemType == anm2::LAYER && document.reference.itemID != -1)
if (itemType == LAYER && document.reference.itemID != -1)
{
if (auto layerIt = document.anm2.content.layers.find(document.reference.itemID);
layerIt != document.anm2.content.layers.end())
if (auto layer = anm2.element_get(ElementType::LAYER_ELEMENT, document.reference.itemID))
{
auto spritesheetID = layerIt->second.spritesheetID;
auto regionIt = document.regionBySpritesheet.find(spritesheetID);
auto regionIt = document.regionBySpritesheet.find(layer->spritesheetId);
if (regionIt != document.regionBySpritesheet.end()) regionStorage = &regionIt->second;
}
}
@@ -294,9 +329,9 @@ namespace anm2ed::imgui::wizard
#undef PROPERTIES_WIDGET
auto frame_change = [&](anm2::ChangeType changeType)
auto frame_change = [&](ChangeType changeType)
{
anm2::FrameChange frameChange;
FrameChange frameChange;
if (isCropX) frameChange.cropX = crop.x;
if (isCropY) frameChange.cropY = crop.y;
if (isSizeX) frameChange.sizeX = size.x;
@@ -309,7 +344,7 @@ namespace anm2ed::imgui::wizard
if (isScaleY) frameChange.scaleY = scale.y;
if (isRotation) frameChange.rotation = std::make_optional(rotation);
if (isDuration) frameChange.duration = std::make_optional(duration);
if (isRegion) frameChange.regionID = std::make_optional(regionId);
if (isRegion) frameChange.regionId = std::make_optional(regionId);
if (isTintR) frameChange.tintR = tint.r;
if (isTintG) frameChange.tintG = tint.g;
if (isTintB) frameChange.tintB = tint.b;
@@ -318,67 +353,148 @@ namespace anm2ed::imgui::wizard
if (isColorOffsetG) frameChange.colorOffsetG = colorOffset.g;
if (isColorOffsetB) frameChange.colorOffsetB = colorOffset.b;
if (isVisibleSet) frameChange.isVisible = std::make_optional(isVisible);
if (isInterpolationSet)
frameChange.interpolation = std::make_optional(static_cast<anm2::Frame::Interpolation>(interpolation));
if (isInterpolationSet) frameChange.interpolation = std::make_optional(static_cast<Interpolation>(interpolation));
if (isFlipXSet) frameChange.isFlipX = std::make_optional(isFlipX);
if (isFlipYSet) frameChange.isFlipY = std::make_optional(isFlipY);
auto all_frames_selection = [](anm2::Item& item)
{
std::set<int> selection{};
for (int i = 0; i < (int)item.frames.size(); ++i)
selection.insert(i);
return selection;
};
if (isFramesDestination && selectedFrameReferences.empty()) return;
if (isItemsDestination && selectedItemReferences.empty()) return;
if (isFramesDestination)
{
if (auto item = document.item_get())
{
DOCUMENT_EDIT(document, localize.get(EDIT_CHANGE_FRAME_PROPERTIES), Document::FRAMES,
item->frames_change(frameChange, itemType, changeType, frames));
auto queuedFrameReferences = selectedFrameReferences;
auto queuedItemReferences = selectedItemReferences;
auto queuedAnimations = animations;
auto queuedIsFramesDestination = isFramesDestination;
auto queuedIsItemsDestination = isItemsDestination;
auto queuedIsRoot = isRoot;
auto queuedIsLayers = isLayers;
auto queuedIsNulls = isNulls;
isChanged = true;
}
}
else
{
auto behavior = [&]()
{
for (auto animationIndex : animations)
{
if (animationIndex < 0 || animationIndex >= (int)document.anm2.animations.items.size()) continue;
auto& animation = document.anm2.animations.items[animationIndex];
manager.command_push({manager.selected,
[=](Manager&, Document& document)
{
auto all_frames_selection = [](const Element& item)
{
std::set<int> selection{};
int index{};
for (const auto& frame : item.children)
if (frame.type == ElementType::FRAME)
{
selection.insert(index);
++index;
}
return selection;
};
if (isRoot)
{
auto selection = all_frames_selection(animation.rootAnimation);
animation.rootAnimation.frames_change(frameChange, anm2::ROOT, changeType, selection);
}
auto& anm2 = document.anm2;
if (isLayers)
{
for (auto& [_, item] : animation.layerAnimations)
{
auto selection = all_frames_selection(item);
item.frames_change(frameChange, anm2::LAYER, changeType, selection);
}
}
if (queuedIsFramesDestination)
{
std::map<Reference, std::set<int>> groupedFrames{};
for (auto frameReference : queuedFrameReferences)
{
auto itemReference = frameReference;
itemReference.frameIndex = -1;
groupedFrames[itemReference].insert(frameReference.frameIndex);
}
if (isNulls)
{
for (auto& [_, item] : animation.nullAnimations)
{
auto selection = all_frames_selection(item);
item.frames_change(frameChange, anm2::NULL_, changeType, selection);
}
}
}
};
document.snapshot(localize.get(EDIT_CHANGE_FRAME_PROPERTIES));
for (auto& [itemReference, selection] : groupedFrames)
{
auto item = anm2.element_get(itemReference.animationIndex,
static_cast<ItemType>(itemReference.itemType),
itemReference.itemID);
if (!item) continue;
frames_change(*item, frameChange, static_cast<ItemType>(itemReference.itemType),
changeType, selection);
}
document.anm2_change(Document::FRAMES);
return;
}
DOCUMENT_EDIT(document, localize.get(EDIT_CHANGE_FRAME_PROPERTIES), Document::FRAMES, behavior());
isChanged = true;
}
if (queuedIsItemsDestination)
{
document.snapshot(localize.get(EDIT_CHANGE_FRAME_PROPERTIES));
for (auto itemReference : queuedItemReferences)
{
if (itemReference.itemType == TRIGGER) continue;
auto item = anm2.element_get(itemReference.animationIndex,
static_cast<ItemType>(itemReference.itemType),
itemReference.itemID);
if (!item) continue;
auto selection = all_frames_selection(*item);
frames_change(*item, frameChange, static_cast<ItemType>(itemReference.itemType),
changeType, selection);
}
document.anm2_change(Document::FRAMES);
return;
}
document.snapshot(localize.get(EDIT_CHANGE_FRAME_PROPERTIES));
for (auto animationIndex : queuedAnimations)
{
auto animation = anm2.element_get(ElementType::ANIMATION, animationIndex);
if (!animation) continue;
if (queuedIsRoot)
{
if (auto item = animation_item_get(*animation, ItemType::ROOT))
{
auto selection = all_frames_selection(*item);
frames_change(*item, frameChange, ItemType::ROOT, changeType, selection);
}
}
if (queuedIsLayers)
{
auto layerAnimations = element_child_first_get(*animation, ElementType::LAYER_ANIMATIONS);
if (layerAnimations)
{
auto item_change = [&](auto&& self, Element& item) -> void
{
if (item.type == ElementType::GROUP)
{
for (auto& child : item.children)
self(self, child);
return;
}
if (item.type == ElementType::LAYER_ANIMATION)
{
auto selection = all_frames_selection(item);
frames_change(item, frameChange, ItemType::LAYER, changeType, selection);
}
};
for (auto& item : layerAnimations->children)
item_change(item_change, item);
}
}
if (queuedIsNulls)
{
auto nullAnimations = element_child_first_get(*animation, ElementType::NULL_ANIMATIONS);
if (nullAnimations)
{
auto item_change = [&](auto&& self, Element& item) -> void
{
if (item.type == ElementType::GROUP)
{
for (auto& child : item.children)
self(self, child);
return;
}
if (item.type == ElementType::NULL_ANIMATION)
{
auto selection = all_frames_selection(item);
frames_change(item, frameChange, ItemType::NULL_, changeType, selection);
}
};
for (auto& item : nullAnimations->children)
item_change(item_change, item);
}
}
}
document.anm2_change(Document::FRAMES);
}});
isChanged = true;
};
ImGui::Separator();
@@ -394,12 +510,19 @@ namespace anm2ed::imgui::wizard
ImGui::SameLine();
ImGui::BeginDisabled(!isSelectedItemsAvailable);
ImGui::RadioButton(localize.get(LABEL_ITEMS), &destination, CHANGE_DESTINATION_ITEMS);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_CHANGE_ALL_DESTINATION_ITEMS));
ImGui::EndDisabled();
ImGui::SameLine();
ImGui::BeginDisabled(!isSelectedAnimationsAvailable);
ImGui::RadioButton(localize.get(LABEL_ANIMATIONS_CHILD), &destination, CHANGE_DESTINATION_ANIMATIONS);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_CHANGE_ALL_DESTINATION_ANIMATIONS));
ImGui::EndDisabled();
ImGui::BeginDisabled(isFramesDestination);
ImGui::BeginDisabled(isFramesDestination || isItemsDestination);
ImGui::Checkbox(localize.get(LABEL_ROOT), &isRoot);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_CHANGE_ALL_ROOT));
ImGui::SameLine();
@@ -418,33 +541,34 @@ namespace anm2ed::imgui::wizard
isTintB || isTintA || isColorOffsetR || isColorOffsetG || isColorOffsetB || isRegion ||
isVisibleSet || isInterpolationSet || isFlipXSet || isFlipYSet;
bool isDestinationValid = isFramesDestination ? isSelectedFramesAvailable
: isSelectedAnimationsAvailable && (isRoot || isLayers || isNulls);
: isItemsDestination ? isSelectedItemsAvailable
: isSelectedAnimationsAvailable && (isRoot || isLayers || isNulls);
auto rowWidgetSize = widget_size_with_row_get(5);
ImGui::BeginDisabled(!isAnyProperty || !isDestinationValid);
if (ImGui::Button(localize.get(LABEL_ADJUST), rowWidgetSize)) frame_change(anm2::ADJUST);
if (ImGui::Button(localize.get(LABEL_ADJUST), rowWidgetSize)) frame_change(ChangeType::ADJUST);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_ADJUST));
ImGui::SameLine();
if (ImGui::Button(localize.get(BASIC_ADD), rowWidgetSize)) frame_change(anm2::ADD);
if (ImGui::Button(localize.get(BASIC_ADD), rowWidgetSize)) frame_change(ChangeType::ADD);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_ADD_VALUES));
ImGui::SameLine();
if (ImGui::Button(localize.get(LABEL_SUBTRACT), rowWidgetSize)) frame_change(anm2::SUBTRACT);
if (ImGui::Button(localize.get(LABEL_SUBTRACT), rowWidgetSize)) frame_change(ChangeType::SUBTRACT);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_SUBTRACT_VALUES));
ImGui::SameLine();
if (ImGui::Button(localize.get(LABEL_MULTIPLY), rowWidgetSize)) frame_change(anm2::MULTIPLY);
if (ImGui::Button(localize.get(LABEL_MULTIPLY), rowWidgetSize)) frame_change(ChangeType::MULTIPLY);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_MULTIPLY_VALUES));
ImGui::SameLine();
if (ImGui::Button(localize.get(LABEL_DIVIDE), rowWidgetSize)) frame_change(anm2::DIVIDE);
if (ImGui::Button(localize.get(LABEL_DIVIDE), rowWidgetSize)) frame_change(ChangeType::DIVIDE);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_DIVIDE_VALUES));
ImGui::EndDisabled();
@@ -1,6 +1,7 @@
#pragma once
#include "document.hpp"
#include "manager.hpp"
#include "settings.hpp"
namespace anm2ed::imgui::wizard
@@ -10,6 +11,6 @@ namespace anm2ed::imgui::wizard
public:
bool isChanged{};
void update(Document&, Settings&, bool = false);
void update(Manager&, Document&, Settings&, bool = false);
};
}
+7 -9
View File
@@ -1,9 +1,9 @@
#include "configure.hpp"
#include "imgui_.hpp"
#include "log.hpp"
#include "path_.hpp"
#include "path.hpp"
#include "sdl.hpp"
#include "util/imgui/imgui.hpp"
using namespace anm2ed::types;
@@ -24,11 +24,9 @@ namespace anm2ed::imgui::wizard
if (ImGui::BeginChild("##Tab Child", childSize, true))
{
ImGui::SeparatorText(localize.get(LABEL_WINDOW_MENU));
input_float_range(localize.get(LABEL_UI_SCALE), temporary.uiScale, 0.5f, 2.0f, 0.25f, 0.25f, "%.2f");
input_float_range(localize.get(LABEL_UI_SCALE), temporary.uiScale, UI_SCALE_MIN, UI_SCALE_MAX,
UI_SCALE_STEP, UI_SCALE_STEP, "%.2fx");
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_UI_SCALE));
input_float_range(localize.get(LABEL_ITEM_HEIGHT), temporary.timelineItemHeight, 0.75f, 1.25f, 0.05f, 0.05f,
"%.2f");
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_ITEM_HEIGHT));
ImGui::Checkbox(localize.get(LABEL_VSYNC), &temporary.isVsync);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_VSYNC));
@@ -70,13 +68,13 @@ namespace anm2ed::imgui::wizard
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_STACK_SIZE));
ImGui::SeparatorText(localize.get(LABEL_COMPATIBILITY));
ImGui::RadioButton(localize.get(LABEL_ISAAC), &temporary.fileCompatibility, anm2::ISAAC);
ImGui::RadioButton(localize.get(LABEL_ISAAC), &temporary.fileCompatibility, ISAAC);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_COMPATIBILITY_ISAAC));
ImGui::SameLine();
ImGui::RadioButton(localize.get(LABEL_ANM2ED), &temporary.fileCompatibility, anm2::ANM2ED);
ImGui::RadioButton(localize.get(LABEL_ANM2ED), &temporary.fileCompatibility, ANM2ED);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_COMPATIBILITY_ANM2ED));
ImGui::SameLine();
ImGui::RadioButton(localize.get(LABEL_ANM2ED_LIMITED), &temporary.fileCompatibility, anm2::ANM2ED_LIMITED);
ImGui::RadioButton(localize.get(LABEL_ANM2ED_LIMITED), &temporary.fileCompatibility, ANM2ED_LIMITED);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_COMPATIBILITY_ANM2ED_LIMITED));
ImGui::Checkbox(localize.get(LABEL_SPECIAL_INTERPOLATED_FRAMES_REMINDER_ON_SAVE),
@@ -1,7 +1,10 @@
#include "generate_animation_from_grid.hpp"
#include "math_.hpp"
#include <algorithm>
#include "math.hpp"
#include "types.hpp"
#include "util/imgui/imgui.hpp"
using namespace anm2ed::types;
using namespace anm2ed::util;
@@ -11,10 +14,12 @@ namespace anm2ed::imgui::wizard
{
GenerateAnimationFromGrid::GenerateAnimationFromGrid() : Canvas(vec2()) {}
void GenerateAnimationFromGrid::update(Document& document, Resources& resources, Settings& settings)
void GenerateAnimationFromGrid::update(Manager& manager, Document& document, Resources& resources, Settings& settings)
{
isEnd = false;
auto& anm2 = document.anm2;
auto& reference = document.reference;
auto& startPosition = settings.generateStartPosition;
auto& size = settings.generateSize;
auto& pivot = settings.generatePivot;
@@ -35,12 +40,17 @@ namespace anm2ed::imgui::wizard
ImGui::InputInt(localize.get(LABEL_GENERATE_ROWS), &rows, STEP, STEP_FAST);
ImGui::InputInt(localize.get(LABEL_GENERATE_COLUMNS), &columns, STEP, STEP_FAST);
input_int_range(localize.get(LABEL_GENERATE_COUNT), count, anm2::FRAME_NUM_MIN, rows * columns);
input_int_range(localize.get(LABEL_GENERATE_COUNT), count, FRAME_NUM_MIN, rows * columns);
ImGui::InputInt(localize.get(BASIC_DURATION), &delay, STEP, STEP_FAST);
}
ImGui::EndChild();
rows = std::max(rows, 1);
columns = std::max(columns, 1);
count = std::clamp(count, FRAME_NUM_MIN, rows * columns);
delay = std::max(delay, FRAME_DURATION_MIN);
ImGui::SameLine();
if (ImGui::BeginChild("##Preview Child", childSize, ImGuiChildFlags_Borders))
@@ -58,28 +68,23 @@ namespace anm2ed::imgui::wizard
viewport_set();
clear(isTransparent ? vec4(0) : vec4(backgroundColor, 1.0f));
if (document.reference.itemType == anm2::LAYER)
if (reference.itemType == LAYER)
{
auto layerIt = document.anm2.content.layers.find(document.reference.itemID);
if (layerIt != document.anm2.content.layers.end())
auto layer = anm2.element_get(ElementType::LAYER_ELEMENT, reference.itemID);
auto sourceTexture = layer ? document.texture_get(layer->spritesheetId) : nullptr;
if (sourceTexture)
{
auto spritesheetIt = document.anm2.content.spritesheets.find(layerIt->second.spritesheetID);
if (spritesheetIt != document.anm2.content.spritesheets.end())
{
auto& texture = spritesheetIt->second.texture;
auto index = std::clamp((int)(time * (count - 1)), 0, (count - 1));
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(sourceTexture->size);
auto uvMax = (vec2(crop) + vec2(size) - vec2(0.5f)) / vec2(sourceTexture->size);
auto index = std::clamp((int)(time * (count - 1)), 0, (count - 1));
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 = transform_get(zoom) * math::quad_model_get(size, {}, pivot);
mat4 transform = transform_get(zoom) * math::quad_model_get(size, {}, pivot);
texture_render(shaderTexture, texture.id, transform, vec4(1.0f), {},
math::uv_vertices_get(uvMin, uvMax).data());
}
texture_render(shaderTexture, sourceTexture->id, transform, vec4(1.0f), {},
math::uv_vertices_get(uvMin, uvMax).data());
}
}
@@ -104,19 +109,32 @@ namespace anm2ed::imgui::wizard
if (ImGui::Button(localize.get(LABEL_GENERATE), widgetSize))
{
auto generate_from_grid = [&]()
{
auto item = document.item_get();
auto animation = document.animation_get();
auto queuedReference = reference;
auto queuedStartPosition = startPosition;
auto queuedSize = size;
auto queuedPivot = pivot;
auto queuedColumns = columns;
auto queuedCount = count;
auto queuedDelay = delay;
if (item && animation)
{
item->frames_generate_from_grid(startPosition, size, pivot, columns, count, delay);
animation->frameNum = animation->length();
}
};
manager.command_push({manager.selected,
[=](Manager&, Document& document)
{
auto itemType = static_cast<ItemType>(queuedReference.itemType);
auto item = document.anm2.element_get(queuedReference.animationIndex, itemType,
queuedReference.itemID);
auto animation =
document.anm2.element_get(ElementType::ANIMATION, queuedReference.animationIndex);
DOCUMENT_EDIT(document, localize.get(EDIT_GENERATE_ANIMATION_FROM_GRID), Document::FRAMES, generate_from_grid());
if (item && animation)
{
document.snapshot(localize.get(EDIT_GENERATE_ANIMATION_FROM_GRID));
frames_generate_from_grid(*item, queuedStartPosition, queuedSize, queuedPivot,
queuedColumns, queuedCount, queuedDelay);
animation->frameNum = animation_length_get(*animation);
document.anm2_change(Document::FRAMES);
}
}});
isEnd = true;
}
@@ -2,6 +2,7 @@
#include "canvas.hpp"
#include "document.hpp"
#include "manager.hpp"
#include "resources.hpp"
#include "settings.hpp"
@@ -15,6 +16,6 @@ namespace anm2ed::imgui::wizard
bool isEnd{};
GenerateAnimationFromGrid();
void update(Document&, Resources&, Settings&);
void update(Manager&, Document&, Resources&, Settings&);
};
}
+10 -8
View File
@@ -3,10 +3,10 @@
#include <ranges>
#include <string>
#include "imgui_.hpp"
#include "util/imgui/imgui.hpp"
#include "log.hpp"
#include "path_.hpp"
#include "process_.hpp"
#include "path.hpp"
#include "process.hpp"
#include "toast.hpp"
using namespace anm2ed::resource;
@@ -16,7 +16,7 @@ namespace anm2ed::imgui::wizard
{
void RenderAnimation::range_to_animation_set(Manager& manager, Document& document)
{
if (auto animation = document.animation_get())
if (auto animation = document.anm2.element_get(ElementType::ANIMATION, document.reference.animationIndex))
{
manager.recordingStart = 0;
manager.recordingEnd = animation->frameNum - 1;
@@ -28,10 +28,12 @@ namespace anm2ed::imgui::wizard
auto& frames = document.frames.selection;
if (!frames.empty())
{
if (auto item = document.item_get())
auto& reference = document.reference;
auto itemType = static_cast<ItemType>(reference.itemType);
if (auto item = document.anm2.element_get(reference.animationIndex, itemType, reference.itemID))
{
int duration{};
for (auto [i, frame] : std::views::enumerate(item->frames))
for (auto [i, frame] : std::views::enumerate(item->children))
{
if ((int)i == *frames.begin()) manager.recordingStart = duration;
if ((int)i == *frames.rbegin()) manager.recordingEnd = duration + frame.duration - 1;
@@ -53,7 +55,7 @@ namespace anm2ed::imgui::wizard
{
isEnd = false;
auto animation = document.animation_get();
auto animation = document.anm2.element_get(ElementType::ANIMATION, document.reference.animationIndex);
if (!animation) return;
auto& ffmpegPath = settings.renderFFmpegPath;
@@ -155,7 +157,7 @@ namespace anm2ed::imgui::wizard
ImGui::SameLine();
ImGui::BeginDisabled(frames.empty() || reference.itemID == anm2::TRIGGER);
ImGui::BeginDisabled(frames.empty() || reference.itemType == TRIGGER);
if (ImGui::Button(localize.get(LABEL_TO_SELECTED_FRAMES))) range_to_frames_set(manager, document);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_TO_SELECTED_FRAMES));
ImGui::EndDisabled();