slowly getting back...also, static linking

This commit is contained in:
2025-09-21 23:57:17 -04:00
parent 8039df667c
commit 7f07eaa128
20 changed files with 4579 additions and 631 deletions

7
.gitignore vendored
View File

@@ -4,8 +4,9 @@ release/
packed/ packed/
vcpkg_installed/ vcpkg_installed/
out/ out/
include/imgui/ external/
include/glm/ external/
include/tinyxml2 external/
external/
workshop/resources workshop/resources
.vs/ .vs/

7
.gitmodules vendored
View File

@@ -7,6 +7,7 @@
branch = docking branch = docking
[submodule "external/tinyxml2"] [submodule "external/tinyxml2"]
path = external/tinyxml2 path = external/tinyxml2
url = https://github.com/external/tinyxml2 url = https://github.com/leethomason/tinyxml2
[submodule "external/SDL"]
path = external/SDL
url = https://github.com/libsdl-org/SDL.git

View File

@@ -1,5 +1,8 @@
{ {
"C_Cpp.formatting": "clangFormat", "C_Cpp.formatting": "clangFormat",
"editor.formatOnSave": true, "editor.formatOnSave": true,
"clang-format.style": "file" "clang-format.style": "file",
"clangd.arguments": [
"--compile-commands-dir=build"
]
} }

View File

