Holy vibecode Batman!
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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{};
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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&);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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&);
|
||||
};
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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&);
|
||||
};
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ namespace anm2ed::imgui
|
||||
class FrameProperties
|
||||
{
|
||||
wizard::ChangeAllFrameProperties changeAllFrameProperties{};
|
||||
bool isBatchMode{};
|
||||
|
||||
public:
|
||||
void update(Manager&, Settings&);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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&);
|
||||
};
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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&);
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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), ®ion.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*)®ion.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();
|
||||
}
|
||||
}
|
||||
@@ -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&);
|
||||
};
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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&);
|
||||
};
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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&);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
+2091
-736
File diff suppressed because it is too large
Load Diff
@@ -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{};
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
@@ -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&);
|
||||
}
|
||||
@@ -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},
|
||||
|
||||
@@ -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 = ®ionIt->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);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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&);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user