spritesheet hashing, navigation fix, etc.
This commit is contained in:
@@ -39,7 +39,7 @@ namespace anm2ed::anm2
|
||||
|
||||
Spritesheet* spritesheet_get(int);
|
||||
bool spritesheet_add(const std::filesystem::path&, const std::filesystem::path&, int&);
|
||||
bool spritesheet_pack(int);
|
||||
bool spritesheet_pack(int, int);
|
||||
bool regions_trim(int, const std::set<int>&);
|
||||
std::vector<std::string> spritesheet_labels_get();
|
||||
std::vector<int> spritesheet_ids_get();
|
||||
|
||||
@@ -29,9 +29,9 @@ namespace anm2ed::anm2
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Anm2::spritesheet_pack(int id)
|
||||
bool Anm2::spritesheet_pack(int id, int padding)
|
||||
{
|
||||
constexpr int PACKING_PADDING = 1;
|
||||
const int packingPadding = std::max(0, padding);
|
||||
|
||||
struct RectI
|
||||
{
|
||||
@@ -256,8 +256,8 @@ namespace anm2ed::anm2
|
||||
auto minPoint = glm::ivec2(glm::min(region.crop, region.crop + region.size));
|
||||
auto maxPoint = glm::ivec2(glm::max(region.crop, region.crop + region.size));
|
||||
auto size = glm::max(maxPoint - minPoint, glm::ivec2(1));
|
||||
int packWidth = size.x + PACKING_PADDING * 2;
|
||||
int packHeight = size.y + PACKING_PADDING * 2;
|
||||
int packWidth = size.x + packingPadding * 2;
|
||||
int packHeight = size.y + packingPadding * 2;
|
||||
items.push_back({regionID, minPoint.x, minPoint.y, size.x, size.y, packWidth, packHeight});
|
||||
}
|
||||
|
||||
@@ -290,8 +290,8 @@ namespace anm2ed::anm2
|
||||
{
|
||||
int sourceX = item.srcX + x;
|
||||
int sourceY = item.srcY + y;
|
||||
int destinationX = destinationRect.x + PACKING_PADDING + x;
|
||||
int destinationY = destinationRect.y + PACKING_PADDING + y;
|
||||
int destinationX = destinationRect.x + packingPadding + x;
|
||||
int destinationY = destinationRect.y + packingPadding + y;
|
||||
|
||||
if (sourceX < 0 || sourceY < 0 || sourceX >= textureSize.x || sourceY >= textureSize.y) continue;
|
||||
if (destinationX < 0 || destinationY < 0 || destinationX >= packedWidth || destinationY >= packedHeight)
|
||||
@@ -312,7 +312,7 @@ namespace anm2ed::anm2
|
||||
if (packedRects.contains(regionID))
|
||||
{
|
||||
auto& rect = packedRects.at(regionID);
|
||||
region.crop = {rect.x + PACKING_PADDING, rect.y + PACKING_PADDING};
|
||||
region.crop = {rect.x + packingPadding, rect.y + packingPadding};
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#include "spritesheet.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <ranges>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "map_.h"
|
||||
@@ -52,6 +54,7 @@ namespace anm2ed::anm2
|
||||
path = path::lower_case_backslash_handle(path);
|
||||
texture = Texture(path);
|
||||
|
||||
regionOrder.clear();
|
||||
for (auto child = element->FirstChildElement("Region"); child; child = child->NextSiblingElement("Region"))
|
||||
{
|
||||
Region region{};
|
||||
@@ -73,13 +76,17 @@ namespace anm2ed::anm2
|
||||
child->QueryFloatAttribute("YPivot", ®ion.pivot.y);
|
||||
}
|
||||
regions.emplace(id, std::move(region));
|
||||
regionOrder.push_back(id);
|
||||
}
|
||||
|
||||
if (regionOrder.size() != regions.size())
|
||||
{
|
||||
regionOrder.clear();
|
||||
regionOrder.reserve(regions.size());
|
||||
for (auto id : regions | std::views::keys)
|
||||
regionOrder.push_back(id);
|
||||
}
|
||||
}
|
||||
|
||||
Spritesheet::Spritesheet(const std::filesystem::path& directory, const std::filesystem::path& path)
|
||||
{
|
||||
@@ -231,4 +238,43 @@ namespace anm2ed::anm2
|
||||
}
|
||||
bool Spritesheet::is_valid() { return texture.is_valid(); }
|
||||
|
||||
uint64_t Spritesheet::hash() const
|
||||
{
|
||||
auto hash_combine = [](std::size_t& seed, std::size_t value)
|
||||
{
|
||||
seed ^= value + 0x9e3779b97f4a7c15ULL + (seed << 6) + (seed >> 2);
|
||||
};
|
||||
|
||||
std::size_t seed{};
|
||||
hash_combine(seed, std::hash<int>{}(texture.size.x));
|
||||
hash_combine(seed, std::hash<int>{}(texture.size.y));
|
||||
hash_combine(seed, std::hash<int>{}(texture.channels));
|
||||
hash_combine(seed, std::hash<int>{}(texture.filter));
|
||||
|
||||
if (!texture.pixels.empty())
|
||||
{
|
||||
std::string_view bytes(reinterpret_cast<const char*>(texture.pixels.data()), texture.pixels.size());
|
||||
hash_combine(seed, std::hash<std::string_view>{}(bytes));
|
||||
}
|
||||
else
|
||||
{
|
||||
hash_combine(seed, 0);
|
||||
}
|
||||
|
||||
for (const auto& [id, region] : regions)
|
||||
{
|
||||
hash_combine(seed, std::hash<int>{}(id));
|
||||
hash_combine(seed, std::hash<std::string>{}(region.name));
|
||||
hash_combine(seed, std::hash<float>{}(region.crop.x));
|
||||
hash_combine(seed, std::hash<float>{}(region.crop.y));
|
||||
hash_combine(seed, std::hash<float>{}(region.size.x));
|
||||
hash_combine(seed, std::hash<float>{}(region.size.y));
|
||||
hash_combine(seed, std::hash<float>{}(region.pivot.x));
|
||||
hash_combine(seed, std::hash<float>{}(region.pivot.y));
|
||||
hash_combine(seed, std::hash<int>{}(static_cast<int>(region.origin)));
|
||||
}
|
||||
|
||||
return static_cast<uint64_t>(seed);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <set>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
#include <tinyxml2/tinyxml2.h>
|
||||
|
||||
#include "texture.h"
|
||||
@@ -48,5 +49,6 @@ namespace anm2ed::anm2
|
||||
void serialize(tinyxml2::XMLDocument&, tinyxml2::XMLElement*, int, Flags = 0);
|
||||
void reload(const std::filesystem::path&, const std::filesystem::path& = {});
|
||||
bool is_valid();
|
||||
uint64_t hash() const;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -75,8 +75,10 @@ namespace anm2ed
|
||||
previewZoom(other.previewZoom), previewPan(other.previewPan), editorPan(other.editorPan),
|
||||
editorZoom(other.editorZoom), overlayIndex(other.overlayIndex), hash(other.hash), saveHash(other.saveHash),
|
||||
autosaveHash(other.autosaveHash), lastAutosaveTime(other.lastAutosaveTime), isValid(other.isValid),
|
||||
isOpen(other.isOpen), isForceDirty(other.isForceDirty), isAnimationPreviewSet(other.isAnimationPreviewSet),
|
||||
isSpritesheetEditorSet(other.isSpritesheetEditorSet)
|
||||
isOpen(other.isOpen), isForceDirty(other.isForceDirty),
|
||||
spritesheetHashes(std::move(other.spritesheetHashes)),
|
||||
spritesheetSaveHashes(std::move(other.spritesheetSaveHashes)),
|
||||
isAnimationPreviewSet(other.isAnimationPreviewSet), isSpritesheetEditorSet(other.isSpritesheetEditorSet)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -99,6 +101,8 @@ namespace anm2ed
|
||||
isValid = other.isValid;
|
||||
isOpen = other.isOpen;
|
||||
isForceDirty = other.isForceDirty;
|
||||
spritesheetHashes = std::move(other.spritesheetHashes);
|
||||
spritesheetSaveHashes = std::move(other.spritesheetSaveHashes);
|
||||
isAnimationPreviewSet = other.isAnimationPreviewSet;
|
||||
isSpritesheetEditorSet = other.isSpritesheetEditorSet;
|
||||
}
|
||||
@@ -182,6 +186,44 @@ namespace anm2ed
|
||||
isForceDirty = false;
|
||||
}
|
||||
|
||||
void Document::spritesheet_hashes_reset()
|
||||
{
|
||||
spritesheetHashes.clear();
|
||||
spritesheetSaveHashes.clear();
|
||||
for (auto& [id, spritesheet] : anm2.content.spritesheets)
|
||||
{
|
||||
auto currentHash = spritesheet.hash();
|
||||
spritesheetHashes[id] = currentHash;
|
||||
spritesheetSaveHashes[id] = currentHash;
|
||||
}
|
||||
}
|
||||
|
||||
void Document::spritesheet_hashes_sync()
|
||||
{
|
||||
for (auto it = spritesheetHashes.begin(); it != spritesheetHashes.end();)
|
||||
{
|
||||
if (!anm2.content.spritesheets.contains(it->first))
|
||||
it = spritesheetHashes.erase(it);
|
||||
else
|
||||
++it;
|
||||
}
|
||||
|
||||
for (auto it = spritesheetSaveHashes.begin(); it != spritesheetSaveHashes.end();)
|
||||
{
|
||||
if (!anm2.content.spritesheets.contains(it->first))
|
||||
it = spritesheetSaveHashes.erase(it);
|
||||
else
|
||||
++it;
|
||||
}
|
||||
|
||||
for (auto& [id, spritesheet] : anm2.content.spritesheets)
|
||||
{
|
||||
auto currentHash = spritesheet.hash();
|
||||
spritesheetHashes[id] = currentHash;
|
||||
if (!spritesheetSaveHashes.contains(id)) spritesheetSaveHashes[id] = currentHash;
|
||||
}
|
||||
}
|
||||
|
||||
void Document::change(ChangeType type)
|
||||
{
|
||||
hash_set();
|
||||
@@ -191,7 +233,10 @@ namespace anm2ed
|
||||
auto animations_set = [&]() { animation.labels_set(anm2.animation_labels_get()); };
|
||||
|
||||
auto spritesheets_set = [&]()
|
||||
{ spritesheet.labels_set(anm2.spritesheet_labels_get(), anm2.spritesheet_ids_get()); };
|
||||
{
|
||||
spritesheet.labels_set(anm2.spritesheet_labels_get(), anm2.spritesheet_ids_get());
|
||||
spritesheet_hashes_sync();
|
||||
};
|
||||
|
||||
auto sounds_set = [&]() { sound.labels_set(anm2.sound_labels_get(), anm2.sound_ids_get()); };
|
||||
|
||||
@@ -244,6 +289,37 @@ namespace anm2ed
|
||||
|
||||
bool Document::is_dirty() const { return hash != saveHash; }
|
||||
bool Document::is_autosave_dirty() const { return hash != autosaveHash; }
|
||||
void Document::spritesheet_hash_update(int id)
|
||||
{
|
||||
if (!anm2.content.spritesheets.contains(id)) return;
|
||||
spritesheetHashes[id] = anm2.content.spritesheets.at(id).hash();
|
||||
}
|
||||
|
||||
void Document::spritesheet_hash_set_saved(int id)
|
||||
{
|
||||
if (!anm2.content.spritesheets.contains(id)) return;
|
||||
auto currentHash = anm2.content.spritesheets.at(id).hash();
|
||||
spritesheetHashes[id] = currentHash;
|
||||
spritesheetSaveHashes[id] = currentHash;
|
||||
}
|
||||
|
||||
bool Document::spritesheet_is_dirty(int id)
|
||||
{
|
||||
if (!anm2.content.spritesheets.contains(id)) return false;
|
||||
if (!spritesheetHashes.contains(id)) spritesheet_hash_update(id);
|
||||
auto saveIt = spritesheetSaveHashes.find(id);
|
||||
if (saveIt == spritesheetSaveHashes.end()) return false;
|
||||
return spritesheetHashes.at(id) != saveIt->second;
|
||||
}
|
||||
|
||||
bool Document::spritesheet_any_dirty()
|
||||
{
|
||||
for (auto& [id, spritesheet] : anm2.content.spritesheets)
|
||||
{
|
||||
if (spritesheet_is_dirty(id)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
std::filesystem::path Document::directory_get() const { return path.parent_path(); }
|
||||
std::filesystem::path Document::filename_get() const { return path.filename(); }
|
||||
bool Document::is_valid() const { return isValid && !path.empty(); }
|
||||
@@ -272,6 +348,7 @@ namespace anm2ed
|
||||
auto pathString = path::to_utf8(spritesheet.path);
|
||||
this->spritesheet.selection = {id};
|
||||
this->spritesheet.reference = id;
|
||||
spritesheet_hash_set_saved(id);
|
||||
toasts.push(std::vformat(localize.get(TOAST_SPRITESHEET_INITIALIZED), std::make_format_args(id, pathString)));
|
||||
logger.info(std::vformat(localize.get(TOAST_SPRITESHEET_INITIALIZED, anm2ed::ENGLISH),
|
||||
std::make_format_args(id, pathString)));
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <filesystem>
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "snapshots.h"
|
||||
|
||||
@@ -64,6 +65,8 @@ namespace anm2ed
|
||||
bool isValid{true};
|
||||
bool isOpen{true};
|
||||
bool isForceDirty{false};
|
||||
std::unordered_map<int, uint64_t> spritesheetHashes{};
|
||||
std::unordered_map<int, uint64_t> spritesheetSaveHashes{};
|
||||
bool isAnimationPreviewSet{false};
|
||||
bool isSpritesheetEditorSet{false};
|
||||
|
||||
@@ -82,6 +85,12 @@ namespace anm2ed
|
||||
std::filesystem::path directory_get() const;
|
||||
std::filesystem::path filename_get() const;
|
||||
bool is_valid() const;
|
||||
void spritesheet_hash_update(int);
|
||||
void spritesheet_hash_set_saved(int);
|
||||
bool spritesheet_is_dirty(int);
|
||||
bool spritesheet_any_dirty();
|
||||
void spritesheet_hashes_reset();
|
||||
void spritesheet_hashes_sync();
|
||||
|
||||
anm2::Frame* frame_get();
|
||||
anm2::Item* item_get();
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#include "path_.h"
|
||||
#include "strings.h"
|
||||
#include "time_.h"
|
||||
#include "toast.h"
|
||||
#include "log.h"
|
||||
|
||||
using namespace anm2ed::resource;
|
||||
using namespace anm2ed::types;
|
||||
@@ -61,7 +63,9 @@ namespace anm2ed::imgui
|
||||
for (int i = 0; i < documentsCount; ++i)
|
||||
{
|
||||
auto& document = manager.documents[i];
|
||||
auto isDirty = document.is_dirty() || document.isForceDirty;
|
||||
auto isDocumentDirty = document.is_dirty() || document.isForceDirty;
|
||||
auto isSpritesheetDirty = document.spritesheet_any_dirty();
|
||||
auto isDirty = isDocumentDirty || isSpritesheetDirty;
|
||||
|
||||
if (!closePopup.is_open())
|
||||
{
|
||||
@@ -87,13 +91,13 @@ namespace anm2ed::imgui
|
||||
}
|
||||
|
||||
auto isRequested = i == manager.pendingSelected;
|
||||
auto font = isDirty ? font::ITALICS : font::REGULAR;
|
||||
auto font = isDocumentDirty ? font::ITALICS : font::REGULAR;
|
||||
auto filename = path::to_utf8(document.filename_get());
|
||||
auto string =
|
||||
isDirty ? std::vformat(localize.get(FORMAT_NOT_SAVED), std::make_format_args(filename)) : filename;
|
||||
isDocumentDirty ? std::vformat(localize.get(FORMAT_NOT_SAVED), std::make_format_args(filename)) : filename;
|
||||
auto label = std::format("{}###Document{}", string, i);
|
||||
|
||||
auto flags = isDirty ? ImGuiTabItemFlags_UnsavedDocument : 0;
|
||||
auto flags = isDocumentDirty ? ImGuiTabItemFlags_UnsavedDocument : 0;
|
||||
if (isRequested) flags |= ImGuiTabItemFlags_SetSelected;
|
||||
|
||||
ImGui::PushFont(resources.fonts[font].get(), font::SIZE);
|
||||
@@ -129,7 +133,13 @@ namespace anm2ed::imgui
|
||||
auto& closeDocument = manager.documents[closeDocumentIndex];
|
||||
|
||||
auto filename = path::to_utf8(closeDocument.filename_get());
|
||||
auto prompt = std::vformat(localize.get(LABEL_DOCUMENT_MODIFIED_PROMPT), std::make_format_args(filename));
|
||||
auto isDocumentDirty = closeDocument.is_dirty() || closeDocument.isForceDirty;
|
||||
auto isSpritesheetDirty = closeDocument.spritesheet_any_dirty();
|
||||
auto promptLabel = isDocumentDirty && isSpritesheetDirty
|
||||
? LABEL_DOCUMENT_AND_SPRITESHEETS_MODIFIED_PROMPT
|
||||
: (isDocumentDirty ? LABEL_DOCUMENT_MODIFIED_PROMPT
|
||||
: LABEL_SPRITESHEETS_MODIFIED_PROMPT);
|
||||
auto prompt = std::vformat(localize.get(promptLabel), std::make_format_args(filename));
|
||||
ImGui::TextUnformatted(prompt.c_str());
|
||||
|
||||
auto widgetSize = imgui::widget_size_with_row_get(3);
|
||||
@@ -143,7 +153,31 @@ namespace anm2ed::imgui
|
||||
shortcut(manager.chords[SHORTCUT_CONFIRM]);
|
||||
if (ImGui::Button(localize.get(BASIC_YES), widgetSize))
|
||||
{
|
||||
if (isDocumentDirty)
|
||||
manager.save(closeDocumentIndex, {}, (anm2::Compatibility)settings.fileCompatibility);
|
||||
|
||||
if (isSpritesheetDirty)
|
||||
{
|
||||
for (auto& [id, spritesheet] : closeDocument.anm2.content.spritesheets)
|
||||
{
|
||||
if (!closeDocument.spritesheet_is_dirty(id)) continue;
|
||||
auto pathString = path::to_utf8(spritesheet.path);
|
||||
if (spritesheet.save(closeDocument.directory_get()))
|
||||
{
|
||||
closeDocument.spritesheet_hash_set_saved(id);
|
||||
toasts.push(std::vformat(localize.get(TOAST_SAVE_SPRITESHEET), std::make_format_args(id, pathString)));
|
||||
logger.info(std::vformat(localize.get(TOAST_SAVE_SPRITESHEET, anm2ed::ENGLISH),
|
||||
std::make_format_args(id, pathString)));
|
||||
}
|
||||
else
|
||||
{
|
||||
toasts.push(
|
||||
std::vformat(localize.get(TOAST_SAVE_SPRITESHEET_FAILED), std::make_format_args(id, pathString)));
|
||||
logger.error(std::vformat(localize.get(TOAST_SAVE_SPRITESHEET_FAILED, anm2ed::ENGLISH),
|
||||
std::make_format_args(id, pathString)));
|
||||
}
|
||||
}
|
||||
}
|
||||
manager.close(closeDocumentIndex);
|
||||
close();
|
||||
}
|
||||
|
||||
@@ -224,6 +224,41 @@ namespace anm2ed::imgui
|
||||
selection.insert(id);
|
||||
}
|
||||
if (ImGui::Shortcut(ImGuiKey_Escape, ImGuiInputFlags_RouteFocused)) selection.clear();
|
||||
auto scroll_to_item = [&](float itemHeight, bool isTarget)
|
||||
{
|
||||
if (!isTarget) return;
|
||||
auto windowHeight = ImGui::GetWindowHeight();
|
||||
auto targetTop = ImGui::GetCursorPosY();
|
||||
auto targetBottom = targetTop + itemHeight;
|
||||
auto visibleTop = ImGui::GetScrollY();
|
||||
auto visibleBottom = visibleTop + windowHeight;
|
||||
if (targetTop < visibleTop)
|
||||
ImGui::SetScrollY(targetTop);
|
||||
else if (targetBottom > visibleBottom)
|
||||
ImGui::SetScrollY(targetBottom - windowHeight);
|
||||
};
|
||||
int scrollTargetId = -1;
|
||||
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) &&
|
||||
(ImGui::IsKeyPressed(ImGuiKey_UpArrow, true) || ImGui::IsKeyPressed(ImGuiKey_DownArrow, true)))
|
||||
{
|
||||
auto& order = spritesheet->regionOrder;
|
||||
if (!order.empty())
|
||||
{
|
||||
int delta = ImGui::IsKeyPressed(ImGuiKey_UpArrow, true) ? -1 : 1;
|
||||
int current = reference;
|
||||
if (current == -1 && !selection.empty()) current = *selection.begin();
|
||||
auto it = std::find(order.begin(), order.end(), current);
|
||||
int index = it == order.end() ? 0 : (int)std::distance(order.begin(), it);
|
||||
index = std::clamp(index + delta, 0, (int)order.size() - 1);
|
||||
int nextId = order[index];
|
||||
selection = {nextId};
|
||||
reference = nextId;
|
||||
document.reference = {document.reference.animationIndex};
|
||||
frame.reference = -1;
|
||||
frame.selection.clear();
|
||||
scrollTargetId = nextId;
|
||||
}
|
||||
}
|
||||
bool isValid = spritesheet->is_valid();
|
||||
auto& texture = isValid ? spritesheet->texture : resources.icons[icon::NONE];
|
||||
auto tintColor = !isValid ? ImVec4(1.0f, 0.25f, 0.25f, 1.0f) : ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
@@ -241,6 +276,8 @@ namespace anm2ed::imgui
|
||||
|
||||
ImGui::PushID(id);
|
||||
|
||||
scroll_to_item(regionChildSize.y, scrollTargetId == id);
|
||||
|
||||
if (ImGui::BeginChild("##Region Child", regionChildSize, ImGuiChildFlags_Borders))
|
||||
{
|
||||
auto cursorPos = ImGui::GetCursorPos();
|
||||
@@ -254,6 +291,7 @@ namespace anm2ed::imgui
|
||||
frame.reference = -1;
|
||||
frame.selection.clear();
|
||||
}
|
||||
if (scrollTargetId == id) ImGui::SetItemDefaultFocus();
|
||||
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) propertiesPopup.open();
|
||||
|
||||
auto viewport = ImGui::GetMainViewport();
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "sounds.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <ranges>
|
||||
#include <vector>
|
||||
|
||||
#include "log.h"
|
||||
#include "path_.h"
|
||||
@@ -220,12 +222,49 @@ namespace anm2ed::imgui
|
||||
selection.insert(id);
|
||||
}
|
||||
if (ImGui::Shortcut(ImGuiKey_Escape, ImGuiInputFlags_RouteFocused)) selection.clear();
|
||||
auto scroll_to_item = [&](float itemHeight, bool isTarget)
|
||||
{
|
||||
if (!isTarget) return;
|
||||
auto windowHeight = ImGui::GetWindowHeight();
|
||||
auto targetTop = ImGui::GetCursorPosY();
|
||||
auto targetBottom = targetTop + itemHeight;
|
||||
auto visibleTop = ImGui::GetScrollY();
|
||||
auto visibleBottom = visibleTop + windowHeight;
|
||||
if (targetTop < visibleTop)
|
||||
ImGui::SetScrollY(targetTop);
|
||||
else if (targetBottom > visibleBottom)
|
||||
ImGui::SetScrollY(targetBottom - windowHeight);
|
||||
};
|
||||
int scrollTargetId = -1;
|
||||
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) &&
|
||||
(ImGui::IsKeyPressed(ImGuiKey_UpArrow, true) || ImGui::IsKeyPressed(ImGuiKey_DownArrow, true)))
|
||||
{
|
||||
std::vector<int> ids{};
|
||||
ids.reserve(anm2.content.sounds.size());
|
||||
for (auto& [id, sound] : anm2.content.sounds)
|
||||
ids.push_back(id);
|
||||
if (!ids.empty())
|
||||
{
|
||||
int delta = ImGui::IsKeyPressed(ImGuiKey_UpArrow, true) ? -1 : 1;
|
||||
int current = reference;
|
||||
if (current == -1 && !selection.empty()) current = *selection.begin();
|
||||
auto it = std::find(ids.begin(), ids.end(), current);
|
||||
int index = it == ids.end() ? 0 : (int)std::distance(ids.begin(), it);
|
||||
index = std::clamp(index + delta, 0, (int)ids.size() - 1);
|
||||
int nextId = ids[index];
|
||||
selection = {nextId};
|
||||
reference = nextId;
|
||||
scrollTargetId = nextId;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& [id, sound] : anm2.content.sounds)
|
||||
{
|
||||
auto isNewSound = newSoundId == id;
|
||||
ImGui::PushID(id);
|
||||
|
||||
scroll_to_item(soundChildSize.y, scrollTargetId == id);
|
||||
|
||||
if (ImGui::BeginChild("##Sound Child", soundChildSize, ImGuiChildFlags_Borders))
|
||||
{
|
||||
auto isSelected = selection.contains(id);
|
||||
@@ -242,6 +281,7 @@ namespace anm2ed::imgui
|
||||
reference = id;
|
||||
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) play(sound);
|
||||
}
|
||||
if (scrollTargetId == id) ImGui::SetItemDefaultFocus();
|
||||
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) open_directory(sound);
|
||||
|
||||
auto textWidth = ImGui::CalcTextSize(pathString.c_str()).x;
|
||||
|
||||
@@ -655,7 +655,10 @@ namespace anm2ed::imgui
|
||||
if (isMouseClicked)
|
||||
document.snapshot(useTool == tool::DRAW ? localize.get(EDIT_DRAW) : localize.get(EDIT_ERASE));
|
||||
if (isMouseDown) spritesheet->texture.pixel_line(ivec2(previousMousePos), ivec2(mousePos), color);
|
||||
if (isMouseReleased) document.change(Document::SPRITESHEETS);
|
||||
if (isMouseReleased)
|
||||
{
|
||||
document.change(Document::SPRITESHEETS);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case tool::COLOR_PICKER:
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "spritesheets.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <ranges>
|
||||
#include <vector>
|
||||
|
||||
#include <filesystem>
|
||||
#include <format>
|
||||
@@ -19,6 +21,8 @@ using namespace glm;
|
||||
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
static constexpr auto PADDING_MAX = 100;
|
||||
|
||||
void Spritesheets::update(Manager& manager, Settings& settings, Resources& resources, Dialog& dialog,
|
||||
Clipboard& clipboard)
|
||||
{
|
||||
@@ -44,7 +48,8 @@ namespace anm2ed::imgui
|
||||
auto id = *selection.begin();
|
||||
if (!anm2.content.spritesheets.contains(id)) return;
|
||||
if (anm2.content.spritesheets.at(id).regions.empty()) return;
|
||||
if (pack) pack();
|
||||
packId = id;
|
||||
packPopup.open();
|
||||
};
|
||||
|
||||
auto add = [&](const std::filesystem::path& path)
|
||||
@@ -85,6 +90,7 @@ namespace anm2ed::imgui
|
||||
{
|
||||
anm2::Spritesheet& spritesheet = anm2.content.spritesheets[id];
|
||||
spritesheet.reload(document.directory_get());
|
||||
document.spritesheet_hash_set_saved(id);
|
||||
auto pathString = path::to_utf8(spritesheet.path);
|
||||
toasts.push(std::vformat(localize.get(TOAST_RELOAD_SPRITESHEET), std::make_format_args(id, pathString)));
|
||||
logger.info(std::vformat(localize.get(TOAST_RELOAD_SPRITESHEET, anm2ed::ENGLISH),
|
||||
@@ -104,6 +110,7 @@ namespace anm2ed::imgui
|
||||
auto& id = *selection.begin();
|
||||
anm2::Spritesheet& spritesheet = anm2.content.spritesheets[id];
|
||||
spritesheet.reload(document.directory_get(), path);
|
||||
document.spritesheet_hash_set_saved(id);
|
||||
auto pathString = path::to_utf8(spritesheet.path);
|
||||
toasts.push(std::vformat(localize.get(TOAST_REPLACE_SPRITESHEET), std::make_format_args(id, pathString)));
|
||||
logger.info(std::vformat(localize.get(TOAST_REPLACE_SPRITESHEET, anm2ed::ENGLISH),
|
||||
@@ -113,16 +120,18 @@ namespace anm2ed::imgui
|
||||
DOCUMENT_EDIT(document, localize.get(EDIT_REPLACE_SPRITESHEET), Document::SPRITESHEETS, behavior());
|
||||
};
|
||||
|
||||
auto save = [&]()
|
||||
auto save = [&](const std::set<int>& ids)
|
||||
{
|
||||
if (selection.empty()) return;
|
||||
if (ids.empty()) return;
|
||||
|
||||
for (auto& id : selection)
|
||||
for (auto& id : ids)
|
||||
{
|
||||
if (!anm2.content.spritesheets.contains(id)) continue;
|
||||
anm2::Spritesheet& spritesheet = anm2.content.spritesheets[id];
|
||||
auto pathString = path::to_utf8(spritesheet.path);
|
||||
if (spritesheet.save(document.directory_get()))
|
||||
{
|
||||
document.spritesheet_hash_set_saved(id);
|
||||
toasts.push(std::vformat(localize.get(TOAST_SAVE_SPRITESHEET), std::make_format_args(id, pathString)));
|
||||
logger.info(std::vformat(localize.get(TOAST_SAVE_SPRITESHEET, anm2ed::ENGLISH),
|
||||
std::make_format_args(id, pathString)));
|
||||
@@ -136,6 +145,20 @@ namespace anm2ed::imgui
|
||||
}
|
||||
};
|
||||
|
||||
auto save_open = [&]()
|
||||
{
|
||||
if (selection.empty()) return;
|
||||
if (settings.fileIsWarnOverwrite)
|
||||
{
|
||||
saveSelection = selection;
|
||||
overwritePopup.open();
|
||||
}
|
||||
else
|
||||
{
|
||||
save(selection);
|
||||
}
|
||||
};
|
||||
|
||||
auto merge = [&]()
|
||||
{
|
||||
if (mergeSelection.size() <= 1) return;
|
||||
@@ -165,12 +188,15 @@ namespace anm2ed::imgui
|
||||
};
|
||||
pack = [&]()
|
||||
{
|
||||
if (selection.size() != 1) return;
|
||||
int id = packId != -1 ? packId : (selection.size() == 1 ? *selection.begin() : -1);
|
||||
if (id == -1) return;
|
||||
if (!anm2.content.spritesheets.contains(id)) return;
|
||||
if (anm2.content.spritesheets.at(id).regions.empty()) return;
|
||||
|
||||
auto behavior = [&]()
|
||||
{
|
||||
auto id = *selection.begin();
|
||||
if (anm2.spritesheet_pack(id))
|
||||
auto padding = std::max(0, settings.packPadding);
|
||||
if (anm2.spritesheet_pack(id, padding))
|
||||
{
|
||||
toasts.push(localize.get(TOAST_PACK_SPRITESHEET));
|
||||
logger.info(localize.get(TOAST_PACK_SPRITESHEET, anm2ed::ENGLISH));
|
||||
@@ -213,7 +239,8 @@ namespace anm2ed::imgui
|
||||
|
||||
auto behavior = [&]()
|
||||
{
|
||||
auto maxSpritesheetIdBefore = anm2.content.spritesheets.empty() ? -1 : anm2.content.spritesheets.rbegin()->first;
|
||||
auto maxSpritesheetIdBefore =
|
||||
anm2.content.spritesheets.empty() ? -1 : anm2.content.spritesheets.rbegin()->first;
|
||||
std::string errorString{};
|
||||
document.snapshot(localize.get(EDIT_PASTE_SPRITESHEETS));
|
||||
if (anm2.spritesheets_deserialize(clipboard.get(), document.directory_get(), merge::APPEND, &errorString))
|
||||
@@ -271,8 +298,7 @@ namespace anm2ed::imgui
|
||||
if (ImGui::MenuItem(localize.get(BASIC_ADD), settings.shortcutAdd.c_str())) add_open();
|
||||
if (ImGui::MenuItem(localize.get(BASIC_REMOVE_UNUSED), settings.shortcutRemove.c_str())) remove_unused();
|
||||
|
||||
bool isPackable =
|
||||
selection.size() == 1 && anm2.content.spritesheets.contains(*selection.begin()) &&
|
||||
bool isPackable = selection.size() == 1 && anm2.content.spritesheets.contains(*selection.begin()) &&
|
||||
!anm2.content.spritesheets.at(*selection.begin()).regions.empty();
|
||||
|
||||
if (ImGui::MenuItem(localize.get(BASIC_RELOAD), nullptr, false, !selection.empty())) reload();
|
||||
@@ -282,7 +308,7 @@ namespace anm2ed::imgui
|
||||
if (ImGui::MenuItem(localize.get(BASIC_PACK), nullptr, false, isPackable)) pack_open();
|
||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled))
|
||||
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_PACK_SPRITESHEET));
|
||||
if (ImGui::MenuItem(localize.get(BASIC_SAVE), nullptr, false, !selection.empty())) save();
|
||||
if (ImGui::MenuItem(localize.get(BASIC_SAVE), nullptr, false, !selection.empty())) save_open();
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
@@ -313,12 +339,51 @@ namespace anm2ed::imgui
|
||||
selection.insert(id);
|
||||
}
|
||||
if (ImGui::Shortcut(ImGuiKey_Escape, ImGuiInputFlags_RouteFocused)) selection.clear();
|
||||
auto scroll_to_item = [&](float itemHeight, bool isTarget)
|
||||
{
|
||||
if (!isTarget) return;
|
||||
auto windowHeight = ImGui::GetWindowHeight();
|
||||
auto targetTop = ImGui::GetCursorPosY();
|
||||
auto targetBottom = targetTop + itemHeight;
|
||||
auto visibleTop = ImGui::GetScrollY();
|
||||
auto visibleBottom = visibleTop + windowHeight;
|
||||
if (targetTop < visibleTop)
|
||||
ImGui::SetScrollY(targetTop);
|
||||
else if (targetBottom > visibleBottom)
|
||||
ImGui::SetScrollY(targetBottom - windowHeight);
|
||||
};
|
||||
int scrollTargetId = -1;
|
||||
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) &&
|
||||
(ImGui::IsKeyPressed(ImGuiKey_UpArrow, true) || ImGui::IsKeyPressed(ImGuiKey_DownArrow, true)))
|
||||
{
|
||||
std::vector<int> ids{};
|
||||
ids.reserve(anm2.content.spritesheets.size());
|
||||
for (auto& [id, sheet] : anm2.content.spritesheets)
|
||||
ids.push_back(id);
|
||||
if (!ids.empty())
|
||||
{
|
||||
int delta = ImGui::IsKeyPressed(ImGuiKey_UpArrow, true) ? -1 : 1;
|
||||
int current = reference;
|
||||
if (current == -1 && !selection.empty()) current = *selection.begin();
|
||||
auto it = std::find(ids.begin(), ids.end(), current);
|
||||
int index = it == ids.end() ? 0 : (int)std::distance(ids.begin(), it);
|
||||
index = std::clamp(index + delta, 0, (int)ids.size() - 1);
|
||||
int nextId = ids[index];
|
||||
selection = {nextId};
|
||||
reference = nextId;
|
||||
region.reference = -1;
|
||||
region.selection.clear();
|
||||
scrollTargetId = nextId;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& [id, spritesheet] : anm2.content.spritesheets)
|
||||
{
|
||||
auto isNewSpritesheet = newSpritesheetId == id;
|
||||
ImGui::PushID(id);
|
||||
|
||||
scroll_to_item(spritesheetChildSize.y, scrollTargetId == id);
|
||||
|
||||
if (ImGui::BeginChild("##Spritesheet Child", spritesheetChildSize, ImGuiChildFlags_Borders))
|
||||
{
|
||||
auto isSelected = selection.contains(id);
|
||||
@@ -338,6 +403,7 @@ namespace anm2ed::imgui
|
||||
region.reference = -1;
|
||||
region.selection.clear();
|
||||
}
|
||||
if (scrollTargetId == id) ImGui::SetItemDefaultFocus();
|
||||
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left))
|
||||
open_directory(spritesheet);
|
||||
|
||||
@@ -411,8 +477,11 @@ namespace anm2ed::imgui
|
||||
spritesheetChildSize.y - spritesheetChildSize.y / 2 - ImGui::GetTextLineHeight() / 2));
|
||||
|
||||
if (isReferenced) ImGui::PushFont(resources.fonts[font::ITALICS].get(), font::SIZE);
|
||||
ImGui::TextUnformatted(
|
||||
std::vformat(localize.get(FORMAT_SPRITESHEET), std::make_format_args(id, pathCStr)).c_str());
|
||||
auto spritesheetLabel = std::vformat(localize.get(FORMAT_SPRITESHEET), std::make_format_args(id, pathCStr));
|
||||
if (document.spritesheet_is_dirty(id))
|
||||
spritesheetLabel =
|
||||
std::vformat(localize.get(FORMAT_SPRITESHEET_NOT_SAVED), std::make_format_args(spritesheetLabel));
|
||||
ImGui::TextUnformatted(spritesheetLabel.c_str());
|
||||
if (isReferenced) ImGui::PopFont();
|
||||
}
|
||||
|
||||
@@ -476,7 +545,7 @@ namespace anm2ed::imgui
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::BeginDisabled(selection.empty());
|
||||
if (ImGui::Button(localize.get(BASIC_SAVE), rowTwoWidgetSize)) save();
|
||||
if (ImGui::Button(localize.get(BASIC_SAVE), rowTwoWidgetSize)) save_open();
|
||||
ImGui::EndDisabled();
|
||||
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_SAVE_SPRITESHEETS));
|
||||
|
||||
@@ -543,5 +612,68 @@ namespace anm2ed::imgui
|
||||
}
|
||||
mergePopup.end();
|
||||
|
||||
packPopup.trigger();
|
||||
if (ImGui::BeginPopupModal(packPopup.label(), &packPopup.isOpen, ImGuiWindowFlags_NoResize))
|
||||
{
|
||||
settings.packPadding = std::max(0, settings.packPadding);
|
||||
|
||||
auto close = [&]()
|
||||
{
|
||||
packId = -1;
|
||||
packPopup.close();
|
||||
};
|
||||
|
||||
auto optionsSize = child_size_get(1);
|
||||
if (ImGui::BeginChild("##Pack Spritesheet Options", optionsSize, ImGuiChildFlags_Borders))
|
||||
{
|
||||
ImGui::DragInt(localize.get(LABEL_PACK_PADDING), &settings.packPadding, DRAG_SPEED, 0, PADDING_MAX);
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
auto widgetSize = widget_size_with_row_get(2);
|
||||
shortcut(manager.chords[SHORTCUT_CONFIRM]);
|
||||
bool isPackable = packId != -1 && anm2.content.spritesheets.contains(packId) &&
|
||||
!anm2.content.spritesheets.at(packId).regions.empty();
|
||||
ImGui::BeginDisabled(!isPackable);
|
||||
if (ImGui::Button(localize.get(BASIC_PACK), widgetSize))
|
||||
{
|
||||
if (pack) pack();
|
||||
close();
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::SameLine();
|
||||
shortcut(manager.chords[SHORTCUT_CANCEL]);
|
||||
if (ImGui::Button(localize.get(BASIC_CANCEL), widgetSize)) close();
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
packPopup.end();
|
||||
|
||||
overwritePopup.trigger();
|
||||
if (ImGui::BeginPopupModal(overwritePopup.label(), &overwritePopup.isOpen, ImGuiWindowFlags_NoResize))
|
||||
{
|
||||
ImGui::TextUnformatted(localize.get(LABEL_OVERWRITE_CONFIRMATION));
|
||||
|
||||
auto widgetSize = widget_size_with_row_get(2);
|
||||
|
||||
if (ImGui::Button(localize.get(BASIC_YES), widgetSize))
|
||||
{
|
||||
save(saveSelection);
|
||||
saveSelection.clear();
|
||||
overwritePopup.close();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::Button(localize.get(BASIC_NO), widgetSize))
|
||||
{
|
||||
saveSelection.clear();
|
||||
overwritePopup.close();
|
||||
}
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
overwritePopup.end();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,11 @@ namespace anm2ed::imgui
|
||||
{
|
||||
int newSpritesheetId{-1};
|
||||
PopupHelper mergePopup{PopupHelper(LABEL_SPRITESHEETS_MERGE_POPUP, imgui::POPUP_SMALL_NO_HEIGHT)};
|
||||
PopupHelper packPopup{PopupHelper(LABEL_SPRITESHEETS_PACK_POPUP, imgui::POPUP_SMALL_NO_HEIGHT)};
|
||||
PopupHelper overwritePopup{PopupHelper(LABEL_TASKBAR_OVERWRITE_FILE, imgui::POPUP_SMALL_NO_HEIGHT)};
|
||||
std::set<int> mergeSelection{};
|
||||
int packId{-1};
|
||||
std::set<int> saveSelection{};
|
||||
|
||||
public:
|
||||
void update(Manager&, Settings&, Resources&, Dialog&, Clipboard& clipboard);
|
||||
|
||||
@@ -200,6 +200,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_SPRITESHEET_NOT_SAVED, "{0} (Not Saved)", "{0} (No guardado)", "{0} (Не сохранено)", "{0} (未保存)", "{0} (저장되지 않음)") \
|
||||
X(FORMAT_SOUND, "#{0} {1}", "#{0} {1}", "#{0} {1}", "#{0} {1}", "#{0} {1}") \
|
||||
X(FORMAT_REGION, "#{0} {1}", "#{0} {1}", "#{0} {1}", "#{0} {1}", "#{0} {1}") \
|
||||
X(FORMAT_TRANSFORM, "Transform: {0}", "Transformar: {0}", "Трансформация: {0}", "变换: {0}", "변환: {0}") \
|
||||
@@ -216,6 +217,7 @@ namespace anm2ed
|
||||
X(LABEL_ANIMATIONS_MERGE_POPUP, "Merge Animations", "Combinar Animaciones", "Соединить анимации", "合并多个动画", "애니메이션 병합") \
|
||||
X(LABEL_SPRITESHEETS_PACK_POPUP, "Pack Spritesheet", "Empaquetar spritesheet", "Упаковать спрайт-лист", "打包图集", "스프라이트 시트 패킹") \
|
||||
X(LABEL_SPRITESHEETS_MERGE_POPUP, "Merge Spritesheets", "Combinar Spritesheets", "Объединить спрайт-листы", "合并图集", "스프라이트 시트 병합") \
|
||||
X(LABEL_PACK_PADDING, "Padding", "Relleno", "Отступ", "填充", "패딩") \
|
||||
X(LABEL_ANIMATIONS_WINDOW, "Animations###Animations", "Animaciones###Animations", "Анимации###Animations", "动画###Animations", "애니메이션###Animations") \
|
||||
X(LABEL_REGIONS_WINDOW, "Regions###Regions", "Regiones###Regions", "Регионы###Regions", "区域###Regions", "영역###Regions") \
|
||||
X(LABEL_ANIMATION_LENGTH, "Animation Length", "Duracion de Animacion", "Длина анимации", "动画时长", "애니메이션 길이") \
|
||||
@@ -246,6 +248,8 @@ namespace anm2ed
|
||||
X(LABEL_DOCUMENTS_OPEN_NEW, "Open New Document", "Abrir Nuevo Documento", "Открыть новый документ", "打开新文件", "새 파일로 열기") \
|
||||
X(LABEL_DOCUMENT_CLOSE, "Close Document", "Cerrar Documento", "Закрыть документ", "关闭文件", "파일 닫기") \
|
||||
X(LABEL_DOCUMENT_MODIFIED_PROMPT, "The document \"{0}\" has been modified.\nDo you want to save it?", "El Documento \"{0}\" ha sido modificado.\n¿Quieres Guardarlo?", "Документ \"{0}\" был изменен. \nХотите сохранить его?", "此文件\"{0}\"已被更改.\n要保存吗?", "\"{0}\" 파일이 수정되었습니다.\n저장하시겠습니까?") \
|
||||
X(LABEL_DOCUMENT_AND_SPRITESHEETS_MODIFIED_PROMPT, "The document \"{0}\" and its spritesheets have been modified.\nDo you want to save them?", "El Documento \"{0}\" y sus spritesheets han sido modificados.\n¿Quieres guardarlos?", "Документ \"{0}\" и его спрайт-листы были изменены.\nХотите сохранить их?", "此文件\"{0}\"及其图集已被更改。\n要保存吗?", "\"{0}\" 파일과 스프라이트 시트가 수정되었습니다.\n저장하시겠습니까?") \
|
||||
X(LABEL_SPRITESHEETS_MODIFIED_PROMPT, "Spritesheets in \"{0}\" have been modified.\nDo you want to save them?", "Los spritesheets en \"{0}\" han sido modificados.\n¿Quieres guardarlos?", "Спрайт-листы в \"{0}\" были изменены.\nХотите сохранить их?", "\"{0}\" 中的图集已被修改。\n要保存吗?", "\"{0}\"의 스프라이트 시트가 수정되었습니다.\n저장하시겠습니까?") \
|
||||
X(LABEL_END, "End", "Fin", "Конец", "结尾", "끝") \
|
||||
X(LABEL_EVENT, "Event", "Evento", "Событие", "事件", "이벤트") \
|
||||
X(LABEL_EVENTS_WINDOW, "Events###Events", "Eventos###Events", "События###Events", "事件###Events", "이벤트###Events") \
|
||||
@@ -424,8 +428,7 @@ namespace anm2ed
|
||||
X(SNAPSHOT_RENAME_ANIMATION, "Rename Animation", "Renombrar Animacion", "Переименовать анимацию", "重命名动画", "애니메이션 이름 바꾸기") \
|
||||
X(TEXT_SELECT_FRAME, "Select a frame first!", "¡Selecciona primero un frame!", "Сначала выберите кадр!", "请先选择帧!", "먼저 프레임을 선택하세요!") \
|
||||
X(TEXT_SELECT_FRAME_OR_REGION, "Select a frame or region first!", "¡Selecciona primero un frame o región!", "Сначала выберите кадр или регион!", "请先选择帧或区域!", "먼저 프레임 또는 영역을 선택하세요!") \
|
||||
X(TEXT_MERGE_SPRITESHEETS_DESCRIPTION, "Merge selected spritesheets into the first selected spritesheet.", "Combina los spritesheets seleccionados en el primer spritesheet seleccionado.", "Объединить выбранные спрайт-листы в первый выбранный спрайт-лист.", "将所选图集合并到第一个选中的图集中。", "선택된 스프라이트 시트를 첫 번째 선택된 스프라이트 시트로 병합합니다.") \
|
||||
X(TEXT_PACK_SPRITESHEET_DESCRIPTION, "Pack this spritesheet using its region rectangles and rebuild the texture from packed regions.", "Empaqueta este spritesheet usando sus rectángulos de región y reconstruye la textura con las regiones empaquetadas.", "Упаковать этот спрайт-лист, используя прямоугольники его регионов, и пересобрать текстуру из упакованных регионов.", "使用该图集的区域矩形进行打包,并用打包后的区域重建纹理。", "이 스프라이트 시트의 영역 사각형을 기준으로 패킹하고, 패킹된 영역으로 텍스처를 다시 만듭니다.") \
|
||||
X(TOOLTIP_MERGE_SPRITESHEETS, "Merge selected spritesheets into the first selected spritesheet.", "Combina los spritesheets seleccionados en el primer spritesheet seleccionado.", "Объединить выбранные спрайт-листы в первый выбранный спрайт-лист.", "将所选图集合并到第一个选中的图集中。", "선택된 스프라이트 시트를 첫 번째 선택된 스프라이트 시트로 병합합니다.") \
|
||||
X(TEXT_SELECT_SPRITESHEET, "Select a spritesheet first!", "¡Selecciona primero un spritesheet!", "Сначала выберите спрайт-лист!", "请先选择图集!", "먼저 스프라이트 시트를 선택하세요!") \
|
||||
X(TEXT_TOOL_ANIMATION_PREVIEW, "This tool can only be used in Animation Preview!", "¡Esta herramienta solo se puede usar en Vista previa de animación!", "Этот инструмент можно использовать только в \"Предпросмотре анимации\"!", "该工具只能在“动画预放”中使用!", "이 도구는 애니메이션 프리뷰에서만 사용할 수 있습니다!") \
|
||||
X(TEXT_TOOL_SPRITESHEET_EDITOR, "This tool can only be used in Spritesheet Editor!", "¡Esta herramienta solo se puede usar en el Editor de spritesheets!", "Этот инструмент можно использовать только в \"Редакторе спрайт-листов\"!", "该工具只能在“图集编辑器”中使用!", "이 도구는 스프라이트 시트 편집기에서만 사용할 수 있습니다!") \
|
||||
|
||||
@@ -155,6 +155,7 @@ namespace anm2ed
|
||||
X(MERGE_SPRITESHEETS_ORIGIN, mergeSpritesheetsOrigin, STRING_UNDEFINED, INT, anm2::APPEND_RIGHT) \
|
||||
X(MERGE_SPRITESHEETS_IS_MAKE_REGIONS, mergeSpritesheetsIsMakeRegions, STRING_UNDEFINED, BOOL, true) \
|
||||
X(MERGE_SPRITESHEETS_REGION_ORIGIN, mergeSpritesheetsRegionOrigin, STRING_UNDEFINED, INT, origin::TOP_LEFT) \
|
||||
X(PACK_PADDING, packPadding, STRING_UNDEFINED, INT, 1) \
|
||||
\
|
||||
X(BAKE_INTERVAL, bakeInterval, STRING_UNDEFINED, INT, 1) \
|
||||
X(BAKE_IS_ROUND_SCALE, bakeIsRoundScale, STRING_UNDEFINED, BOOL, true) \
|
||||
|
||||
Reference in New Issue
Block a user