@@ -1,35 +1,39 @@
cmake_minimum_required(VERSION 3.15) cmake_minimum_required(VERSION 3.15)
project(anm2ed CXX)
# Optional: auto-pick up vcpkg toolchain on Windows
if(WIN32 AND DEFINED ENV{VCPKG_ROOT} AND NOT DEFINED CMAKE_TOOLCHAIN_FILE) if(WIN32 AND DEFINED ENV{VCPKG_ROOT} AND NOT DEFINED CMAKE_TOOLCHAIN_FILE)
set(CMAKE_TOOLCHAIN_FILE "$ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" set(CMAKE_TOOLCHAIN_FILE "$ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake"
CACHE STRING "Vcpkg toolchain file") CACHE STRING "Vcpkg toolchain file")
endif() endif()
project(anm2ed CXX)
find_package(SDL3 REQUIRED)
find_package(OpenGL REQUIRED) find_package(OpenGL REQUIRED)
set(GLAD_SRC # Export compile_commands.json (for clangd, etc.)
${CMAKE_CURRENT_SOURCE_DIR}/include/glad/glad.cpp set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
if(CMAKE_EXPORT_COMPILE_COMMANDS)
execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink
${CMAKE_BINARY_DIR}/compile_commands.json
${CMAKE_SOURCE_DIR}/compile_commands.json
) )
endif()
set(GLAD_SRC ${CMAKE_CURRENT_SOURCE_DIR}/include/glad/glad.cpp)
set(IMGUI_SRC set(IMGUI_SRC
${CMAKE_CURRENT_SOURCE_DIR}/external/imgui/imgui.cpp external/imgui/imgui.cpp
${CMAKE_CURRENT_SOURCE_DIR}/external/imgui/imgui_draw.cpp external/imgui/imgui_draw.cpp
${CMAKE_CURRENT_SOURCE_DIR}/external/imgui/imgui_widgets.cpp external/imgui/imgui_widgets.cpp
${CMAKE_CURRENT_SOURCE_DIR}/external/imgui/imgui_tables.cpp external/imgui/imgui_tables.cpp
${CMAKE_CURRENT_SOURCE_DIR}/external/imgui/backends/imgui_impl_sdl3.cpp external/imgui/backends/imgui_impl_sdl3.cpp
${CMAKE_CURRENT_SOURCE_DIR}/external/imgui/backends/imgui_impl_opengl3.cpp external/imgui/backends/imgui_impl_opengl3.cpp
) )
set(TINYXML2_SRC set(TINYXML2_SRC external/tinyxml2/tinyxml2.cpp)
${CMAKE_CURRENT_SOURCE_DIR}/external/tinyxml2/tinyxml2.cpp
)
file(GLOB PROJECT_SRC file(GLOB PROJECT_SRC CONFIGURE_DEPENDS
${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp src/*.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/*.h src/*.h
) )
add_executable(${PROJECT_NAME} add_executable(${PROJECT_NAME}
@@ -39,39 +43,48 @@ add_executable(${PROJECT_NAME}
${PROJECT_SRC} ${PROJECT_SRC}
) )
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
set(SDL_SHARED OFF CACHE BOOL "" FORCE)
set(SDL_STATIC ON CACHE BOOL "" FORCE)
add_subdirectory(external/SDL EXCLUDE_FROM_ALL)
if(WIN32) if(WIN32)
enable_language(RC) enable_language(RC)
target_sources(${PROJECT_NAME} PRIVATE Icon.rc) target_sources(${PROJECT_NAME} PRIVATE Icon.rc)
set_target_properties(${PROJECT_NAME} PROPERTIES WIN32_EXECUTABLE TRUE) set_target_properties(${PROJECT_NAME} PROPERTIES WIN32_EXECUTABLE TRUE)
endif()
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_23) set_property(TARGET ${PROJECT_NAME} PROPERTY
MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
target_include_directories(${PROJECT_NAME} PRIVATE target_compile_options(${PROJECT_NAME} PRIVATE /EHsc)
${CMAKE_CURRENT_SOURCE_DIR}/include
${CMAKE_CURRENT_SOURCE_DIR}/include/glad
${CMAKE_CURRENT_SOURCE_DIR}/external
${CMAKE_CURRENT_SOURCE_DIR}/external/imgui
${CMAKE_CURRENT_SOURCE_DIR}/external/glm
${CMAKE_CURRENT_SOURCE_DIR}/external/tinyxml2
${CMAKE_CURRENT_SOURCE_DIR}/src
)
if(MSVC)
target_compile_options(${PROJECT_NAME} PRIVATE /std:c++latest /EHsc)
target_link_options(${PROJECT_NAME} PRIVATE /STACK:0xffffff) target_link_options(${PROJECT_NAME} PRIVATE /STACK:0xffffff)
else() else()
target_compile_options(${PROJECT_NAME} PRIVATE -O2 -std=c++23 -Wall -Wextra -pedantic -fmax-errors=1) target_compile_options(${PROJECT_NAME} PRIVATE
-O2 -Wall -Wextra -pedantic -fmax-errors=1
)
if(CMAKE_BUILD_TYPE STREQUAL "Debug") if(CMAKE_BUILD_TYPE STREQUAL "Debug")
target_compile_options(${PROJECT_NAME} PRIVATE -DDEBUG -g) target_compile_definitions(${PROJECT_NAME} PRIVATE DEBUG)
else() target_compile_options(${PROJECT_NAME} PRIVATE -g)
set(CMAKE_BUILD_TYPE "Release")
endif() endif()
target_link_libraries(${PROJECT_NAME} PRIVATE m) target_link_libraries(${PROJECT_NAME} PRIVATE m)
endif() endif()
target_link_libraries(${PROJECT_NAME} PRIVATE OpenGL::GL SDL3::SDL3) target_compile_definitions(${PROJECT_NAME} PRIVATE IMGUI_DISABLE_OBSOLETE_FUNCTIONS IMGUI_DEBUG_PARANOID IMGUI_ENABLE_DOCKING)
message("System: ${CMAKE_SYSTEM_NAME}") target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_23)
message("Project: ${PROJECT_NAME}")
message("Build: ${CMAKE_BUILD_TYPE}") target_include_directories(${PROJECT_NAME} PRIVATE
external
external/imgui
external/glm
external/tinyxml2
include
include/glad
src
)
target_link_libraries(${PROJECT_NAME} PRIVATE OpenGL::GL SDL3::SDL3-static)
message(STATUS "System: ${CMAKE_SYSTEM_NAME}")
message(STATUS "Project: ${PROJECT_NAME}")
message(STATUS "Build: ${CMAKE_BUILD_TYPE}")

View File

@@ -1 +1 @@
build/compile_commands.json /home/anon/sda/Personal/Repos/anm2ed/build/compile_commands.json

View File

@@ -1,5 +1,6 @@
#pragma once #pragma once
#include <set>
#define GLAD_GL_IMPLEMENTATION #define GLAD_GL_IMPLEMENTATION
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
#include <glad/glad.h> #include <glad/glad.h>
@@ -10,7 +11,6 @@
#include <algorithm> #include <algorithm>
#include <chrono> #include <chrono>
#include <climits>
#include <cmath> #include <cmath>
#include <cstring> #include <cstring>
#include <filesystem> #include <filesystem>
@@ -241,6 +241,35 @@ template <typename T> static inline void map_insert_shift(std::map<int, T>& map,
map[insertID] = value; map[insertID] = value;
} }
template <typename Map> auto map_keys_to_set(const Map& m) {
using Key = typename Map::key_type;
std::unordered_set<Key> s;
s.reserve(m.size());
for (const auto& [key, _] : m) {
s.insert(key);
}
return s;
}
template <typename Set> Set set_symmetric_difference(const Set& a, const Set& b) {
Set result;
result.reserve(a.size() + b.size());
for (const auto& x : a) {
if (!b.contains(x)) {
result.insert(x);
}
}
for (const auto& x : b) {
if (!a.contains(x)) {
result.insert(x);
}
}
return result;
}
template <typename T> static inline T* vector_find(std::vector<T>& v, const T& value) { template <typename T> static inline T* vector_find(std::vector<T>& v, const T& value) {
auto it = std::find(v.begin(), v.end(), value); auto it = std::find(v.begin(), v.end(), value);
return (it != v.end()) ? &(*it) : nullptr; return (it != v.end()) ? &(*it) : nullptr;
@@ -257,11 +286,49 @@ template <typename T> static inline void vector_value_swap(std::vector<T>& v, co
} }
} }
static inline void unordered_set_id_toggle(std::unordered_set<int>& set, int id) { template <typename T> static inline T* vector_get(std::vector<T>& v, size_t index) {
if (auto it = set.find(id); it != set.end()) if (index < v.size())
return &v[index];
return nullptr;
}
template <typename T> static inline void vector_move(std::vector<T>& v, size_t from, size_t to) {
if (from >= v.size() || to >= v.size() || from == to)
return;
if (from < to) {
std::rotate(v.begin() + from, v.begin() + from + 1, v.begin() + to + 1);
} else {
std::rotate(v.begin() + to, v.begin() + from, v.begin() + from + 1);
}
}
template <typename T> void vector_erase_indices(std::vector<T>& v, const std::set<int>& indices) {
size_t i = 0;
v.erase(std::remove_if(v.begin(), v.end(), [&](const T&) { return indices.count(i++) > 0; }), v.end());
}
template <typename T> static inline void set_key_toggle(std::set<T>& set, T key) {
if (auto it = set.find(key); it != set.end())
set.erase(it); set.erase(it);
else else
set.insert(id); set.insert(key);
}
template <typename T> static inline void set_list(std::set<T>& s, const T& key, bool isCtrl, bool isShift, T* lastSelected) {
if (isShift && lastSelected) {
s.clear();
T a = std::min(*lastSelected, key);
T b = std::max(*lastSelected, key);
for (T i = a; i <= b; i++)
s.insert(i);
} else if (isCtrl) {
set_key_toggle(s, key);
*lastSelected = key;
} else {
s = {key};
*lastSelected = key;
}
} }
static inline mat4 quad_model_get(vec2 size = {}, vec2 position = {}, vec2 pivot = {}, vec2 scale = vec2(1.0f), float rotation = {}) { static inline mat4 quad_model_get(vec2 size = {}, vec2 position = {}, vec2 pivot = {}, vec2 scale = vec2(1.0f), float rotation = {}) {

File diff suppressed because it is too large Load Diff

View File

@@ -17,7 +17,8 @@ static void _anm2_frame_serialize(Anm2Frame* frame, Anm2Type type, XMLDocument*
XMLDocument localDocument; XMLDocument localDocument;
XMLDocument* useDocument = document ? document : &localDocument; XMLDocument* useDocument = document ? document : &localDocument;
XMLElement* element = useDocument->NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_FRAME]); XMLElement* element = type == ANM2_TRIGGER ? useDocument->NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_TRIGGER])
: useDocument->NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_FRAME]);
if (type == ANM2_TRIGGER) { if (type == ANM2_TRIGGER) {
element->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_EVENT_ID], frame->eventID); // EventID element->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_EVENT_ID], frame->eventID); // EventID
@@ -118,8 +119,8 @@ static void _anm2_animation_serialize(Anm2Animation* animation, XMLDocument* doc
// Triggers // Triggers
XMLElement* triggersElement = useDocument->NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_TRIGGERS]); XMLElement* triggersElement = useDocument->NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_TRIGGERS]);
for (auto& frame : animation->triggers.frames) for (auto& trigger : animation->triggers.frames)
_anm2_frame_serialize(&frame, ANM2_TRIGGER, useDocument, triggersElement); _anm2_frame_serialize(&trigger, ANM2_TRIGGER, useDocument, triggersElement);
element->InsertEndChild(triggersElement); element->InsertEndChild(triggersElement);
@@ -220,11 +221,9 @@ bool anm2_serialize(Anm2* self, const std::string& path) {
// Animations // Animations
XMLElement* animationsElement = document.NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_ANIMATIONS]); XMLElement* animationsElement = document.NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_ANIMATIONS]);
if (self->defaultAnimationID != ID_NONE) animationsElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_DEFAULT_ANIMATION], self->defaultAnimation.c_str()); // DefaultAnimation
animationsElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_DEFAULT_ANIMATION],
self->animations[self->defaultAnimationID].name.c_str()); // DefaultAnimation
for (auto& [id, animation] : self->animations) for (auto& animation : self->animations)
_anm2_animation_serialize(&animation, &document, animationsElement); _anm2_animation_serialize(&animation, &document, animationsElement);
animatedActorElement->InsertEndChild(animationsElement); animatedActorElement->InsertEndChild(animationsElement);
@@ -426,13 +425,9 @@ bool anm2_deserialize(Anm2* self, const std::string& path, bool isTextures) {
anm2_new(self); anm2_new(self);
self->path = path; self->path = path;
std::string defaultAnimation{};
int id{}; int id{};
// Save old working directory and then use anm2's path as directory WorkingDirectory workingDirectory(path);
// (used for loading textures from anm2 correctly which are relative)
std::filesystem::path workingPath = std::filesystem::current_path();
working_directory_from_file_set(path);
const XMLElement* root = document.RootElement(); const XMLElement* root = document.RootElement();
@@ -506,13 +501,13 @@ bool anm2_deserialize(Anm2* self, const std::string& path, bool isTextures) {
switch (ANM2_ATTRIBUTE_STRING_TO_ENUM(attribute->Name())) { switch (ANM2_ATTRIBUTE_STRING_TO_ENUM(attribute->Name())) {
case ANM2_ATTRIBUTE_NAME: case ANM2_ATTRIBUTE_NAME:
addLayer.name = std::string(attribute->Value()); addLayer.name = std::string(attribute->Value());
break; // Name break;
case ANM2_ATTRIBUTE_ID: case ANM2_ATTRIBUTE_ID:
id = std::atoi(attribute->Value()); id = std::atoi(attribute->Value());
break; // ID break;
case ANM2_ATTRIBUTE_SPRITESHEET_ID: case ANM2_ATTRIBUTE_SPRITESHEET_ID:
addLayer.spritesheetID = std::atoi(attribute->Value()); addLayer.spritesheetID = std::atoi(attribute->Value());
break; // ID break;
default: default:
break; break;
} }
@@ -575,7 +570,7 @@ bool anm2_deserialize(Anm2* self, const std::string& path, bool isTextures) {
for (const XMLAttribute* attribute = animations->FirstAttribute(); attribute; attribute = attribute->Next()) { for (const XMLAttribute* attribute = animations->FirstAttribute(); attribute; attribute = attribute->Next()) {
switch (ANM2_ATTRIBUTE_STRING_TO_ENUM(attribute->Name())) { switch (ANM2_ATTRIBUTE_STRING_TO_ENUM(attribute->Name())) {
case ANM2_ATTRIBUTE_DEFAULT_ANIMATION: case ANM2_ATTRIBUTE_DEFAULT_ANIMATION:
defaultAnimation = std::string(attribute->Value()); self->defaultAnimation = std::string(attribute->Value());
break; // DefaultAnimation break; // DefaultAnimation
default: default:
break; break;
@@ -585,18 +580,12 @@ bool anm2_deserialize(Anm2* self, const std::string& path, bool isTextures) {
// Animation // Animation
for (const XMLElement* animation = animations->FirstChildElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_ANIMATION]); animation; for (const XMLElement* animation = animations->FirstChildElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_ANIMATION]); animation;
animation = animation->NextSiblingElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_ANIMATION])) animation = animation->NextSiblingElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_ANIMATION]))
_anm2_animation_deserialize(&self->animations[map_next_id_get(self->animations)], animation); _anm2_animation_deserialize(&self->animations.emplace_back(Anm2Animation()), animation);
} }
for (auto& [id, animation] : self->animations)
if (animation.name == defaultAnimation)
self->defaultAnimationID = id;
if (isTextures) if (isTextures)
anm2_spritesheet_texture_pixels_download(self); anm2_spritesheet_texture_pixels_download(self);
std::filesystem::current_path(workingPath);
log_info(std::format(ANM2_READ_INFO, path)); log_info(std::format(ANM2_READ_INFO, path));
return true; return true;
@@ -625,7 +614,7 @@ int anm2_layer_add(Anm2* self) {
void anm2_layer_remove(Anm2* self, int id) { void anm2_layer_remove(Anm2* self, int id) {
self->layers.erase(id); self->layers.erase(id);
for (auto& [_, animation] : self->animations) for (auto& animation : self->animations)
anm2_animation_layer_animation_remove(&animation, id); anm2_animation_layer_animation_remove(&animation, id);
} }
@@ -641,36 +630,32 @@ void anm2_null_remove(Anm2* self, int id) {
self->nulls.erase(id); self->nulls.erase(id);
for (auto& [_, animation] : self->animations) for (auto& animation : self->animations)
anm2_animation_null_animation_remove(&animation, id); anm2_animation_null_animation_remove(&animation, id);
} }
int anm2_animation_add(Anm2* self, Anm2Animation* animation, int id) { int anm2_animation_add(Anm2* self, Anm2Animation* animation, int index) {
int addID = map_next_id_get(self->animations);
Anm2Animation localAnimation; Anm2Animation addAnimation = animation ? *animation : Anm2Animation();
Anm2Animation* addAnimation = animation ? animation : &localAnimation;
if (!animation) if (!animation)
addAnimation->rootAnimation.frames.push_back(Anm2Frame{}); addAnimation.rootAnimation.frames.push_back(Anm2Frame{});
if (id != ID_NONE) { int addIndex = index != INDEX_NONE ? index : (int)self->animations.size() - 1;
map_insert_shift(self->animations, id, *addAnimation);
return id + 1;
} else
self->animations[addID] = *addAnimation;
return addID; self->animations.insert(self->animations.begin() + addIndex, addAnimation);
return addIndex;
} }
void anm2_animation_remove(Anm2* self, int id) { self->animations.erase(id); } void anm2_animations_remove(Anm2* self, const std::set<int> indices) { vector_erase_indices(self->animations, indices); };
void anm2_new(Anm2* self) { void anm2_new(Anm2* self) {
*self = Anm2{}; *self = Anm2{};
_anm2_created_on_set(self); _anm2_created_on_set(self);
} }
Anm2Animation* anm2_animation_from_reference(Anm2* self, Anm2Reference reference) { return map_find(self->animations, reference.animationID); } Anm2Animation* anm2_animation_from_reference(Anm2* self, Anm2Reference reference) { return vector_get(self->animations, reference.animationIndex); }
Anm2Item* anm2_item_from_reference(Anm2* self, Anm2Reference reference) { Anm2Item* anm2_item_from_reference(Anm2* self, Anm2Reference reference) {
if (reference.itemType == ANM2_NONE) if (reference.itemType == ANM2_NONE)
@@ -928,8 +913,8 @@ void anm2_item_frame_set(Anm2* self, Anm2Reference reference, const Anm2FrameCha
} }
} }
void anm2_animation_merge(Anm2* self, int animationID, const std::vector<int>& mergeIDs, Anm2MergeType type) { void anm2_animation_merge(Anm2* self, int animationIndex, std::set<int> mergeIndices, Anm2MergeType type) {
Anm2Animation newAnimation = self->animations[animationID]; Anm2Animation newAnimation = self->animations[animationIndex];
auto merge_item = [&](Anm2Item& destinationItem, const Anm2Item& sourceItem) { auto merge_item = [&](Anm2Item& destinationItem, const Anm2Item& sourceItem) {
switch (type) { switch (type) {
@@ -950,11 +935,11 @@ void anm2_animation_merge(Anm2* self, int animationID, const std::vector<int>& m
} }
}; };
for (auto mergeID : mergeIDs) { for (auto mergeIndices : mergeIndices) {
if (animationID == mergeID) if (animationIndex == mergeIndices)
continue; continue;
const Anm2Animation& mergeAnimation = self->animations[mergeID]; const Anm2Animation& mergeAnimation = self->animations[mergeIndices];
merge_item(newAnimation.rootAnimation, mergeAnimation.rootAnimation); merge_item(newAnimation.rootAnimation, mergeAnimation.rootAnimation);
@@ -967,9 +952,9 @@ void anm2_animation_merge(Anm2* self, int animationID, const std::vector<int>& m
merge_item(newAnimation.triggers, mergeAnimation.triggers); merge_item(newAnimation.triggers, mergeAnimation.triggers);
} }
self->animations[animationID] = newAnimation; self->animations[animationIndex] = newAnimation;
anm2_animation_length_set(&self->animations[animationID]); anm2_animation_length_set(&self->animations[animationIndex]);
} }
void anm2_frame_bake(Anm2* self, Anm2Reference reference, int interval, bool isRoundScale, bool isRoundRotation) { void anm2_frame_bake(Anm2* self, Anm2Reference reference, int interval, bool isRoundScale, bool isRoundRotation) {
@@ -981,7 +966,7 @@ void anm2_frame_bake(Anm2* self, Anm2Reference reference, int interval, bool isR
if (!frame) if (!frame)
return; return;
Anm2Reference referenceNext = {reference.animationID, reference.itemType, reference.itemID, reference.frameIndex + 1}; Anm2Reference referenceNext = {reference.animationIndex, reference.itemType, reference.itemID, reference.frameIndex + 1};
Anm2Frame* frameNext = anm2_frame_from_reference(self, referenceNext); Anm2Frame* frameNext = anm2_frame_from_reference(self, referenceNext);
if (!frameNext) if (!frameNext)
frameNext = frame; frameNext = frame;
@@ -1028,7 +1013,7 @@ void anm2_scale(Anm2* self, float scale) {
frame.pivot = vec2((int)(frame.pivot.x * scale), (int)(frame.pivot.y * scale)); frame.pivot = vec2((int)(frame.pivot.x * scale), (int)(frame.pivot.y * scale));
}; };
for (auto& [_, animation] : self->animations) { for (auto& animation : self->animations) {
for (auto& frame : animation.rootAnimation.frames) for (auto& frame : animation.rootAnimation.frames)
frame_scale(frame); frame_scale(frame);
@@ -1112,7 +1097,7 @@ vec4 anm2_animation_rect_get(Anm2* self, Anm2Reference reference, bool isRootTra
for (float t = 0.0f; t <= animation->frameNum; t += 1.0f) { for (float t = 0.0f; t <= animation->frameNum; t += 1.0f) {
for (const auto& [id, _] : animation->layerAnimations) { for (const auto& [id, _] : animation->layerAnimations) {
anm2_frame_from_time(self, &frame, {reference.animationID, ANM2_LAYER, id}, t); anm2_frame_from_time(self, &frame, {reference.animationIndex, ANM2_LAYER, id}, t);
if (!frame.isVisible) if (!frame.isVisible)
continue; continue;
if (frame.size.x <= 0 || frame.size.y <= 0) if (frame.size.x <= 0 || frame.size.y <= 0)
@@ -1120,7 +1105,7 @@ vec4 anm2_animation_rect_get(Anm2* self, Anm2Reference reference, bool isRootTra
mat4 rootModel(1.0f); mat4 rootModel(1.0f);
if (isRootTransform) { if (isRootTransform) {
anm2_frame_from_time(self, &root, {reference.animationID, ANM2_ROOT}, t); anm2_frame_from_time(self, &root, {reference.animationIndex, ANM2_ROOT}, t);
rootModel = quad_model_parent_get(root.position, root.pivot, PERCENT_TO_UNIT(root.scale), root.rotation); rootModel = quad_model_parent_get(root.position, root.pivot, PERCENT_TO_UNIT(root.scale), root.rotation);
} }
@@ -1149,7 +1134,7 @@ void anm2_animation_serialize_to_string(Anm2Animation* animation, std::string* s
void anm2_frame_serialize_to_string(Anm2Frame* frame, Anm2Type type, std::string* string) { _anm2_frame_serialize(frame, type, nullptr, nullptr, string); } void anm2_frame_serialize_to_string(Anm2Frame* frame, Anm2Type type, std::string* string) { _anm2_frame_serialize(frame, type, nullptr, nullptr, string); }
bool anm2_animation_deserialize_from_xml(Anm2Animation* animation, const std::string& xml) { bool anm2_animations_deserialize_from_xml(std::vector<Anm2Animation>& animations, const std::string& xml) {
XMLDocument document; XMLDocument document;
auto animation_deserialize_error = [&]() { auto animation_deserialize_error = [&]() {
@@ -1160,13 +1145,12 @@ bool anm2_animation_deserialize_from_xml(Anm2Animation* animation, const std::st
if (document.Parse(xml.c_str()) != XML_SUCCESS) if (document.Parse(xml.c_str()) != XML_SUCCESS)
return animation_deserialize_error(); return animation_deserialize_error();
const XMLElement* element = document.RootElement(); for (const XMLElement* element = document.RootElement(); element; element = element->NextSiblingElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_ANIMATION])) {
if (!element) if (std::string(document.RootElement()->Name()) != std::string(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_ANIMATION]))
return animation_deserialize_error();
if (std::string(element->Name()) != std::string(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_ANIMATION]))
return animation_deserialize_error(); return animation_deserialize_error();
_anm2_animation_deserialize(&animations.emplace_back(Anm2Animation()), element);
}
_anm2_animation_deserialize(animation, element);
return true; return true;
} }

View File

@@ -23,6 +23,7 @@
#define ANM2_WRITE_INFO "Wrote anm2 to file: {}" #define ANM2_WRITE_INFO "Wrote anm2 to file: {}"
#define ANM2_CREATED_ON_FORMAT "%d-%B-%Y %I:%M:%S %p" #define ANM2_CREATED_ON_FORMAT "%d-%B-%Y %I:%M:%S %p"
#define ANM2_ANIMATION_DEFAULT "New Animation"
#define ANM2_EXTENSION "anm2" #define ANM2_EXTENSION "anm2"
#define ANM2_SPRITESHEET_EXTENSION "png" #define ANM2_SPRITESHEET_EXTENSION "png"
@@ -185,7 +186,7 @@ struct Anm2Item {
struct Anm2Animation { struct Anm2Animation {
int frameNum = ANM2_FRAME_NUM_MIN; int frameNum = ANM2_FRAME_NUM_MIN;
std::string name = "New Animation"; std::string name = ANM2_ANIMATION_DEFAULT;
bool isLoop = true; bool isLoop = true;
Anm2Item rootAnimation; Anm2Item rootAnimation;
std::unordered_map<int, Anm2Item> layerAnimations; std::unordered_map<int, Anm2Item> layerAnimations;
@@ -200,12 +201,12 @@ struct Anm2 {
std::string path{}; std::string path{};
std::string createdBy = "robot"; std::string createdBy = "robot";
std::string createdOn{}; std::string createdOn{};
std::string defaultAnimation = ANM2_ANIMATION_DEFAULT;
std::map<int, Anm2Spritesheet> spritesheets; std::map<int, Anm2Spritesheet> spritesheets;
std::map<int, Anm2Layer> layers; std::map<int, Anm2Layer> layers;
std::map<int, Anm2Null> nulls; std::map<int, Anm2Null> nulls;
std::map<int, Anm2Event> events; std::map<int, Anm2Event> events;
std::map<int, Anm2Animation> animations; std::vector<Anm2Animation> animations;
int defaultAnimationID = ID_NONE;
int fps = ANM2_FPS_DEFAULT; int fps = ANM2_FPS_DEFAULT;
int version{}; int version{};
@@ -213,7 +214,7 @@ struct Anm2 {
}; };
struct Anm2Reference { struct Anm2Reference {
int animationID = ID_NONE; int animationIndex = ID_NONE;
Anm2Type itemType = ANM2_NONE; Anm2Type itemType = ANM2_NONE;
int itemID = ID_NONE; int itemID = ID_NONE;
int frameIndex = INDEX_NONE; int frameIndex = INDEX_NONE;
@@ -231,11 +232,11 @@ Anm2Animation* anm2_animation_from_reference(Anm2* self, Anm2Reference reference
Anm2Frame* anm2_frame_add(Anm2* self, Anm2Frame* frame, Anm2Reference reference); Anm2Frame* anm2_frame_add(Anm2* self, Anm2Frame* frame, Anm2Reference reference);
Anm2Frame* anm2_frame_from_reference(Anm2* self, Anm2Reference reference); Anm2Frame* anm2_frame_from_reference(Anm2* self, Anm2Reference reference);
Anm2Item* anm2_item_from_reference(Anm2* self, Anm2Reference reference); Anm2Item* anm2_item_from_reference(Anm2* self, Anm2Reference reference);
bool anm2_animation_deserialize_from_xml(Anm2Animation* animation, const std::string& xml); bool anm2_animations_deserialize_from_xml(std::vector<Anm2Animation>& animations, const std::string& xml);
bool anm2_deserialize(Anm2* self, const std::string& path, bool isTextures = true); bool anm2_deserialize(Anm2* self, const std::string& path, bool isTextures = true);
bool anm2_frame_deserialize_from_xml(Anm2Frame* frame, const std::string& xml); bool anm2_frame_deserialize_from_xml(Anm2Frame* frame, const std::string& xml);
bool anm2_serialize(Anm2* self, const std::string& path); bool anm2_serialize(Anm2* self, const std::string& path);
int anm2_animation_add(Anm2* self, Anm2Animation* animation = nullptr, int id = ID_NONE); int anm2_animation_add(Anm2* self, Anm2Animation* animation = nullptr, int index = INDEX_NONE);
int anm2_animation_length_get(Anm2Animation* self); int anm2_animation_length_get(Anm2Animation* self);
int anm2_frame_index_from_time(Anm2* self, Anm2Reference reference, float time); int anm2_frame_index_from_time(Anm2* self, Anm2Reference reference, float time);
int anm2_layer_add(Anm2* self); int anm2_layer_add(Anm2* self);
@@ -244,10 +245,10 @@ vec4 anm2_animation_rect_get(Anm2* anm2, Anm2Reference reference, bool isRootTra
void anm2_animation_layer_animation_add(Anm2Animation* animation, int id); void anm2_animation_layer_animation_add(Anm2Animation* animation, int id);
void anm2_animation_layer_animation_remove(Anm2Animation* animation, int id); void anm2_animation_layer_animation_remove(Anm2Animation* animation, int id);
void anm2_animation_length_set(Anm2Animation* self); void anm2_animation_length_set(Anm2Animation* self);
void anm2_animation_merge(Anm2* self, int animationID, const std::vector<int>& mergeIDs, Anm2MergeType type); void anm2_animation_merge(Anm2* self, int animationID, std::set<int> mergeIndices, Anm2MergeType type);
void anm2_animation_null_animation_add(Anm2Animation* animation, int id); void anm2_animation_null_animation_add(Anm2Animation* animation, int id);
void anm2_animation_null_animation_remove(Anm2Animation* animation, int id); void anm2_animation_null_animation_remove(Anm2Animation* animation, int id);
void anm2_animation_remove(Anm2* self, int id); void anm2_animations_remove(Anm2* self, const std::set<int> indices);
void anm2_animation_serialize_to_string(Anm2Animation* animation, std::string* string); void anm2_animation_serialize_to_string(Anm2Animation* animation, std::string* string);
void anm2_frame_bake(Anm2* self, Anm2Reference reference, int interval, bool isRoundScale, bool isRoundRotation); void anm2_frame_bake(Anm2* self, Anm2Reference reference, int interval, bool isRoundScale, bool isRoundRotation);
void anm2_frame_from_time(Anm2* self, Anm2Frame* frame, Anm2Reference reference, float time); void anm2_frame_from_time(Anm2* self, Anm2Frame* frame, Anm2Reference reference, float time);

View File

@@ -10,27 +10,27 @@ void clipboard_copy(Clipboard* self) {
switch (self->type) { switch (self->type) {
case CLIPBOARD_FRAME: { case CLIPBOARD_FRAME: {
Anm2Reference* reference = std::get_if<Anm2Reference>(&self->location); if (Anm2Reference* reference = std::get_if<Anm2Reference>(&self->destination)) {
if (!reference) if (Anm2Frame* frame = anm2_frame_from_reference(self->anm2, *reference)) {
break;
Anm2Frame* frame = anm2_frame_from_reference(self->anm2, *reference);
if (!frame)
break;
anm2_frame_serialize_to_string(frame, reference->itemType, &clipboardText); anm2_frame_serialize_to_string(frame, reference->itemType, &clipboardText);
clipboard_text_set(); clipboard_text_set();
}
}
break; break;
} }
case CLIPBOARD_ANIMATION: { case CLIPBOARD_ANIMATION: {
int* id = std::get_if<int>(&self->location); if (std::set<int>* set = std::get_if<std::set<int>>(&self->source)) {
if (!id) for (auto& i : *set) {
break; if (Anm2Animation* animation = anm2_animation_from_reference(self->anm2, {i})) {
Anm2Animation* animation = map_find(self->anm2->animations, *id); std::string animationText{};
if (!animation) anm2_animation_serialize_to_string(animation, &animationText);
break; clipboardText += animationText;
anm2_animation_serialize_to_string(animation, &clipboardText); }
}
clipboard_text_set(); clipboard_text_set();
}
break; break;
} break; }
default: default:
break; break;
} }
@@ -41,17 +41,13 @@ void clipboard_cut(Clipboard* self) {
switch (self->type) { switch (self->type) {
case CLIPBOARD_FRAME: { case CLIPBOARD_FRAME: {
Anm2Reference* reference = std::get_if<Anm2Reference>(&self->location); if (Anm2Reference* reference = std::get_if<Anm2Reference>(&self->destination))
if (!reference)
break;
anm2_frame_remove(self->anm2, *reference); anm2_frame_remove(self->anm2, *reference);
break; break;
} }
case CLIPBOARD_ANIMATION: { case CLIPBOARD_ANIMATION: {
int* id = std::get_if<int>(&self->location); if (std::set<int>* set = std::get_if<std::set<int>>(&self->source))
if (!id) anm2_animations_remove(self->anm2, *set);
break;
anm2_animation_remove(self->anm2, *id);
break; break;
} }
default: default:
@@ -69,25 +65,25 @@ bool clipboard_paste(Clipboard* self) {
switch (self->type) { switch (self->type) {
case CLIPBOARD_FRAME: { case CLIPBOARD_FRAME: {
Anm2Reference* reference = std::get_if<Anm2Reference>(&self->location); if (Anm2Reference* reference = std::get_if<Anm2Reference>(&self->destination)) {
if (!reference)
break;
Anm2Frame frame; Anm2Frame frame;
if (anm2_frame_deserialize_from_xml(&frame, clipboard_string())) if (!anm2_frame_deserialize_from_xml(&frame, clipboard_string()))
anm2_frame_add(self->anm2, &frame, *reference);
else
return false; return false;
anm2_frame_add(self->anm2, &frame, *reference);
}
break; break;
} }
case CLIPBOARD_ANIMATION: { case CLIPBOARD_ANIMATION: {
int* id = std::get_if<int>(&self->location); if (int* index = std::get_if<int>(&self->destination)) {
if (!id) std::vector<Anm2Animation> clipboardAnimations;
break; if (!anm2_animations_deserialize_from_xml(clipboardAnimations, clipboard_string()))
Anm2Animation animation;
if (anm2_animation_deserialize_from_xml(&animation, clipboard_string()))
anm2_animation_add(self->anm2, &animation, *id);
else
return false; return false;
int useIndex = std::clamp(*index + 1, 0, (int)self->anm2->animations.size());
for (auto& animation : clipboardAnimations)
anm2_animation_add(self->anm2, &animation, useIndex++);
}
break; break;
} }
default: default:
@@ -98,5 +94,4 @@ bool clipboard_paste(Clipboard* self) {
} }
void clipboard_init(Clipboard* self, Anm2* anm2) { self->anm2 = anm2; } void clipboard_init(Clipboard* self, Anm2* anm2) { self->anm2 = anm2; }
bool clipboard_is_value(void) { return SDL_HasClipboardText(); } bool clipboard_is_value(void) { return SDL_HasClipboardText(); }

View File

@@ -1,17 +1,19 @@
#pragma once #pragma once
#include "anm2.h" #include "anm2.h"
#include <variant>
#define CLIPBOARD_TEXT_SET_WARNING "Unable to set clipboard text! ({})" #define CLIPBOARD_TEXT_SET_WARNING "Unable to set clipboard text! ({})"
enum ClipboardType { CLIPBOARD_NONE, CLIPBOARD_FRAME, CLIPBOARD_ANIMATION }; enum ClipboardType { CLIPBOARD_NONE, CLIPBOARD_FRAME, CLIPBOARD_ANIMATION };
using ClipboardLocation = std::variant<std::monostate, Anm2Reference, int>; using ClipboardValue = std::variant<std::monostate, Anm2Reference, std::set<int>, int>;
struct Clipboard { struct Clipboard {
Anm2* anm2 = nullptr; Anm2* anm2 = nullptr;
ClipboardType type; ClipboardType type;
ClipboardLocation location; ClipboardValue source;
ClipboardValue destination;
}; };
bool clipboard_is_value(void); bool clipboard_is_value(void);

File diff suppressed because it is too large Load Diff

View File

@@ -12,11 +12,9 @@
#include "snapshots.h" #include "snapshots.h"
#include "tool.h" #include "tool.h"
#include "window.h" #include "window.h"
#include <SDL3/SDL_mouse.h>
#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS
#define IMGUI_DEBUG_PARANOID
#define IMGUI_IMPL_OPENGL_LOADER_CUSTOM #define IMGUI_IMPL_OPENGL_LOADER_CUSTOM
#define IMGUI_ENABLE_DOCKING
#define IM_VEC2_CLASS_EXTRA \ #define IM_VEC2_CLASS_EXTRA \
inline bool operator==(const ImVec2& rhs) const { return x == rhs.x && y == rhs.y; } \ inline bool operator==(const ImVec2& rhs) const { return x == rhs.x && y == rhs.y; } \
inline bool operator!=(const ImVec2& rhs) const { return !(*this == rhs); } \ inline bool operator!=(const ImVec2& rhs) const { return !(*this == rhs); } \
@@ -83,7 +81,6 @@
#define IMGUI_CHORD_NONE (ImGuiMod_None) #define IMGUI_CHORD_NONE (ImGuiMod_None)
#define IMGUI_FRAME_BORDER 2.0f #define IMGUI_FRAME_BORDER 2.0f
#define IMGUI_LOG_DURATION 3.0f #define IMGUI_LOG_DURATION 3.0f
#define IMGUI_LOG_PADDING 10.0f
#define IMGUI_TEXT_HEIGHT_PADDING 4.0f #define IMGUI_TEXT_HEIGHT_PADDING 4.0f
#define IMGUI_PLAYHEAD_LINE_COLOR IM_COL32(UCHAR_MAX, UCHAR_MAX, UCHAR_MAX, UCHAR_MAX) #define IMGUI_PLAYHEAD_LINE_COLOR IM_COL32(UCHAR_MAX, UCHAR_MAX, UCHAR_MAX, UCHAR_MAX)
#define IMGUI_TRIGGERS_EVENT_COLOR IM_COL32(UCHAR_MAX, UCHAR_MAX, UCHAR_MAX, 128) #define IMGUI_TRIGGERS_EVENT_COLOR IM_COL32(UCHAR_MAX, UCHAR_MAX, UCHAR_MAX, 128)
@@ -97,7 +94,7 @@
#define IMGUI_ACTION_FRAME_CROP "Frame Crop" #define IMGUI_ACTION_FRAME_CROP "Frame Crop"
#define IMGUI_ACTION_FRAME_MOVE "Frame Move" #define IMGUI_ACTION_FRAME_MOVE "Frame Move"
#define IMGUI_ACTION_ANIMATION_SWAP "Animation Swap" #define IMGUI_ACTION_ANIMATION_MOVE "Move Animation"
#define IMGUI_ACTION_TRIGGER_MOVE "Trigger At Frame" #define IMGUI_ACTION_TRIGGER_MOVE "Trigger At Frame"
#define IMGUI_ACTION_ITEM_SWAP "Item Swap" #define IMGUI_ACTION_ITEM_SWAP "Item Swap"
#define IMGUI_ACTION_FRAME_DELAY "Frame Delay" #define IMGUI_ACTION_FRAME_DELAY "Frame Delay"
@@ -140,7 +137,11 @@
#define IMGUI_LOG_ANIMATION_PASTE_ERROR "Failed to parse clipboard text as an animation." #define IMGUI_LOG_ANIMATION_PASTE_ERROR "Failed to parse clipboard text as an animation."
#define IMGUI_LOG_FRAME_PASTE_ERROR "Failed to parse clipboard text as a frame." #define IMGUI_LOG_FRAME_PASTE_ERROR "Failed to parse clipboard text as a frame."
#define IMGUI_LOG_RELOAD_SPRITESHEET "Reloaded spritesheet(s)." #define IMGUI_LOG_RELOAD_SPRITESHEET "Reloaded spritesheet(s)."
#define IMGUI_LOG_REMOVE_UNUSED_SPRITESHEETS "Removed unused spritesheet(s)."
#define IMGUI_LOG_REMOVE_UNUSED_EVENTS "Removed unused event(s)."
#define IMGUI_LOG_ADD_SPRITESHEET_ERROR "Failed to add spritesheet: {}. Make sure it's a valid PNG file." #define IMGUI_LOG_ADD_SPRITESHEET_ERROR "Failed to add spritesheet: {}. Make sure it's a valid PNG file."
#define IMGUI_LOG_ADD_SPRITESHEET "Added spritesheet #{}: {}"
#define IMGUI_LOG_REPLACE_SPRITESHEET "Replaced spritesheet #{}: {}"
#define IMGUI_NONE "None" #define IMGUI_NONE "None"
#define IMGUI_ANIMATION_DEFAULT_FORMAT "(*) {}" #define IMGUI_ANIMATION_DEFAULT_FORMAT "(*) {}"
@@ -168,6 +169,8 @@
#define IMGUI_TRIGGERS_FONT_SCALE 2.0 #define IMGUI_TRIGGERS_FONT_SCALE 2.0
const ImVec2 IMGUI_TOOL_BUTTON_SIZE = {24, 24};
const ImVec2 IMGUI_TOOL_COLOR_SIZE = {24, 24};
const ImVec2 IMGUI_TIMELINE_FRAME_SIZE = {12, 36}; const ImVec2 IMGUI_TIMELINE_FRAME_SIZE = {12, 36};
const ImVec2 IMGUI_TIMELINE_FRAME_ATLAS_OFFSET = {ATLAS_SIZE_SMALL.x * 0.25f, (IMGUI_TIMELINE_FRAME_SIZE.y * 0.5f) - (ATLAS_SIZE_SMALL.y * 0.5f)}; const ImVec2 IMGUI_TIMELINE_FRAME_ATLAS_OFFSET = {ATLAS_SIZE_SMALL.x * 0.25f, (IMGUI_TIMELINE_FRAME_SIZE.y * 0.5f) - (ATLAS_SIZE_SMALL.y * 0.5f)};
const ImVec2 IMGUI_TIMELINE_ITEM_SELECTABLE_SIZE = {150, 0}; const ImVec2 IMGUI_TIMELINE_ITEM_SELECTABLE_SIZE = {150, 0};
@@ -198,6 +201,8 @@ const ImGuiKey IMGUI_INPUT_ZOOM_OUT = ImGuiKey_2;
const ImGuiKey IMGUI_INPUT_ENTER = ImGuiKey_Enter; const ImGuiKey IMGUI_INPUT_ENTER = ImGuiKey_Enter;
const ImGuiKey IMGUI_INPUT_RENAME = ImGuiKey_F2; const ImGuiKey IMGUI_INPUT_RENAME = ImGuiKey_F2;
const ImGuiKey IMGUI_INPUT_DEFAULT = ImGuiKey_Home; const ImGuiKey IMGUI_INPUT_DEFAULT = ImGuiKey_Home;
const ImGuiKeyChord IMGUI_CHORD_SELECT_ALL = ImGuiMod_Ctrl | ImGuiKey_A;
const ImGuiKeyChord IMGUI_CHORD_SELECT_NONE = ImGuiKey_Escape;
const ImGuiMouseButton IMGUI_MOUSE_DEFAULT = ImGuiMouseButton_Middle; const ImGuiMouseButton IMGUI_MOUSE_DEFAULT = ImGuiMouseButton_Middle;
enum ImguiPopupType { IMGUI_POPUP_NONE, IMGUI_POPUP_BY_ITEM, IMGUI_POPUP_CENTER_WINDOW }; enum ImguiPopupType { IMGUI_POPUP_NONE, IMGUI_POPUP_BY_ITEM, IMGUI_POPUP_CENTER_WINDOW };
@@ -228,14 +233,22 @@ struct Imgui {
Clipboard* clipboard = nullptr; Clipboard* clipboard = nullptr;
SDL_Window* window = nullptr; SDL_Window* window = nullptr;
SDL_GLContext* glContext = nullptr; SDL_GLContext* glContext = nullptr;
Anm2 saveAnm2 = Anm2();
ImFont* fonts[FONT_COUNT] = {};
ImGuiStyle style;
std::vector<ImguiLogItem> log{};
std::string pendingPopup{}; std::string pendingPopup{};
ImguiPopupType pendingPopupType = IMGUI_POPUP_NONE; ImguiPopupType pendingPopupType = IMGUI_POPUP_NONE;
ImVec2 pendingPopupPosition{}; ImVec2 pendingPopupPosition{};
std::vector<ImguiLogItem> log; std::set<int> selectedAnimationIndices{};
ImGuiStyle style; int lastAnimationIndex = ID_NONE;
Anm2 saveAnm2; std::set<int> selectedSpritesheetIDs{};
SDL_SystemCursor cursor; int lastSpritesheetID = ID_NONE;
SDL_SystemCursor pendingCursor; int selectedEventID = ID_NONE;
int selectedLayerID = ID_NONE;
int selectedNullID = ID_NONE;
SDL_SystemCursor cursor = SDL_SYSTEM_CURSOR_DEFAULT;
SDL_SystemCursor pendingCursor = SDL_SYSTEM_CURSOR_DEFAULT;
bool isCursorSet = false; bool isCursorSet = false;
bool isContextualActionsEnabled = true; bool isContextualActionsEnabled = true;
bool isQuit = false; bool isQuit = false;
@@ -254,9 +267,12 @@ static void imgui_log_push(Imgui* self, const std::string& text) {
} }
static inline void imgui_anm2_new(Imgui* self) { static inline void imgui_anm2_new(Imgui* self) {
*self->reference = Anm2Reference();
anm2_free(self->anm2); anm2_free(self->anm2);
anm2_new(self->anm2); anm2_new(self->anm2);
*self->reference = Anm2Reference();
self->selectedAnimationIndices = {};
self->selectedSpritesheetIDs = {};
} }
static inline void imgui_file_open(Imgui* self) { dialog_anm2_open(self->dialog); } static inline void imgui_file_open(Imgui* self) { dialog_anm2_open(self->dialog); }
@@ -576,7 +592,8 @@ enum ImguiItemType {
IMGUI_ITEM, IMGUI_ITEM,
IMGUI_TEXT, IMGUI_TEXT,
IMGUI_IMAGE, IMGUI_IMAGE,
IMGUI_WINDOW, IMGUI_BEGIN_WINDOW,
IMGUI_END_WINDOW,
IMGUI_DOCKSPACE, IMGUI_DOCKSPACE,
IMGUI_BEGIN_CHILD, IMGUI_BEGIN_CHILD,
IMGUI_END_CHILD, IMGUI_END_CHILD,
@@ -611,6 +628,7 @@ static ImGuiKeyChord* imgui_hotkey_chord_registry(void) {
} }
typedef void (*ImguiFunction)(Imgui*); typedef void (*ImguiFunction)(Imgui*);
using IntStringMap = std::map<int, std::string>;
#define IMGUI_ITEM_MEMBERS \ #define IMGUI_ITEM_MEMBERS \
X(label, std::string, false, {}) \ X(label, std::string, false, {}) \
@@ -619,14 +637,16 @@ typedef void (*ImguiFunction)(Imgui*);
X(popup, std::string, false, {}) \ X(popup, std::string, false, {}) \
X(dragDrop, std::string, true, {}) \ X(dragDrop, std::string, true, {}) \
X(focusWindow, std::string, true, {}) \ X(focusWindow, std::string, true, {}) \
X(items, std::vector<std::string>, false, {}) \ X(items, IntStringMap, false, {}) \
X(atlas, AtlasType, false, ATLAS_NONE) \ X(atlas, AtlasType, false, ATLAS_NONE) \
X(textureID, int, false, ID_NONE) \ X(textureID, int, false, ID_NONE) \
X(chord, ImGuiKeyChord, true, {}) \ X(chord, ImGuiKeyChord, true, {}) \
X(hotkey, HotkeyType, true, {}) \ X(hotkey, HotkeyType, true, {}) \
X(mnemonicKey, ImGuiKey, false, ImGuiKey_None) \ X(mnemonicKey, ImGuiKey, false, ImGuiKey_None) \
X(mnemonicIndex, int, false, INDEX_NONE) \ X(mnemonicIndex, int, false, INDEX_NONE) \
X(position, vec2, true, {}) \
X(size, vec2, true, {}) \ X(size, vec2, true, {}) \
X(scale, float, false, 1.0f) \
X(uvMin, vec2, false, vec2()) \ X(uvMin, vec2, false, vec2()) \
X(uvMax, vec2, false, vec2(1.0f)) \ X(uvMax, vec2, false, vec2(1.0f)) \
X(popupSize, vec2, false, {}) \ X(popupSize, vec2, false, {}) \
@@ -638,9 +658,12 @@ typedef void (*ImguiFunction)(Imgui*);
X(isMnemonicDisabled, bool, false, false) \ X(isMnemonicDisabled, bool, false, false) \
X(isEmptyFormat, bool, false, false) \ X(isEmptyFormat, bool, false, false) \
X(isUseItemActivated, bool, false, false) \ X(isUseItemActivated, bool, false, false) \
X(isSizeToText, bool, false, false) \ X(isWidthToText, bool, false, false) \
X(isSizeToRegion, bool, false, false) \ X(isWidthToRegion, bool, false, false) \
X(isHeightToRegion, bool, false, false) \
X(isHotkeyInLabel, bool, false, false) \ X(isHotkeyInLabel, bool, false, false) \
X(isAllowHotkeyWhenFocusWindow, bool, false, false) \
X(isAtlasStretch, bool, false, false) \
X(isSameLine, bool, false, false) \ X(isSameLine, bool, false, false) \
X(isSeparator, bool, false, false) \ X(isSeparator, bool, false, false) \
X(id, int, false, 0) \ X(id, int, false, 0) \
@@ -651,16 +674,19 @@ typedef void (*ImguiFunction)(Imgui*);
X(min, int, true, {}) \ X(min, int, true, {}) \
X(max, int, true, {}) \ X(max, int, true, {}) \
X(value, int, false, {}) \ X(value, int, false, {}) \
X(atlasOffset, vec2, false, {}) \ X(atlasOffset, vec2, true, {}) \
X(cursorPosition, vec2, true, {}) \ X(cursorPosition, vec2, true, {}) \
X(cursorOffset, vec2, false, {}) \ X(cursorOffset, vec2, false, {}) \
X(textPosition, vec2, true, {}) \
X(textOffset, vec2, false, {}) \
X(itemSpacing, vec2, true, {}) \ X(itemSpacing, vec2, true, {}) \
X(windowPadding, vec2, true, {}) \ X(windowPadding, vec2, true, {}) \
X(framePadding, vec2, true, {}) \ X(framePadding, vec2, true, {}) \
X(border, int, true, {}) \ X(border, int, true, {}) \
X(flags, int, false, {}) \ X(flags, int, false, {}) \
X(windowFlags, int, false, {}) \ X(windowFlags, int, false, {}) \
X(rowCount, int, true, {}) X(rowCount, int, true, {}) \
X(font, ImFont*, true, {})
struct ImguiItemOverride { struct ImguiItemOverride {
#define X(name, type, isOptional, ...) std::optional<type> name = {}; #define X(name, type, isOptional, ...) std::optional<type> name = {};
@@ -714,6 +740,8 @@ struct ImguiItem {
IMGUI_ITEM_MEMBERS IMGUI_ITEM_MEMBERS
#undef X #undef X
out.id += out.idOffset;
return out; return out;
} }
@@ -761,27 +789,27 @@ IMGUI_ITEM(IMGUI_TASKBAR, self.label = "Taskbar", self.size = {0, 32},
ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoSavedSettings); ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoSavedSettings);
IMGUI_ITEM(IMGUI_FILE, self.label = "&File", self.tooltip = "Opens the file menu, for reading/writing anm2 files.", self.popup = "## File Popup", IMGUI_ITEM(IMGUI_FILE, self.label = "&File", self.tooltip = "Opens the file menu, for reading/writing anm2 files.", self.popup = "## File Popup",
self.popupType = IMGUI_POPUP_BY_ITEM, self.isSizeToText = true, self.isSameLine = true); self.popupType = IMGUI_POPUP_BY_ITEM, self.isWidthToText = true, self.isHeightToRegion = true, self.isSameLine = true);
IMGUI_ITEM(IMGUI_NEW, self.label = "&New", self.tooltip = "Load a blank .anm2 file to edit.", self.function = imgui_file_new, self.hotkey = HOTKEY_NEW, IMGUI_ITEM(IMGUI_NEW, self.label = "&New", self.tooltip = "Load a blank .anm2 file to edit.", self.function = imgui_file_new, self.hotkey = HOTKEY_NEW,
self.isSizeToText = true, self.isHotkeyInLabel = true); self.isWidthToRegion = true, self.isHotkeyInLabel = true);
IMGUI_ITEM(IMGUI_OPEN, self.label = "&Open", self.tooltip = "Open an existing .anm2 file to edit.", self.function = imgui_file_open, self.hotkey = HOTKEY_OPEN, IMGUI_ITEM(IMGUI_OPEN, self.label = "&Open", self.tooltip = "Open an existing .anm2 file to edit.", self.function = imgui_file_open, self.hotkey = HOTKEY_OPEN,
self.isSizeToText = true, self.isHotkeyInLabel = true); self.isWidthToRegion = true, self.isHotkeyInLabel = true);
IMGUI_ITEM(IMGUI_SAVE, self.label = "&Save", IMGUI_ITEM(IMGUI_SAVE, self.label = "&Save",
self.tooltip = "Saves the current .anm2 file to its path.\nIf no " self.tooltip = "Saves the current .anm2 file to its path.\nIf no "
"path exists, one can be chosen.", "path exists, one can be chosen.",
self.function = imgui_file_save, self.hotkey = HOTKEY_SAVE, self.isSizeToText = true, self.isHotkeyInLabel = true); self.function = imgui_file_save, self.hotkey = HOTKEY_SAVE, self.isWidthToText = true, self.isHotkeyInLabel = true);
IMGUI_ITEM(IMGUI_SAVE_AS, self.label = "S&ave As", self.tooltip = "Saves the current .anm2 file to a chosen path.", self.function = imgui_file_save_as, IMGUI_ITEM(IMGUI_SAVE_AS, self.label = "S&ave As", self.tooltip = "Saves the current .anm2 file to a chosen path.", self.function = imgui_file_save_as,
self.hotkey = HOTKEY_SAVE_AS, self.isSizeToText = true, self.isHotkeyInLabel = true); self.hotkey = HOTKEY_SAVE_AS, self.isWidthToText = true, self.isHotkeyInLabel = true);
IMGUI_ITEM(IMGUI_EXPLORE_ANM2_LOCATION, self.label = "E&xplore Anm2 Location", self.tooltip = "Open the system's file explorer in the anm2's path.", IMGUI_ITEM(IMGUI_EXPLORE_ANM2_LOCATION, self.label = "E&xplore Anm2 Location", self.tooltip = "Open the system's file explorer in the anm2's path.",
self.function = imgui_explore, self.isSizeToText = true, self.isSeparator = true); self.function = imgui_explore, self.isWidthToText = true, self.isSeparator = true);
IMGUI_ITEM(IMGUI_EXIT, self.label = "&Exit", self.tooltip = "Exits the program.", self.function = imgui_quit, self.hotkey = HOTKEY_EXIT, IMGUI_ITEM(IMGUI_EXIT, self.label = "&Exit", self.tooltip = "Exits the program.", self.function = imgui_quit, self.hotkey = HOTKEY_EXIT,
self.isSizeToText = true, self.isHotkeyInLabel = true); self.isWidthToText = true, self.isHotkeyInLabel = true);
IMGUI_ITEM(IMGUI_EXIT_CONFIRMATION, self.label = "Exit Confirmation", self.tooltip = "Unsaved changes will be lost!\nAre you sure you want to exit?"); IMGUI_ITEM(IMGUI_EXIT_CONFIRMATION, self.label = "Exit Confirmation", self.tooltip = "Unsaved changes will be lost!\nAre you sure you want to exit?");
@@ -790,7 +818,8 @@ IMGUI_ITEM(IMGUI_OPEN_CONFIRMATION, self.label = "Open Confirmation", self.toolt
IMGUI_ITEM(IMGUI_NO_ANM2_PATH_CONFIRMATION, self.label = "No Anm2 Path", self.tooltip = "You will need to load or make a new .anm2 file first!\n"); IMGUI_ITEM(IMGUI_NO_ANM2_PATH_CONFIRMATION, self.label = "No Anm2 Path", self.tooltip = "You will need to load or make a new .anm2 file first!\n");
IMGUI_ITEM(IMGUI_WIZARD, self.label = "&Wizard", self.tooltip = "Opens the wizard menu, for neat functions related to the .anm2.", IMGUI_ITEM(IMGUI_WIZARD, self.label = "&Wizard", self.tooltip = "Opens the wizard menu, for neat functions related to the .anm2.",
self.popup = "## Wizard Popup", self.popupType = IMGUI_POPUP_BY_ITEM, self.isSizeToText = true, self.isSameLine = true); self.popup = "## Wizard Popup", self.popupType = IMGUI_POPUP_BY_ITEM, self.isHeightToRegion = true, self.isWidthToText = true,
self.isSameLine = true);
#define IMGUI_GENERATE_ANIMATION_FROM_GRID_PADDING 40 #define IMGUI_GENERATE_ANIMATION_FROM_GRID_PADDING 40
IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID, self.label = "&Generate Animation from Grid", self.tooltip = "Generate a new animation from grid values.", IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID, self.label = "&Generate Animation from Grid", self.tooltip = "Generate a new animation from grid values.",
@@ -875,11 +904,10 @@ IMGUI_ITEM(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_CANCEL, self.label = "Cancel", self
self.rowCount = IMGUI_CHANGE_ALL_FRAME_PROPERTIES_OPTIONS_ROW_COUNT); self.rowCount = IMGUI_CHANGE_ALL_FRAME_PROPERTIES_OPTIONS_ROW_COUNT);
IMGUI_ITEM(IMGUI_SCALE_ANM2, self.label = "S&cale Anm2", self.tooltip = "Scale up all size and position-related frame properties in the anm2.", IMGUI_ITEM(IMGUI_SCALE_ANM2, self.label = "S&cale Anm2", self.tooltip = "Scale up all size and position-related frame properties in the anm2.",
self.popup = "Scale Anm2", self.popupType = IMGUI_POPUP_CENTER_WINDOW, self.popupSize = {260, 75}, self.isSizeToText = true, self.popup = "Scale Anm2", self.popupType = IMGUI_POPUP_CENTER_WINDOW, self.popupSize = {260, 75}, self.isWidthToText = true,
self.isSeparator = true); self.isSeparator = true);
IMGUI_ITEM(IMGUI_SCALE_ANM2_OPTIONS_CHILD, self.label = "## Scale Anm2 Options Child", IMGUI_ITEM(IMGUI_SCALE_ANM2_OPTIONS_CHILD, self.label = "## Scale Anm2 Options Child", self.flags = true);
self.size = {IMGUI_SCALE_ANM2.popupSize.x, IMGUI_SCALE_ANM2.popupSize.y - IMGUI_FOOTER_CHILD.size->y}, self.flags = true);
IMGUI_ITEM(IMGUI_SCALE_ANM2_VALUE, self.label = "Value", IMGUI_ITEM(IMGUI_SCALE_ANM2_VALUE, self.label = "Value",
self.tooltip = "The size and position-related frame properties in " self.tooltip = "The size and position-related frame properties in "
@@ -894,11 +922,9 @@ IMGUI_ITEM(IMGUI_RENDER_ANIMATION, self.label = "&Render Animation",
"options can be customized.", "options can be customized.",
self.popup = "Render Animation", self.popupSize = {500, 170}, self.popupType = IMGUI_POPUP_CENTER_WINDOW); self.popup = "Render Animation", self.popupSize = {500, 170}, self.popupType = IMGUI_POPUP_CENTER_WINDOW);
IMGUI_ITEM(IMGUI_RENDER_ANIMATION_CHILD, self.label = "## Render Animation Child", IMGUI_ITEM(IMGUI_RENDER_ANIMATION_CHILD, self.label = "## Render Animation Child", self.flags = true);
self.size = {IMGUI_RENDER_ANIMATION.popupSize.x, IMGUI_RENDER_ANIMATION.popupSize.y - IMGUI_FOOTER_CHILD.size->y}, self.flags = true);
IMGUI_ITEM(IMGUI_RENDER_ANIMATION_FOOTER_CHILD, self.label = "## Render Animation Footer Child", IMGUI_ITEM(IMGUI_RENDER_ANIMATION_FOOTER_CHILD, self.label = "## Render Animation Footer Child", self.flags = true);
self.size = {IMGUI_RENDER_ANIMATION.popupSize.x, IMGUI_FOOTER_CHILD.size->y}, self.flags = true);
IMGUI_ITEM(IMGUI_RENDER_ANIMATION_LOCATION_BROWSE, self.label = "## Location Browse", self.tooltip = "Open file explorer to pick rendered animation location.", IMGUI_ITEM(IMGUI_RENDER_ANIMATION_LOCATION_BROWSE, self.label = "## Location Browse", self.tooltip = "Open file explorer to pick rendered animation location.",
self.atlas = ATLAS_FOLDER, self.isSameLine = true); self.atlas = ATLAS_FOLDER, self.isSameLine = true);
@@ -920,7 +946,11 @@ IMGUI_ITEM(IMGUI_RENDER_ANIMATION_FFMPEG_PATH, self.label = "FFmpeg Path",
IMGUI_ITEM(IMGUI_RENDER_ANIMATION_OUTPUT, self.label = "Output", IMGUI_ITEM(IMGUI_RENDER_ANIMATION_OUTPUT, self.label = "Output",
self.tooltip = "Select the rendered animation output.\nIt can either be " self.tooltip = "Select the rendered animation output.\nIt can either be "
"one animated image or a sequence of frames.", "one animated image or a sequence of frames.",
self.items = {std::begin(RENDER_TYPE_STRINGS), std::end(RENDER_TYPE_STRINGS)}, self.value = RENDER_PNG); self.items = {{RENDER_PNG, RENDER_TYPE_STRINGS[RENDER_PNG]},
{RENDER_GIF, RENDER_TYPE_STRINGS[RENDER_GIF]},
{RENDER_WEBM, RENDER_TYPE_STRINGS[RENDER_WEBM]},
{RENDER_MP4, RENDER_TYPE_STRINGS[RENDER_MP4]}},
self.value = RENDER_PNG);
IMGUI_ITEM(IMGUI_RENDER_ANIMATION_FORMAT, self.label = "Format", IMGUI_ITEM(IMGUI_RENDER_ANIMATION_FORMAT, self.label = "Format",
self.tooltip = "(PNG images only).\nSet the format of each output frame; i.e., " self.tooltip = "(PNG images only).\nSet the format of each output frame; i.e., "
@@ -950,28 +980,29 @@ IMGUI_ITEM(IMGUI_RENDERING_ANIMATION_INFO, self.label = "Recording frames. Once
IMGUI_ITEM(IMGUI_RENDERING_ANIMATION_CANCEL, self.label = "Cancel", self.tooltip = "Cancel rendering the animation.", self.rowCount = 1); IMGUI_ITEM(IMGUI_RENDERING_ANIMATION_CANCEL, self.label = "Cancel", self.tooltip = "Cancel rendering the animation.", self.rowCount = 1);
IMGUI_ITEM(IMGUI_PLAYBACK, self.label = "&Playback", self.tooltip = "Opens the playback menu, for configuring playback settings.", IMGUI_ITEM(IMGUI_PLAYBACK, self.label = "&Playback", self.tooltip = "Opens the playback menu, for configuring playback settings.",
self.popup = "## Playback Popup", self.popupType = IMGUI_POPUP_BY_ITEM, self.isSizeToText = true, self.isSameLine = true); self.popup = "## Playback Popup", self.popupType = IMGUI_POPUP_BY_ITEM, self.isWidthToText = true, self.isHeightToRegion = true,
self.isSameLine = true);
IMGUI_ITEM(IMGUI_ALWAYS_LOOP, self.label = "&Always Loop", IMGUI_ITEM(IMGUI_ALWAYS_LOOP, self.label = "&Always Loop",
self.tooltip = "Sets the animation playback to always loop, " self.tooltip = "Sets the animation playback to always loop, "
"regardless of the animation's loop setting.", "regardless of the animation's loop setting.",
self.isSizeToText = true); self.isWidthToText = true);
IMGUI_ITEM(IMGUI_CLAMP_PLAYHEAD, self.label = "&Clamp Playhead", IMGUI_ITEM(IMGUI_CLAMP_PLAYHEAD, self.label = "&Clamp Playhead",
self.tooltip = "The playhead (draggable icon on timeline) won't be " self.tooltip = "The playhead (draggable icon on timeline) won't be "
"able to exceed the animation length.", "able to exceed the animation length.",
self.isSizeToText = true); self.isWidthToText = true);
IMGUI_ITEM(IMGUI_SETTINGS, self.label = "&Settings", self.tooltip = "Opens the setting menu, for configuring general program settings.", IMGUI_ITEM(IMGUI_SETTINGS, self.label = "&Settings", self.tooltip = "Opens the setting menu, for configuring general program settings.",
self.popup = "## Settings Popup", self.popupType = IMGUI_POPUP_BY_ITEM, self.isSizeToText = true); self.popup = "## Settings Popup", self.popupType = IMGUI_POPUP_BY_ITEM, self.isWidthToText = true, self.isHeightToRegion = true);
IMGUI_ITEM(IMGUI_VSYNC, self.label = "&Vsync", IMGUI_ITEM(IMGUI_VSYNC, self.label = "&Vsync",
self.tooltip = "Toggle vertical sync; synchronizes program " self.tooltip = "Toggle vertical sync; synchronizes program "
"framerate with your monitor's refresh rate.", "framerate with your monitor's refresh rate.",
self.isSizeToText = true, self.isSeparator = true); self.isWidthToText = true, self.isSeparator = true);
IMGUI_ITEM(IMGUI_HOTKEYS, self.label = "&Hotkeys", self.tooltip = "Change the program's hotkeys.", self.popup = "Hotkeys", self.popupSize = {500, 405}, IMGUI_ITEM(IMGUI_HOTKEYS, self.label = "&Hotkeys", self.tooltip = "Change the program's hotkeys.", self.popup = "Hotkeys", self.popupSize = {500, 405},
self.isSizeToText = true, self.isSeparator = true); self.isWidthToText = true, self.isSeparator = true);
IMGUI_ITEM(IMGUI_HOTKEYS_CHILD, self.label = "## Hotkeys Child", self.size = {IMGUI_HOTKEYS.popupSize.x, IMGUI_HOTKEYS.popupSize.y - 35}, self.flags = true); IMGUI_ITEM(IMGUI_HOTKEYS_CHILD, self.label = "## Hotkeys Child", self.size = {IMGUI_HOTKEYS.popupSize.x, IMGUI_HOTKEYS.popupSize.y - 35}, self.flags = true);
@@ -984,12 +1015,18 @@ IMGUI_ITEM(IMGUI_HOTKEYS_OPTIONS_CHILD, self.label = "## Merge Options Child", s
IMGUI_ITEM(IMGUI_HOTKEYS_CONFIRM, self.label = "Confirm", self.tooltip = "Use these hotkeys.", self.rowCount = 1); IMGUI_ITEM(IMGUI_HOTKEYS_CONFIRM, self.label = "Confirm", self.tooltip = "Use these hotkeys.", self.rowCount = 1);
IMGUI_ITEM(IMGUI_DEFAULT_SETTINGS, self.label = "&Reset to Default Settings", self.tooltip = "Reset the program's settings to their default state.", IMGUI_ITEM(IMGUI_DEFAULT_SETTINGS, self.label = "&Reset to Default Settings", self.tooltip = "Reset the program's settings to their default state.",
self.isSizeToText = true); self.isWidthToText = true);
IMGUI_ITEM(IMGUI_ANM2S, self.label = "## Anm2s", self.size = {0, 32},
self.flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar |
ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoSavedSettings);
IMGUI_ITEM(IMGUI_ANM2, self.label = "## Anm2");
IMGUI_ITEM(IMGUI_LAYERS, self.label = "Layers", self.flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); IMGUI_ITEM(IMGUI_LAYERS, self.label = "Layers", self.flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
IMGUI_ITEM(IMGUI_LAYERS_CHILD, self.label = "## Layers Child", self.flags = true); IMGUI_ITEM(IMGUI_LAYERS_CHILD, self.label = "## Layers Child", self.flags = true);
IMGUI_ITEM(IMGUI_LAYER, self.label = "## Layer Item", self.dragDrop = "## Layer Drag Drop", self.atlas = ATLAS_LAYER, self.isSizeToRegion = true, IMGUI_ITEM(IMGUI_LAYER, self.label = "## Layer Item", self.dragDrop = "## Layer Drag Drop", self.atlas = ATLAS_LAYER, self.isWidthToRegion = true,
self.idOffset = 3000); self.idOffset = 3000);
IMGUI_ITEM(IMGUI_LAYER_SPRITESHEET_ID, self.label = "## Spritesheet ID", self.tooltip = "Change the spritesheet ID this layer uses.", IMGUI_ITEM(IMGUI_LAYER_SPRITESHEET_ID, self.label = "## Spritesheet ID", self.tooltip = "Change the spritesheet ID this layer uses.",
@@ -1024,7 +1061,7 @@ IMGUI_ITEM(IMGUI_ANIMATIONS, self.label = "Animations", self.flags = ImGuiWindow
IMGUI_ITEM(IMGUI_ANIMATIONS_CHILD, self.label = "## Animations Child", self.flags = true); IMGUI_ITEM(IMGUI_ANIMATIONS_CHILD, self.label = "## Animations Child", self.flags = true);
IMGUI_ITEM(IMGUI_ANIMATION, self.label = "## Animation Item", self.dragDrop = "## Animation Drag Drop", self.atlas = ATLAS_ANIMATION, IMGUI_ITEM(IMGUI_ANIMATION, self.label = "## Animation Item", self.dragDrop = "## Animation Drag Drop", self.atlas = ATLAS_ANIMATION,
self.isSizeToRegion = true, self.idOffset = 2000); self.isWidthToRegion = true, self.idOffset = 2000);
#define IMGUI_ANIMATIONS_OPTIONS_ROW_COUNT 5 #define IMGUI_ANIMATIONS_OPTIONS_ROW_COUNT 5
IMGUI_ITEM(IMGUI_ANIMATION_ADD, self.label = "Add", self.tooltip = "Adds a new animation.", self.snapshotAction = "Add Animation", IMGUI_ITEM(IMGUI_ANIMATION_ADD, self.label = "Add", self.tooltip = "Adds a new animation.", self.snapshotAction = "Add Animation",
@@ -1071,12 +1108,28 @@ IMGUI_ITEM(IMGUI_MERGE_DELETE_ANIMATIONS_AFTER, self.label = "Delete Animations
IMGUI_ITEM(IMGUI_MERGE_CONFIRM, self.label = "Merge", self.tooltip = "Merge the selected animations with the options set.", IMGUI_ITEM(IMGUI_MERGE_CONFIRM, self.label = "Merge", self.tooltip = "Merge the selected animations with the options set.",
self.snapshotAction = "Merge Animations", self.rowCount = IMGUI_CONFIRM_POPUP_ROW_COUNT, self.isSameLine = true); self.snapshotAction = "Merge Animations", self.rowCount = IMGUI_CONFIRM_POPUP_ROW_COUNT, self.isSameLine = true);
IMGUI_ITEM(IMGUI_ANIMATION_REMOVE, self.label = "Remove", self.tooltip = "Remove the selected animation.", self.snapshotAction = "Remove Animation", IMGUI_ITEM(IMGUI_ANIMATION_REMOVE, self.label = "Remove", self.tooltip = "Remove the selected animation(s).", self.snapshotAction = "Remove Animation(s)",
self.rowCount = IMGUI_ANIMATIONS_OPTIONS_ROW_COUNT, self.chord = ImGuiKey_Delete, self.focusWindow = IMGUI_ANIMATIONS.label, self.isSameLine = true); self.rowCount = IMGUI_ANIMATIONS_OPTIONS_ROW_COUNT, self.chord = ImGuiKey_Delete, self.focusWindow = IMGUI_ANIMATIONS.label, self.isSameLine = true);
IMGUI_ITEM(IMGUI_ANIMATION_DEFAULT, self.label = "Default", self.tooltip = "Set the selected animation as the default one.", IMGUI_ITEM(IMGUI_ANIMATION_DEFAULT, self.label = "Default", self.tooltip = "Set the referenced animation as the default one.",
self.snapshotAction = "Default Animation", self.rowCount = IMGUI_ANIMATIONS_OPTIONS_ROW_COUNT, self.isSameLine = true); self.snapshotAction = "Default Animation", self.rowCount = IMGUI_ANIMATIONS_OPTIONS_ROW_COUNT, self.isSameLine = true);
static inline void imgui_animations_select_all(Imgui* self) {
for (int i = 0; i < (int)self->anm2->animations.size(); i++)
self->selectedAnimationIndices.insert(i);
}
IMGUI_ITEM(IMGUI_ANIMATION_SELECT_ALL, self.label = "## Select None", self.hotkey = HOTKEY_SELECT_ALL, self.focusWindow = IMGUI_ANIMATIONS.label,
self.function = imgui_animations_select_all, self.isAllowHotkeyWhenFocusWindow = true);
static inline void imgui_animations_select_none(Imgui* self) {
self->selectedAnimationIndices.clear();
*self->reference = Anm2Reference();
}
IMGUI_ITEM(IMGUI_ANIMATION_SELECT_NONE, self.label = "## Select None", self.hotkey = HOTKEY_SELECT_NONE, self.focusWindow = IMGUI_ANIMATIONS.label,
self.function = imgui_animations_select_none, self.isAllowHotkeyWhenFocusWindow = true);
IMGUI_ITEM(IMGUI_EVENTS, self.label = "Events", self.flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); IMGUI_ITEM(IMGUI_EVENTS, self.label = "Events", self.flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
IMGUI_ITEM(IMGUI_EVENTS_CHILD, self.label = "## Events Child", self.flags = true); IMGUI_ITEM(IMGUI_EVENTS_CHILD, self.label = "## Events Child", self.flags = true);
@@ -1100,8 +1153,7 @@ IMGUI_ITEM(IMGUI_SPRITESHEET_CHILD, self.label = "## Spritesheet Child", self.fl
IMGUI_ITEM(IMGUI_SPRITESHEET, self.label = "## Spritesheet", self.dragDrop = "## Spritesheet Drag Drop"); IMGUI_ITEM(IMGUI_SPRITESHEET, self.label = "## Spritesheet", self.dragDrop = "## Spritesheet Drag Drop");
IMGUI_ITEM(IMGUI_SPRITESHEET_TEXT, self.label = "## Spritesheet Text", self.atlas = ATLAS_SPRITESHEET, self.itemSpacing = vec2(8, 0)); IMGUI_ITEM(IMGUI_SPRITESHEET_TEXT, self.label = "## Spritesheet Text", self.atlas = ATLAS_SPRITESHEET, self.itemSpacing = vec2(8, 0));
IMGUI_ITEM(IMGUI_SPRITESHEETS_FOOTER_CHILD, self.label = "## Spritesheets Footer Child", self.size = {0, IMGUI_FOOTER_CHILD.size->y * 1.66f}, IMGUI_ITEM(IMGUI_SPRITESHEETS_FOOTER_CHILD, self.label = "## Spritesheets Footer Child", self.flags = true);
self.flags = true);
#define IMGUI_SPRITESHEETS_OPTIONS_FIRST_ROW_COUNT 4 #define IMGUI_SPRITESHEETS_OPTIONS_FIRST_ROW_COUNT 4
#define IMGUI_SPRITESHEETS_OPTIONS_SECOND_ROW_COUNT 3 #define IMGUI_SPRITESHEETS_OPTIONS_SECOND_ROW_COUNT 3
@@ -1117,12 +1169,12 @@ IMGUI_ITEM(IMGUI_SPRITESHEETS_REPLACE, self.label = "Replace", self.tooltip = "R
IMGUI_ITEM(IMGUI_SPRITESHEETS_REMOVE_UNUSED, self.label = "Remove Unused", IMGUI_ITEM(IMGUI_SPRITESHEETS_REMOVE_UNUSED, self.label = "Remove Unused",
self.tooltip = "Remove all unused spritesheets in the anm2 (i.e., " self.tooltip = "Remove all unused spritesheets in the anm2 (i.e., "
"the spritesheet isn't used in any layer animations).", "the spritesheet isn't used in any layer animations).",
self.rowCount = IMGUI_SPRITESHEETS_OPTIONS_FIRST_ROW_COUNT); self.snapshotAction = "Remove Unused Spritesheets", self.rowCount = IMGUI_SPRITESHEETS_OPTIONS_FIRST_ROW_COUNT);
IMGUI_ITEM(IMGUI_SPRITESHEETS_SELECT_ALL, self.label = "Select All", self.tooltip = "Select all spritesheets.", self.chord = ImGuiMod_Ctrl + ImGuiKey_A, IMGUI_ITEM(IMGUI_SPRITESHEETS_SELECT_ALL, self.label = "Select All", self.tooltip = "Select all spritesheets.", self.hotkey = HOTKEY_SELECT_ALL,
self.focusWindow = IMGUI_SPRITESHEETS.label, self.rowCount = IMGUI_SPRITESHEETS_OPTIONS_SECOND_ROW_COUNT, self.isSameLine = true); self.focusWindow = IMGUI_SPRITESHEETS.label, self.rowCount = IMGUI_SPRITESHEETS_OPTIONS_SECOND_ROW_COUNT, self.isSameLine = true);
IMGUI_ITEM(IMGUI_SPRITESHEETS_SELECT_NONE, self.label = "Select None", self.tooltip = "Unselect all spritesheets.", self.chord = ImGuiKey_Escape, IMGUI_ITEM(IMGUI_SPRITESHEETS_SELECT_NONE, self.label = "Select None", self.tooltip = "Unselect all spritesheets.", self.hotkey = HOTKEY_SELECT_NONE,
self.focusWindow = IMGUI_SPRITESHEETS.label, self.rowCount = IMGUI_SPRITESHEETS_OPTIONS_SECOND_ROW_COUNT, self.isSameLine = true); self.focusWindow = IMGUI_SPRITESHEETS.label, self.rowCount = IMGUI_SPRITESHEETS_OPTIONS_SECOND_ROW_COUNT, self.isSameLine = true);
IMGUI_ITEM(IMGUI_SPRITESHEET_SAVE, self.label = "Save", self.tooltip = "Save the selected spritesheets to their original locations.", IMGUI_ITEM(IMGUI_SPRITESHEET_SAVE, self.label = "Save", self.tooltip = "Save the selected spritesheets to their original locations.",
@@ -1274,7 +1326,7 @@ IMGUI_ITEM(IMGUI_TOOLS, self.label = "Tools");
IMGUI_ITEM(IMGUI_TOOL_PAN, self.label = "## Pan", IMGUI_ITEM(IMGUI_TOOL_PAN, self.label = "## Pan",
self.tooltip = "Use the pan tool.\nWill shift the view as the cursor is dragged.\nYou " self.tooltip = "Use the pan tool.\nWill shift the view as the cursor is dragged.\nYou "
"can also use the middle mouse button to pan at any time.", "can also use the middle mouse button to pan at any time.",
self.function = imgui_tool_pan_set, self.hotkey = HOTKEY_PAN, self.atlas = ATLAS_PAN); self.function = imgui_tool_pan_set, self.hotkey = HOTKEY_PAN, self.atlas = ATLAS_PAN, self.isAtlasStretch = true);
IMGUI_ITEM(IMGUI_TOOL_MOVE, self.label = "## Move", IMGUI_ITEM(IMGUI_TOOL_MOVE, self.label = "## Move",
self.tooltip = "Use the move tool.\nAnimation Preview: Will move the position " self.tooltip = "Use the move tool.\nAnimation Preview: Will move the position "
@@ -1374,12 +1426,12 @@ IMGUI_ITEM(IMGUI_TIMELINE_ITEM_PROPERTIES_TYPE_CHILD, self.label = "## Item Prop
IMGUI_ITEM(IMGUI_TIMELINE_ITEM_PROPERTIES_LAYER, self.label = "Layer", IMGUI_ITEM(IMGUI_TIMELINE_ITEM_PROPERTIES_LAYER, self.label = "Layer",
self.tooltip = "The item will be a layer item.\nA layer item is a " self.tooltip = "The item will be a layer item.\nA layer item is a "
"primary graphical item, using a spritesheet.", "primary graphical item, using a spritesheet.",
self.isSizeToText = true, self.value = ANM2_LAYER, self.isSameLine = true); self.isWidthToText = true, self.value = ANM2_LAYER, self.isSameLine = true);
IMGUI_ITEM(IMGUI_TIMELINE_ITEM_PROPERTIES_NULL, self.label = "Null", IMGUI_ITEM(IMGUI_TIMELINE_ITEM_PROPERTIES_NULL, self.label = "Null",
self.tooltip = "The item will be a null item.\nA null item is an " self.tooltip = "The item will be a null item.\nA null item is an "
"invisible item, often accessed by a game engine.", "invisible item, often accessed by a game engine.",
self.isSizeToText = true, self.value = ANM2_NULL); self.isWidthToText = true, self.value = ANM2_NULL);
IMGUI_ITEM(IMGUI_TIMELINE_ITEM_PROPERTIES_ITEMS_CHILD, self.label = "## Item Properties Items", self.size = {IMGUI_POPUP_ITEM_PROPERTIES_SIZE.x, 250}, IMGUI_ITEM(IMGUI_TIMELINE_ITEM_PROPERTIES_ITEMS_CHILD, self.label = "## Item Properties Items", self.size = {IMGUI_POPUP_ITEM_PROPERTIES_SIZE.x, 250},
self.flags = true); self.flags = true);
@@ -1598,16 +1650,16 @@ IMGUI_ITEM(IMGUI_CONTEXT_MENU, self.label = "## Context Menu");
IMGUI_ITEM(IMGUI_CUT, self.label = "Cut", IMGUI_ITEM(IMGUI_CUT, self.label = "Cut",
self.tooltip = "Cuts the currently selected contextual element; " self.tooltip = "Cuts the currently selected contextual element; "
"removing it and putting it to the clipboard.", "removing it and putting it to the clipboard.",
self.snapshotAction = "Cut", self.function = imgui_cut, self.hotkey = HOTKEY_CUT, self.isSizeToText = true); self.snapshotAction = "Cut", self.function = imgui_cut, self.hotkey = HOTKEY_CUT, self.isWidthToText = true);
IMGUI_ITEM(IMGUI_COPY, self.label = "Copy", self.tooltip = "Copies the currently selected contextual element to the clipboard.", self.snapshotAction = "Copy", IMGUI_ITEM(IMGUI_COPY, self.label = "Copy", self.tooltip = "Copies the currently selected contextual element to the clipboard.", self.function = imgui_copy,
self.function = imgui_copy, self.hotkey = HOTKEY_COPY, self.isSizeToText = true); self.hotkey = HOTKEY_COPY, self.isWidthToText = true);
IMGUI_ITEM(IMGUI_PASTE, self.label = "Paste", self.tooltip = "Pastes the currently selection contextual element from the clipboard.", IMGUI_ITEM(IMGUI_PASTE, self.label = "Paste", self.tooltip = "Pastes the currently selection contextual element from the clipboard.",
self.snapshotAction = "Paste", self.function = imgui_paste, self.hotkey = HOTKEY_PASTE, self.isSizeToText = true); self.snapshotAction = "Paste", self.function = imgui_paste, self.hotkey = HOTKEY_PASTE, self.isWidthToText = true);
IMGUI_ITEM(IMGUI_CHANGE_INPUT_TEXT, self.label = "## Input Text", self.tooltip = "Rename the selected item.", self.snapshotAction = "Rename Item", IMGUI_ITEM(IMGUI_CHANGE_INPUT_TEXT, self.label = "## Input Text", self.tooltip = "Rename the selected item.", self.snapshotAction = "Rename",
self.flags = ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_EnterReturnsTrue, self.max = UCHAR_MAX); self.flags = ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_EnterReturnsTrue, self.max = 0xFFFF);
IMGUI_ITEM(IMGUI_CHANGE_INPUT_INT, self.label = "## Input Int", self.tooltip = "Change the selected item's value.", self.snapshotAction = "Change Value", IMGUI_ITEM(IMGUI_CHANGE_INPUT_INT, self.label = "## Input Int", self.tooltip = "Change the selected item's value.", self.snapshotAction = "Change Value",
self.step = 0); self.step = 0);

View File

@@ -87,8 +87,8 @@ void preview_draw(Preview* self) {
if (self->settings->previewIsAxes) if (self->settings->previewIsAxes)
canvas_axes_draw(&self->canvas, shaderAxis, transform, self->settings->previewAxesColor); canvas_axes_draw(&self->canvas, shaderAxis, transform, self->settings->previewAxesColor);
auto animation_draw = [&](int animationID) { auto animation_draw = [&](int animationIndex) {
Anm2Animation* animation = map_find(self->anm2->animations, animationID); Anm2Animation* animation = anm2_animation_from_reference(self->anm2, {animationIndex});
if (!animation) if (!animation)
return; return;
@@ -107,7 +107,7 @@ void preview_draw(Preview* self) {
return; return;
Anm2Frame frame; Anm2Frame frame;
anm2_frame_from_time(self->anm2, &frame, Anm2Reference{animationID, ANM2_LAYER, id}, time); anm2_frame_from_time(self->anm2, &frame, Anm2Reference{animationIndex, ANM2_LAYER, id}, time);
if (!frame.isVisible) if (!frame.isVisible)
return; return;
@@ -151,7 +151,7 @@ void preview_draw(Preview* self) {
return; return;
Anm2Frame frame; Anm2Frame frame;
anm2_frame_from_time(self->anm2, &frame, Anm2Reference{animationID, ANM2_NULL, id}, time); anm2_frame_from_time(self->anm2, &frame, Anm2Reference{animationIndex, ANM2_NULL, id}, time);
if (!frame.isVisible) if (!frame.isVisible)
return; return;
@@ -180,7 +180,7 @@ void preview_draw(Preview* self) {
auto base_draw = [&](float time, vec3 colorOffset = {}, float alphaOffset = {}, bool isOnionskin = {}) { auto base_draw = [&](float time, vec3 colorOffset = {}, float alphaOffset = {}, bool isOnionskin = {}) {
Anm2Frame root; Anm2Frame root;
anm2_frame_from_time(self->anm2, &root, Anm2Reference{animationID, ANM2_ROOT}, time); anm2_frame_from_time(self->anm2, &root, Anm2Reference{animationIndex, ANM2_ROOT}, time);
mat4 rootModel = mat4 rootModel =
self->settings->previewIsRootTransform ? quad_model_parent_get(root.position, {}, PERCENT_TO_UNIT(root.scale), root.rotation) : mat4(1.0f); self->settings->previewIsRootTransform ? quad_model_parent_get(root.position, {}, PERCENT_TO_UNIT(root.scale), root.rotation) : mat4(1.0f);
@@ -218,7 +218,7 @@ void preview_draw(Preview* self) {
onionskins_draw(); onionskins_draw();
}; };
animation_draw(self->reference->animationID); animation_draw(self->reference->animationIndex);
animation_draw(self->animationOverlayID); animation_draw(self->animationOverlayID);
canvas_unbind(); canvas_unbind();

View File

@@ -2,25 +2,16 @@
#include "COMMON.h" #include "COMMON.h"
enum RenderType enum RenderType { RENDER_PNG, RENDER_GIF, RENDER_WEBM, RENDER_MP4, RENDER_COUNT };
{
RENDER_PNG,
RENDER_GIF,
RENDER_WEBM,
RENDER_MP4,
RENDER_COUNT
};
const inline std::string RENDER_TYPE_STRINGS[] = const inline std::string RENDER_TYPE_STRINGS[] = {
{
"PNG Images", "PNG Images",
"GIF image", "GIF image",
"WebM video", "WebM video",
"MP4 video", "MP4 video",
}; };
const inline std::string RENDER_EXTENSIONS[RENDER_COUNT] = const inline std::string RENDER_EXTENSIONS[RENDER_COUNT] = {
{
".png", ".png",
".gif", ".gif",
".webm", ".webm",

View File

@@ -1,7 +1,8 @@
#include "resources.h" #include "resources.h"
#include "RESOURCE.h"
void resources_init(Resources* self) { void resources_init(Resources* self) {
texture_from_path_init(&self->atlas, ATLAS_PATH); texture_from_memory_init(&self->atlas, TEXTURE_ATLAS_SIZE, TEXTURE_ATLAS, TEXTURE_ATLAS_LENGTH);
for (int i = 0; i < SHADER_COUNT; i++) for (int i = 0; i < SHADER_COUNT; i++)
shader_init(&self->shaders[i], SHADER_DATA[i].vertex, SHADER_DATA[i].fragment); shader_init(&self->shaders[i], SHADER_DATA[i].vertex, SHADER_DATA[i].fragment);

View File

@@ -61,6 +61,8 @@
X(HOTKEY_COPY, hotkeyCopy, TYPE_STRING, "Ctrl+C") \ X(HOTKEY_COPY, hotkeyCopy, TYPE_STRING, "Ctrl+C") \
X(HOTKEY_CUT, hotkeyCut, TYPE_STRING, "Ctrl+X") \ X(HOTKEY_CUT, hotkeyCut, TYPE_STRING, "Ctrl+X") \
X(HOTKEY_PASTE, hotkeyPaste, TYPE_STRING, "Ctrl+V") \ X(HOTKEY_PASTE, hotkeyPaste, TYPE_STRING, "Ctrl+V") \
X(HOTKEY_SELECT_ALL, hotkeySelectAll, TYPE_STRING, "Ctrl+A") \
X(HOTKEY_SELECT_NONE, hotkeySelectNone, TYPE_STRING, "Ctrl+Shift+A") \
\ \
X(PLAYBACK_IS_LOOP, playbackIsLoop, TYPE_BOOL, true) \ X(PLAYBACK_IS_LOOP, playbackIsLoop, TYPE_BOOL, true) \
X(PLAYBACK_IS_CLAMP_PLAYHEAD, playbackIsClampPlayhead, TYPE_BOOL, true) \ X(PLAYBACK_IS_CLAMP_PLAYHEAD, playbackIsClampPlayhead, TYPE_BOOL, true) \
@@ -211,7 +213,9 @@ constexpr int SETTINGS_COUNT = (int)std::size(SETTINGS_ENTRIES);
X(REDO, "Redo") \ X(REDO, "Redo") \
X(COPY, "Copy") \ X(COPY, "Copy") \
X(CUT, "Cut") \ X(CUT, "Cut") \
X(PASTE, "Paste") X(PASTE, "Paste") \
X(SELECT_ALL, "Select All") \
X(SELECT_NONE, "Select None")
typedef enum { typedef enum {
#define X(name, str) HOTKEY_##name, #define X(name, str) HOTKEY_##name,
@@ -257,7 +261,9 @@ const inline HotkeyMember SETTINGS_HOTKEY_MEMBERS[HOTKEY_COUNT] = {nullptr,
&Settings::hotkeyRedo, &Settings::hotkeyRedo,
&Settings::hotkeyCopy, &Settings::hotkeyCopy,
&Settings::hotkeyCut, &Settings::hotkeyCut,
&Settings::hotkeyPaste}; &Settings::hotkeyPaste,
&Settings::hotkeySelectAll,
&Settings::hotkeySelectNone};
const inline std::string SETTINGS_IMGUI_DEFAULT = R"( const inline std::string SETTINGS_IMGUI_DEFAULT = R"(
# Dear ImGui # Dear ImGui

View File

@@ -17,7 +17,7 @@ struct SnapshotStack {
Snapshot snapshots[SNAPSHOT_STACK_MAX]; Snapshot snapshots[SNAPSHOT_STACK_MAX];
int top = 0; int top = 0;
bool is_empty() const { return top == 0; } bool empty() const { return top == 0; }
}; };
struct Snapshots { struct Snapshots {

View File

@@ -76,6 +76,22 @@ bool texture_from_rgba_write(const std::string& path, const uint8_t* data, ivec2
return isSuccess; return isSuccess;
} }
bool texture_from_memory_init(Texture* self, ivec2 size, const uint8_t* data, size_t length) {
*self = Texture{};
self->size = size;
u8* textureData = stbi_load_from_memory(data, length, &self->size.x, &self->size.y, nullptr, TEXTURE_CHANNELS);
if (!textureData)
return false;
self->isInvalid = false;
_texture_gl_set(self, textureData);
return true;
}
bool texture_from_gl_write(Texture* self, const std::string& path) { return texture_from_rgba_write(path, texture_download(self).data(), self->size); } bool texture_from_gl_write(Texture* self, const std::string& path) { return texture_from_rgba_write(path, texture_download(self).data(), self->size); }
void texture_free(Texture* self) { void texture_free(Texture* self) {

View File

@@ -18,8 +18,9 @@ struct Texture {
bool texture_from_gl_write(Texture* self, const std::string& path); bool texture_from_gl_write(Texture* self, const std::string& path);
bool texture_from_path_init(Texture* self, const std::string& path); bool texture_from_path_init(Texture* self, const std::string& path);
bool texture_from_rgba_init(Texture* self, ivec2 size, const u8* data); bool texture_from_rgba_init(Texture* self, ivec2 size, const uint8_t* data);
bool texture_from_rgba_write(const std::string& path, const u8* data, ivec2 size); bool texture_from_rgba_write(const std::string& path, const uint8_t* data, ivec2 size);
bool texture_pixel_set(Texture* self, ivec2 position, vec4 color); bool texture_pixel_set(Texture* self, ivec2 position, vec4 color);
void texture_free(Texture* self); void texture_free(Texture* self);
std::vector<u8> texture_download(const Texture* self); bool texture_from_memory_init(Texture* self, ivec2 size, const uint8_t* data, size_t length);
std::vector<uint8_t> texture_download(const Texture* self);