diff --git a/src/anm2/anm2.cpp b/src/anm2/anm2.cpp index ff66961..4e040ad 100644 --- a/src/anm2/anm2.cpp +++ b/src/anm2/anm2.cpp @@ -19,7 +19,7 @@ namespace anm2ed::anm2 { Anm2::Anm2() { info.createdOn = time::get("%m/%d/%Y %I:%M:%S %p"); } - Anm2::Anm2(const std::string& path, std::string* errorString) + Anm2::Anm2(const std::filesystem::path& path, std::string* errorString) { XMLDocument document; @@ -51,7 +51,7 @@ namespace anm2ed::anm2 return element; } - bool Anm2::serialize(const std::string& path, std::string* errorString) + bool Anm2::serialize(const std::filesystem::path& path, std::string* errorString) { XMLDocument document; document.InsertFirstChild(to_element(document)); @@ -326,7 +326,8 @@ namespace anm2ed::anm2 animations.items.push_back(std::move(processed)); } - if (animations.defaultAnimation.empty() && !source.animations.defaultAnimation.empty()) { + if (animations.defaultAnimation.empty() && !source.animations.defaultAnimation.empty()) + { animations.defaultAnimation = source.animations.defaultAnimation; } } diff --git a/src/anm2/anm2.h b/src/anm2/anm2.h index f1f5c7f..84de141 100644 --- a/src/anm2/anm2.h +++ b/src/anm2/anm2.h @@ -33,16 +33,17 @@ namespace anm2ed::anm2 Anm2(); tinyxml2::XMLElement* to_element(tinyxml2::XMLDocument&); - bool serialize(const std::string&, std::string* = nullptr); + bool serialize(const std::filesystem::path&, std::string* = nullptr); std::string to_string(); - Anm2(const std::string&, std::string* = nullptr); + Anm2(const std::filesystem::path&, std::string* = nullptr); uint64_t hash(); Spritesheet* spritesheet_get(int); - bool spritesheet_add(const std::string&, const std::string&, int&); + bool spritesheet_add(const std::filesystem::path&, const std::filesystem::path&, int&); std::vector spritesheet_labels_get(); std::set spritesheets_unused(); - bool spritesheets_deserialize(const std::string&, const std::string&, types::merge::Type type, std::string*); + bool spritesheets_deserialize(const std::string&, const std::filesystem::path&, types::merge::Type type, + std::string*); void layer_add(int&); std::set layers_unused(); @@ -59,10 +60,10 @@ namespace anm2ed::anm2 std::set events_unused(); bool events_deserialize(const std::string&, types::merge::Type, std::string*); - bool sound_add(const std::string& directory, const std::string& path, int& id); + bool sound_add(const std::filesystem::path& directory, const std::filesystem::path& path, int& id); std::vector sound_labels_get(); std::set sounds_unused(); - bool sounds_deserialize(const std::string&, const std::string&, types::merge::Type, std::string*); + bool sounds_deserialize(const std::string&, const std::filesystem::path&, types::merge::Type, std::string*); Animation* animation_get(int); std::vector animation_labels_get(); @@ -75,7 +76,6 @@ namespace anm2ed::anm2 Reference null_animation_add(Reference = {}, std::string = {}, types::destination::Type = types::destination::ALL); Frame* frame_get(int, Type, int, int = -1); - void merge(const Anm2& source, const std::filesystem::path& destinationDirectory = {}, - const std::filesystem::path& sourceDirectory = {}); + void merge(const Anm2&, const std::filesystem::path&, const std::filesystem::path&); }; } diff --git a/src/anm2/anm2_sounds.cpp b/src/anm2/anm2_sounds.cpp index d8abbd9..2b46925 100644 --- a/src/anm2/anm2_sounds.cpp +++ b/src/anm2/anm2_sounds.cpp @@ -9,7 +9,7 @@ using namespace tinyxml2; namespace anm2ed::anm2 { - bool Anm2::sound_add(const std::string& directory, const std::string& path, int& id) + bool Anm2::sound_add(const std::filesystem::path& directory, const std::filesystem::path& path, int& id) { id = map::next_id_get(content.sounds); content.sounds[id] = Sound(directory, path); @@ -19,9 +19,12 @@ namespace anm2ed::anm2 std::vector Anm2::sound_labels_get() { std::vector labels{}; - labels.emplace_back("None"); + labels.emplace_back(localize.get(BASIC_NONE)); for (auto& [id, sound] : content.sounds) - labels.emplace_back(sound.path.string()); + { + auto pathCStr = sound.path.c_str(); + labels.emplace_back(std::vformat(localize.get(FORMAT_SOUND), std::make_format_args(id, pathCStr))); + } return labels; } @@ -39,7 +42,7 @@ namespace anm2ed::anm2 return unused; } - bool Anm2::sounds_deserialize(const std::string& string, const std::string& directory, merge::Type type, + bool Anm2::sounds_deserialize(const std::string& string, const std::filesystem::path& directory, merge::Type type, std::string* errorString) { XMLDocument document{}; diff --git a/src/anm2/anm2_spritesheets.cpp b/src/anm2/anm2_spritesheets.cpp index 134127c..f351d46 100644 --- a/src/anm2/anm2_spritesheets.cpp +++ b/src/anm2/anm2_spritesheets.cpp @@ -13,7 +13,7 @@ namespace anm2ed::anm2 { Spritesheet* Anm2::spritesheet_get(int id) { return map::find(content.spritesheets, id); } - bool Anm2::spritesheet_add(const std::string& directory, const std::string& path, int& id) + bool Anm2::spritesheet_add(const std::filesystem::path& directory, const std::filesystem::path& path, int& id) { Spritesheet spritesheet(directory, path); if (!spritesheet.is_valid()) return false; @@ -41,14 +41,14 @@ namespace anm2ed::anm2 labels.emplace_back(localize.get(BASIC_NONE)); for (auto& [id, spritesheet] : content.spritesheets) { - auto string = spritesheet.path.string(); - labels.emplace_back(std::vformat(localize.get(FORMAT_SPRITESHEET), std::make_format_args(id, string))); + auto pathCStr = spritesheet.path.c_str(); + labels.emplace_back(std::vformat(localize.get(FORMAT_SPRITESHEET), std::make_format_args(id, pathCStr))); } return labels; } - bool Anm2::spritesheets_deserialize(const std::string& string, const std::string& directory, merge::Type type, - std::string* errorString) + bool Anm2::spritesheets_deserialize(const std::string& string, const std::filesystem::path& directory, + merge::Type type, std::string* errorString) { XMLDocument document{}; diff --git a/src/anm2/sound.cpp b/src/anm2/sound.cpp index deb61e9..8a1b901 100644 --- a/src/anm2/sound.cpp +++ b/src/anm2/sound.cpp @@ -9,14 +9,14 @@ using namespace tinyxml2; namespace anm2ed::anm2 { - Sound::Sound(const Sound& other) : path(other.path) { audio = path.empty() ? Audio() : Audio(path); } + Sound::Sound(const Sound& other) : path(other.path), audio(other.audio) {} Sound& Sound::operator=(const Sound& other) { if (this != &other) { path = other.path; - audio = path.empty() ? Audio() : Audio(path); + audio = other.audio; } return *this; } @@ -33,7 +33,7 @@ namespace anm2ed::anm2 } } - Sound::Sound(const std::string& directory, const std::string& path) + Sound::Sound(const std::filesystem::path& directory, const std::filesystem::path& path) { filesystem::WorkingDirectory workingDirectory(directory); this->path = !path.empty() ? make_relative_or_keep(path) : this->path; @@ -41,11 +41,6 @@ namespace anm2ed::anm2 audio = Audio(this->path); } - Sound::Sound(const std::filesystem::path& directory, const std::filesystem::path& path) - : Sound(directory.string(), path.string()) - { - } - Sound::Sound(XMLElement* element, int& id) { if (!element) return; diff --git a/src/anm2/sound.h b/src/anm2/sound.h index 642f5fc..4a55e33 100644 --- a/src/anm2/sound.h +++ b/src/anm2/sound.h @@ -20,7 +20,6 @@ namespace anm2ed::anm2 Sound(const Sound&); Sound& operator=(const Sound&); Sound(tinyxml2::XMLElement*, int&); - Sound(const std::string&, const std::string&); Sound(const std::filesystem::path&, const std::filesystem::path&); tinyxml2::XMLElement* to_element(tinyxml2::XMLDocument&, int); std::string to_string(int); diff --git a/src/anm2/spritesheet.cpp b/src/anm2/spritesheet.cpp index 88f6093..0a58717 100644 --- a/src/anm2/spritesheet.cpp +++ b/src/anm2/spritesheet.cpp @@ -33,7 +33,7 @@ namespace anm2ed::anm2 } } - Spritesheet::Spritesheet(const std::string& directory, const std::string& path) + Spritesheet::Spritesheet(const std::filesystem::path& directory, const std::filesystem::path& path) { filesystem::WorkingDirectory workingDirectory(directory); this->path = !path.empty() ? make_relative_or_keep(path) : this->path; @@ -41,11 +41,6 @@ namespace anm2ed::anm2 texture = Texture(this->path); } - Spritesheet::Spritesheet(const std::filesystem::path& directory, const std::filesystem::path& path) - : Spritesheet(directory.string(), path.string()) - { - } - XMLElement* Spritesheet::to_element(XMLDocument& document, int id) { auto element = document.NewElement("Spritesheet"); @@ -67,7 +62,7 @@ namespace anm2ed::anm2 return xml::document_to_string(document); } - bool Spritesheet::save(const std::string& directory, const std::string& path) + bool Spritesheet::save(const std::filesystem::path& directory, const std::filesystem::path& path) { filesystem::WorkingDirectory workingDirectory(directory); this->path = !path.empty() ? make_relative_or_keep(path) : this->path; diff --git a/src/anm2/spritesheet.h b/src/anm2/spritesheet.h index f0c1d0a..4c28808 100644 --- a/src/anm2/spritesheet.h +++ b/src/anm2/spritesheet.h @@ -16,11 +16,10 @@ namespace anm2ed::anm2 Spritesheet() = default; Spritesheet(tinyxml2::XMLElement*, int&); - Spritesheet(const std::string&, const std::string& = {}); Spritesheet(const std::filesystem::path&, const std::filesystem::path& = {}); tinyxml2::XMLElement* to_element(tinyxml2::XMLDocument&, int); std::string to_string(int id); - bool save(const std::string&, const std::string& = {}); + bool save(const std::filesystem::path&, const std::filesystem::path& = {}); void serialize(tinyxml2::XMLDocument&, tinyxml2::XMLElement*, int); void reload(const std::filesystem::path&); bool is_valid(); diff --git a/src/dialog.cpp b/src/dialog.cpp index 8bafbf0..81c0949 100644 --- a/src/dialog.cpp +++ b/src/dialog.cpp @@ -58,12 +58,12 @@ namespace anm2ed this->type = type; } - void Dialog::file_explorer_open(const std::string& path) + void Dialog::file_explorer_open(const std::filesystem::path& path) { #ifdef _WIN32 ShellExecuteA(NULL, "open", path.c_str(), NULL, NULL, SW_SHOWNORMAL); #elif __unix__ - system(std::format("xdg-open \"{}\" &", path).c_str()); + system(std::format("xdg-open \"{}\" &", path.c_str()).c_str()); #else toasts.push(localize.get(TOAST_NOT_SUPPORTED)); logger.warning(localize.get(TOAST_NOT_SUPPORTED, anm2ed::ENGLISH)); @@ -74,11 +74,4 @@ namespace anm2ed bool Dialog::is_selected(dialog::Type type) const { return this->type == type && !path.empty(); } - void Dialog::set_string_to_selected_path(std::string& string, dialog::Type type) - { - if (type == NONE) return; - if (!is_selected(type)) return; - string = path; - reset(); - } }; diff --git a/src/dialog.h b/src/dialog.h index 8750870..bcb1fc4 100644 --- a/src/dialog.h +++ b/src/dialog.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include @@ -43,6 +43,7 @@ namespace anm2ed::dialog X(ANM2_OPEN, ANM2) \ X(ANM2_SAVE, ANM2) \ X(SOUND_OPEN, SOUND) \ + X(SOUND_REPLACE, SOUND) \ X(SPRITESHEET_OPEN, PNG) \ X(SPRITESHEET_REPLACE, PNG) \ X(FFMPEG_PATH_SET, EXECUTABLE) \ @@ -75,7 +76,7 @@ namespace anm2ed { public: SDL_Window* window{}; - std::string path{}; + std::filesystem::path path{}; dialog::Type type{dialog::NONE}; int selectedFilter{-1}; @@ -86,7 +87,6 @@ namespace anm2ed void folder_open(dialog::Type type); bool is_selected(dialog::Type type) const; void reset(); - void file_explorer_open(const std::string&); - void set_string_to_selected_path(std::string& set, dialog::Type type); + void file_explorer_open(const std::filesystem::path&); }; } diff --git a/src/imgui/imgui_.cpp b/src/imgui/imgui_.cpp index 48b7e9e..3bc0ee5 100644 --- a/src/imgui/imgui_.cpp +++ b/src/imgui/imgui_.cpp @@ -154,8 +154,7 @@ namespace anm2ed::imgui { if (ImGui::Selectable(label.c_str(), isSelected, flags)) isActivated = true; - if (state == RENAME_FORCE_EDIT || (ImGui::IsWindowFocused() && ImGui::IsKeyPressed(ImGuiKey_F2)) || - (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left))) + if (state == RENAME_FORCE_EDIT || (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left))) { state = RENAME_BEGIN; editID = id; diff --git a/src/imgui/imgui_.h b/src/imgui/imgui_.h index 8c19181..7481d9f 100644 --- a/src/imgui/imgui_.h +++ b/src/imgui/imgui_.h @@ -206,9 +206,8 @@ namespace anm2ed::imgui using std::set::erase; MultiSelectStorage(); - void start(size_t, ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_BoxSelect2d | - ImGuiMultiSelectFlags_ClearOnEscape | - ImGuiMultiSelectFlags_ScopeWindow); + void start(size_t, + ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_ScopeWindow); void apply(); void finish(); void set_index_map(std::vector*); diff --git a/src/imgui/taskbar.cpp b/src/imgui/taskbar.cpp index 9869436..57ce9c3 100644 --- a/src/imgui/taskbar.cpp +++ b/src/imgui/taskbar.cpp @@ -742,7 +742,12 @@ namespace anm2ed::imgui ImGui::SameLine(); input_text_string(localize.get(LABEL_FFMPEG_PATH), &ffmpegPath); ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_FFMPEG_PATH)); - dialog.set_string_to_selected_path(ffmpegPath, dialog::FFMPEG_PATH_SET); + + if (dialog.is_selected(dialog::FFMPEG_PATH_SET)) + { + ffmpegPath = dialog.path; + dialog.reset(); + } if (ImGui::ImageButton("##Path Set", resources.icons[icon::FOLDER].id, icon_size_get())) { @@ -755,7 +760,12 @@ namespace anm2ed::imgui auto pathLabel = type == render::PNGS ? LABEL_OUTPUT_DIRECTORY : LABEL_OUTPUT_PATH; input_text_string(localize.get(pathLabel), &path); ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_OUTPUT_PATH)); - dialog.set_string_to_selected_path(path, dialogType); + + if (dialog.is_selected(dialogType)) + { + path = dialog.path; + dialog.reset(); + } if (ImGui::Combo(localize.get(LABEL_TYPE), &type, render::STRINGS, render::COUNT)) render_set(); ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_RENDER_TYPE)); diff --git a/src/imgui/window/animation_preview.cpp b/src/imgui/window/animation_preview.cpp index 598256b..6bfd45f 100644 --- a/src/imgui/window/animation_preview.cpp +++ b/src/imgui/window/animation_preview.cpp @@ -275,6 +275,22 @@ namespace anm2ed::imgui auto center_view = [&]() { pan = vec2(); }; + auto fit_view = [&]() + { + if (animation) set_to_rect(zoom, pan, animation->rect(isRootTransform)); + }; + + auto zoom_adjust = [&](float delta) + { + auto focus = position_translate(zoom, pan, size * 0.5f); + auto previousZoom = zoom; + zoom_set(zoom, pan, focus, delta); + if (zoom != previousZoom) hasPendingZoomPanAdjust = true; + }; + + auto zoom_in = [&]() { zoom_adjust(zoomStep); }; + auto zoom_out = [&]() { zoom_adjust(-zoomStep); }; + if (ImGui::Begin(localize.get(LABEL_ANIMATION_PREVIEW_WINDOW), &settings.windowIsAnimationPreview)) { auto childSize = ImVec2(row_widget_width_get(4), @@ -306,14 +322,13 @@ namespace anm2ed::imgui auto widgetSize = widget_size_with_row_get(2); shortcut(manager.chords[SHORTCUT_CENTER_VIEW]); - if (ImGui::Button(localize.get(LABEL_CENTER_VIEW), widgetSize)) pan = vec2(); + if (ImGui::Button(localize.get(LABEL_CENTER_VIEW), widgetSize)) center_view(); set_item_tooltip_shortcut(localize.get(TOOLTIP_CENTER_VIEW), settings.shortcutCenterView); ImGui::SameLine(); shortcut(manager.chords[SHORTCUT_FIT]); - if (ImGui::Button(localize.get(LABEL_FIT), widgetSize)) - if (animation) set_to_rect(zoom, pan, animation->rect(isRootTransform)); + if (ImGui::Button(localize.get(LABEL_FIT), widgetSize)) fit_view(); set_item_tooltip_shortcut(localize.get(TOOLTIP_FIT), settings.shortcutFit); auto mousePosInt = ivec2(mousePos); @@ -813,6 +828,29 @@ namespace anm2ed::imgui } } } + + if (ImGui::BeginPopupContextWindow("##Animation Preview Context Menu", ImGuiMouseButton_Right)) + { + if (ImGui::MenuItem(localize.get(SHORTCUT_STRING_UNDO), settings.shortcutUndo.c_str(), false, + document.is_able_to_undo())) + document.undo(); + + if (ImGui::MenuItem(localize.get(SHORTCUT_STRING_REDO), settings.shortcutRedo.c_str(), false, + document.is_able_to_redo())) + document.redo(); + + ImGui::Separator(); + + if (ImGui::MenuItem(localize.get(LABEL_CENTER_VIEW), settings.shortcutCenterView.c_str())) center_view(); + if (ImGui::MenuItem(localize.get(LABEL_FIT), settings.shortcutFit.c_str(), false, 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(); + } ImGui::End(); manager.progressPopup.trigger(); diff --git a/src/imgui/window/animations.cpp b/src/imgui/window/animations.cpp index 1f6e4df..6dcd285 100644 --- a/src/imgui/window/animations.cpp +++ b/src/imgui/window/animations.cpp @@ -1,6 +1,5 @@ #include "animations.h" -#include #include #include @@ -20,27 +19,185 @@ namespace anm2ed::imgui auto& document = *manager.get(); auto& anm2 = document.anm2; auto& reference = document.reference; - auto& hovered = document.animation.hovered; auto& selection = document.animation.selection; auto& mergeSelection = document.merge.selection; auto& mergeReference = document.merge.reference; auto& overlayIndex = document.overlayIndex; - hovered = -1; + auto rename_format_get = [&](int index) + { return std::format("###Document #{} Animation #{}", manager.selected, index); }; - auto animations_remove = [&]() + auto rename = [&]() { - if (!selection.empty()) + if (!selection.empty()) renameQueued = *selection.begin(); + }; + + auto add = [&]() + { + auto behavior = [&]() { - for (auto it = selection.rbegin(); it != selection.rend(); ++it) + anm2::Animation animation; + animation.name = localize.get(TEXT_NEW_ANIMATION); + if (anm2::Animation* referenceAnimation = document.animation_get()) { - auto i = *it; - if (overlayIndex == i) overlayIndex = -1; - if (reference.animationIndex == i) reference.animationIndex = -1; - anm2.animations.items.erase(anm2.animations.items.begin() + i); + 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(); } - selection.clear(); - } + 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 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 indices{}; + std::string errorString{}; + if (anm2.animations_deserialize(clipboardText, start, indices, &errorString)) + selection = indices; + 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)) @@ -70,9 +227,13 @@ namespace anm2ed::imgui ImGui::PushFont(resources.fonts[font].get(), font::SIZE); ImGui::SetNextItemSelectionUserData((int)i); - if (isNewAnimation) renameState = RENAME_FORCE_EDIT; - if (selectable_input_text(animation.name, std::format("###Document #{} Animation #{}", manager.selected, i), - animation.name, selection.contains((int)i), ImGuiSelectableFlags_None, renameState)) + 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(); @@ -85,7 +246,6 @@ namespace anm2ed::imgui document.change(Document::ANIMATIONS); } } - if (ImGui::IsItemHovered()) hovered = (int)i; if (isNewAnimation) { @@ -145,68 +305,42 @@ namespace anm2ed::imgui selection.finish(); - auto copy = [&]() - { - if (!selection.empty()) - { - std::string clipboardText{}; - for (auto& i : selection) - clipboardText += anm2.animations.items[i].to_string(); - clipboard.set(clipboardText); - } - else if (hovered > -1) - clipboard.set(anm2.animations.items[hovered].to_string()); - }; - - auto cut = [&]() - { - copy(); - DOCUMENT_EDIT(document, localize.get(EDIT_CUT_ANIMATIONS), Document::ANIMATIONS, animations_remove()); - }; - - auto paste = [&]() - { - auto clipboardText = clipboard.get(); - - auto deserialize = [&]() - { - auto start = selection.empty() ? anm2.animations.items.size() : *selection.rbegin() + 1; - std::set indices{}; - std::string errorString{}; - if (anm2.animations_deserialize(clipboardText, start, indices, &errorString)) - selection = indices; - 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, deserialize()); - }; - + 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(BASIC_CUT), settings.shortcutCut.c_str(), false, - !selection.empty() || hovered > -1)) - { - cut(); - } - if (ImGui::MenuItem(localize.get(BASIC_COPY), settings.shortcutCopy.c_str(), false, - !selection.empty() || hovered > -1)) - { + 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(); } } @@ -215,133 +349,45 @@ namespace anm2ed::imgui auto widgetSize = widget_size_with_row_get(5); shortcut(manager.chords[SHORTCUT_ADD]); - if (ImGui::Button(localize.get(BASIC_ADD), widgetSize)) - { - auto add = [&]() - { - 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, 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)) - { - auto duplicate = [&]() - { - 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_DUPLICATE_ANIMATIONS), Document::ANIMATIONS, duplicate()); - } - set_item_tooltip_shortcut(localize.get(TOOLTIP_DUPLICATE_ANIMATION), settings.shortcutDuplicate); - - ImGui::SameLine(); - - if (shortcut(manager.chords[SHORTCUT_MERGE], shortcut::FOCUSED) && !selection.empty()) - { - auto merge_quick = [&]() - { - 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 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, merge_quick()) - } - - ImGui::BeginDisabled(selection.size() != 1); - { - if (ImGui::Button(localize.get(LABEL_MERGE), widgetSize)) - { - mergePopup.open(); - mergeSelection.clear(); - mergeReference = *selection.begin(); - } - } - ImGui::EndDisabled(); - set_item_tooltip_shortcut(localize.get(TOOLTIP_OPEN_MERGE_POPUP), settings.shortcutMerge); - - ImGui::SameLine(); - - shortcut(manager.chords[SHORTCUT_REMOVE]); - if (ImGui::Button(localize.get(BASIC_REMOVE), widgetSize)) - DOCUMENT_EDIT(document, localize.get(EDIT_REMOVE_ANIMATIONS), Document::ANIMATIONS, animations_remove()); - set_item_tooltip_shortcut(localize.get(TOOLTIP_REMOVE_ANIMATION), settings.shortcutRemove); - - ImGui::SameLine(); - - shortcut(manager.chords[SHORTCUT_DEFAULT]); - ImGui::BeginDisabled(selection.size() != 1); - if (ImGui::Button(localize.get(BASIC_DEFAULT), widgetSize)) - { - DOCUMENT_EDIT(document, localize.get(EDIT_DEFAULT_ANIMATION), Document::ANIMATIONS, - anm2.animations.defaultAnimation = anm2.animations.items[*selection.begin()].name); - } - ImGui::EndDisabled(); - set_item_tooltip_shortcut(localize.get(TOOLTIP_SET_DEFAULT_ANIMATION), settings.shortcutDefault); - } + 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 merge_close = [&]() + auto close = [&]() { mergeSelection.clear(); mergePopup.close(); @@ -361,16 +407,16 @@ namespace anm2ed::imgui { mergeSelection.start(anm2.animations.items.size()); - for (std::size_t index = 0; index < anm2.animations.items.size(); ++index) + for (int i = 0; i < (int)anm2.animations.items.size(); i++) { - if ((int)index == mergeReference) continue; + if (i == mergeReference) continue; - auto& animation = anm2.animations.items[index]; + auto& animation = anm2.animations.items[i]; - ImGui::PushID((int)index); + ImGui::PushID(i); - ImGui::SetNextItemSelectionUserData((int)index); - ImGui::Selectable(animation.name.c_str(), mergeSelection.contains((int)index)); + ImGui::SetNextItemSelectionUserData(i); + ImGui::Selectable(animation.name.c_str(), mergeSelection.contains(i)); ImGui::PopID(); } @@ -408,30 +454,19 @@ namespace anm2ed::imgui auto widgetSize = widget_size_with_row_get(2); ImGui::BeginDisabled(mergeSelection.empty()); + if (ImGui::Button(localize.get(LABEL_MERGE), widgetSize)) { - if (ImGui::Button(localize.get(LABEL_MERGE), widgetSize)) - { - auto merge = [&]() - { - if (mergeSelection.contains(overlayIndex)) overlayIndex = -1; - auto merged = - anm2.animations_merge(mergeReference, mergeSelection, (merge::Type)type, isDeleteAnimationsAfter); - - selection = {merged}; - reference = {merged}; - }; - - DOCUMENT_EDIT(document, localize.get(EDIT_MERGE_ANIMATIONS), Document::ANIMATIONS, merge()); - merge_close(); - } + merge(); + close(); } ImGui::EndDisabled(); + ImGui::SameLine(); - if (ImGui::Button(localize.get(LABEL_CLOSE), widgetSize)) merge_close(); + if (ImGui::Button(localize.get(LABEL_CLOSE), widgetSize)) close(); ImGui::EndPopup(); } + ImGui::End(); } - ImGui::End(); } -} +} \ No newline at end of file diff --git a/src/imgui/window/animations.h b/src/imgui/window/animations.h index 0c24091..0a08fea 100644 --- a/src/imgui/window/animations.h +++ b/src/imgui/window/animations.h @@ -12,6 +12,8 @@ namespace anm2ed::imgui { PopupHelper mergePopup{PopupHelper(LABEL_ANIMATIONS_MERGE_POPUP)}; int newAnimationSelectedIndex{-1}; + int renameQueued{-1}; + bool isInContextMenu{}; RenameState renameState{RENAME_SELECTABLE}; public: diff --git a/src/imgui/window/events.cpp b/src/imgui/window/events.cpp index a2dd6f8..3259610 100644 --- a/src/imgui/window/events.cpp +++ b/src/imgui/window/events.cpp @@ -18,11 +18,75 @@ namespace anm2ed::imgui auto& document = *manager.get(); auto& anm2 = document.anm2; auto& unused = document.event.unused; - auto& hovered = document.event.hovered; auto& reference = document.event.reference; auto& selection = document.event.selection; - hovered = -1; + 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 = [&]() + { + if (unused.empty()) return; + + auto behavior = [&]() + { + for (auto& id : unused) + anm2.content.events.erase(id); + unused.clear(); + }; + + 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 = [&]() + { + std::string errorString{}; + document.snapshot(localize.get(EDIT_PASTE_EVENTS)); + if (anm2.events_deserialize(clipboard.get(), merge::APPEND, &errorString)) + 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)) { @@ -38,16 +102,19 @@ namespace anm2ed::imgui ImGui::PushID(id); ImGui::SetNextItemSelectionUserData(id); - if (isNewEvent) renameState = RENAME_FORCE_EDIT; - if (selectable_input_text(event.name, std::format("###Document #{} Event #{}", manager.selected, id), - event.name, selection.contains(id), ImGuiSelectableFlags_None, renameState)) + 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 (ImGui::IsItemHovered()) hovered = id; if (isNewEvent) { @@ -68,51 +135,39 @@ namespace anm2ed::imgui selection.finish(); - auto copy = [&]() - { - if (!selection.empty()) - { - std::string clipboardText{}; - for (auto& id : selection) - clipboardText += anm2.content.events[id].to_string(id); - clipboard.set(clipboardText); - } - else if (hovered > -1) - clipboard.set(anm2.content.events[hovered].to_string(hovered)); - }; - - auto paste = [&](merge::Type type) - { - std::string errorString{}; - document.snapshot(localize.get(EDIT_PASTE_EVENTS)); - if (anm2.events_deserialize(clipboard.get(), type, &errorString)) - 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))); - } - }; - + 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(merge::APPEND); + if (shortcut(manager.chords[SHORTCUT_PASTE], shortcut::FOCUSED)) paste(); if (ImGui::BeginPopupContextWindow("##Context Menu", ImGuiPopupFlags_MouseButtonRight)) { - ImGui::MenuItem(localize.get(BASIC_CUT), settings.shortcutCut.c_str(), false, false); - if (ImGui::MenuItem(localize.get(BASIC_COPY), settings.shortcutCopy.c_str(), false, - !selection.empty() || hovered > -1)) + 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(), false, + !unused.empty())) + remove_unused(); + + ImGui::Separator(); + + if (ImGui::MenuItem(localize.get(BASIC_COPY), settings.shortcutCopy.c_str(), false, !selection.empty())) copy(); - if (ImGui::BeginMenu(localize.get(BASIC_PASTE), !clipboard.is_empty())) - { - if (ImGui::MenuItem(localize.get(BASIC_APPEND), settings.shortcutPaste.c_str())) paste(merge::APPEND); - if (ImGui::MenuItem(localize.get(BASIC_REPLACE))) paste(merge::REPLACE); - - ImGui::EndMenu(); - } + if (ImGui::MenuItem(localize.get(BASIC_PASTE), settings.shortcutPaste.c_str(), false, !clipboard.is_empty())) + paste(); ImGui::EndPopup(); } @@ -122,37 +177,13 @@ namespace anm2ed::imgui auto widgetSize = widget_size_with_row_get(2); shortcut(manager.chords[SHORTCUT_ADD]); - if (ImGui::Button(localize.get(BASIC_ADD), widgetSize)) - { - auto add = [&]() - { - 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, 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]); ImGui::BeginDisabled(unused.empty()); - if (ImGui::Button(localize.get(BASIC_REMOVE_UNUSED), widgetSize)) - { - auto remove_unused = [&]() - { - for (auto& id : unused) - anm2.content.events.erase(id); - unused.clear(); - }; - - DOCUMENT_EDIT(document, localize.get(EDIT_REMOVE_UNUSED_EVENTS), Document::EVENTS, remove_unused()); - } + shortcut(manager.chords[SHORTCUT_REMOVE]); + if (ImGui::Button(localize.get(BASIC_REMOVE_UNUSED), widgetSize)) remove_unused(); ImGui::EndDisabled(); set_item_tooltip_shortcut(localize.get(TOOLTIP_REMOVE_UNUSED_EVENTS), settings.shortcutRemove); } diff --git a/src/imgui/window/events.h b/src/imgui/window/events.h index cf62f6b..5d956c5 100644 --- a/src/imgui/window/events.h +++ b/src/imgui/window/events.h @@ -10,6 +10,7 @@ namespace anm2ed::imgui class Events { int newEventId{-1}; + int renameQueued{-1}; RenameState renameState{RENAME_SELECTABLE}; public: diff --git a/src/imgui/window/layers.cpp b/src/imgui/window/layers.cpp index 47161f0..4a73ac1 100644 --- a/src/imgui/window/layers.cpp +++ b/src/imgui/window/layers.cpp @@ -19,11 +19,56 @@ namespace anm2ed::imgui auto& anm2 = document.anm2; auto& reference = document.layer.reference; auto& unused = document.layer.unused; - auto& hovered = document.layer.hovered; auto& selection = document.layer.selection; auto& propertiesPopup = manager.layerPropertiesPopup; - hovered = -1; + auto add = [&]() { manager.layer_properties_open(); }; + + auto remove_unused = [&]() + { + if (unused.empty()) return; + auto behavior = [&]() + { + for (auto& id : unused) + anm2.content.layers.erase(id); + unused.clear(); + }; + + 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 = [&]() + { + std::string errorString{}; + document.snapshot(localize.get(EDIT_PASTE_LAYERS)); + if (anm2.layers_deserialize(clipboard.get(), merge::APPEND, &errorString)) + 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)) { @@ -49,13 +94,7 @@ namespace anm2ed::imgui ImGui::SetScrollHereY(0.5f); newLayerId = -1; } - if (ImGui::IsItemHovered()) - { - hovered = id; - if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) manager.layer_properties_open(id); - } - else - hovered = -1; + if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) properties(id); if (ImGui::BeginItemTooltip()) { @@ -72,51 +111,37 @@ namespace anm2ed::imgui selection.finish(); - auto copy = [&]() - { - if (!selection.empty()) - { - std::string clipboardText{}; - for (auto& id : selection) - clipboardText += anm2.content.layers[id].to_string(id); - clipboard.set(clipboardText); - } - else if (hovered > -1) - clipboard.set(anm2.content.layers[hovered].to_string(hovered)); - }; - - auto paste = [&](merge::Type type) - { - std::string errorString{}; - document.snapshot(localize.get(EDIT_PASTE_LAYERS)); - if (anm2.layers_deserialize(clipboard.get(), type, &errorString)) - 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))); - } - }; - + 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(merge::APPEND); + if (shortcut(manager.chords[SHORTCUT_PASTE], shortcut::FOCUSED)) paste(); if (ImGui::BeginPopupContextWindow("##Context Menu", ImGuiPopupFlags_MouseButtonRight)) { - ImGui::MenuItem(localize.get(BASIC_CUT), settings.shortcutCut.c_str(), false, false); - if (ImGui::MenuItem(localize.get(BASIC_COPY), settings.shortcutCopy.c_str(), false, - !selection.empty() || hovered > -1)) + 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(), false, + !unused.empty())) + remove_unused(); + + ImGui::Separator(); + + if (ImGui::MenuItem(localize.get(BASIC_COPY), settings.shortcutCopy.c_str(), false, !selection.empty())) copy(); - if (ImGui::BeginMenu(localize.get(BASIC_PASTE), !clipboard.is_empty())) - { - if (ImGui::MenuItem(localize.get(BASIC_APPEND), settings.shortcutPaste.c_str())) paste(merge::APPEND); - if (ImGui::MenuItem(localize.get(BASIC_REPLACE))) paste(merge::REPLACE); - - ImGui::EndMenu(); - } + if (ImGui::MenuItem(localize.get(BASIC_PASTE), settings.shortcutPaste.c_str(), false, !clipboard.is_empty())) + paste(); ImGui::EndPopup(); } @@ -126,23 +151,13 @@ namespace anm2ed::imgui auto widgetSize = widget_size_with_row_get(2); shortcut(manager.chords[SHORTCUT_ADD]); - if (ImGui::Button(localize.get(BASIC_ADD), widgetSize)) manager.layer_properties_open(); + 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]); ImGui::BeginDisabled(unused.empty()); - if (ImGui::Button(localize.get(BASIC_REMOVE_UNUSED), widgetSize)) - { - auto remove_unused = [&]() - { - for (auto& id : unused) - anm2.content.layers.erase(id); - unused.clear(); - }; - - DOCUMENT_EDIT(document, localize.get(EDIT_REMOVE_UNUSED_LAYERS), Document::LAYERS, remove_unused()); - } + shortcut(manager.chords[SHORTCUT_REMOVE]); + if (ImGui::Button(localize.get(BASIC_REMOVE_UNUSED), widgetSize)) remove_unused(); ImGui::EndDisabled(); set_item_tooltip_shortcut(localize.get(TOOLTIP_REMOVE_UNUSED_LAYERS), settings.shortcutRemove); } @@ -171,7 +186,7 @@ namespace anm2ed::imgui { if (reference == -1) { - auto add = [&]() + auto layer_add = [&]() { auto id = map::next_id_get(anm2.content.layers); anm2.content.layers[id] = layer; @@ -179,17 +194,17 @@ namespace anm2ed::imgui newLayerId = id; }; - DOCUMENT_EDIT(document, localize.get(EDIT_ADD_LAYER), Document::LAYERS, add()); + DOCUMENT_EDIT(document, localize.get(EDIT_ADD_LAYER), Document::LAYERS, layer_add()); } else { - auto set = [&]() + auto layer_set = [&]() { anm2.content.layers[reference] = layer; selection = {reference}; }; - DOCUMENT_EDIT(document, localize.get(EDIT_SET_LAYER_PROPERTIES), Document::LAYERS, set()); + DOCUMENT_EDIT(document, localize.get(EDIT_SET_LAYER_PROPERTIES), Document::LAYERS, layer_set()); } manager.layer_properties_close(); diff --git a/src/imgui/window/nulls.cpp b/src/imgui/window/nulls.cpp index d8f71ea..2db84a4 100644 --- a/src/imgui/window/nulls.cpp +++ b/src/imgui/window/nulls.cpp @@ -19,11 +19,56 @@ namespace anm2ed::imgui auto& anm2 = document.anm2; auto& reference = document.null.reference; auto& unused = document.null.unused; - auto& hovered = document.null.hovered; auto& selection = document.null.selection; auto& propertiesPopup = manager.nullPropertiesPopup; - hovered = -1; + auto add = [&]() { manager.null_properties_open(); }; + + auto remove_unused = [&]() + { + if (unused.empty()) return; + auto behavior = [&]() + { + for (auto& id : unused) + anm2.content.nulls.erase(id); + unused.clear(); + }; + + 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 = [&]() + { + std::string errorString{}; + document.snapshot(localize.get(EDIT_PASTE_NULLS)); + if (anm2.nulls_deserialize(clipboard.get(), merge::APPEND, &errorString)) + 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)) { @@ -48,11 +93,7 @@ namespace anm2ed::imgui ImGui::SetScrollHereY(0.5f); newNullId = -1; } - if (ImGui::IsItemHovered()) - { - hovered = id; - if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) manager.null_properties_open(id); - } + if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) properties(id); if (isReferenced) ImGui::PopFont(); @@ -69,50 +110,37 @@ namespace anm2ed::imgui selection.finish(); - auto copy = [&]() - { - if (!selection.empty()) - { - std::string clipboardText{}; - for (auto& id : selection) - clipboardText += anm2.content.nulls[id].to_string(id); - clipboard.set(clipboardText); - } - else if (hovered > -1) - clipboard.set(anm2.content.nulls[hovered].to_string(hovered)); - }; - - auto paste = [&](merge::Type type) - { - std::string errorString{}; - document.snapshot(localize.get(EDIT_PASTE_NULLS)); - if (anm2.nulls_deserialize(clipboard.get(), type, &errorString)) - 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))); - } - }; - + 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(merge::APPEND); + if (shortcut(manager.chords[SHORTCUT_PASTE], shortcut::FOCUSED)) paste(); if (ImGui::BeginPopupContextWindow("##Context Menu", ImGuiPopupFlags_MouseButtonRight)) { - ImGui::MenuItem(localize.get(BASIC_CUT), settings.shortcutCut.c_str(), false, false); - if (ImGui::MenuItem(localize.get(BASIC_COPY), settings.shortcutCopy.c_str(), false, - selection.empty() || hovered > -1)) + 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(), false, + !unused.empty())) + remove_unused(); + + ImGui::Separator(); + + if (ImGui::MenuItem(localize.get(BASIC_COPY), settings.shortcutCopy.c_str(), false, !selection.empty())) copy(); - if (ImGui::BeginMenu(localize.get(BASIC_PASTE), !clipboard.is_empty())) - { - if (ImGui::MenuItem(localize.get(BASIC_APPEND), settings.shortcutPaste.c_str())) paste(merge::APPEND); - if (ImGui::MenuItem(localize.get(BASIC_REPLACE))) paste(merge::REPLACE); - - ImGui::EndMenu(); - } + if (ImGui::MenuItem(localize.get(BASIC_PASTE), settings.shortcutPaste.c_str(), false, !clipboard.is_empty())) + paste(); ImGui::EndPopup(); } @@ -122,23 +150,13 @@ namespace anm2ed::imgui auto widgetSize = widget_size_with_row_get(2); shortcut(manager.chords[SHORTCUT_ADD]); - if (ImGui::Button(localize.get(BASIC_ADD), widgetSize)) manager.null_properties_open(); + 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]); ImGui::BeginDisabled(unused.empty()); - if (ImGui::Button(localize.get(BASIC_REMOVE_UNUSED), widgetSize)) - { - auto remove_unused = [&]() - { - for (auto& id : unused) - anm2.content.nulls.erase(id); - unused.clear(); - }; - - DOCUMENT_EDIT(document, localize.get(EDIT_REMOVE_UNUSED_NULLS), Document::EVENTS, remove_unused()); - } + shortcut(manager.chords[SHORTCUT_REMOVE]); + if (ImGui::Button(localize.get(BASIC_REMOVE_UNUSED), widgetSize)) remove_unused(); ImGui::EndDisabled(); set_item_tooltip_shortcut(localize.get(TOOLTIP_REMOVE_UNUSED_NULLS), settings.shortcutRemove); } @@ -168,7 +186,7 @@ namespace anm2ed::imgui { if (reference == -1) { - auto add = [&]() + auto null_add = [&]() { auto id = map::next_id_get(anm2.content.nulls); anm2.content.nulls[id] = null; @@ -176,17 +194,17 @@ namespace anm2ed::imgui newNullId = id; }; - DOCUMENT_EDIT(document, localize.get(EDIT_ADD_NULL), Document::NULLS, add()); + DOCUMENT_EDIT(document, localize.get(EDIT_ADD_NULL), Document::NULLS, null_add()); } else { - auto set = [&]() + auto null_set = [&]() { anm2.content.nulls[reference] = null; selection = {reference}; }; - DOCUMENT_EDIT(document, localize.get(EDIT_SET_NULL_PROPERTIES), Document::NULLS, set()); + DOCUMENT_EDIT(document, localize.get(EDIT_SET_NULL_PROPERTIES), Document::NULLS, null_set()); } manager.null_properties_close(); diff --git a/src/imgui/window/sounds.cpp b/src/imgui/window/sounds.cpp index 1fa6a21..993b6de 100644 --- a/src/imgui/window/sounds.cpp +++ b/src/imgui/window/sounds.cpp @@ -2,13 +2,16 @@ #include +#include "filesystem_.h" #include "log.h" #include "strings.h" #include "toast.h" using namespace anm2ed::dialog; +using namespace anm2ed::util; using namespace anm2ed::types; using namespace anm2ed::resource; +using namespace glm; namespace anm2ed::imgui { @@ -18,157 +21,307 @@ namespace anm2ed::imgui auto& anm2 = document.anm2; auto& reference = document.sound.reference; auto& unused = document.sound.unused; - auto& hovered = document.null.hovered; auto& selection = document.sound.selection; + auto style = ImGui::GetStyle(); - hovered = -1; + 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.string(); + 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 = [&]() + { + if (unused.empty()) return; + + auto behavior = [&]() + { + for (auto& id : unused) + anm2.content.sounds.erase(id); + unused.clear(); + }; + + 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 = sound.path.string(); + 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().string(), path); + auto pathString = sound.path.string(); + 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; + filesystem::WorkingDirectory workingDirectory(document.directory_get()); + dialog.file_explorer_open(sound.path.parent_path()); + }; + + 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 = [&]() + { + std::string errorString{}; + document.snapshot(localize.get(TOAST_SOUNDS_PASTE)); + if (anm2.sounds_deserialize(clipboard.get(), document.directory_get().string(), merge::APPEND, &errorString)) + 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(), false, !unused.empty())) + 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(); - if (ImGui::BeginChild("##Sounds Child", childSize, true)) + 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()); for (auto& [id, sound] : anm2.content.sounds) { - auto isSelected = selection.contains(id); - auto isReferenced = reference == id; - const std::string pathString = sound.path.empty() ? std::string{anm2::NO_PATH} : sound.path.string(); - const char* pathLabel = pathString.c_str(); - ImGui::PushID(id); - ImGui::SetNextItemSelectionUserData(id); - if (isReferenced) ImGui::PushFont(resources.fonts[font::ITALICS].get(), font::SIZE); - if (ImGui::Selectable(pathLabel, isSelected)) sound.play(); - if (ImGui::IsItemHovered()) hovered = id; - if (newSoundId == id) - { - ImGui::SetScrollHereY(0.5f); - newSoundId = -1; - } - if (isReferenced) ImGui::PopFont(); - - if (ImGui::BeginItemTooltip()) + if (ImGui::BeginChild("##Sound Child", soundChildSize, ImGuiChildFlags_Borders)) { - ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE); - ImGui::TextUnformatted(pathLabel); - ImGui::PopFont(); - ImGui::Text("%s: %d", localize.get(BASIC_ID), id); - ImGui::Text("%s", localize.get(TOOLTIP_SOUNDS_PLAY)); - if (!sound.is_valid()) + 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 pathCStr = sound.path.c_str(); + + ImGui::SetNextItemSelectionUserData(id); + ImGui::SetNextItemStorageID(id); + if (ImGui::Selectable("##Sound Selectable", isSelected, 0, soundChildSize)) { - ImGui::Spacing(); - ImGui::TextWrapped("%s", localize.get(TOOLTIP_SOUND_INVALID)); + reference = id; + play(sound); } - ImGui::EndTooltip(); + if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) open_directory(sound); + if (newSoundId == id) + { + ImGui::SetScrollHereY(0.5f); + newSoundId = -1; + } + + auto textWidth = ImGui::CalcTextSize(pathCStr).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(sound.path.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, pathCStr)).c_str()); } + + ImGui::EndChild(); ImGui::PopID(); } + context_menu(); + + ImGui::PopStyleVar(); selection.finish(); - - auto copy = [&]() - { - if (!selection.empty()) - { - std::string clipboardText{}; - for (auto& id : selection) - clipboardText += anm2.content.sounds[id].to_string(id); - clipboard.set(clipboardText); - } - else if (hovered > -1) - clipboard.set(anm2.content.sounds[hovered].to_string(hovered)); - }; - - auto paste = [&](merge::Type type) - { - std::string errorString{}; - document.snapshot(localize.get(TOAST_SOUNDS_PASTE)); - if (anm2.sounds_deserialize(clipboard.get(), document.directory_get().string(), type, &errorString)) - 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))); - } - }; - - if (imgui::shortcut(manager.chords[SHORTCUT_COPY], shortcut::FOCUSED)) copy(); - if (imgui::shortcut(manager.chords[SHORTCUT_PASTE], shortcut::FOCUSED)) paste(merge::APPEND); - - if (ImGui::BeginPopupContextWindow("##Context Menu", ImGuiPopupFlags_MouseButtonRight)) - { - ImGui::MenuItem(localize.get(BASIC_CUT), settings.shortcutCut.c_str(), false, false); - if (ImGui::MenuItem(localize.get(BASIC_COPY), settings.shortcutCopy.c_str(), - !selection.empty() && hovered > -1)) - copy(); - - if (ImGui::BeginMenu(localize.get(BASIC_PASTE), !clipboard.is_empty())) - { - if (ImGui::MenuItem(localize.get(BASIC_APPEND), settings.shortcutPaste.c_str())) paste(merge::APPEND); - if (ImGui::MenuItem(localize.get(BASIC_REPLACE))) paste(merge::REPLACE); - - ImGui::EndMenu(); - } - - ImGui::EndPopup(); - } } - ImGui::EndChild(); - auto widgetSize = imgui::widget_size_with_row_get(2); + ImGui::EndChild(); + ImGui::PopStyleVar(); + + auto widgetSize = imgui::widget_size_with_row_get(4); imgui::shortcut(manager.chords[SHORTCUT_ADD]); - if (ImGui::Button(localize.get(BASIC_ADD), widgetSize)) dialog.file_open(dialog::SOUND_OPEN); + 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]); ImGui::BeginDisabled(unused.empty()); - if (ImGui::Button(localize.get(BASIC_REMOVE_UNUSED), widgetSize)) - { - auto remove_unused = [&]() - { - for (auto& id : unused) - anm2.content.sounds.erase(id); - unused.clear(); - }; - - DOCUMENT_EDIT(document, localize.get(EDIT_REMOVE_UNUSED_SOUNDS), Document::SOUNDS, remove_unused()); - }; - ImGui::EndDisabled(); + 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::EndDisabled(); + + 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(); + } } ImGui::End(); - if (dialog.is_selected(dialog::SOUND_OPEN)) - { - auto add = [&]() - { - int id{}; - if (anm2.sound_add(document.directory_get().string(), dialog.path, id)) - { - selection = {id}; - newSoundId = id; - toasts.push(std::vformat(localize.get(TOAST_SOUND_INITIALIZED), std::make_format_args(id, dialog.path))); - logger.info(std::vformat(localize.get(TOAST_SOUND_INITIALIZED, anm2ed::ENGLISH), - std::make_format_args(id, dialog.path))); - } - else - { - toasts.push(std::vformat(localize.get(TOAST_SOUND_INITIALIZE_FAILED), std::make_format_args(dialog.path))); - logger.error(std::vformat(localize.get(TOAST_SOUND_INITIALIZE_FAILED, anm2ed::ENGLISH), - std::make_format_args(dialog.path))); - } - }; - - DOCUMENT_EDIT(document, localize.get(EDIT_ADD_SOUND), Document::SOUNDS, add()); - - 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(); } } diff --git a/src/imgui/window/spritesheet_editor.cpp b/src/imgui/window/spritesheet_editor.cpp index 4649c43..533c90f 100644 --- a/src/imgui/window/spritesheet_editor.cpp +++ b/src/imgui/window/spritesheet_editor.cpp @@ -80,6 +80,23 @@ namespace anm2ed::imgui auto center_view = [&]() { pan = -size * 0.5f; }; + auto fit_view = [&]() + { + if (spritesheet && spritesheet->texture.is_valid()) + set_to_rect(zoom, pan, {0, 0, (float)spritesheet->texture.size.x, (float)spritesheet->texture.size.y}); + }; + + auto zoom_adjust = [&](float delta) + { + auto focus = position_translate(zoom, pan, size * 0.5f); + auto previousZoom = zoom; + zoom_set(zoom, pan, focus, delta); + if (zoom != previousZoom) hasPendingZoomPanAdjust = true; + }; + + auto zoom_in = [&]() { zoom_adjust(zoomStep); }; + auto zoom_out = [&]() { zoom_adjust(-zoomStep); }; + if (ImGui::Begin(localize.get(LABEL_SPRITESHEET_EDITOR_WINDOW), &settings.windowIsSpritesheetEditor)) { @@ -121,8 +138,7 @@ namespace anm2ed::imgui ImGui::SameLine(); imgui::shortcut(manager.chords[SHORTCUT_FIT]); - if (ImGui::Button(localize.get(LABEL_FIT), widgetSize)) - if (spritesheet) set_to_rect(zoom, pan, {0, 0, spritesheet->texture.size.x, spritesheet->texture.size.y}); + if (ImGui::Button(localize.get(LABEL_FIT), widgetSize)) fit_view(); imgui::set_item_tooltip_shortcut(localize.get(TOOLTIP_FIT), settings.shortcutFit); auto mousePosInt = ivec2(mousePos); @@ -425,6 +441,32 @@ namespace anm2ed::imgui } } } + + if (ImGui::BeginPopupContextWindow("##Spritesheet Editor Context Menu", ImGuiMouseButton_Right)) + { + if (ImGui::MenuItem(localize.get(SHORTCUT_STRING_UNDO), settings.shortcutUndo.c_str(), false, + document.is_able_to_undo())) + document.undo(); + + if (ImGui::MenuItem(localize.get(SHORTCUT_STRING_REDO), settings.shortcutRedo.c_str(), false, + document.is_able_to_redo())) + document.redo(); + + ImGui::Separator(); + + if (ImGui::MenuItem(localize.get(LABEL_CENTER_VIEW), settings.shortcutCenterView.c_str())) center_view(); + + if (ImGui::MenuItem(localize.get(LABEL_FIT), settings.shortcutFit.c_str(), false, + spritesheet && spritesheet->texture.is_valid())) + fit_view(); + + ImGui::Separator(); + + if (ImGui::MenuItem(localize.get(SHORTCUT_STRING_ZOOM_IN), settings.shortcutZoomIn.c_str())) zoom_in(); + if (ImGui::MenuItem(localize.get(SHORTCUT_STRING_ZOOM_OUT), settings.shortcutZoomOut.c_str())) zoom_out(); + + ImGui::EndPopup(); + } ImGui::End(); if (!document.isSpritesheetEditorSet) diff --git a/src/imgui/window/spritesheets.cpp b/src/imgui/window/spritesheets.cpp index 9430cc4..411754a 100644 --- a/src/imgui/window/spritesheets.cpp +++ b/src/imgui/window/spritesheets.cpp @@ -2,11 +2,12 @@ #include +#include #include -#include "log.h" #include "document.h" #include "filesystem_.h" +#include "log.h" #include "strings.h" #include "toast.h" @@ -24,70 +25,185 @@ namespace anm2ed::imgui auto& anm2 = document.anm2; auto& selection = document.spritesheet.selection; auto& unused = document.spritesheet.unused; - auto& hovered = document.spritesheet.hovered; auto& reference = document.spritesheet.reference; + auto style = ImGui::GetStyle(); - hovered = -1; + auto add_open = [&]() { dialog.file_open(dialog::SPRITESHEET_OPEN); }; + auto replace_open = [&]() { dialog.file_open(dialog::SPRITESHEET_REPLACE); }; - if (ImGui::Begin(localize.get(LABEL_SPRITESHEETS_WINDOW), &settings.windowIsSpritesheets)) + auto add = [&](const std::filesystem::path& path) { - auto style = ImGui::GetStyle(); + if (path.empty()) return; + document.spritesheet_add(path.string()); + newSpritesheetId = document.spritesheet.reference; + }; - auto context_menu = [&]() + auto remove_unused = [&]() + { + if (unused.empty()) return; + + auto behavior = [&]() { - auto copy = [&]() + for (auto& id : unused) { - if (!selection.empty()) - { - std::string clipboardText{}; - for (auto& id : selection) - clipboardText += anm2.content.spritesheets[id].to_string(id); - clipboard.set(clipboardText); - } - else if (hovered > -1) - clipboard.set(anm2.content.spritesheets[hovered].to_string(hovered)); - }; + anm2::Spritesheet& spritesheet = anm2.content.spritesheets[id]; + auto pathString = spritesheet.path.string(); + 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); + } + unused.clear(); + }; - auto paste = [&](merge::Type type) + DOCUMENT_EDIT(document, localize.get(EDIT_REMOVE_UNUSED_SPRITESHEETS), Document::SPRITESHEETS, behavior()); + }; + + auto reload = [&]() + { + if (selection.empty()) return; + + auto behavior = [&]() + { + for (auto& id : selection) { - std::string errorString{}; - document.snapshot(localize.get(EDIT_PASTE_SPRITESHEETS)); - if (anm2.spritesheets_deserialize(clipboard.get(), document.directory_get().string(), type, &errorString)) + anm2::Spritesheet& spritesheet = anm2.content.spritesheets[id]; + spritesheet.reload(document.directory_get()); + auto pathString = spritesheet.path.string(); + 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 = anm2::Spritesheet(document.directory_get().string(), path); + auto pathString = spritesheet.path.string(); + 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 save = [&]() + { + if (selection.empty()) return; + + for (auto& id : selection) + { + anm2::Spritesheet& spritesheet = anm2.content.spritesheets[id]; + auto pathString = spritesheet.path.string(); + if (spritesheet.save(document.directory_get().string())) + { + 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 open_directory = [&](anm2::Spritesheet& spritesheet) + { + if (spritesheet.path.empty()) return; + filesystem::WorkingDirectory workingDirectory(document.directory_get()); + dialog.file_explorer_open(spritesheet.path.parent_path()); + }; + + 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 = [&]() + { + std::string errorString{}; + document.snapshot(localize.get(EDIT_PASTE_SPRITESHEETS)); + if (anm2.spritesheets_deserialize(clipboard.get(), document.directory_get().string(), merge::APPEND, + &errorString)) document.change(Document::SPRITESHEETS); else { - toasts.push(std::vformat(localize.get(TOAST_DESERIALIZE_SPRITESHEETS_FAILED), - std::make_format_args(errorString))); + 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))); - } }; - - if (shortcut(manager.chords[SHORTCUT_COPY], shortcut::FOCUSED)) copy(); - if (shortcut(manager.chords[SHORTCUT_PASTE], shortcut::FOCUSED)) paste(merge::APPEND); - - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, style.WindowPadding); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing); - if (ImGui::BeginPopupContextWindow("##Context Menu", ImGuiPopupFlags_MouseButtonRight)) - { - ImGui::MenuItem(localize.get(BASIC_CUT), settings.shortcutCut.c_str(), false, true); - - if (ImGui::MenuItem(localize.get(BASIC_COPY), settings.shortcutCopy.c_str(), false, - !selection.empty() || hovered > -1)) - copy(); - - if (ImGui::BeginMenu(localize.get(BASIC_PASTE), !clipboard.is_empty())) - { - if (ImGui::MenuItem(localize.get(BASIC_APPEND), settings.shortcutPaste.c_str())) paste(merge::APPEND); - if (ImGui::MenuItem(localize.get(BASIC_REPLACE))) paste(merge::REPLACE); - - ImGui::EndMenu(); - } - ImGui::EndPopup(); - } - ImGui::PopStyleVar(2); }; + 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_ADD), settings.shortcutAdd.c_str())) add_open(); + if (ImGui::MenuItem(localize.get(BASIC_REMOVE_UNUSED), settings.shortcutRemove.c_str(), false, !unused.empty())) + 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(); + if (ImGui::MenuItem(localize.get(BASIC_SAVE), nullptr, false, !selection.empty())) save(); + + 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()); @@ -97,7 +213,7 @@ namespace anm2ed::imgui ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2()); - selection.start(anm2.content.spritesheets.size(), ImGuiMultiSelectFlags_ClearOnEscape); + selection.start(anm2.content.spritesheets.size()); for (auto& [id, spritesheet] : anm2.content.spritesheets) { @@ -108,25 +224,17 @@ namespace anm2ed::imgui auto isSelected = selection.contains(id); auto isReferenced = id == reference; auto cursorPos = ImGui::GetCursorPos(); - bool isTextureValid = spritesheet.texture.is_valid(); - auto& texture = isTextureValid ? spritesheet.texture : resources.icons[icon::NONE]; - auto textureRef = ImTextureRef(texture.id); - auto tintColor = !isTextureValid ? ImVec4(1.0f, 0.25f, 0.25f, 1.0f) : ImVec4(1.0f, 1.0f, 1.0f, 1.0f); + 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 = spritesheet.path.empty() ? std::string{anm2::NO_PATH} : spritesheet.path.string(); auto pathCStr = pathString.c_str(); ImGui::SetNextItemSelectionUserData(id); ImGui::SetNextItemStorageID(id); if (ImGui::Selectable("##Spritesheet Selectable", isSelected, 0, spritesheetChildSize)) reference = id; - if (ImGui::IsItemHovered()) - { - if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) - { - filesystem::WorkingDirectory workingDirectory(document.directory_get().string()); - dialog.file_explorer_open(spritesheet.path.parent_path().string()); - } - hovered = id; - } + if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) + open_directory(spritesheet); if (newSpritesheetId == id) { ImGui::SetScrollHereY(0.5f); @@ -157,7 +265,7 @@ namespace anm2ed::imgui auto noScrollFlags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse; if (ImGui::BeginChild("##Spritesheet Tooltip Image Child", to_imvec2(textureSize), childFlags, noScrollFlags)) - ImGui::ImageWithBg(textureRef, to_imvec2(textureSize), ImVec2(), ImVec2(1, 1), ImVec4(), tintColor); + ImGui::ImageWithBg(texture.id, to_imvec2(textureSize), ImVec2(), ImVec2(1, 1), ImVec4(), tintColor); ImGui::EndChild(); ImGui::PopStyleVar(); @@ -172,7 +280,7 @@ namespace anm2ed::imgui ImGui::TextUnformatted(std::vformat(localize.get(FORMAT_ID), std::make_format_args(id)).c_str()); - if (!isTextureValid) + if (!isValid) ImGui::TextUnformatted(localize.get(TOOLTIP_SPRITESHEET_INVALID)); else ImGui::TextUnformatted(std::vformat(localize.get(FORMAT_TEXTURE_SIZE), @@ -196,7 +304,7 @@ namespace anm2ed::imgui imageSize.y = imageSize.x / aspectRatio; ImGui::SetCursorPos(cursorPos); - ImGui::ImageWithBg(textureRef, imageSize, ImVec2(), ImVec2(1, 1), ImVec4(), tintColor); + ImGui::ImageWithBg(texture.id, imageSize, ImVec2(), ImVec2(1, 1), ImVec4(), tintColor); ImGui::SetCursorPos( ImVec2(spritesheetChildSize.y + style.ItemSpacing.x, @@ -206,8 +314,6 @@ namespace anm2ed::imgui ImGui::TextUnformatted( std::vformat(localize.get(FORMAT_SPRITESHEET), std::make_format_args(id, pathCStr)).c_str()); if (isReferenced) ImGui::PopFont(); - - context_menu(); } ImGui::EndChild(); @@ -225,124 +331,47 @@ namespace anm2ed::imgui auto rowOneWidgetSize = widget_size_with_row_get(3); shortcut(manager.chords[SHORTCUT_ADD]); - if (ImGui::Button(localize.get(BASIC_ADD), rowOneWidgetSize)) dialog.file_open(dialog::SPRITESHEET_OPEN); + 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)) { - document.spritesheet_add(dialog.path); - newSpritesheetId = document.spritesheet.reference; + add(dialog.path); dialog.reset(); } ImGui::SameLine(); ImGui::BeginDisabled(selection.empty()); - { - if (ImGui::Button(localize.get(BASIC_RELOAD), rowOneWidgetSize)) - { - auto reload = [&]() - { - for (auto& id : selection) - { - anm2::Spritesheet& spritesheet = anm2.content.spritesheets[id]; - spritesheet.reload(document.directory_get()); - auto pathString = spritesheet.path.string(); - 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, reload()); - } - ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_RELOAD_SPRITESHEETS)); - } + 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)) dialog.file_open(dialog::SPRITESHEET_REPLACE); - ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_REPLACE_SPRITESHEET)); - } + 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)) { - auto replace = [&]() - { - auto& id = *selection.begin(); - anm2::Spritesheet& spritesheet = anm2.content.spritesheets[id]; - spritesheet = anm2::Spritesheet(document.directory_get().string(), dialog.path); - auto pathString = spritesheet.path.string(); - 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, replace()); + replace(dialog.path); dialog.reset(); } auto rowTwoWidgetSize = widget_size_with_row_get(2); ImGui::BeginDisabled(unused.empty()); - { - shortcut(manager.chords[SHORTCUT_REMOVE]); - if (ImGui::Button(localize.get(BASIC_REMOVE_UNUSED), rowTwoWidgetSize)) - { - auto remove_unused = [&]() - { - for (auto& id : unused) - { - anm2::Spritesheet& spritesheet = anm2.content.spritesheets[id]; - auto pathString = spritesheet.path.string(); - 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); - } - unused.clear(); - }; - - DOCUMENT_EDIT(document, localize.get(EDIT_REMOVE_UNUSED_SPRITESHEETS), Document::SPRITESHEETS, - remove_unused()); - } - set_item_tooltip_shortcut(localize.get(TOOLTIP_REMOVE_UNUSED_SPRITESHEETS), settings.shortcutRemove); - } + 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::EndDisabled(); ImGui::SameLine(); ImGui::BeginDisabled(selection.empty()); - { - if (ImGui::Button(localize.get(BASIC_SAVE), rowTwoWidgetSize)) - { - for (auto& id : selection) - { - anm2::Spritesheet& spritesheet = anm2.content.spritesheets[id]; - auto pathString = spritesheet.path.string(); - if (spritesheet.save(document.directory_get().string())) - { - 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 (ImGui::Button(localize.get(BASIC_SAVE), rowTwoWidgetSize)) save(); ImGui::EndDisabled(); ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_SAVE_SPRITESHEETS)); } diff --git a/src/imgui/window/timeline.cpp b/src/imgui/window/timeline.cpp index e6a2541..9370644 100644 --- a/src/imgui/window/timeline.cpp +++ b/src/imgui/window/timeline.cpp @@ -457,6 +457,7 @@ namespace anm2ed::imgui reference = {reference.animationIndex, type, id}; frames_selection_reset(); if (type == anm2::LAYER || type == anm2::NULL_) items.reference = (int)type; + items.selection = {(int)id}; }; auto fit_animation_length = [&]() @@ -689,6 +690,67 @@ namespace anm2ed::imgui item_selection_sync(); }; + auto item_context_menu = [&]() + { + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, style.WindowPadding); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing); + + auto selectionType = item_selection_type_get(); + bool hasSelection = !itemSelection.empty() && (selectionType == anm2::LAYER || selectionType == anm2::NULL_); + auto currentItem = document.item_get(); + + if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenOverlappedByWindow) && + ImGui::IsMouseClicked(ImGuiMouseButton_Right)) + ImGui::OpenPopup("##Items Context Menu"); + + if (ImGui::BeginPopup("##Items 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_ADD), settings.shortcutAdd.c_str(), false, animation)) + { + item_properties_reset(); + propertiesPopup.open(); + } + + if (ImGui::MenuItem(localize.get(BASIC_REMOVE), settings.shortcutRemove.c_str(), false, + hasSelection || currentItem)) + { + auto remove = [&]() + { + if (hasSelection) + { + std::vector ids{}; + ids.reserve(itemSelection.size()); + for (auto value : itemSelection) + ids.push_back(item_selection_decode(value)); + std::sort(ids.begin(), ids.end()); + for (auto id : ids) + animation->item_remove(selectionType, id); + item_selection_clear(); + } + else if (reference.itemType == anm2::LAYER || reference.itemType == anm2::NULL_) + animation->item_remove(reference.itemType, reference.itemID); + reference_clear(); + }; + + DOCUMENT_EDIT(document, localize.get(EDIT_REMOVE_ITEMS), Document::ITEMS, remove()); + } + + ImGui::EndPopup(); + } + + ImGui::PopStyleVar(2); + }; + auto item_child = [&](anm2::Type type, int id, int& index) { ImGui::PushID(index); @@ -1118,6 +1180,8 @@ namespace anm2ed::imgui ImGui::EndTable(); } ImGui::PopStyleVar(2); + + item_context_menu(); } ImGui::PopStyleVar(2); ImGui::EndChild(); @@ -1310,7 +1374,9 @@ namespace anm2ed::imgui { float frameTime{}; - if (ImGui::IsWindowHovered() && ImGui::IsMouseReleased(ImGuiMouseButton_Left) && !ImGui::IsAnyItemHovered()) + if (ImGui::IsWindowHovered() && + (ImGui::IsMouseReleased(ImGuiMouseButton_Left) || ImGui::IsMouseReleased(ImGuiMouseButton_Right)) && + !ImGui::IsAnyItemHovered()) reference_set_item(type, id); for (int i = frameMin; i < frameMax; i++) @@ -1503,6 +1569,7 @@ namespace anm2ed::imgui if (type == anm2::TRIGGER || ImGui::IsKeyDown(ImGuiMod_Ctrl)) { draggedFrame = &frame; + draggedFrameType = type; draggedFrameIndex = (int)i; draggedFrameStart = hoveredTime; if (type != anm2::TRIGGER) draggedFrameStartDuration = draggedFrame->duration; @@ -1674,47 +1741,48 @@ namespace anm2ed::imgui frameSelectionSnapshot.assign(frames.selection.begin(), frames.selection.end()); frameSelectionSnapshotReference = reference; } + } + } - if (draggedFrame) + if (draggedFrame) + { + ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); + + if (!isDraggedFrameSnapshot && hoveredTime != draggedFrameStart) + { + isDraggedFrameSnapshot = true; + document.snapshot(draggedFrameType == anm2::TRIGGER ? localize.get(EDIT_TRIGGER_AT_FRAME) + : localize.get(EDIT_FRAME_DURATION)); + } + + if (draggedFrameType == anm2::TRIGGER) + { + draggedFrame->atFrame = + glm::clamp(hoveredTime, 0, settings.playbackIsClamp ? animation->frameNum - 1 : anm2::FRAME_NUM_MAX - 1); + + for (auto [i, trigger] : std::views::enumerate(animation->triggers.frames)) { - ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); - - if (!isDraggedFrameSnapshot && hoveredTime != draggedFrameStart) - { - isDraggedFrameSnapshot = true; - document.snapshot(type == anm2::TRIGGER ? localize.get(EDIT_TRIGGER_AT_FRAME) - : localize.get(EDIT_FRAME_DURATION)); - } - - if (type == anm2::TRIGGER) - { - draggedFrame->atFrame = glm::clamp( - hoveredTime, 0, settings.playbackIsClamp ? animation->frameNum - 1 : anm2::FRAME_NUM_MAX - 1); - - for (auto [i, trigger] : std::views::enumerate(animation->triggers.frames)) - { - if ((int)i == draggedFrameIndex) continue; - if (trigger.atFrame == draggedFrame->atFrame) draggedFrame->atFrame--; - } - } - else - { - draggedFrame->duration = glm::clamp(draggedFrameStartDuration + (hoveredTime - draggedFrameStart), - anm2::FRAME_DURATION_MIN, anm2::FRAME_DURATION_MAX); - } - - if (ImGui::IsMouseReleased(ImGuiMouseButton_Left)) - { - document.change(Document::FRAMES); - draggedFrame = nullptr; - draggedFrameIndex = -1; - draggedFrameStart = -1; - draggedFrameStartDuration = -1; - isDraggedFrameSnapshot = false; - if (type == anm2::TRIGGER) item->frames_sort_by_at_frame(); - } + if ((int)i == draggedFrameIndex) continue; + if (trigger.atFrame == draggedFrame->atFrame) draggedFrame->atFrame--; } } + else + { + draggedFrame->duration = glm::clamp(draggedFrameStartDuration + (hoveredTime - draggedFrameStart), + anm2::FRAME_DURATION_MIN, anm2::FRAME_DURATION_MAX); + } + + if (ImGui::IsMouseReleased(ImGuiMouseButton_Left)) + { + document.change(Document::FRAMES); + draggedFrame = nullptr; + draggedFrameType = anm2::NONE; + draggedFrameIndex = -1; + draggedFrameStart = -1; + draggedFrameStartDuration = -1; + isDraggedFrameSnapshot = false; + if (type == anm2::TRIGGER) item->frames_sort_by_at_frame(); + } } context_menu(); diff --git a/src/imgui/window/timeline.h b/src/imgui/window/timeline.h index 8fae805..3923271 100644 --- a/src/imgui/window/timeline.h +++ b/src/imgui/window/timeline.h @@ -31,6 +31,7 @@ namespace anm2ed::imgui int addItemSpritesheetID{-1}; int hoveredTime{}; anm2::Frame* draggedFrame{}; + anm2::Type draggedFrameType{}; int draggedFrameIndex{-1}; int draggedFrameStart{-1}; int draggedFrameStartDuration{-1}; diff --git a/src/loader.cpp b/src/loader.cpp index 51de859..32dc249 100644 --- a/src/loader.cpp +++ b/src/loader.cpp @@ -21,6 +21,9 @@ using namespace anm2ed::util; namespace anm2ed { + constexpr auto WINDOW_4K_SIZE = glm::ivec2(3840, 2160); + constexpr auto UI_SCALE_4K_DEFAULT = 1.5f; + constexpr auto SOCKET_ADDRESS = "127.0.0.1"; constexpr auto SOCKET_PORT = 11414; @@ -71,7 +74,7 @@ namespace anm2ed } } - std::string Loader::settings_path() { return filesystem::path_preferences_get() + "settings.ini"; } + std::filesystem::path Loader::settings_path() { return filesystem::path_preferences_get() / "settings.ini"; } Loader::Loader(int argc, const char** argv) { @@ -137,11 +140,11 @@ namespace anm2ed { if (auto mode = SDL_GetCurrentDisplayMode(display)) { - if (mode->w >= 3840 || mode->h >= 2160) - settings.uiScale = 1.5f; - else - logger.warning(std::format("Failed to query primary display mode: {}", SDL_GetError())); + if (mode->w >= WINDOW_4K_SIZE.x || mode->h >= WINDOW_4K_SIZE.y) settings.uiScale = UI_SCALE_4K_DEFAULT; + SDL_SetWindowSize(window, mode->w - 1, mode->h - 1); } + else + logger.warning(std::format("Failed to query primary display mode: {}", SDL_GetError())); } else logger.warning("Failed to detect primary display for UI scaling."); diff --git a/src/loader.h b/src/loader.h index 3a2c1d5..5caeaeb 100644 --- a/src/loader.h +++ b/src/loader.h @@ -14,7 +14,7 @@ namespace anm2ed { class Loader { - std::string settings_path(); + std::filesystem::path settings_path(); public: Socket socket{}; diff --git a/src/log.cpp b/src/log.cpp index 4aba110..25bac06 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -48,7 +48,7 @@ namespace anm2ed Logger::Logger() { - open(filesystem::path_preferences_get() + "log.txt"); + open(filesystem::path_preferences_get() / "log.txt"); info("Initializing Anm2Ed"); } diff --git a/src/manager.cpp b/src/manager.cpp index fabe1fe..b198fae 100644 --- a/src/manager.cpp +++ b/src/manager.cpp @@ -57,9 +57,8 @@ namespace anm2ed if (documents.empty()) selectionHistory.clear(); } - std::filesystem::path Manager::recent_files_path_get() { return filesystem::path_preferences_get() + "recent.txt"; } - std::filesystem::path Manager::autosave_path_get() { return filesystem::path_preferences_get() + "autosave.txt"; } - std::filesystem::path Manager::autosave_directory_get() { return filesystem::path_preferences_get() + "autosave"; } + std::filesystem::path Manager::recent_files_path_get() { return filesystem::path_preferences_get() / "recent.txt"; } + std::filesystem::path Manager::autosave_path_get() { return filesystem::path_preferences_get() / "autosave.txt"; } Manager::Manager() { @@ -79,8 +78,8 @@ namespace anm2ed if (!document.is_valid()) { documents.pop_back(); - toasts.push(std::vformat(localize.get(TOAST_OPEN_DOCUMENT_FAILED), - std::make_format_args(pathString, errorString))); + toasts.push( + std::vformat(localize.get(TOAST_OPEN_DOCUMENT_FAILED), std::make_format_args(pathString, errorString))); logger.error(std::vformat(localize.get(TOAST_OPEN_DOCUMENT_FAILED, anm2ed::ENGLISH), std::make_format_args(pathString, errorString))); return; @@ -92,8 +91,7 @@ namespace anm2ed pendingSelected = selected; selection_history_push(selected); toasts.push(std::vformat(localize.get(TOAST_OPEN_DOCUMENT), std::make_format_args(pathString))); - logger.info(std::vformat(localize.get(TOAST_OPEN_DOCUMENT, anm2ed::ENGLISH), - std::make_format_args(pathString))); + logger.info(std::vformat(localize.get(TOAST_OPEN_DOCUMENT, anm2ed::ENGLISH), std::make_format_args(pathString))); } void Manager::new_(const std::filesystem::path& path) { open(path, true); } diff --git a/src/manager.h b/src/manager.h index c2880dc..f272f5a 100644 --- a/src/manager.h +++ b/src/manager.h @@ -91,7 +91,5 @@ namespace anm2ed void autosave_files_clear(); void chords_set(Settings&); - - std::filesystem::path autosave_directory_get(); }; } diff --git a/src/resource/audio.cpp b/src/resource/audio.cpp index d89a023..260912f 100644 --- a/src/resource/audio.cpp +++ b/src/resource/audio.cpp @@ -1,6 +1,8 @@ #include "audio.h" +#include #include +#include #include namespace anm2ed::resource @@ -11,20 +13,42 @@ namespace anm2ed::resource return mixer; } - Audio::Audio(const char* path) + Audio::Audio(const std::filesystem::path& path) { - if (path && *path) internal = MIX_LoadAudio(mixer_get(), path, true); + if (path.empty()) return; + + size_t fileSize = 0; + void* fileData = SDL_LoadFile(path.string().c_str(), &fileSize); + if (!fileData || fileSize == 0) + { + if (fileData) SDL_free(fileData); + return; + } + + data.assign(static_cast(fileData), static_cast(fileData) + fileSize); + SDL_free(fileData); + + SDL_IOStream* io = SDL_IOFromConstMem(data.data(), data.size()); + if (!io) data.clear(); + else + { + internal = MIX_LoadAudio_IO(mixer_get(), io, true, true); + if (!internal) data.clear(); + } } - Audio::Audio(const std::string& path) : Audio(path.c_str()) {} - - Audio::Audio(const std::filesystem::path& path) : Audio(path.string()) {} - - Audio::Audio(const unsigned char* data, size_t size) + Audio::Audio(const unsigned char* memory, size_t size) { - SDL_IOStream* io = SDL_IOFromConstMem(data, size); - if (!io) return; - internal = MIX_LoadAudio_IO(mixer_get(), io, true, true); + if (!memory || size == 0) return; + data.assign(memory, memory + size); + + SDL_IOStream* io = SDL_IOFromConstMem(data.data(), data.size()); + if (!io) data.clear(); + else + { + internal = MIX_LoadAudio_IO(mixer_get(), io, true, true); + if (!internal) data.clear(); + } } void Audio::unload() @@ -82,10 +106,25 @@ namespace anm2ed::resource bool Audio::is_playing() const { return track && MIX_TrackPlaying(track); } + Audio::Audio(const Audio& other) + { + if (other.data.empty()) return; + + data = other.data; + SDL_IOStream* io = SDL_IOFromConstMem(data.data(), data.size()); + if (!io) data.clear(); + else + { + internal = MIX_LoadAudio_IO(mixer_get(), io, true, true); + if (!internal) data.clear(); + } + } + Audio::Audio(Audio&& other) noexcept { internal = std::exchange(other.internal, nullptr); track = std::exchange(other.track, nullptr); + data = std::move(other.data); } Audio& Audio::operator=(Audio&& other) noexcept @@ -95,6 +134,28 @@ namespace anm2ed::resource unload(); internal = std::exchange(other.internal, nullptr); track = std::exchange(other.track, nullptr); + data = std::move(other.data); + } + return *this; + } + + Audio& Audio::operator=(const Audio& other) + { + if (this != &other) + { + unload(); + data.clear(); + if (!other.data.empty()) + { + data = other.data; + SDL_IOStream* io = SDL_IOFromConstMem(data.data(), data.size()); + if (!io) data.clear(); + else + { + internal = MIX_LoadAudio_IO(mixer_get(), io, true, true); + if (!internal) data.clear(); + } + } } return *this; } diff --git a/src/resource/audio.h b/src/resource/audio.h index 0a31ddd..65301cc 100644 --- a/src/resource/audio.h +++ b/src/resource/audio.h @@ -1,9 +1,9 @@ #pragma once #include -#include -#include #include +#include +#include namespace anm2ed::resource { @@ -11,20 +11,19 @@ namespace anm2ed::resource { MIX_Audio* internal{nullptr}; MIX_Track* track{nullptr}; + std::vector data{}; MIX_Mixer* mixer_get(); void unload(); public: - Audio(const char*); Audio(const unsigned char*, size_t); - Audio(const std::string&); Audio(const std::filesystem::path&); ~Audio(); Audio() = default; + Audio(const Audio&); Audio(Audio&&) noexcept; + Audio& operator=(const Audio&); Audio& operator=(Audio&&) noexcept; - Audio(const Audio&) = delete; - Audio& operator=(const Audio&) = delete; bool is_valid(); void play(bool loop = false, MIX_Mixer* = nullptr); diff --git a/src/resource/icon.h b/src/resource/icon.h index 2bb51d8..fe47f2f 100644 --- a/src/resource/icon.h +++ b/src/resource/icon.h @@ -153,6 +153,10 @@ namespace anm2ed::resource::icon )"; + inline constexpr auto SOUND_DATA = R"( + +)"; + #define SVG_LIST \ X(NONE, NONE_DATA, SIZE_SMALL) \ X(FILE, FILE_DATA, SIZE_NORMAL) \ @@ -161,6 +165,7 @@ namespace anm2ed::resource::icon X(ROOT, ROOT_DATA, SIZE_NORMAL) \ X(LAYER, LAYER_DATA, SIZE_NORMAL) \ X(NULL_, NULL_DATA, SIZE_NORMAL) \ + X(SOUND, SOUND_DATA, SIZE_NORMAL) \ X(TRIGGERS, TRIGGERS_DATA, SIZE_NORMAL) \ X(VISIBLE, VISIBLE_DATA, SIZE_NORMAL) \ X(INVISIBLE, INVISIBLE_DATA, SIZE_NORMAL) \ diff --git a/src/resource/strings.h b/src/resource/strings.h index 0d815a9..4f735c6 100644 --- a/src/resource/strings.h +++ b/src/resource/strings.h @@ -66,9 +66,12 @@ namespace anm2ed X(BASIC_NULL_ANIMATION, "Null", "Null", "Нуль", "无", "Null") \ X(BASIC_OFFSET, "Offset", "Offset", "Смещение", "偏移", "오프셋") \ X(BASIC_OPEN, "Open", "Abrir", "Открыть", "打开", "열기") \ + X(BASIC_OPEN_DIRECTORY, "Open Directory", "Abrir Directorio", "Открыть директорию", "打开目录", "디렉터리 열기") \ X(BASIC_PASTE, "Paste", "Pegar", "Вставить", "粘贴", "붙여넣기") \ X(BASIC_PIVOT, "Pivot", "Pivote", "Точка вращения", "枢轴", "중심점") \ X(BASIC_POSITION, "Position", "Posicion", "Позиция", "位置", "위치") \ + X(BASIC_PROPERTIES, "Properties", "Propiedades", "Свойства", "属性", "속성") \ + X(BASIC_RENAME, "Rename", "Renombrar", "Переименовать", "重命名", "이름 변경") \ X(BASIC_RELOAD, "Reload", "Recargar", "Перезагрузить", "重新加载", "다시 불러오기") \ X(BASIC_REMOVE, "Remove", "Remover", "Удалить", "删除", "제거") \ X(BASIC_REMOVE_UNUSED, "Remove Unused", "Remover no utilizados", "Удалить неизпользуемые", "删除未使用", "미사용 시트 제거") \ @@ -132,8 +135,10 @@ namespace anm2ed X(EDIT_PASTE_FRAMES, "Paste Frame(s)", "Pegar Frames", "Вставить кадры", "粘贴帧", "프레임 붙여넣기") \ X(EDIT_PASTE_LAYERS, "Paste Layer(s)", "Pegar Capa(s)", "Вставить слои", "粘贴动画层", "레이어 붙여넣기") \ X(EDIT_PASTE_NULLS, "Paste Null(s)", "Pegar Null(s)", "Вставить нули", "粘贴Null", "Null 붙여넣기") \ + X(EDIT_PASTE_SOUNDS, "Paste Sound(s)", "Pegar Sonido(s)", "Вставить звук(и)", "粘贴声音", "사운드 붙여넣기") \ X(EDIT_PASTE_SPRITESHEETS, "Paste Spritesheet(s)", "Pegar Spritesheet(s)", "Вставить спрайт-листы", "粘贴图集", "스프라이트 시트 붙여넣기") \ X(EDIT_RELOAD_SPRITESHEETS, "Reload Spritesheet(s)", "Recargar Spritesheet(s)", "Перезагрузить спрайт-листы", "重新加载图集", "스프라이트 시트 다시 불러오기") \ + X(EDIT_RELOAD_SOUNDS, "Reload Sound(s)", "Recargar Sonido(s)", "Перезагрузить звук(и)", "重新加载声音", "사운드 다시 불러오기") \ X(EDIT_REMOVE_ANIMATIONS, "Remove Animation(s)", "Remover Animacion(es)", "Удалить анимации", "删除动画层", "애니메이션 제거") \ X(EDIT_REMOVE_ITEMS, "Remove Item(s)", "Remover Item(s)", "Удалить предметы", "删除物品", "항목 제거") \ X(EDIT_REMOVE_UNUSED_EVENTS, "Remove Unused Events", "Remover Eventos No Utilizados", "Удалить неизпользуемые события", "删除未使用的事件", "미사용 이벤트 제거") \ @@ -143,6 +148,7 @@ namespace anm2ed X(EDIT_REMOVE_UNUSED_SPRITESHEETS, "Remove Unused Spritesheets", "Remover Spritesheets No Utilizadas", "Удалить неизпользуемые спрайт-листы", "删除未使用的图集", "미사용 스프라이트 시트 제거") \ X(EDIT_RENAME_EVENT, "Rename Event", "Renombrar Evento", "Переименовать событие", "重命名事件", "이벤트 이름 바꾸기") \ X(EDIT_REPLACE_SPRITESHEET, "Replace Spritesheet", "Reemplazar Spritesheet", "Заменить спрайт-лист", "替换图集", "스프라이트 시트 교체") \ + X(EDIT_REPLACE_SOUND, "Replace Sound", "Reemplazar Sonido", "Заменить звук", "替换声音", "사운드 교체") \ X(EDIT_SET_LAYER_PROPERTIES, "Set Layer Properties", "Establecer Propiedades de Capa", "Установить свойства слоя", "更改动画层属性", "레이어 속성 설정") \ X(EDIT_SET_NULL_PROPERTIES, "Set Null Properties", "Establecer Propiedades Null", "Установить свойства нуля", "更改Null属性", "Null 속성 설정") \ X(EDIT_SPLIT_FRAME, "Split Frame", "Dividir Frame", "Разделить кадр", "拆分帧", "프레임 분할") \ @@ -175,6 +181,7 @@ namespace anm2ed X(FORMAT_SIZE, "Size: ({0}, {1})", "Tamaño: ({0}, {1})", "Размер: ({0}, {1})", "大小: ({0}, {1})", "크기: ({0}, {1})") \ X(FORMAT_SOUND_LABEL, "Sound: {0}", "Sonido: {0}", "Звук: {0}", "声音: {0}", "사운드: {0}") \ X(FORMAT_SPRITESHEET, "#{0} {1}", "#{0} {1}", "#{0} {1}", "#{0} {1}", "#{0} {1}") \ + X(FORMAT_SOUND, "#{0} {1}", "#{0} {1}", "#{0} {1}", "#{0} {1}", "#{0} {1}") \ X(FORMAT_TRANSFORM, "Transform: {0}", "Transformar: {0}", "Трансформация: {0}", "变换: {0}", "변환: {0}") \ X(FORMAT_RECT, "Rect: {0}", "Rect: {0}", "Прямоугольник: {0}", "矩形: {0}", "사각형: {0}") \ X(FORMAT_FRAMES_COUNT, "Frames: {0}", "Frames: {0}", "Кадры: {0}", "帧: {0}", "프레임: {0}") \ @@ -246,7 +253,6 @@ namespace anm2ed X(LABEL_LAYER, "Layer", "Capa", "Слой", "动画层", "레이어") \ X(LABEL_LAYERS_CHILD, "Layers List", "Lista de Capas", "", "动画层列表", "레이어 목록") \ X(LABEL_LAYERS_WINDOW, "Layers###Layers", "Capas###Layers", "Слои###Layers", "动画层###Layers", "레이어###Layers") \ - X(LABEL_LENGTH, "Length", "Duracion", "", "长度", "길이") \ X(LABEL_THIS_ANIMATION, "This Animation", "Esta Animacion", "Эта анимация", "此动画", "이 애니메이션") \ X(LABEL_DESTINATION, "Destination", "Destino", "Назначение", "目标", "대상") \ X(LABEL_LOCALIZATION, "Localization", "Localizacion", "Локализация", "本地化", "현지화") \ @@ -361,6 +367,7 @@ namespace anm2ed X(SHORTCUT_STRING_PLAY_PAUSE, "Play/Pause", "Reproducir/Pausar", "Возпроизвести/Пауза", "播放/暂停", "재생/일시정지") \ X(SHORTCUT_STRING_PREVIOUS_FRAME, "Previous Frame", "Frame Anterior", "Предыдущий кадр", "前一帧", "이전 프레임") \ X(SHORTCUT_STRING_REDO, "Redo", "Rehacer", "Повторить", "重做", "다시 실행") \ + X(SHORTCUT_STRING_RENAME, "Rename", "Renombrar", "Переименовать", "重命名", "이름 변경") \ X(SHORTCUT_STRING_REMOVE, "Remove", "Remover", "Удалить", "去除", "제거") \ X(SHORTCUT_STRING_ROTATE, "Rotate", "Rotar", "Поворачивать", "旋转", "회전") \ X(SHORTCUT_STRING_SAVE, "Save", "Guardar", "Сохранить", "保存", "저장") \ @@ -404,8 +411,10 @@ namespace anm2ed X(TOAST_PNG_DIRECTORY_NOT_SET, "PNG output directory must be set.", "El directorio de salida de PNG debe ser ajustado.", "Директория для вывода PNG должна быть установлена.", "必须设置PNG输出目录.", "PNG를 출력할 경로를 설정해야 합니다.") \ X(TOAST_REDO, "Redo: {0}", "Rehacer: {0}", "Повтор: {0}", "重做: {0}", "다시 실행: {0}") \ X(TOAST_RELOAD_SPRITESHEET, "Reloaded spritesheet #{0}: {1}", "Se ha recargado spritesheet #{0}: {1}", "Спрайт-лист #{0} перезагружен: {1}", "重新加载了图集 #{0}: {1}", "{0}번 스프라이트 시트 다시 불러옴: {1}") \ + X(TOAST_RELOAD_SOUND, "Reloaded sound #{0}: {1}", "Se ha recargado sonido #{0}: {1}", "Звук #{0} перезагружен: {1}", "重新加载了声音 #{0}: {1}", "{0}번 사운드 다시 불러옴: {1}") \ X(TOAST_REMOVE_SPRITESHEET, "Removed spritesheet #{0}: {1}", "Se ha removido spritesheet #{0}: {1}", "Спрайт-лист #{0} удален: {1}", "去除了图集 #{0}: {1}", "{0}번 스프라이트 시트 제거됨: {1}") \ X(TOAST_REPLACE_SPRITESHEET, "Replaced spritesheet #{0}: {1}", "Se ha reemplazado spritesheet #{0}: {1}", "Спрайт-лист #{0} заменен: {1}", "替换了图集 #{0}: {1}", "{0}번 스프라이트 시트 교체됨: {1}") \ + X(TOAST_REPLACE_SOUND, "Replaced sound #{0}: {1}", "Se ha reemplazado sonido #{0}: {1}", "Звук #{0} заменен: {1}", "已替换声音 #{0}: {1}", "{0}번 사운드 교체됨: {1}") \ X(TOAST_SAVE_DOCUMENT, "Saved document to: {0}", "Documento Guardado en: {0}", "Документ сохранен в: {0}", "保存文件至: {0}", "{0}에 파일을 저장했습니다.") \ X(TOAST_SAVE_DOCUMENT_FAILED, "Could not save document to: {0} ({1})", "No se pudo guardar el documento en: {0} ({1})", "Не удалось сохранить документ в: {0} ({1})", "无法保存文件至: {0} ({1})", "{0}에 파일을 저장할 수 없습니다.") \ X(TOAST_SAVE_SPRITESHEET, "Saved spritesheet #{0}: {1}", "Spritesheet Guardada #{0}: {1}", "Спрайт-лист #{0} сохранен: {1}", "已保存图集 #{0}: {1}", "{1}에 {0}번 스프라이트 시트를 저장했습니다.") \ @@ -509,6 +518,7 @@ namespace anm2ed X(TOOLTIP_PREVIEW_ZOOM, "Change the zoom of the preview.", "Cambia el zoom de la vista previa.", "Изменить масштаб предпросмотра.", "更改预览视图的缩放.", "미리보기의 줌을 변경합니다.") \ X(TOOLTIP_RAW, "Record only the raw animation; i.e., only its layers, to its bounds.", "Graba solo la animacion cruda; i. e., Solo sus capas.", "Записывать только «сырую» анимацию, т. е. только ее слои.", "仅保存原生动画; 比如只保存动画层.", "Raw 애니메이션만 녹화합니다. 즉, 레이어만 녹화합니다.") \ X(TOOLTIP_RELOAD_SPRITESHEETS, "Reloads the selected spritesheets.", "Recarga la spritesheet seleccionada.", "Перезагружает выбранные спрайт-листы.", "重新加载所选图集.", "선택한 스프라이트 시트를 다시 불러옵니다.") \ + X(TOOLTIP_RELOAD_SOUNDS, "Reloads the selected sounds.", "Recarga los sonidos seleccionados.", "Перезагружает выбранные звуки.", "重新加载所选声音.", "선택한 사운드를 다시 불러옵니다.") \ X(TOOLTIP_REMOVE_ANIMATION, "Remove the selected animation(s).", "Remueve la(s) animacion(es) seleccionada(s).", "Удалить выбранные анимации.", "去除所选动画.", "선택한 애니메이션을 제거합니다.") \ X(TOOLTIP_REMOVE_ITEMS, "Remove the selected item(s).", "Remueve el/los item(s) seleccionado(s).", "Удалить выбранные предметы.", "去除所选物品.", "선택한 항목을 제거합니다.") \ X(TOOLTIP_REMOVE_UNUSED_EVENTS, "Remove unused events (i.e., ones not used by any trigger in any animation.)", "Remueve eventos no utilizados (i. e., aquellos no usados por algun trigger en ninguna animacion.)", "Удалить неиспользуемые события (т. е. события, которые не использует ни один триггер в ни одной анимации.)", "去除未使用的事件 (未被任何动画触发的事件.)", "사용되지 않는 이벤트(어떤 애니메이션의 트리거에서도 사용되지 않는 것)를 제거합니다.") \ @@ -520,6 +530,7 @@ namespace anm2ed X(TOOLTIP_RENDER_TYPE, "Set the type of the output.", "Ajusta el tipo de la salida", "Установить тип вывода.", "设置输出的类型.", "출력 유형을 설정합니다.") \ X(TOOLTIP_REPEAT_DELAY, "Set how often, after repeating begins, key inputs will be fired.", "Ajusta que tanto, despues de que empieza la repeticion, seran disparadas las entradas clave.", "Установить, как часто после начала повторения будут срабатывать нажатия клавиш.", "更改键盘按键开启重复时, 重复的速率.", "반복 입력이 시작된 후 입력이 얼마나 자주 들어갈지 설정합니다.") \ X(TOOLTIP_REPLACE_SPRITESHEET, "Replace the selected spritesheet with a new one.", "Reemplaza la spritesheet seleccionada con una nueva.", "Заменить выбранный спрайт-лист на новый.", "替换所选旧图集为新图集.", "선택된 스프라이트 시트를 새 시트로 교체합니다.") \ + X(TOOLTIP_REPLACE_SOUND, "Replace the selected sound with a new one.", "Reemplaza el sonido seleccionado con uno nuevo.", "Заменяет выбранный звук новым.", "用新的声音替换所选声音.", "선택한 사운드를 새 사운드로 교체합니다.") \ X(TOOLTIP_ROOT_TRANSFORM, "Root frames will transform the rest of the animation.", "Los Frames root transformaran el resto de la animacion.", "Корневые кадры трансформируют остаток анимации.", "与“根”有关的帧会跟随整个动画而变换.", "Root 프레임이 나머지 애니메이션을 변형합니다.") \ X(TOOLTIP_ROTATION, "Change the rotation of the frame.", "Cambia la rotacion del Frame.", "Изменить поворот кадра.", "更改此帧的旋转.", "프레임의 회전값을 변경합니다.") \ X(TOOLTIP_ROUND_ROTATION, "Rotation will be rounded to the nearest whole number.", "La rotacion sera aproximada al numero entero mas cercano.", "Поворот будет округлен к самому близкому целому числу.", "旋转数值会被取整.", "회전값을 가장 가까운 정수로 반올림합니다.") \ @@ -534,7 +545,7 @@ namespace anm2ed X(TOOLTIP_SPLIT, "Based on the playhead time, split the selected frame into two.", "Basado en tiempo del encabezado de reproduccion, divide el Frame seleccionado en dos.", "С учётом позиции ползунка воспроизведения разделяет выбранный кадр на два.", "根据播放头位置,将所选帧拆分成两个。", "재생 헤드 시간에 따라 선택한 프레임을 두 개로 분할합니다.") \ X(TOOLTIP_SIZE, "Change the crop size the frame uses.", "Cambia el tamaño de recorte que usa el Frame.", "Изменить размер обрезки, который использует этот кадр.", "更改此帧的裁剪大小.", "프레임에 대응되는 스프라이트 시트의 사용 영역의 크기를 변경합니다.") \ X(TOOLTIP_SOUND, "Toggle sounds playing with triggers.\nBind sounds to events in the Events window.", "Alterna los sonidos reproduciendoce con triggers.\nEnlaza sonidos a eventos en la Ventana de Eventos.", "Переключить воспроизведения звуков с помощью триггеров.\nПривязывайте звуки к событиям в окне событий.", "切换是否在触发器触发时播放声音.\n可以在事件窗口里链接声音与事件.", "트리거와 함께 사운드를 재생할지 정합니다.\n사운드는 이벤트 창에서 이벤트에 연결하세요.") \ - X(TOOLTIP_SOUNDS_PLAY, "Click to play.", "Click para reproducir.", "Нажмите, чтобы возпроизвести.", "点击播放.", "클릭하여 재생합니다.") \ + X(TEXT_SOUND_PLAY, "Click to play.", "Click para reproducir.", "Нажмите, чтобы возпроизвести.", "点击播放.", "클릭하여 재생합니다.") \ X(TOOLTIP_SOUND_INVALID, "This sound could not be loaded. Replace the file.", "Este sonido no se pudo cargar. Reemplaza el archivo.", "Этот звук не удалось загрузить. Замените файл.", "无法加载此声音。请替换文件。", "이 사운드를 불러올 수 없습니다. 파일을 교체하세요.") \ X(TOOLTIP_SOUND_ADD, "Add a sound.", "Añadir un sonido.", "Добавить звук.", "添加一个声音.", "사운드를 추가합니다.") \ X(TOOLTIP_SPRITESHEET_BORDER, "Toggle a border appearing around the spritesheet.", "Alterna un borde apareciendo alrededor del spritesheet", "Переключить показ границ около спрайт-листа.", "切换是否显示在图集的边框.", "스프라이트 시트 주변에 경계선을 표시하거나 숨깁니다.") \ diff --git a/src/resource/texture.cpp b/src/resource/texture.cpp index 3da354a..9d8c66b 100644 --- a/src/resource/texture.cpp +++ b/src/resource/texture.cpp @@ -64,7 +64,7 @@ namespace anm2ed::resource Texture::Texture(Texture&& other) { *this = std::move(other); } - Texture& Texture::operator=(const Texture& other) // Copy + Texture& Texture::operator=(const Texture& other) { if (this != &other) { @@ -78,7 +78,7 @@ namespace anm2ed::resource return *this; } - Texture& Texture::operator=(Texture&& other) // Move + Texture& Texture::operator=(Texture&& other) { if (this != &other) { @@ -114,7 +114,7 @@ namespace anm2ed::resource upload(data); } - Texture::Texture(const std::string& pngPath) + Texture::Texture(const std::filesystem::path& pngPath) { if (const uint8_t* data = stbi_load(pngPath.c_str(), &size.x, &size.y, nullptr, CHANNELS); data) { @@ -123,15 +123,11 @@ namespace anm2ed::resource } } - Texture::Texture(const std::filesystem::path& pngPath) : Texture(pngPath.string()) {} - - bool Texture::write_png(const std::string& path) + bool Texture::write_png(const std::filesystem::path& path) { return stbi_write_png(path.c_str(), size.x, size.y, CHANNELS, this->pixels.data(), size.x * CHANNELS); } - bool Texture::write_png(const std::filesystem::path& path) { return write_png(path.string()); } - vec4 Texture::pixel_read(vec2 position) const { if (pixels.size() < CHANNELS || size.x <= 0 || size.y <= 0) return vec4(0.0f); diff --git a/src/resource/texture.h b/src/resource/texture.h index 7a41d55..842c8a8 100644 --- a/src/resource/texture.h +++ b/src/resource/texture.h @@ -1,7 +1,6 @@ #pragma once #include -#include #include #include @@ -37,9 +36,7 @@ namespace anm2ed::resource Texture& operator=(Texture&&); Texture(const uint8_t*, glm::ivec2); Texture(const char*, size_t, glm::ivec2); - Texture(const std::string&); Texture(const std::filesystem::path&); - bool write_png(const std::string&); bool write_png(const std::filesystem::path&); static bool write_pixels_png(const std::filesystem::path&, glm::ivec2, const uint8_t*); void pixel_set(glm::ivec2, glm::vec4); diff --git a/src/settings.cpp b/src/settings.cpp index e7fe4b6..137a2ad 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -119,10 +119,10 @@ DockSpace ID=0x123F8F08 Window=0x6D581B32 Pos=8,62 Size=1902,994 Spl DockNode ID=0x00000006 Parent=0x123F8F08 SizeRef=1910,268 Selected=0x597925B7 )"; - Settings::Settings(const std::string& path) + Settings::Settings(const std::filesystem::path& path) { if (filesystem::path_is_exist(path)) - logger.info(std::format("Using settings from: {}", path)); + logger.info(std::format("Using settings from: {}", path.string())); else { logger.warning("Settings file does not exist; using default"); @@ -133,7 +133,7 @@ DockSpace ID=0x123F8F08 Window=0x6D581B32 Pos=8,62 Size=1902,994 Spl std::ifstream file(path); if (!file.is_open()) { - logger.error(std::format("Failed to open settings file: {}", path)); + logger.error(std::format("Failed to open settings file: {}", path.string())); return; } @@ -266,7 +266,7 @@ DockSpace ID=0x123F8F08 Window=0x6D581B32 Pos=8,62 Size=1902,994 Spl file.close(); } - void Settings::save(const std::string& path, const std::string& imguiData) + void Settings::save(const std::filesystem::path& path, const std::string& imguiData) { std::ofstream file(path, std::ios::out | std::ios::binary); file << "[Settings]\n"; diff --git a/src/settings.h b/src/settings.h index 26b5015..d414f40 100644 --- a/src/settings.h +++ b/src/settings.h @@ -17,6 +17,12 @@ namespace anm2ed constexpr auto FFMPEG_PATH_DEFAULT = "/usr/bin/ffmpeg"; #endif +#ifdef _WIN32 + constexpr auto OUTPUT_PATH_DEFAULT = ".\\output.gif"; +#else + constexpr auto OUTPUT_PATH_DEFAULT = "./output.gif"; +#endif + #define SETTINGS_TYPES \ X(INT, int) \ X(BOOL, bool) \ @@ -42,7 +48,7 @@ namespace anm2ed #define SETTINGS_MEMBERS \ /* Symbol / Name / String / Type / Default */ \ - X(WINDOW_SIZE, windowSize, STRING_UNDEFINED, IVEC2_WH, {1600, 900}) \ + X(WINDOW_SIZE, windowSize, STRING_UNDEFINED, IVEC2_WH, {1200, 720}) \ X(IS_VSYNC, isVsync, STRING_UNDEFINED, BOOL, true) \ X(UI_SCALE, uiScale, STRING_UNDEFINED, FLOAT, 1.0f) \ X(THEME, theme, STRING_UNDEFINED, INT, types::theme::DARK) \ @@ -150,7 +156,7 @@ namespace anm2ed X(TOOL_COLOR, toolColor, STRING_UNDEFINED, VEC4, {1.0, 1.0, 1.0, 1.0}) \ \ X(RENDER_TYPE, renderType, STRING_UNDEFINED, INT, render::GIF) \ - X(RENDER_PATH, renderPath, STRING_UNDEFINED, STRING, "./output.gif") \ + X(RENDER_PATH, renderPath, STRING_UNDEFINED, STRING, OUTPUT_PATH_DEFAULT) \ X(RENDER_ROWS, renderRows, STRING_UNDEFINED, INT, 0) \ X(RENDER_COLUMNS, renderColumns, STRING_UNDEFINED, INT, 0) \ X(RENDER_FORMAT, renderFormat, STRING_UNDEFINED, STRING, "{}.png") \ @@ -176,6 +182,7 @@ namespace anm2ed X(SHORTCUT_DUPLICATE, shortcutDuplicate, SHORTCUT_STRING_DUPLICATE, STRING, "Ctrl+J") \ X(SHORTCUT_ADD, shortcutAdd, SHORTCUT_STRING_ADD, STRING, "Insert") \ X(SHORTCUT_REMOVE, shortcutRemove, SHORTCUT_STRING_REMOVE, STRING, "Delete") \ + X(SHORTCUT_RENAME, shortcutRename, SHORTCUT_STRING_RENAME, STRING, "F2") \ X(SHORTCUT_DEFAULT, shortcutDefault, SHORTCUT_STRING_DEFAULT, STRING, "Home") \ X(SHORTCUT_MERGE, shortcutMerge, SHORTCUT_STRING_MERGE, STRING, "Ctrl+E") \ /* Tools */ \ @@ -241,8 +248,8 @@ namespace anm2ed Settings() = default; - Settings(const std::string&); - void save(const std::string&, const std::string&); + Settings(const std::filesystem::path&); + void save(const std::filesystem::path&, const std::string&); }; constexpr StringType SHORTCUT_STRING_TYPES[] = { diff --git a/src/util/filesystem_.cpp b/src/util/filesystem_.cpp index 7ee4bd4..a4471e6 100644 --- a/src/util/filesystem_.cpp +++ b/src/util/filesystem_.cpp @@ -2,48 +2,76 @@ #include #include +#include +#include #include - -#include "string_.h" +#include namespace anm2ed::util::filesystem { - std::string path_preferences_get() + namespace { - auto path = SDL_GetPrefPath(nullptr, "anm2ed"); - std::string string = path; - SDL_free(path); - return string; + template CharT to_lower_char(CharT character) + { + if constexpr (std::is_same_v) + return static_cast(std::towlower(static_cast(character))); + else + return static_cast(std::tolower(static_cast(character))); + } } - bool path_is_exist(const std::string& path) + std::filesystem::path path_preferences_get() + { + auto path = SDL_GetPrefPath(nullptr, "anm2ed"); + auto filePath = std::filesystem::path(path); + SDL_free(path); + return filePath; + } + + std::filesystem::path path_to_lower(const std::filesystem::path& path) + { + auto native = path.native(); + for (auto& character : native) + character = to_lower_char(character); + return std::filesystem::path(native); + } + + std::filesystem::path path_backslash_replace(const std::filesystem::path& path) + { + auto native = path.native(); + constexpr auto backslash = static_cast('\\'); + constexpr auto slash = static_cast('/'); + for (auto& character : native) + if (character == backslash) character = slash; + return std::filesystem::path(native); + } + + bool path_is_exist(const std::filesystem::path& path) { std::error_code errorCode; return std::filesystem::exists(path, errorCode) && ((void)std::filesystem::status(path, errorCode), !errorCode); } - bool path_is_extension(const std::string& path, const std::string& extension) + bool path_is_extension(const std::filesystem::path& path, const std::string& extension) { auto e = std::filesystem::path(path).extension().string(); std::transform(e.begin(), e.end(), e.begin(), ::tolower); return e == ("." + extension); } - std::filesystem::path path_lower_case_backslash_handle(std::filesystem::path& path) + std::filesystem::path path_lower_case_backslash_handle(const std::filesystem::path& path) { - auto asString = path.generic_string(); - if (path_is_exist(asString)) return path; + auto newPath = path; + if (path_is_exist(newPath)) return newPath; - asString = string::backslash_replace(asString); - path = asString; - if (path_is_exist(asString)) return path; + newPath = path_backslash_replace(newPath); + if (path_is_exist(newPath)) return newPath; - asString = string::to_lower(asString); - path = asString; - return path; + newPath = path_to_lower(newPath); + return newPath; } - WorkingDirectory::WorkingDirectory(const std::string& path, bool isFile) + WorkingDirectory::WorkingDirectory(const std::filesystem::path& path, bool isFile) { previous = std::filesystem::current_path(); if (isFile) @@ -56,10 +84,5 @@ namespace anm2ed::util::filesystem std::filesystem::current_path(path); } - WorkingDirectory::WorkingDirectory(const std::filesystem::path& path, bool isFile) - : WorkingDirectory(path.string(), isFile) - { - } - WorkingDirectory::~WorkingDirectory() { std::filesystem::current_path(previous); } } diff --git a/src/util/filesystem_.h b/src/util/filesystem_.h index b1a26c9..7d9e8b8 100644 --- a/src/util/filesystem_.h +++ b/src/util/filesystem_.h @@ -5,19 +5,20 @@ namespace anm2ed::util::filesystem { - std::string path_preferences_get(); + std::filesystem::path path_preferences_get(); + std::filesystem::path path_to_lower(const std::filesystem::path&); + std::filesystem::path path_backslash_replace(const std::filesystem::path&); - bool path_is_exist(const std::string&); - bool path_is_extension(const std::string&, const std::string&); + bool path_is_exist(const std::filesystem::path&); + bool path_is_extension(const std::filesystem::path&, const std::string&); - std::filesystem::path path_lower_case_backslash_handle(std::filesystem::path&); + std::filesystem::path path_lower_case_backslash_handle(const std::filesystem::path&); class WorkingDirectory { public: std::filesystem::path previous; - WorkingDirectory(const std::string&, bool = false); WorkingDirectory(const std::filesystem::path&, bool = false); ~WorkingDirectory(); }; diff --git a/src/util/string_.cpp b/src/util/string_.cpp index 2cef87f..ae1af0a 100644 --- a/src/util/string_.cpp +++ b/src/util/string_.cpp @@ -19,13 +19,7 @@ namespace anm2ed::util::string return transformed; } - std::string quote(const std::string& string) - { - return "\"" + string + "\""; - } + std::string quote(const std::string& string) { return "\"" + string + "\""; } - bool to_bool(const std::string& string) - { - return to_lower(string) == "true"; - } + bool to_bool(const std::string& string) { return to_lower(string) == "true"; } }