timeline refactor, bit broken rn
This commit is contained in:
BIN
.Icon.ico-autosave.kra
Normal file
BIN
.Icon.ico-autosave.kra
Normal file
Binary file not shown.
@@ -1,7 +1,7 @@
|
|||||||
ColumnLimit: 120
|
ColumnLimit: 120
|
||||||
PointerAlignment: Left
|
PointerAlignment: Left
|
||||||
ReferenceAlignment: Left
|
ReferenceAlignment: Left
|
||||||
AllowShortFunctionsOnASingleLine: None
|
AllowShortFunctionsOnASingleLine: All
|
||||||
AllowShortIfStatementsOnASingleLine: true
|
AllowShortIfStatementsOnASingleLine: true
|
||||||
CommentPragmas: '^'
|
CommentPragmas: '^'
|
||||||
BreakBeforeBraces: Allman
|
BreakBeforeBraces: Allman
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ else ()
|
|||||||
target_compile_options(${PROJECT_NAME} PRIVATE -O0 -pg)
|
target_compile_options(${PROJECT_NAME} PRIVATE -O0 -pg)
|
||||||
else ()
|
else ()
|
||||||
set(CMAKE_BUILD_TYPE "Release")
|
set(CMAKE_BUILD_TYPE "Release")
|
||||||
target_compile_options(${PROJECT_NAME} PRIVATE -O2)
|
target_compile_options(${PROJECT_NAME} PRIVATE -Os)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
target_link_libraries(${PROJECT_NAME} PRIVATE m)
|
target_link_libraries(${PROJECT_NAME} PRIVATE m)
|
||||||
@@ -135,4 +135,25 @@ target_link_libraries(${PROJECT_NAME} PRIVATE GL SDL3-static SDL3_mixer::SDL3_mi
|
|||||||
message(STATUS "System: ${CMAKE_SYSTEM_NAME}")
|
message(STATUS "System: ${CMAKE_SYSTEM_NAME}")
|
||||||
message(STATUS "Project: ${PROJECT_NAME}")
|
message(STATUS "Project: ${PROJECT_NAME}")
|
||||||
message(STATUS "Compiler: ${CMAKE_CXX_COMPILER}")
|
message(STATUS "Compiler: ${CMAKE_CXX_COMPILER}")
|
||||||
|
|
||||||
|
get_target_property(PROJECT_COMPILE_OPTIONS ${PROJECT_NAME} COMPILE_OPTIONS)
|
||||||
|
if (NOT PROJECT_COMPILE_OPTIONS)
|
||||||
|
set(PROJECT_COMPILE_OPTIONS "<none>")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
string(TOUPPER "${CMAKE_BUILD_TYPE}" BUILD_TYPE_UPPER)
|
||||||
|
set(EFFECTIVE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
|
||||||
|
if (BUILD_TYPE_UPPER)
|
||||||
|
set(CONFIG_FLAGS_VAR "CMAKE_CXX_FLAGS_${BUILD_TYPE_UPPER}")
|
||||||
|
if (DEFINED ${CONFIG_FLAGS_VAR})
|
||||||
|
string(APPEND EFFECTIVE_CXX_FLAGS " ${${CONFIG_FLAGS_VAR}}")
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
string(STRIP "${EFFECTIVE_CXX_FLAGS}" EFFECTIVE_CXX_FLAGS)
|
||||||
|
if (EFFECTIVE_CXX_FLAGS STREQUAL "")
|
||||||
|
set(EFFECTIVE_CXX_FLAGS "<none>")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
message(STATUS "Compiler Flags: ${EFFECTIVE_CXX_FLAGS}")
|
||||||
|
message(STATUS "Target Compile Options: ${PROJECT_COMPILE_OPTIONS}")
|
||||||
message(STATUS "Build: ${CMAKE_BUILD_TYPE}")
|
message(STATUS "Build: ${CMAKE_BUILD_TYPE}")
|
||||||
|
|||||||
@@ -105,10 +105,7 @@ namespace anm2ed::anm2
|
|||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Animation::serialize(XMLDocument& document, XMLElement* parent)
|
void Animation::serialize(XMLDocument& document, XMLElement* parent) { parent->InsertEndChild(to_element(document)); }
|
||||||
{
|
|
||||||
parent->InsertEndChild(to_element(document));
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string Animation::to_string()
|
std::string Animation::to_string()
|
||||||
{
|
{
|
||||||
@@ -153,13 +150,13 @@ namespace anm2ed::anm2
|
|||||||
|
|
||||||
if (isRootTransform)
|
if (isRootTransform)
|
||||||
{
|
{
|
||||||
auto root = rootAnimation.frame_generate(t, anm2::ROOT);
|
auto root = rootAnimation.frame_generate(t, ROOT);
|
||||||
transform *= math::quad_model_parent_get(root.position, {}, math::percent_to_unit(root.scale), root.rotation);
|
transform *= math::quad_model_parent_get(root.position, {}, math::percent_to_unit(root.scale), root.rotation);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto& [id, layerAnimation] : layerAnimations)
|
for (auto& [id, layerAnimation] : layerAnimations)
|
||||||
{
|
{
|
||||||
auto frame = layerAnimation.frame_generate(t, anm2::LAYER);
|
auto frame = layerAnimation.frame_generate(t, LAYER);
|
||||||
|
|
||||||
if (frame.size == vec2() || !frame.isVisible) continue;
|
if (frame.size == vec2() || !frame.isVisible) continue;
|
||||||
|
|
||||||
|
|||||||
@@ -131,6 +131,8 @@ namespace anm2ed::anm2
|
|||||||
finalIndex -= numDeletedBefore;
|
finalIndex -= numDeletedBefore;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
animation.frameNum = animation.length();
|
||||||
|
|
||||||
return finalIndex;
|
return finalIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
84
src/anm2/anm2_type.h
Normal file
84
src/anm2/anm2_type.h
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "icon.h"
|
||||||
|
|
||||||
|
#include <glm/glm/vec2.hpp>
|
||||||
|
#include <glm/glm/vec3.hpp>
|
||||||
|
#include <glm/glm/vec4.hpp>
|
||||||
|
|
||||||
|
namespace anm2ed::anm2
|
||||||
|
{
|
||||||
|
constexpr auto ROOT_COLOR = glm::vec4(0.140f, 0.310f, 0.560f, 1.000f);
|
||||||
|
constexpr auto ROOT_COLOR_ACTIVE = glm::vec4(0.240f, 0.520f, 0.880f, 1.000f);
|
||||||
|
constexpr auto ROOT_COLOR_HOVERED = glm::vec4(0.320f, 0.640f, 1.000f, 1.000f);
|
||||||
|
|
||||||
|
constexpr auto LAYER_COLOR = glm::vec4(0.640f, 0.320f, 0.110f, 1.000f);
|
||||||
|
constexpr auto LAYER_COLOR_ACTIVE = glm::vec4(0.840f, 0.450f, 0.170f, 1.000f);
|
||||||
|
constexpr auto LAYER_COLOR_HOVERED = glm::vec4(0.960f, 0.560f, 0.240f, 1.000f);
|
||||||
|
|
||||||
|
constexpr auto NULL_COLOR = glm::vec4(0.140f, 0.430f, 0.200f, 1.000f);
|
||||||
|
constexpr auto NULL_COLOR_ACTIVE = glm::vec4(0.250f, 0.650f, 0.350f, 1.000f);
|
||||||
|
constexpr auto NULL_COLOR_HOVERED = glm::vec4(0.350f, 0.800f, 0.480f, 1.000f);
|
||||||
|
|
||||||
|
constexpr auto TRIGGER_COLOR = glm::vec4(0.620f, 0.150f, 0.260f, 1.000f);
|
||||||
|
constexpr auto TRIGGER_COLOR_ACTIVE = glm::vec4(0.820f, 0.250f, 0.380f, 1.000f);
|
||||||
|
constexpr auto TRIGGER_COLOR_HOVERED = glm::vec4(0.950f, 0.330f, 0.490f, 1.000f);
|
||||||
|
|
||||||
|
#define TYPE_LIST \
|
||||||
|
X(NONE, "", "", resource::icon::NONE, glm::vec4(), glm::vec4(), glm::vec4()) \
|
||||||
|
X(ROOT, "Root", "RootAnimation", resource::icon::ROOT, ROOT_COLOR, ROOT_COLOR_ACTIVE, ROOT_COLOR_HOVERED) \
|
||||||
|
X(LAYER, "Layer", "LayerAnimation", resource::icon::LAYER, LAYER_COLOR, LAYER_COLOR_ACTIVE, LAYER_COLOR_HOVERED) \
|
||||||
|
X(NULL_, "Null", "NullAnimation", resource::icon::NULL_, NULL_COLOR, NULL_COLOR_ACTIVE, NULL_COLOR_HOVERED) \
|
||||||
|
X(TRIGGER, "Triggers", "Triggers", resource::icon::TRIGGERS, TRIGGER_COLOR, TRIGGER_COLOR_ACTIVE, \
|
||||||
|
TRIGGER_COLOR_HOVERED)
|
||||||
|
|
||||||
|
enum Type
|
||||||
|
{
|
||||||
|
#define X(symbol, string, itemString, icon, color, colorActive, colorHovered) symbol,
|
||||||
|
TYPE_LIST
|
||||||
|
#undef X
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr const char* TYPE_STRINGS[] = {
|
||||||
|
#define X(symbol, string, itemString, icon, color, colorActive, colorHovered) string,
|
||||||
|
TYPE_LIST
|
||||||
|
#undef X
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr const char* TYPE_ITEM_STRINGS[] = {
|
||||||
|
#define X(symbol, string, itemString, icon, color, colorActive, colorHovered) itemString,
|
||||||
|
TYPE_LIST
|
||||||
|
#undef X
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr resource::icon::Type TYPE_ICONS[] = {
|
||||||
|
#define X(symbol, string, itemString, icon, color, colorActive, colorHovered) icon,
|
||||||
|
TYPE_LIST
|
||||||
|
#undef X
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr glm::vec4 TYPE_COLOR[] = {
|
||||||
|
#define X(symbol, string, itemString, icon, color, colorActive, colorHovered) color,
|
||||||
|
TYPE_LIST
|
||||||
|
#undef X
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr glm::vec4 TYPE_COLOR_ACTIVE[] = {
|
||||||
|
#define X(symbol, string, itemString, icon, color, colorActive, colorHovered) colorActive,
|
||||||
|
TYPE_LIST
|
||||||
|
#undef X
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr glm::vec4 TYPE_COLOR_HOVERED[] = {
|
||||||
|
#define X(symbol, string, itemString, icon, color, colorActive, colorHovered) colorHovered,
|
||||||
|
TYPE_LIST
|
||||||
|
#undef X
|
||||||
|
};
|
||||||
|
|
||||||
|
enum ChangeType
|
||||||
|
{
|
||||||
|
ADD,
|
||||||
|
SUBTRACT,
|
||||||
|
ADJUST
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -114,19 +114,13 @@ namespace anm2ed::anm2
|
|||||||
return xml::document_to_string(document);
|
return xml::document_to_string(document);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Frame::shorten()
|
void Frame::shorten() { delay = glm::clamp(--delay, FRAME_DELAY_MIN, FRAME_DELAY_MAX); }
|
||||||
{
|
|
||||||
delay = glm::clamp(--delay, FRAME_DELAY_MIN, FRAME_DELAY_MAX);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Frame::extend()
|
void Frame::extend() { delay = glm::clamp(++delay, FRAME_DELAY_MIN, FRAME_DELAY_MAX); }
|
||||||
{
|
|
||||||
delay = glm::clamp(++delay, FRAME_DELAY_MIN, FRAME_DELAY_MAX);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Frame::is_visible(Type type)
|
bool Frame::is_visible(Type type)
|
||||||
{
|
{
|
||||||
if (type == anm2::TRIGGER)
|
if (type == TRIGGER)
|
||||||
return isVisible && eventID > -1;
|
return isVisible && eventID > -1;
|
||||||
else
|
else
|
||||||
return isVisible;
|
return isVisible;
|
||||||
|
|||||||
@@ -4,10 +4,7 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <tinyxml2/tinyxml2.h>
|
#include <tinyxml2/tinyxml2.h>
|
||||||
|
|
||||||
#include <glm/glm/vec2.hpp>
|
#include "anm2_type.h"
|
||||||
#include <glm/glm/vec3.hpp>
|
|
||||||
#include <glm/glm/vec4.hpp>
|
|
||||||
|
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
namespace anm2ed::anm2
|
namespace anm2ed::anm2
|
||||||
@@ -15,39 +12,6 @@ namespace anm2ed::anm2
|
|||||||
constexpr auto FRAME_DELAY_MIN = 1;
|
constexpr auto FRAME_DELAY_MIN = 1;
|
||||||
constexpr auto FRAME_DELAY_MAX = 100000;
|
constexpr auto FRAME_DELAY_MAX = 100000;
|
||||||
|
|
||||||
#define TYPE_LIST \
|
|
||||||
X(NONE, "None", "None") \
|
|
||||||
X(ROOT, "Root", "RootAnimation") \
|
|
||||||
X(LAYER, "Layer", "LayerAnimation") \
|
|
||||||
X(NULL_, "Null", "NullAnimation") \
|
|
||||||
X(TRIGGER, "Trigger", "Triggers")
|
|
||||||
|
|
||||||
enum Type
|
|
||||||
{
|
|
||||||
#define X(symbol, string, animationString) symbol,
|
|
||||||
TYPE_LIST
|
|
||||||
#undef X
|
|
||||||
};
|
|
||||||
|
|
||||||
constexpr const char* TYPE_STRINGS[] = {
|
|
||||||
#define X(symbol, string, animationString) string,
|
|
||||||
TYPE_LIST
|
|
||||||
#undef X
|
|
||||||
};
|
|
||||||
|
|
||||||
constexpr const char* TYPE_ANIMATION_STRINGS[] = {
|
|
||||||
#define X(symbol, string, animationString) animationString,
|
|
||||||
TYPE_LIST
|
|
||||||
#undef X
|
|
||||||
};
|
|
||||||
|
|
||||||
enum ChangeType
|
|
||||||
{
|
|
||||||
ADD,
|
|
||||||
SUBTRACT,
|
|
||||||
ADJUST
|
|
||||||
};
|
|
||||||
|
|
||||||
#define MEMBERS \
|
#define MEMBERS \
|
||||||
X(isVisible, bool, true) \
|
X(isVisible, bool, true) \
|
||||||
X(isInterpolated, bool, false) \
|
X(isInterpolated, bool, false) \
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ namespace anm2ed::anm2
|
|||||||
|
|
||||||
XMLElement* Item::to_element(XMLDocument& document, Type type, int id)
|
XMLElement* Item::to_element(XMLDocument& document, Type type, int id)
|
||||||
{
|
{
|
||||||
auto element = document.NewElement(TYPE_ANIMATION_STRINGS[type]);
|
auto element = document.NewElement(TYPE_ITEM_STRINGS[type]);
|
||||||
|
|
||||||
if (type == LAYER) element->SetAttribute("LayerId", id);
|
if (type == LAYER) element->SetAttribute("LayerId", id);
|
||||||
if (type == NULL_) element->SetAttribute("NullId", id);
|
if (type == NULL_) element->SetAttribute("NullId", id);
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include <window.h>
|
#include <window.h>
|
||||||
|
#elif __unix__
|
||||||
|
#else
|
||||||
|
#include "toast.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <format>
|
#include <format>
|
||||||
@@ -57,8 +60,10 @@ namespace anm2ed
|
|||||||
{
|
{
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
ShellExecuteA(NULL, "open", path.c_str(), NULL, NULL, SW_SHOWNORMAL);
|
ShellExecuteA(NULL, "open", path.c_str(), NULL, NULL, SW_SHOWNORMAL);
|
||||||
#else
|
#elif __unix__
|
||||||
system(std::format("xdg-open \"{}\" &", path).c_str());
|
system(std::format("xdg-open \"{}\" &", path).c_str());
|
||||||
|
#else
|
||||||
|
toasts.info("Operation not supported.");
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,9 +9,7 @@ namespace anm2ed::dialog
|
|||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
#define EXECUTABLE_FILTER {"Executable", "exe"}
|
#define EXECUTABLE_FILTER {"Executable", "exe"}
|
||||||
#else
|
#else
|
||||||
#define EXECUTABLE_FILTER \
|
#define EXECUTABLE_FILTER {"Executable", "*"}
|
||||||
{ \
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define FILTER_LIST \
|
#define FILTER_LIST \
|
||||||
|
|||||||
@@ -74,12 +74,9 @@ namespace anm2ed::imgui
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto isRequested = i == manager.pendingSelected;
|
auto isRequested = i == manager.pendingSelected;
|
||||||
|
|
||||||
auto font = isDirty ? font::ITALICS : font::REGULAR;
|
auto font = isDirty ? font::ITALICS : font::REGULAR;
|
||||||
|
|
||||||
auto string = isDirty ? std::format("[Not Saved] {}", document.filename_get().string())
|
auto string = isDirty ? std::format("[Not Saved] {}", document.filename_get().string())
|
||||||
: document.filename_get().string();
|
: document.filename_get().string();
|
||||||
|
|
||||||
auto label = std::format("{}###Document{}", string, i);
|
auto label = std::format("{}###Document{}", string, i);
|
||||||
|
|
||||||
auto flags = isDirty ? ImGuiTabItemFlags_UnsavedDocument : 0;
|
auto flags = isDirty ? ImGuiTabItemFlags_UnsavedDocument : 0;
|
||||||
@@ -89,7 +86,9 @@ namespace anm2ed::imgui
|
|||||||
if (ImGui::BeginTabItem(label.c_str(), &document.isOpen, flags))
|
if (ImGui::BeginTabItem(label.c_str(), &document.isOpen, flags))
|
||||||
{
|
{
|
||||||
manager.set(i);
|
manager.set(i);
|
||||||
|
|
||||||
if (isRequested) manager.pendingSelected = -1;
|
if (isRequested) manager.pendingSelected = -1;
|
||||||
|
|
||||||
ImGui::EndTabItem();
|
ImGui::EndTabItem();
|
||||||
}
|
}
|
||||||
ImGui::PopFont();
|
ImGui::PopFont();
|
||||||
|
|||||||
@@ -166,10 +166,7 @@ namespace anm2ed::imgui
|
|||||||
return (width - (ImGui::GetStyle().ItemSpacing.x * (float)(count - 1))) / (float)count;
|
return (width - (ImGui::GetStyle().ItemSpacing.x * (float)(count - 1))) / (float)count;
|
||||||
}
|
}
|
||||||
|
|
||||||
ImVec2 widget_size_with_row_get(int count, float width)
|
ImVec2 widget_size_with_row_get(int count, float width) { return ImVec2(row_widget_width_get(count, width), 0); }
|
||||||
{
|
|
||||||
return ImVec2(row_widget_width_get(count, width), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
float footer_height_get(int itemCount)
|
float footer_height_get(int itemCount)
|
||||||
{
|
{
|
||||||
@@ -265,17 +262,13 @@ namespace anm2ed::imgui
|
|||||||
return ImGui::Shortcut(string_to_chord(string), flags);
|
return ImGui::Shortcut(string_to_chord(string), flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
MultiSelectStorage::MultiSelectStorage()
|
MultiSelectStorage::MultiSelectStorage() { internal.AdapterSetItemSelected = external_storage_set; }
|
||||||
{
|
|
||||||
internal.AdapterSetItemSelected = external_storage_set;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MultiSelectStorage::start(size_t size)
|
void MultiSelectStorage::start(size_t size, ImGuiMultiSelectFlags flags)
|
||||||
{
|
{
|
||||||
internal.UserData = this;
|
internal.UserData = this;
|
||||||
|
|
||||||
auto io = ImGui::BeginMultiSelect(ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_BoxSelect2d,
|
auto io = ImGui::BeginMultiSelect(flags, this->size(), size);
|
||||||
this->size(), size);
|
|
||||||
internal.ApplyRequests(io);
|
internal.ApplyRequests(io);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,10 +292,7 @@ namespace anm2ed::imgui
|
|||||||
isJustOpened = true;
|
isJustOpened = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PopupHelper::is_open()
|
bool PopupHelper::is_open() { return isOpen; }
|
||||||
{
|
|
||||||
return isOpen;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PopupHelper::trigger()
|
void PopupHelper::trigger()
|
||||||
{
|
{
|
||||||
@@ -322,13 +312,7 @@ namespace anm2ed::imgui
|
|||||||
ImGui::SetNextWindowSize(ImVec2(viewport->Size.x * POPUP_MULTIPLIERS[type], 0));
|
ImGui::SetNextWindowSize(ImVec2(viewport->Size.x * POPUP_MULTIPLIERS[type], 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
void PopupHelper::end()
|
void PopupHelper::end() { isJustOpened = false; }
|
||||||
{
|
|
||||||
isJustOpened = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PopupHelper::close()
|
void PopupHelper::close() { isOpen = false; }
|
||||||
{
|
|
||||||
isOpen = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -190,7 +190,9 @@ namespace anm2ed::imgui
|
|||||||
using std::set<int>::erase;
|
using std::set<int>::erase;
|
||||||
|
|
||||||
MultiSelectStorage();
|
MultiSelectStorage();
|
||||||
void start(size_t);
|
void start(size_t, ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_BoxSelect2d |
|
||||||
|
ImGuiMultiSelectFlags_ClearOnEscape |
|
||||||
|
ImGuiMultiSelectFlags_ScopeWindow);
|
||||||
void finish();
|
void finish();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,23 @@
|
|||||||
#include "taskbar.h"
|
#include "taskbar.h"
|
||||||
|
|
||||||
#include <imgui/imgui.h>
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iterator>
|
||||||
#include <ranges>
|
#include <ranges>
|
||||||
|
|
||||||
|
#include <imgui/imgui.h>
|
||||||
|
|
||||||
#include "math_.h"
|
#include "math_.h"
|
||||||
#include "render.h"
|
#include "render.h"
|
||||||
#include "shader.h"
|
#include "shader.h"
|
||||||
|
#include "toast.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
|
#include "icon.h"
|
||||||
|
|
||||||
using namespace anm2ed::resource;
|
using namespace anm2ed::resource;
|
||||||
using namespace anm2ed::types;
|
using namespace anm2ed::types;
|
||||||
using namespace anm2ed::canvas;
|
using namespace anm2ed::canvas;
|
||||||
@@ -16,10 +26,111 @@ using namespace glm;
|
|||||||
|
|
||||||
namespace anm2ed::imgui
|
namespace anm2ed::imgui
|
||||||
{
|
{
|
||||||
Taskbar::Taskbar() : generate(vec2())
|
#ifdef __unix__
|
||||||
|
|
||||||
|
namespace
|
||||||
{
|
{
|
||||||
|
constexpr std::array<int, 7> ICON_SIZES{16, 24, 32, 48, 64, 128, 256};
|
||||||
|
|
||||||
|
bool ensure_parent_directory_exists(const std::filesystem::path& path)
|
||||||
|
{
|
||||||
|
std::error_code ec;
|
||||||
|
std::filesystem::create_directories(path.parent_path(), ec);
|
||||||
|
if (ec)
|
||||||
|
{
|
||||||
|
toasts.warning(std::format("Could not create directory for {} ({})", path.string(), ec.message()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool write_binary_blob(const std::filesystem::path& path, const std::uint8_t* data, size_t size)
|
||||||
|
{
|
||||||
|
if (!ensure_parent_directory_exists(path)) return false;
|
||||||
|
|
||||||
|
std::ofstream file(path, std::ios::binary | std::ios::trunc);
|
||||||
|
if (!file.is_open())
|
||||||
|
{
|
||||||
|
toasts.warning(std::format("Could not open {} for writing", path.string()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
file.write(reinterpret_cast<const char*>(data), static_cast<std::streamsize>(size));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool run_command_checked(const std::string& command, const std::string& description)
|
||||||
|
{
|
||||||
|
auto result = std::system(command.c_str());
|
||||||
|
if (result != 0)
|
||||||
|
{
|
||||||
|
toasts.warning(std::format("{} failed (exit code {})", description, result));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool install_icon_set(const std::string& context, const std::string& iconName, const std::filesystem::path& path)
|
||||||
|
{
|
||||||
|
bool success = true;
|
||||||
|
for (auto size : ICON_SIZES)
|
||||||
|
{
|
||||||
|
auto command = std::format("xdg-icon-resource install --noupdate --novendor --context {} --size {} \"{}\" {}",
|
||||||
|
context, size, path.string(), iconName);
|
||||||
|
success &= run_command_checked(command, std::format("Install {} icon ({}px)", iconName, size));
|
||||||
|
}
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool uninstall_icon_set(const std::string& context, const std::string& iconName)
|
||||||
|
{
|
||||||
|
bool success = true;
|
||||||
|
for (auto size : ICON_SIZES)
|
||||||
|
{
|
||||||
|
auto command =
|
||||||
|
std::format("xdg-icon-resource uninstall --noupdate --context {} --size {} {}", context, size, iconName);
|
||||||
|
success &= run_command_checked(command, std::format("Remove {} icon ({}px)", iconName, size));
|
||||||
|
}
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool remove_file_if_exists(const std::filesystem::path& path)
|
||||||
|
{
|
||||||
|
std::error_code ec;
|
||||||
|
if (!std::filesystem::exists(path, ec)) return true;
|
||||||
|
std::filesystem::remove(path, ec);
|
||||||
|
if (ec)
|
||||||
|
{
|
||||||
|
toasts.warning(std::format("Could not remove {} ({})", path.string(), ec.message()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constexpr auto MIME_TYPE = R"(<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<mime-type xmlns="http://www.freedesktop.org/standards/shared-mime-info" type="application/x-anm2+xml">
|
||||||
|
<!--Created automatically by update-mime-database. DO NOT EDIT!-->
|
||||||
|
<comment>Anm2 Animation</comment>
|
||||||
|
<glob pattern="*.anm2"/>
|
||||||
|
</mime-type>
|
||||||
|
)";
|
||||||
|
|
||||||
|
constexpr auto DESKTOP_ENTRY_FORMAT = R"([Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=Anm2Ed
|
||||||
|
Icon=anm2ed
|
||||||
|
Comment=Animation editor for .anm2 files
|
||||||
|
Exec={}
|
||||||
|
Terminal=false
|
||||||
|
Categories=Graphics;Development;
|
||||||
|
MimeType=application/x-anm2+xml;
|
||||||
|
)";
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Taskbar::Taskbar() : generate(vec2()) {}
|
||||||
|
|
||||||
void Taskbar::update(Manager& manager, Settings& settings, Resources& resources, Dialog& dialog, bool& isQuitting)
|
void Taskbar::update(Manager& manager, Settings& settings, Resources& resources, Dialog& dialog, bool& isQuitting)
|
||||||
{
|
{
|
||||||
auto document = manager.get();
|
auto document = manager.get();
|
||||||
@@ -119,6 +230,141 @@ namespace anm2ed::imgui
|
|||||||
configurePopup.open();
|
configurePopup.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
|
if (ImGui::MenuItem("Associate .anm2 Files with Editor", nullptr, false,
|
||||||
|
!isAnm2Association || !isAbleToAssociateAnm2))
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
|
||||||
|
#elif __unix__
|
||||||
|
auto cache_icons = []()
|
||||||
|
{
|
||||||
|
auto programIconPath = std::filesystem::path(filesystem::path_icon_get());
|
||||||
|
auto fileIconPath = std::filesystem::path(filesystem::path_icon_file_get());
|
||||||
|
auto iconBytes = std::size(resource::icon::PROGRAM);
|
||||||
|
|
||||||
|
bool isSuccess = write_binary_blob(programIconPath, resource::icon::PROGRAM, iconBytes) &&
|
||||||
|
write_binary_blob(fileIconPath, resource::icon::PROGRAM, iconBytes);
|
||||||
|
|
||||||
|
if (isSuccess)
|
||||||
|
{
|
||||||
|
isSuccess = install_icon_set("apps", "anm2ed", programIconPath) &&
|
||||||
|
install_icon_set("mimetypes", "application-x-anm2+xml", fileIconPath) &&
|
||||||
|
run_command_checked("xdg-icon-resource forceupdate --theme hicolor", "Refresh icon cache");
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_file_if_exists(programIconPath);
|
||||||
|
remove_file_if_exists(fileIconPath);
|
||||||
|
|
||||||
|
if (isSuccess) toasts.info("Cached program and file icons.");
|
||||||
|
return isSuccess;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto register_mime = []()
|
||||||
|
{
|
||||||
|
auto path = std::filesystem::path(filesystem::path_mime_get());
|
||||||
|
if (!ensure_parent_directory_exists(path)) return false;
|
||||||
|
|
||||||
|
std::ofstream file(path, std::ofstream::out | std::ofstream::trunc);
|
||||||
|
if (!file.is_open())
|
||||||
|
{
|
||||||
|
toasts.warning(std::format("Could not write .anm2 MIME type: {}", path.string()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
file << MIME_TYPE;
|
||||||
|
file.close();
|
||||||
|
toasts.info(std::format("Wrote .anm2 MIME type to: {}", path.string()));
|
||||||
|
|
||||||
|
auto mimeRoot = path.parent_path().parent_path();
|
||||||
|
auto command = std::format("update-mime-database \"{}\"", mimeRoot.string());
|
||||||
|
return run_command_checked(command, "Update MIME database");
|
||||||
|
};
|
||||||
|
|
||||||
|
auto register_desktop_entry = []()
|
||||||
|
{
|
||||||
|
auto path = std::filesystem::path(filesystem::path_application_get());
|
||||||
|
if (!ensure_parent_directory_exists(path)) return false;
|
||||||
|
|
||||||
|
std::ofstream file(path, std::ofstream::out | std::ofstream::trunc);
|
||||||
|
if (!file.is_open())
|
||||||
|
{
|
||||||
|
toasts.warning(std::format("Could not write desktop entry: {}", path.string()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto desktopEntry = std::format(DESKTOP_ENTRY_FORMAT, filesystem::path_executable_get());
|
||||||
|
file << desktopEntry;
|
||||||
|
file.close();
|
||||||
|
toasts.info(std::format("Wrote desktop entry to: {}", path.string()));
|
||||||
|
|
||||||
|
auto desktopDir = path.parent_path();
|
||||||
|
auto desktopUpdate =
|
||||||
|
std::format("update-desktop-database \"{}\"", desktopDir.empty() ? "." : desktopDir.string());
|
||||||
|
auto desktopFileName = path.filename().string();
|
||||||
|
auto setDefault = std::format("xdg-mime default {} application/x-anm2+xml",
|
||||||
|
desktopFileName.empty() ? path.string() : desktopFileName);
|
||||||
|
|
||||||
|
auto databaseUpdated = run_command_checked(desktopUpdate, "Update desktop database");
|
||||||
|
auto defaultRegistered = run_command_checked(setDefault, "Set default handler for .anm2");
|
||||||
|
return databaseUpdated && defaultRegistered;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto iconsCached = cache_icons();
|
||||||
|
auto mimeRegistered = register_mime();
|
||||||
|
auto desktopRegistered = register_desktop_entry();
|
||||||
|
|
||||||
|
isAnm2Association = iconsCached && mimeRegistered && desktopRegistered;
|
||||||
|
if (isAnm2Association)
|
||||||
|
toasts.info("Associated .anm2 files with the editor.");
|
||||||
|
else
|
||||||
|
toasts.warning("Association incomplete. Please review the warnings above.");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
ImGui::SetItemTooltip(
|
||||||
|
"Associate .anm2 files with the application (i.e., clicking on them in a file explorer will "
|
||||||
|
"open the application).");
|
||||||
|
|
||||||
|
if (ImGui::MenuItem("Remove .anm2 File Association", nullptr, false,
|
||||||
|
isAnm2Association || !isAbleToAssociateAnm2))
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
|
||||||
|
#elif __unix__
|
||||||
|
{
|
||||||
|
auto iconsRemoved =
|
||||||
|
uninstall_icon_set("apps", "anm2ed") && uninstall_icon_set("mimetypes", "application-x-anm2+xml") &&
|
||||||
|
run_command_checked("xdg-icon-resource forceupdate --theme hicolor", "Refresh icon cache");
|
||||||
|
if (iconsRemoved)
|
||||||
|
toasts.info("Removed cached icons.");
|
||||||
|
else
|
||||||
|
toasts.warning("Could not remove all cached icons.");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto path = std::filesystem::path(filesystem::path_mime_get());
|
||||||
|
auto removed = remove_file_if_exists(path);
|
||||||
|
if (removed) toasts.info(std::format("Removed .anm2 MIME type: {}", path.string()));
|
||||||
|
|
||||||
|
auto mimeRoot = path.parent_path().parent_path();
|
||||||
|
run_command_checked(std::format("update-mime-database \"{}\"", mimeRoot.string()), "Update MIME database");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto path = std::filesystem::path(filesystem::path_application_get());
|
||||||
|
if (remove_file_if_exists(path)) toasts.info(std::format("Removed desktop entry: {}", path.string()));
|
||||||
|
|
||||||
|
auto desktopDir = path.parent_path();
|
||||||
|
run_command_checked(
|
||||||
|
std::format("update-desktop-database \"{}\"", desktopDir.empty() ? "." : desktopDir.string()),
|
||||||
|
"Update desktop database");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
isAnm2Association = false;
|
||||||
|
}
|
||||||
|
ImGui::SetItemTooltip("Unassociate .anm2 files with the application.");
|
||||||
|
|
||||||
ImGui::EndMenu();
|
ImGui::EndMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -546,7 +792,7 @@ namespace anm2ed::imgui
|
|||||||
if (dialogType == dialog::PNG_DIRECTORY_SET)
|
if (dialogType == dialog::PNG_DIRECTORY_SET)
|
||||||
dialog.folder_open(dialogType);
|
dialog.folder_open(dialogType);
|
||||||
else
|
else
|
||||||
dialog.file_open(dialogType);
|
dialog.file_save(dialogType);
|
||||||
}
|
}
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
input_text_string(type == render::PNGS ? "Directory" : "Path", &path);
|
input_text_string(type == render::PNGS ? "Directory" : "Path", &path);
|
||||||
@@ -581,11 +827,16 @@ namespace anm2ed::imgui
|
|||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
|
|
||||||
ImGui::Checkbox("Raw", &isRaw);
|
ImGui::Checkbox("Raw", &isRaw);
|
||||||
ImGui::SetItemTooltip("Record only the layers of the animation.");
|
ImGui::SetItemTooltip("Record only the raw animation; i.e., only its layers, to its bounds.");
|
||||||
|
|
||||||
|
ImGui::SameLine();
|
||||||
|
|
||||||
|
ImGui::Checkbox("Sound", &settings.timelineIsSound);
|
||||||
|
ImGui::SetItemTooltip("Toggle sounds playing with triggers.\nBind sounds to events in the Events window.\nThe "
|
||||||
|
"output animation will use the played sounds.");
|
||||||
|
|
||||||
if (ImGui::Button("Render", widgetSize))
|
if (ImGui::Button("Render", widgetSize))
|
||||||
{
|
{
|
||||||
manager.isRecording = true;
|
|
||||||
manager.isRecordingStart = true;
|
manager.isRecordingStart = true;
|
||||||
playback.time = start;
|
playback.time = start;
|
||||||
playback.isPlaying = true;
|
playback.isPlaying = true;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "canvas.h"
|
#include "canvas.h"
|
||||||
#include "dialog.h"
|
#include "dialog.h"
|
||||||
|
#include "filesystem_.h"
|
||||||
#include "imgui_.h"
|
#include "imgui_.h"
|
||||||
#include "manager.h"
|
#include "manager.h"
|
||||||
#include "resources.h"
|
#include "resources.h"
|
||||||
@@ -20,6 +21,15 @@ namespace anm2ed::imgui
|
|||||||
PopupHelper aboutPopup{PopupHelper("About")};
|
PopupHelper aboutPopup{PopupHelper("About")};
|
||||||
Settings editSettings{};
|
Settings editSettings{};
|
||||||
int selectedShortcut{-1};
|
int selectedShortcut{-1};
|
||||||
|
|
||||||
|
#if defined(_WIN32) || defined(__unix__)
|
||||||
|
bool isAbleToAssociateAnm2 = true;
|
||||||
|
#else
|
||||||
|
bool isAbleToAssociateAnm2 = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool isAnm2Association = std::filesystem::exists(util::filesystem::path_application_get());
|
||||||
|
|
||||||
bool isQuittingMode{};
|
bool isQuittingMode{};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ using namespace anm2ed::types;
|
|||||||
|
|
||||||
namespace anm2ed::imgui
|
namespace anm2ed::imgui
|
||||||
{
|
{
|
||||||
constexpr auto LIFETIME = 3.0f;
|
constexpr auto LIFETIME = 4.0f;
|
||||||
|
constexpr auto FADE_THRESHOLD = 1.0f;
|
||||||
|
|
||||||
Toast::Toast(const std::string& message)
|
Toast::Toast(const std::string& message)
|
||||||
{
|
{
|
||||||
@@ -30,8 +31,6 @@ namespace anm2ed::imgui
|
|||||||
{
|
{
|
||||||
Toast& toast = toasts[i];
|
Toast& toast = toasts[i];
|
||||||
|
|
||||||
toast.lifetime -= ImGui::GetIO().DeltaTime;
|
|
||||||
|
|
||||||
if (toast.lifetime <= 0.0f)
|
if (toast.lifetime <= 0.0f)
|
||||||
{
|
{
|
||||||
toasts.erase(toasts.begin() + i);
|
toasts.erase(toasts.begin() + i);
|
||||||
@@ -39,7 +38,9 @@ namespace anm2ed::imgui
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto alpha = toast.lifetime / LIFETIME;
|
toast.lifetime -= ImGui::GetIO().DeltaTime;
|
||||||
|
|
||||||
|
auto alpha = toast.lifetime <= FADE_THRESHOLD ? toast.lifetime / FADE_THRESHOLD : 1.0f;
|
||||||
borderColor.w = alpha;
|
borderColor.w = alpha;
|
||||||
textColor.w = alpha;
|
textColor.w = alpha;
|
||||||
|
|
||||||
@@ -57,6 +58,8 @@ namespace anm2ed::imgui
|
|||||||
{
|
{
|
||||||
ImGui::TextUnformatted(toast.message.c_str());
|
ImGui::TextUnformatted(toast.message.c_str());
|
||||||
position.y -= ImGui::GetWindowSize().y + ImGui::GetStyle().ItemSpacing.y;
|
position.y -= ImGui::GetWindowSize().y + ImGui::GetStyle().ItemSpacing.y;
|
||||||
|
|
||||||
|
if (ImGui::IsWindowHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) toast.lifetime = 0.0f;
|
||||||
}
|
}
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
ImGui::PopStyleColor(2);
|
ImGui::PopStyleColor(2);
|
||||||
|
|||||||
@@ -42,51 +42,22 @@ namespace anm2ed::imgui
|
|||||||
auto& isSound = settings.timelineIsSound;
|
auto& isSound = settings.timelineIsSound;
|
||||||
auto& isOnlyShowLayers = settings.timelineIsOnlyShowLayers;
|
auto& isOnlyShowLayers = settings.timelineIsOnlyShowLayers;
|
||||||
|
|
||||||
if (isSound && !anm2.content.sounds.empty())
|
if (!anm2.content.sounds.empty() && isSound)
|
||||||
if (auto animation = document.animation_get(); animation)
|
{
|
||||||
if (animation->triggers.isVisible && !isOnlyShowLayers)
|
if (auto animation = document.animation_get();
|
||||||
if (auto trigger = animation->triggers.frame_generate(playback.time, anm2::TRIGGER);
|
animation && animation->triggers.isVisible && (!isOnlyShowLayers || manager.isRecording))
|
||||||
trigger.is_visible(anm2::TRIGGER))
|
{
|
||||||
if (anm2.content.sounds.contains(trigger.soundID)) anm2.content.sounds[trigger.soundID].audio.play();
|
if (auto trigger = animation->triggers.frame_generate(playback.time, anm2::TRIGGER);
|
||||||
|
trigger.is_visible(anm2::TRIGGER))
|
||||||
|
if (anm2.content.sounds.contains(trigger.soundID)) anm2.content.sounds[trigger.soundID].audio.play(mixer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
document.reference.frameTime = playback.time;
|
document.reference.frameTime = playback.time;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (manager.isRecording)
|
if (manager.isRecording)
|
||||||
{
|
{
|
||||||
if (manager.isRecordingStart)
|
|
||||||
{
|
|
||||||
if (settings.renderIsRawAnimation)
|
|
||||||
{
|
|
||||||
savedSettings = settings;
|
|
||||||
settings.previewBackgroundColor = vec4();
|
|
||||||
settings.previewIsGrid = false;
|
|
||||||
settings.previewIsAxes = false;
|
|
||||||
settings.timelineIsOnlyShowLayers = true;
|
|
||||||
|
|
||||||
savedZoom = zoom;
|
|
||||||
savedPan = pan;
|
|
||||||
|
|
||||||
if (auto animation = document.animation_get())
|
|
||||||
{
|
|
||||||
auto rect = animation->rect(isRootTransform);
|
|
||||||
size = vec2(rect.z, rect.w) * scale;
|
|
||||||
set_to_rect(zoom, pan, rect);
|
|
||||||
}
|
|
||||||
|
|
||||||
isSizeTrySet = false;
|
|
||||||
|
|
||||||
bind();
|
|
||||||
viewport_set();
|
|
||||||
clear(settings.previewBackgroundColor);
|
|
||||||
unbind();
|
|
||||||
}
|
|
||||||
|
|
||||||
manager.isRecordingStart = false;
|
|
||||||
|
|
||||||
return; // Need to wait an additional frame. Kind of hacky, but oh well.
|
|
||||||
}
|
|
||||||
|
|
||||||
auto pixels = pixels_get();
|
auto pixels = pixels_get();
|
||||||
renderFrames.push_back(Texture(pixels.data(), size));
|
renderFrames.push_back(Texture(pixels.data(), size));
|
||||||
|
|
||||||
@@ -120,7 +91,7 @@ namespace anm2ed::imgui
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (animation_render(ffmpegPath, path, renderFrames, (render::Type)type, size, anm2.info.fps))
|
if (animation_render(ffmpegPath, path, renderFrames, audioStream, (render::Type)type, size, anm2.info.fps))
|
||||||
toasts.info(std::format("Exported rendered animation to: {}", path));
|
toasts.info(std::format("Exported rendered animation to: {}", path));
|
||||||
else
|
else
|
||||||
toasts.warning(std::format("Could not output rendered animation: {}", path));
|
toasts.warning(std::format("Could not output rendered animation: {}", path));
|
||||||
@@ -133,12 +104,49 @@ namespace anm2ed::imgui
|
|||||||
settings = savedSettings;
|
settings = savedSettings;
|
||||||
isSizeTrySet = true;
|
isSizeTrySet = true;
|
||||||
|
|
||||||
|
if (settings.timelineIsSound) audioStream.capture_end(mixer);
|
||||||
|
|
||||||
playback.isPlaying = false;
|
playback.isPlaying = false;
|
||||||
playback.isFinished = false;
|
playback.isFinished = false;
|
||||||
manager.isRecording = false;
|
manager.isRecording = false;
|
||||||
manager.progressPopup.close();
|
manager.progressPopup.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (manager.isRecordingStart)
|
||||||
|
{
|
||||||
|
savedSettings = settings;
|
||||||
|
|
||||||
|
if (settings.timelineIsSound) audioStream.capture_begin(mixer);
|
||||||
|
|
||||||
|
if (settings.renderIsRawAnimation)
|
||||||
|
{
|
||||||
|
settings.previewBackgroundColor = vec4();
|
||||||
|
settings.previewIsGrid = false;
|
||||||
|
settings.previewIsAxes = false;
|
||||||
|
settings.timelineIsOnlyShowLayers = true;
|
||||||
|
|
||||||
|
savedZoom = zoom;
|
||||||
|
savedPan = pan;
|
||||||
|
|
||||||
|
if (auto animation = document.animation_get())
|
||||||
|
{
|
||||||
|
if (auto rect = animation->rect(isRootTransform); rect != vec4(-1.0f))
|
||||||
|
{
|
||||||
|
size_set(vec2(rect.w, rect.z) * scale);
|
||||||
|
set_to_rect(zoom, pan, rect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isSizeTrySet = false;
|
||||||
|
|
||||||
|
bind();
|
||||||
|
clear(settings.previewBackgroundColor);
|
||||||
|
unbind();
|
||||||
|
}
|
||||||
|
|
||||||
|
manager.isRecordingStart = false;
|
||||||
|
manager.isRecording = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AnimationPreview::update(Manager& manager, Settings& settings, Resources& resources)
|
void AnimationPreview::update(Manager& manager, Settings& settings, Resources& resources)
|
||||||
@@ -270,8 +278,8 @@ namespace anm2ed::imgui
|
|||||||
auto cursorScreenPos = ImGui::GetCursorScreenPos();
|
auto cursorScreenPos = ImGui::GetCursorScreenPos();
|
||||||
|
|
||||||
if (isSizeTrySet) size_set(to_vec2(ImGui::GetContentRegionAvail()));
|
if (isSizeTrySet) size_set(to_vec2(ImGui::GetContentRegionAvail()));
|
||||||
bind();
|
|
||||||
viewport_set();
|
viewport_set();
|
||||||
|
bind();
|
||||||
clear(backgroundColor);
|
clear(backgroundColor);
|
||||||
if (isAxes) axes_render(shaderAxes, zoom, pan, axesColor);
|
if (isAxes) axes_render(shaderAxes, zoom, pan, axesColor);
|
||||||
if (isGrid) grid_render(shaderGrid, zoom, pan, gridSize, gridOffset, gridColor);
|
if (isGrid) grid_render(shaderGrid, zoom, pan, gridSize, gridOffset, gridColor);
|
||||||
@@ -412,7 +420,7 @@ namespace anm2ed::imgui
|
|||||||
|
|
||||||
isPreviewHovered = ImGui::IsItemHovered();
|
isPreviewHovered = ImGui::IsItemHovered();
|
||||||
|
|
||||||
if (animation && animation->triggers.isVisible && !isOnlyShowLayers)
|
if (animation && animation->triggers.isVisible && !isOnlyShowLayers && !manager.isRecording)
|
||||||
{
|
{
|
||||||
if (auto trigger = animation->triggers.frame_generate(frameTime, anm2::TRIGGER);
|
if (auto trigger = animation->triggers.frame_generate(frameTime, anm2::TRIGGER);
|
||||||
trigger.isVisible && trigger.eventID > -1)
|
trigger.isVisible && trigger.eventID > -1)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "audio_stream.h"
|
||||||
#include "canvas.h"
|
#include "canvas.h"
|
||||||
#include "manager.h"
|
#include "manager.h"
|
||||||
#include "resources.h"
|
#include "resources.h"
|
||||||
@@ -9,6 +10,8 @@ namespace anm2ed::imgui
|
|||||||
{
|
{
|
||||||
class AnimationPreview : public Canvas
|
class AnimationPreview : public Canvas
|
||||||
{
|
{
|
||||||
|
MIX_Mixer* mixer = MIX_CreateMixerDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, nullptr);
|
||||||
|
AudioStream audioStream = AudioStream(mixer);
|
||||||
bool isPreviewHovered{};
|
bool isPreviewHovered{};
|
||||||
bool isSizeTrySet{true};
|
bool isSizeTrySet{true};
|
||||||
Settings savedSettings{};
|
Settings savedSettings{};
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ namespace anm2ed::imgui
|
|||||||
|
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2());
|
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2());
|
||||||
|
|
||||||
selection.start(anm2.content.spritesheets.size());
|
selection.start(anm2.content.spritesheets.size(), ImGuiMultiSelectFlags_ClearOnEscape);
|
||||||
|
|
||||||
for (auto& [id, spritesheet] : anm2.content.spritesheets)
|
for (auto& [id, spritesheet] : anm2.content.spritesheets)
|
||||||
{
|
{
|
||||||
@@ -168,16 +168,16 @@ namespace anm2ed::imgui
|
|||||||
|
|
||||||
context_menu();
|
context_menu();
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::EndChild();
|
ImGui::EndChild();
|
||||||
|
|
||||||
ImGui::PopID();
|
ImGui::PopID();
|
||||||
}
|
}
|
||||||
|
|
||||||
selection.finish();
|
|
||||||
|
|
||||||
ImGui::PopStyleVar(2);
|
ImGui::PopStyleVar(2);
|
||||||
|
|
||||||
context_menu();
|
context_menu();
|
||||||
|
selection.finish();
|
||||||
}
|
}
|
||||||
ImGui::EndChild();
|
ImGui::EndChild();
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "clipboard.h"
|
#include "clipboard.h"
|
||||||
#include "document.h"
|
|
||||||
#include "manager.h"
|
#include "manager.h"
|
||||||
#include "resources.h"
|
#include "resources.h"
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
@@ -25,14 +24,6 @@ namespace anm2ed::imgui
|
|||||||
ImDrawList* pickerLineDrawList{};
|
ImDrawList* pickerLineDrawList{};
|
||||||
ImGuiStyle style{};
|
ImGuiStyle style{};
|
||||||
|
|
||||||
void context_menu(Document&, Settings&, Clipboard&);
|
|
||||||
void item_child(Manager&, Document&, anm2::Animation*, Settings&, Resources&, Clipboard&, anm2::Type, int, int&);
|
|
||||||
void items_child(Manager&, Document&, anm2::Animation*, Settings&, Resources&, Clipboard&);
|
|
||||||
void frame_child(Document&, anm2::Animation*, Settings&, Resources&, Clipboard&, anm2::Type, int, int&, float);
|
|
||||||
void frames_child(Document&, anm2::Animation*, Settings&, Resources&, Clipboard&);
|
|
||||||
|
|
||||||
void popups(Document&, anm2::Animation*, Settings&);
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void update(Manager&, Settings&, Resources&, Clipboard&);
|
void update(Manager&, Settings&, Resources&, Clipboard&);
|
||||||
};
|
};
|
||||||
|
|||||||
131
src/loader.cpp
131
src/loader.cpp
@@ -1,5 +1,7 @@
|
|||||||
#include "loader.h"
|
#include "loader.h"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
#include <imgui/backends/imgui_impl_opengl3.h>
|
#include <imgui/backends/imgui_impl_opengl3.h>
|
||||||
#include <imgui/backends/imgui_impl_sdl3.h>
|
#include <imgui/backends/imgui_impl_sdl3.h>
|
||||||
|
|
||||||
@@ -8,11 +10,57 @@
|
|||||||
#include "filesystem_.h"
|
#include "filesystem_.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
|
||||||
|
#include "socket.h"
|
||||||
|
|
||||||
using namespace anm2ed::types;
|
using namespace anm2ed::types;
|
||||||
using namespace anm2ed::util;
|
using namespace anm2ed::util;
|
||||||
|
|
||||||
namespace anm2ed
|
namespace anm2ed
|
||||||
{
|
{
|
||||||
|
constexpr auto SOCKET_ADDRESS = "127.0.0.1";
|
||||||
|
constexpr auto SOCKET_PORT = 11414;
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
bool socket_paths_send(Socket& socket, const std::vector<std::string>& paths)
|
||||||
|
{
|
||||||
|
uint32_t count = htonl(static_cast<uint32_t>(paths.size()));
|
||||||
|
if (!socket.send(&count, sizeof(count))) return false;
|
||||||
|
|
||||||
|
for (const auto& path : paths)
|
||||||
|
{
|
||||||
|
uint32_t length = htonl(static_cast<uint32_t>(path.size()));
|
||||||
|
if (!socket.send(&length, sizeof(length))) return false;
|
||||||
|
if (!path.empty() && !socket.send(path.data(), path.size())) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> socket_paths_receive(Socket& socket)
|
||||||
|
{
|
||||||
|
uint32_t count{};
|
||||||
|
if (!socket.receive(&count, sizeof(count))) return {};
|
||||||
|
count = ntohl(count);
|
||||||
|
|
||||||
|
std::vector<std::string> paths;
|
||||||
|
paths.reserve(count);
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < count; ++i)
|
||||||
|
{
|
||||||
|
uint32_t length{};
|
||||||
|
if (!socket.receive(&length, sizeof(length))) return {};
|
||||||
|
length = ntohl(length);
|
||||||
|
|
||||||
|
std::string path(length, '\0');
|
||||||
|
if (length > 0 && !socket.receive(path.data(), length)) return {};
|
||||||
|
paths.emplace_back(std::move(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
return paths;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::string Loader::settings_path()
|
std::string Loader::settings_path()
|
||||||
{
|
{
|
||||||
return filesystem::path_preferences_get() + "settings.ini";
|
return filesystem::path_preferences_get() + "settings.ini";
|
||||||
@@ -23,6 +71,45 @@ namespace anm2ed
|
|||||||
for (int i = 1; i < argc; i++)
|
for (int i = 1; i < argc; i++)
|
||||||
arguments.emplace_back(argv[i]);
|
arguments.emplace_back(argv[i]);
|
||||||
|
|
||||||
|
Socket testSocket;
|
||||||
|
if (!testSocket.open(SERVER))
|
||||||
|
logger.warning(std::format("Failed to open socket; single instancing will not work."));
|
||||||
|
|
||||||
|
bool isPrimaryInstance = false;
|
||||||
|
|
||||||
|
if (testSocket.bind({SOCKET_ADDRESS, SOCKET_PORT}))
|
||||||
|
{
|
||||||
|
socket = std::move(testSocket);
|
||||||
|
|
||||||
|
if (!socket.listen())
|
||||||
|
logger.warning("Could not listen on socket; single instancing disabled.");
|
||||||
|
else
|
||||||
|
{
|
||||||
|
isPrimaryInstance = true;
|
||||||
|
isSocketThread = true;
|
||||||
|
logger.info(std::format("Opened socket at {}:{}", SOCKET_ADDRESS, SOCKET_PORT));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logger.info(std::format("Existing instance of program exists; passing arguments..."));
|
||||||
|
Socket clientSocket;
|
||||||
|
if (!clientSocket.open(CLIENT))
|
||||||
|
logger.warning("Could not open client socket to forward arguments.");
|
||||||
|
else if (!clientSocket.connect({SOCKET_ADDRESS, SOCKET_PORT}))
|
||||||
|
logger.warning("Could not connect to existing instance.");
|
||||||
|
else if (!socket_paths_send(clientSocket, arguments))
|
||||||
|
logger.warning("Failed to transfer arguments to existing instance.");
|
||||||
|
else
|
||||||
|
logger.info("Sent arguments to existing instance. Exiting.");
|
||||||
|
|
||||||
|
if (!isPrimaryInstance)
|
||||||
|
{
|
||||||
|
isError = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
settings = Settings(settings_path());
|
settings = Settings(settings_path());
|
||||||
|
|
||||||
if (!SDL_Init(SDL_INIT_VIDEO))
|
if (!SDL_Init(SDL_INIT_VIDEO))
|
||||||
@@ -93,10 +180,54 @@ namespace anm2ed
|
|||||||
io.ConfigWindowsMoveFromTitleBarOnly = true;
|
io.ConfigWindowsMoveFromTitleBarOnly = true;
|
||||||
|
|
||||||
ImGui::LoadIniSettingsFromDisk(settings_path().c_str());
|
ImGui::LoadIniSettingsFromDisk(settings_path().c_str());
|
||||||
|
|
||||||
|
if (isSocketThread)
|
||||||
|
{
|
||||||
|
isSocketRunning = true;
|
||||||
|
socketThread = std::thread(
|
||||||
|
[this]()
|
||||||
|
{
|
||||||
|
while (isSocketRunning)
|
||||||
|
{
|
||||||
|
auto client = socket.accept();
|
||||||
|
if (!client.is_valid())
|
||||||
|
{
|
||||||
|
if (!isSocketRunning) break;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto paths = socket_paths_receive(client);
|
||||||
|
for (auto& path : paths)
|
||||||
|
{
|
||||||
|
if (path.empty()) continue;
|
||||||
|
SDL_Event event{};
|
||||||
|
event.type = SDL_EVENT_DROP_FILE;
|
||||||
|
event.drop.data = SDL_strdup(path.c_str());
|
||||||
|
event.drop.windowID = window ? SDL_GetWindowID(window) : 0;
|
||||||
|
SDL_PushEvent(&event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Loader::~Loader()
|
Loader::~Loader()
|
||||||
{
|
{
|
||||||
|
if (isSocketThread)
|
||||||
|
{
|
||||||
|
isSocketRunning = false;
|
||||||
|
|
||||||
|
if (socket.is_valid())
|
||||||
|
{
|
||||||
|
Socket wakeSocket;
|
||||||
|
if (wakeSocket.open(CLIENT) && wakeSocket.connect({SOCKET_ADDRESS, SOCKET_PORT}))
|
||||||
|
socket_paths_send(wakeSocket, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.close();
|
||||||
|
if (socketThread.joinable()) socketThread.join();
|
||||||
|
}
|
||||||
|
|
||||||
if (ImGui::GetCurrentContext())
|
if (ImGui::GetCurrentContext())
|
||||||
{
|
{
|
||||||
settings.save(settings_path(), ImGui::SaveIniSettingsToMemory(nullptr));
|
settings.save(settings_path(), ImGui::SaveIniSettingsToMemory(nullptr));
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
|
#include "socket.h"
|
||||||
|
|
||||||
namespace anm2ed
|
namespace anm2ed
|
||||||
{
|
{
|
||||||
@@ -14,11 +17,15 @@ namespace anm2ed
|
|||||||
std::string settings_path();
|
std::string settings_path();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
Socket socket{};
|
||||||
|
std::thread socketThread{};
|
||||||
|
std::atomic_bool isSocketRunning{};
|
||||||
SDL_Window* window{};
|
SDL_Window* window{};
|
||||||
SDL_GLContext glContext{};
|
SDL_GLContext glContext{};
|
||||||
Settings settings;
|
Settings settings;
|
||||||
std::vector<std::string> arguments;
|
std::vector<std::string> arguments;
|
||||||
bool isError{};
|
bool isError{};
|
||||||
|
bool isSocketThread{};
|
||||||
|
|
||||||
Loader(int, const char**);
|
Loader(int, const char**);
|
||||||
~Loader();
|
~Loader();
|
||||||
|
|||||||
@@ -14,20 +14,9 @@ namespace anm2ed
|
|||||||
{
|
{
|
||||||
constexpr std::size_t RECENT_LIMIT = 10;
|
constexpr std::size_t RECENT_LIMIT = 10;
|
||||||
|
|
||||||
std::filesystem::path Manager::recent_files_path_get()
|
std::filesystem::path Manager::recent_files_path_get() { return filesystem::path_preferences_get() + "recent.txt"; }
|
||||||
{
|
std::filesystem::path Manager::autosave_path_get() { return filesystem::path_preferences_get() + "autosave.txt"; }
|
||||||
return filesystem::path_preferences_get() + "recent.txt";
|
std::filesystem::path Manager::autosave_directory_get() { return filesystem::path_preferences_get() + "autosave"; }
|
||||||
}
|
|
||||||
|
|
||||||
std::filesystem::path Manager::autosave_path_get()
|
|
||||||
{
|
|
||||||
return filesystem::path_preferences_get() + "autosave.txt";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::filesystem::path Manager::autosave_directory_get()
|
|
||||||
{
|
|
||||||
return filesystem::path_preferences_get() + "autosave";
|
|
||||||
}
|
|
||||||
|
|
||||||
Manager::Manager()
|
Manager::Manager()
|
||||||
{
|
{
|
||||||
@@ -35,10 +24,7 @@ namespace anm2ed
|
|||||||
autosave_files_load();
|
autosave_files_load();
|
||||||
}
|
}
|
||||||
|
|
||||||
Document* Manager::get(int index)
|
Document* Manager::get(int index) { return vector::find(documents, index > -1 ? index : selected); }
|
||||||
{
|
|
||||||
return vector::find(documents, index > -1 ? index : selected);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Manager::open(const std::string& path, bool isNew, bool isRecent)
|
void Manager::open(const std::string& path, bool isNew, bool isRecent)
|
||||||
{
|
{
|
||||||
@@ -68,10 +54,7 @@ namespace anm2ed
|
|||||||
toasts.info(std::format("Opened document: {}", path));
|
toasts.info(std::format("Opened document: {}", path));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Manager::new_(const std::string& path)
|
void Manager::new_(const std::string& path) { open(path, true); }
|
||||||
{
|
|
||||||
open(path, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Manager::save(int index, const std::string& path)
|
void Manager::save(int index, const std::string& path)
|
||||||
{
|
{
|
||||||
@@ -83,10 +66,7 @@ namespace anm2ed
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Manager::save(const std::string& path)
|
void Manager::save(const std::string& path) { save(selected, path); }
|
||||||
{
|
|
||||||
save(selected, path);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Manager::autosave(Document& document)
|
void Manager::autosave(Document& document)
|
||||||
{
|
{
|
||||||
@@ -155,15 +135,9 @@ namespace anm2ed
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Manager::layer_properties_trigger()
|
void Manager::layer_properties_trigger() { layerPropertiesPopup.trigger(); }
|
||||||
{
|
|
||||||
layerPropertiesPopup.trigger();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Manager::layer_properties_end()
|
void Manager::layer_properties_end() { layerPropertiesPopup.end(); }
|
||||||
{
|
|
||||||
layerPropertiesPopup.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Manager::layer_properties_close()
|
void Manager::layer_properties_close()
|
||||||
{
|
{
|
||||||
@@ -186,15 +160,9 @@ namespace anm2ed
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Manager::null_properties_trigger()
|
void Manager::null_properties_trigger() { nullPropertiesPopup.trigger(); }
|
||||||
{
|
|
||||||
nullPropertiesPopup.trigger();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Manager::null_properties_end()
|
void Manager::null_properties_end() { nullPropertiesPopup.end(); }
|
||||||
{
|
|
||||||
nullPropertiesPopup.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Manager::null_properties_close()
|
void Manager::null_properties_close()
|
||||||
{
|
{
|
||||||
@@ -313,8 +281,5 @@ namespace anm2ed
|
|||||||
autosave_files_write();
|
autosave_files_write();
|
||||||
}
|
}
|
||||||
|
|
||||||
Manager::~Manager()
|
Manager::~Manager() { autosave_files_clear(); }
|
||||||
{
|
|
||||||
autosave_files_clear();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
#include "render.h"
|
#include "render.h"
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <filesystem>
|
||||||
#include <format>
|
#include <format>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
@@ -9,7 +12,7 @@
|
|||||||
#define PCLOSE _pclose
|
#define PCLOSE _pclose
|
||||||
#define PWRITE_MODE "wb"
|
#define PWRITE_MODE "wb"
|
||||||
#define PREAD_MODE "r"
|
#define PREAD_MODE "r"
|
||||||
#else
|
#elif __unix__
|
||||||
#define POPEN popen
|
#define POPEN popen
|
||||||
#define PCLOSE pclose
|
#define PCLOSE pclose
|
||||||
#define PWRITE_MODE "w"
|
#define PWRITE_MODE "w"
|
||||||
@@ -25,44 +28,87 @@ namespace anm2ed
|
|||||||
{
|
{
|
||||||
constexpr auto FFMPEG_POPEN_ERROR = "popen() (for FFmpeg) failed!\n{}";
|
constexpr auto FFMPEG_POPEN_ERROR = "popen() (for FFmpeg) failed!\n{}";
|
||||||
|
|
||||||
constexpr auto GIF_FORMAT = "\"{0}\" -y "
|
|
||||||
"-f rawvideo -pix_fmt rgba -s {1}x{2} -r {3} -i pipe:0 "
|
|
||||||
"-lavfi \"split[s0][s1];"
|
|
||||||
"[s0]palettegen=stats_mode=full[p];"
|
|
||||||
"[s1][p]paletteuse=dither=floyd_steinberg\" "
|
|
||||||
"-loop 0 \"{4}\"";
|
|
||||||
|
|
||||||
constexpr auto WEBM_FORMAT = "\"{0}\" -y "
|
|
||||||
"-f rawvideo -pix_fmt rgba -s {1}x{2} -r {3} -i pipe:0 "
|
|
||||||
"-c:v libvpx-vp9 -crf 30 -b:v 0 -pix_fmt yuva420p -row-mt 1 -threads 0 -speed 2 "
|
|
||||||
"-auto-alt-ref 0 -an \"{4}\"";
|
|
||||||
|
|
||||||
constexpr auto* MP4_FORMAT = "\"{0}\" -y "
|
|
||||||
"-f rawvideo -pix_fmt rgba -s {1}x{2} -r {3} -i pipe:0 "
|
|
||||||
"-vf \"format=yuv420p,scale=trunc(iw/2)*2:trunc(ih/2)*2\" "
|
|
||||||
"-c:v libx265 -crf 20 -preset slow "
|
|
||||||
"-tag:v hvc1 -movflags +faststart -an \"{4}\"";
|
|
||||||
|
|
||||||
bool animation_render(const std::string& ffmpegPath, const std::string& path, std::vector<Texture>& frames,
|
bool animation_render(const std::string& ffmpegPath, const std::string& path, std::vector<Texture>& frames,
|
||||||
render::Type type, ivec2 size, int fps)
|
AudioStream& audioStream, render::Type type, ivec2 size, int fps)
|
||||||
{
|
{
|
||||||
if (frames.empty() || size.x <= 0 || size.y <= 0 || fps <= 0 || ffmpegPath.empty() || path.empty()) return false;
|
if (frames.empty() || size.x <= 0 || size.y <= 0 || fps <= 0 || ffmpegPath.empty() || path.empty()) return false;
|
||||||
|
|
||||||
|
std::filesystem::path audioPath{};
|
||||||
|
std::string audioInputArguments{};
|
||||||
|
std::string audioOutputArguments{"-an"};
|
||||||
std::string command{};
|
std::string command{};
|
||||||
|
|
||||||
|
auto remove_audio_file = [&]()
|
||||||
|
{
|
||||||
|
if (!audioPath.empty())
|
||||||
|
{
|
||||||
|
std::error_code ec;
|
||||||
|
std::filesystem::remove(audioPath, ec);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (type != render::GIF && !audioStream.stream.empty() && audioStream.spec.freq > 0 &&
|
||||||
|
audioStream.spec.channels > 0)
|
||||||
|
{
|
||||||
|
audioPath = std::filesystem::temp_directory_path() / std::format("{}.f32", path);
|
||||||
|
|
||||||
|
std::ofstream audioFile(audioPath, std::ios::binary);
|
||||||
|
|
||||||
|
if (audioFile)
|
||||||
|
{
|
||||||
|
auto data = (const char*)audioStream.stream.data();
|
||||||
|
auto byteCount = audioStream.stream.size() * sizeof(float);
|
||||||
|
audioFile.write(data, byteCount);
|
||||||
|
audioFile.close();
|
||||||
|
|
||||||
|
audioInputArguments = std::format("-f f32le -ar {0} -ac {1} -i \"{2}\"", audioStream.spec.freq,
|
||||||
|
audioStream.spec.channels, audioPath.string());
|
||||||
|
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case render::WEBM:
|
||||||
|
audioOutputArguments = "-c:a libopus -b:a 160k -shortest";
|
||||||
|
break;
|
||||||
|
case render::MP4:
|
||||||
|
audioOutputArguments = "-c:a aac -b:a 192k -shortest";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logger.warning("Failed to open temporary audio file; exporting video without audio.");
|
||||||
|
remove_audio_file();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
command = std::format("\"{0}\" -y -f rawvideo -pix_fmt rgba -s {1}x{2} -r {3} -i pipe:0", ffmpegPath, size.x,
|
||||||
|
size.y, fps);
|
||||||
|
|
||||||
|
if (!audioInputArguments.empty()) command += " " + audioInputArguments;
|
||||||
|
|
||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
case render::GIF:
|
case render::GIF:
|
||||||
command = std::format(GIF_FORMAT, ffmpegPath, size.x, size.y, fps, path);
|
command +=
|
||||||
|
" -lavfi \"split[s0][s1];[s0]palettegen=stats_mode=full[p];[s1][p]paletteuse=dither=floyd_steinberg\""
|
||||||
|
" -loop 0";
|
||||||
|
command += std::format(" \"{}\"", path);
|
||||||
break;
|
break;
|
||||||
case render::WEBM:
|
case render::WEBM:
|
||||||
command = std::format(WEBM_FORMAT, ffmpegPath, size.x, size.y, fps, path);
|
command += " -c:v libvpx-vp9 -crf 30 -b:v 0 -pix_fmt yuva420p -row-mt 1 -threads 0 -speed 2 -auto-alt-ref 0";
|
||||||
|
if (!audioOutputArguments.empty()) command += " " + audioOutputArguments;
|
||||||
|
command += std::format(" \"{}\"", path);
|
||||||
break;
|
break;
|
||||||
case render::MP4:
|
case render::MP4:
|
||||||
command = std::format(MP4_FORMAT, ffmpegPath, size.x, size.y, fps, path);
|
command += " -vf \"format=yuv420p,scale=trunc(iw/2)*2:trunc(ih/2)*2\" -c:v libx265 -crf 20 -preset slow"
|
||||||
|
" -tag:v hvc1 -movflags +faststart";
|
||||||
|
if (!audioOutputArguments.empty()) command += " " + audioOutputArguments;
|
||||||
|
command += std::format(" \"{}\"", path);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if _WIN32
|
#if _WIN32
|
||||||
@@ -75,6 +121,7 @@ namespace anm2ed
|
|||||||
|
|
||||||
if (!fp)
|
if (!fp)
|
||||||
{
|
{
|
||||||
|
remove_audio_file();
|
||||||
logger.error(std::format(FFMPEG_POPEN_ERROR, strerror(errno)));
|
logger.error(std::format(FFMPEG_POPEN_ERROR, strerror(errno)));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -85,12 +132,14 @@ namespace anm2ed
|
|||||||
|
|
||||||
if (fwrite(frame.pixels.data(), 1, frameSize, fp) != frameSize)
|
if (fwrite(frame.pixels.data(), 1, frameSize, fp) != frameSize)
|
||||||
{
|
{
|
||||||
|
remove_audio_file();
|
||||||
PCLOSE(fp);
|
PCLOSE(fp);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto code = PCLOSE(fp);
|
auto code = PCLOSE(fp);
|
||||||
|
remove_audio_file();
|
||||||
return (code == 0);
|
return (code == 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "audio_stream.h"
|
||||||
#include "texture.h"
|
#include "texture.h"
|
||||||
|
|
||||||
namespace anm2ed::render
|
namespace anm2ed::render
|
||||||
@@ -33,6 +34,6 @@ namespace anm2ed::render
|
|||||||
|
|
||||||
namespace anm2ed
|
namespace anm2ed
|
||||||
{
|
{
|
||||||
bool animation_render(const std::string&, const std::string&, std::vector<resource::Texture>&, render::Type,
|
bool animation_render(const std::string&, const std::string&, std::vector<resource::Texture>&, AudioStream&,
|
||||||
glm::ivec2, int);
|
render::Type, glm::ivec2, int);
|
||||||
}
|
}
|
||||||
26
src/resource/audio_stream.cpp
Normal file
26
src/resource/audio_stream.cpp
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#include "audio_stream.h"
|
||||||
|
|
||||||
|
namespace anm2ed
|
||||||
|
{
|
||||||
|
void AudioStream::callback(void* userData, MIX_Mixer* mixer, const SDL_AudioSpec* spec, float* pcm, int samples)
|
||||||
|
{
|
||||||
|
auto self = (AudioStream*)userData;
|
||||||
|
self->stream.insert(self->stream.end(), pcm, pcm + samples);
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioStream::AudioStream(MIX_Mixer* mixer)
|
||||||
|
{
|
||||||
|
MIX_GetMixerFormat(mixer, &spec);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioStream::capture_begin(MIX_Mixer* mixer)
|
||||||
|
{
|
||||||
|
MIX_SetPostMixCallback(mixer, callback, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioStream::capture_end(MIX_Mixer* mixer)
|
||||||
|
{
|
||||||
|
MIX_SetPostMixCallback(mixer, nullptr, this);
|
||||||
|
stream.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/resource/audio_stream.h
Normal file
20
src/resource/audio_stream.h
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL3_mixer/SDL_mixer.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace anm2ed
|
||||||
|
{
|
||||||
|
class AudioStream
|
||||||
|
{
|
||||||
|
static void callback(void*, MIX_Mixer*, const SDL_AudioSpec*, float*, int);
|
||||||
|
|
||||||
|
public:
|
||||||
|
std::vector<float> stream{};
|
||||||
|
SDL_AudioSpec spec{};
|
||||||
|
|
||||||
|
AudioStream(MIX_Mixer*);
|
||||||
|
void capture_begin(MIX_Mixer*);
|
||||||
|
void capture_end(MIX_Mixer*);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -153,7 +153,7 @@ namespace anm2ed::resource::icon
|
|||||||
<svg viewBox="0 0 24 48" fill="#FFF" xmlns="http://www.w3.org/2000/svg"> <path d="M4 0H20V38L12 48L4 38V0Z"/> </svg>
|
<svg viewBox="0 0 24 48" fill="#FFF" xmlns="http://www.w3.org/2000/svg"> <path d="M4 0H20V38L12 48L4 38V0Z"/> </svg>
|
||||||
)";
|
)";
|
||||||
|
|
||||||
#define LIST \
|
#define SVG_LIST \
|
||||||
X(NONE, NONE_DATA, SIZE_SMALL) \
|
X(NONE, NONE_DATA, SIZE_SMALL) \
|
||||||
X(FILE, FILE_DATA, SIZE_NORMAL) \
|
X(FILE, FILE_DATA, SIZE_NORMAL) \
|
||||||
X(FOLDER, FOLDER_DATA, SIZE_NORMAL) \
|
X(FOLDER, FOLDER_DATA, SIZE_NORMAL) \
|
||||||
@@ -195,7 +195,7 @@ namespace anm2ed::resource::icon
|
|||||||
enum Type
|
enum Type
|
||||||
{
|
{
|
||||||
#define X(name, data, size) name,
|
#define X(name, data, size) name,
|
||||||
LIST
|
SVG_LIST
|
||||||
#undef X
|
#undef X
|
||||||
COUNT
|
COUNT
|
||||||
};
|
};
|
||||||
@@ -209,7 +209,21 @@ namespace anm2ed::resource::icon
|
|||||||
|
|
||||||
const Info ICONS[COUNT] = {
|
const Info ICONS[COUNT] = {
|
||||||
#define X(name, data, size) {data, std::strlen(data) - 1, size},
|
#define X(name, data, size) {data, std::strlen(data) - 1, size},
|
||||||
LIST
|
SVG_LIST
|
||||||
#undef X
|
#undef X
|
||||||
};
|
};
|
||||||
|
|
||||||
|
constexpr uint8_t PROGRAM[] = {
|
||||||
|
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00,
|
||||||
|
0x40, 0x00, 0x00, 0x00, 0x40, 0x08, 0x04, 0x00, 0x00, 0x00, 0x00, 0x60, 0xb9, 0x55, 0x00, 0x00, 0x00, 0x09, 0x70,
|
||||||
|
0x48, 0x59, 0x73, 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b, 0x12, 0x01, 0xd2, 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00,
|
||||||
|
0x8b, 0x49, 0x44, 0x41, 0x54, 0x68, 0xde, 0xed, 0xd7, 0x3d, 0x0e, 0x80, 0x20, 0x0c, 0x86, 0x61, 0x7b, 0x53, 0x3c,
|
||||||
|
0x99, 0xbd, 0x69, 0x1d, 0x8c, 0x83, 0x4d, 0xd0, 0x44, 0x29, 0x7f, 0xbe, 0x5d, 0x08, 0x30, 0xf4, 0x19, 0x80, 0x2f,
|
||||||
|
0xc8, 0xd2, 0xb8, 0x04, 0xc0, 0xb4, 0x80, 0x64, 0xc7, 0xb8, 0xf9, 0x86, 0x02, 0x60, 0x36, 0x80, 0xd9, 0x75, 0xbe,
|
||||||
|
0xba, 0x7d, 0x00, 0xf3, 0x00, 0x72, 0x8d, 0x7c, 0x83, 0x73, 0x5d, 0xa5, 0xf0, 0x43, 0x04, 0xa0, 0x1a, 0x20, 0xaa,
|
||||||
|
0x11, 0x80, 0xf1, 0x01, 0x1a, 0x14, 0x5b, 0x00, 0xfa, 0x03, 0x24, 0xbb, 0x0f, 0x93, 0xa7, 0xb0, 0xf9, 0x1c, 0x46,
|
||||||
|
0x00, 0x00, 0x00, 0xe8, 0x06, 0xa0, 0xa5, 0x3f, 0x2a, 0x99, 0x07, 0x0d, 0x40, 0xbf, 0x80, 0xa8, 0x02, 0x30, 0x0e,
|
||||||
|
0x40, 0xdf, 0x1e, 0xb2, 0x56, 0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x07, 0x90, 0x86, 0xff, 0x05, 0xd4,
|
||||||
|
0x2e, 0x00, 0x00, 0x00, 0x34, 0x07, 0xec, 0x94, 0x51, 0xac, 0x41, 0x55, 0x6e, 0xe4, 0x26, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82};
|
||||||
}
|
}
|
||||||
@@ -28,15 +28,9 @@ using namespace glm;
|
|||||||
|
|
||||||
namespace anm2ed::resource
|
namespace anm2ed::resource
|
||||||
{
|
{
|
||||||
bool Texture::is_valid()
|
bool Texture::is_valid() { return id != 0; }
|
||||||
{
|
|
||||||
return id != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t Texture::pixel_size_get()
|
size_t Texture::pixel_size_get() { return size.x * size.y * CHANNELS; }
|
||||||
{
|
|
||||||
return size.x * size.y * CHANNELS;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Texture::upload(const uint8_t* data)
|
void Texture::upload(const uint8_t* data)
|
||||||
{
|
{
|
||||||
@@ -66,15 +60,9 @@ namespace anm2ed::resource
|
|||||||
if (is_valid()) glDeleteTextures(1, &id);
|
if (is_valid()) glDeleteTextures(1, &id);
|
||||||
}
|
}
|
||||||
|
|
||||||
Texture::Texture(const Texture& other)
|
Texture::Texture(const Texture& other) { *this = other; }
|
||||||
{
|
|
||||||
*this = other;
|
|
||||||
}
|
|
||||||
|
|
||||||
Texture::Texture(Texture&& other)
|
Texture::Texture(Texture&& other) { *this = std::move(other); }
|
||||||
{
|
|
||||||
*this = std::move(other);
|
|
||||||
}
|
|
||||||
|
|
||||||
Texture& Texture::operator=(const Texture& other) // Copy
|
Texture& Texture::operator=(const Texture& other) // Copy
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ namespace anm2ed::resource
|
|||||||
size_t pixel_size_get();
|
size_t pixel_size_get();
|
||||||
void upload();
|
void upload();
|
||||||
void upload(const uint8_t*);
|
void upload(const uint8_t*);
|
||||||
Texture();
|
|
||||||
|
|
||||||
|
Texture();
|
||||||
~Texture();
|
~Texture();
|
||||||
Texture(const Texture&);
|
Texture(const Texture&);
|
||||||
Texture(Texture&&);
|
Texture(Texture&&);
|
||||||
|
|||||||
154
src/settings.cpp
154
src/settings.cpp
@@ -10,97 +10,97 @@ namespace anm2ed
|
|||||||
{
|
{
|
||||||
constexpr auto IMGUI_DEFAULT = R"(
|
constexpr auto IMGUI_DEFAULT = R"(
|
||||||
# Dear ImGui
|
# Dear ImGui
|
||||||
[Window][## Window]
|
[Window][##DockSpace]
|
||||||
Pos=0,32
|
Pos=0,54
|
||||||
Size=1600,868
|
Size=1918,1010
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
[Window][Debug##Default]
|
|
||||||
Pos=60,60
|
|
||||||
Size=400,400
|
|
||||||
Collapsed=0
|
|
||||||
|
|
||||||
[Window][Tools]
|
|
||||||
Pos=8,40
|
|
||||||
Size=38,516
|
|
||||||
Collapsed=0
|
|
||||||
DockId=0x0000000B,0
|
|
||||||
|
|
||||||
[Window][Animations]
|
|
||||||
Pos=1289,307
|
|
||||||
Size=303,249
|
|
||||||
Collapsed=0
|
|
||||||
DockId=0x0000000A,0
|
|
||||||
|
|
||||||
[Window][Events]
|
|
||||||
Pos=957,264
|
|
||||||
Size=330,292
|
|
||||||
Collapsed=0
|
|
||||||
DockId=0x00000008,2
|
|
||||||
|
|
||||||
[Window][Spritesheets]
|
|
||||||
Pos=1289,40
|
|
||||||
Size=303,265
|
|
||||||
Collapsed=0
|
|
||||||
DockId=0x00000009,0
|
|
||||||
|
|
||||||
[Window][Animation Preview]
|
[Window][Animation Preview]
|
||||||
Pos=48,40
|
Pos=60,62
|
||||||
Size=907,516
|
Size=983,691
|
||||||
|
Collapsed=0
|
||||||
|
DockId=0x00000003,1
|
||||||
|
|
||||||
|
[Window][Animations]
|
||||||
|
Pos=1451,494
|
||||||
|
Size=459,259
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x0000000C,0
|
DockId=0x0000000C,0
|
||||||
|
|
||||||
[Window][Spritesheet Editor]
|
[Window][Events]
|
||||||
Pos=48,40
|
Pos=1045,463
|
||||||
Size=907,516
|
Size=404,290
|
||||||
Collapsed=0
|
|
||||||
DockId=0x0000000C,1
|
|
||||||
|
|
||||||
[Window][Timeline]
|
|
||||||
Pos=8,558
|
|
||||||
Size=1584,334
|
|
||||||
Collapsed=0
|
|
||||||
DockId=0x00000004,0
|
|
||||||
|
|
||||||
[Window][Frame Properties]
|
|
||||||
Pos=957,40
|
|
||||||
Size=330,222
|
|
||||||
Collapsed=0
|
|
||||||
DockId=0x00000007,0
|
|
||||||
|
|
||||||
[Window][Onionskin]
|
|
||||||
Pos=957,264
|
|
||||||
Size=330,292
|
|
||||||
Collapsed=0
|
|
||||||
DockId=0x00000008,3
|
|
||||||
|
|
||||||
[Window][Layers]
|
|
||||||
Pos=957,264
|
|
||||||
Size=330,292
|
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000008,0
|
DockId=0x00000008,0
|
||||||
|
|
||||||
[Window][Nulls]
|
[Window][Frame Properties]
|
||||||
Pos=957,264
|
Pos=1045,62
|
||||||
Size=330,292
|
Size=404,399
|
||||||
|
Collapsed=0
|
||||||
|
DockId=0x00000007,0
|
||||||
|
|
||||||
|
[Window][Layers]
|
||||||
|
Pos=1045,463
|
||||||
|
Size=404,290
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000008,1
|
DockId=0x00000008,1
|
||||||
|
|
||||||
|
[Window][Nulls]
|
||||||
|
Pos=1045,463
|
||||||
|
Size=404,290
|
||||||
|
Collapsed=0
|
||||||
|
DockId=0x00000008,2
|
||||||
|
|
||||||
|
[Window][Onionskin]
|
||||||
|
Pos=8,755
|
||||||
|
Size=1902,301
|
||||||
|
Collapsed=0
|
||||||
|
DockId=0x00000006,1
|
||||||
|
|
||||||
|
[Window][Spritesheet Editor]
|
||||||
|
Pos=60,62
|
||||||
|
Size=983,691
|
||||||
|
Collapsed=0
|
||||||
|
DockId=0x00000003,0
|
||||||
|
|
||||||
|
[Window][Spritesheets]
|
||||||
|
Pos=1451,62
|
||||||
|
Size=459,430
|
||||||
|
Collapsed=0
|
||||||
|
DockId=0x0000000B,0
|
||||||
|
|
||||||
|
[Window][Tools]
|
||||||
|
Pos=8,62
|
||||||
|
Size=50,691
|
||||||
|
Collapsed=0
|
||||||
|
DockId=0x00000001,0
|
||||||
|
|
||||||
|
[Window][Timeline]
|
||||||
|
Pos=8,755
|
||||||
|
Size=1902,301
|
||||||
|
Collapsed=0
|
||||||
|
DockId=0x00000006,0
|
||||||
|
|
||||||
|
[Window][Sounds]
|
||||||
|
Pos=1045,463
|
||||||
|
Size=404,290
|
||||||
|
Collapsed=0
|
||||||
|
DockId=0x00000008,3
|
||||||
|
|
||||||
[Docking][Data]
|
[Docking][Data]
|
||||||
DockSpace ID=0xFC02A410 Window=0x0E46F4F7 Pos=8,40 Size=1584,852 Split=Y
|
DockSpace ID=0x123F8F08 Window=0x6D581B32 Pos=8,62 Size=1902,994 Split=Y Selected=0x4EFD0020
|
||||||
DockNode ID=0x00000003 Parent=0xFC02A410 SizeRef=1902,680 Split=X
|
DockNode ID=0x00000005 Parent=0x123F8F08 SizeRef=1910,691 Split=X
|
||||||
DockNode ID=0x00000001 Parent=0x00000003 SizeRef=1017,1016 Split=X Selected=0x024430EF
|
DockNode ID=0x00000001 Parent=0x00000005 SizeRef=50,994 Selected=0x18A5FDB9
|
||||||
DockNode ID=0x00000005 Parent=0x00000001 SizeRef=1264,654 Split=X Selected=0x024430EF
|
DockNode ID=0x00000002 Parent=0x00000005 SizeRef=1850,994 Split=X Selected=0x4EFD0020
|
||||||
DockNode ID=0x0000000B Parent=0x00000005 SizeRef=38,654 Selected=0x18A5FDB9
|
DockNode ID=0x00000003 Parent=0x00000002 SizeRef=983,994 Selected=0x024430EF
|
||||||
DockNode ID=0x0000000C Parent=0x00000005 SizeRef=1224,654 CentralNode=1 Selected=0x024430EF
|
DockNode ID=0x00000004 Parent=0x00000002 SizeRef=865,994 Split=X Selected=0x4EFD0020
|
||||||
DockNode ID=0x00000006 Parent=0x00000001 SizeRef=330,654 Split=Y Selected=0x754E368F
|
DockNode ID=0x00000009 Parent=0x00000004 SizeRef=404,497 Split=Y Selected=0xCD8384B1
|
||||||
DockNode ID=0x00000007 Parent=0x00000006 SizeRef=631,293 Selected=0x754E368F
|
DockNode ID=0x00000007 Parent=0x00000009 SizeRef=181,399 Selected=0x754E368F
|
||||||
DockNode ID=0x00000008 Parent=0x00000006 SizeRef=631,385 Selected=0xCD8384B1
|
DockNode ID=0x00000008 Parent=0x00000009 SizeRef=181,290 Selected=0x8A65D963
|
||||||
DockNode ID=0x00000002 Parent=0x00000003 SizeRef=303,1016 Split=Y Selected=0x4EFD0020
|
DockNode ID=0x0000000A Parent=0x00000004 SizeRef=459,497 Split=Y Selected=0x4EFD0020
|
||||||
DockNode ID=0x00000009 Parent=0x00000002 SizeRef=634,349 Selected=0x4EFD0020
|
DockNode ID=0x0000000B Parent=0x0000000A SizeRef=710,430 CentralNode=1 Selected=0x4EFD0020
|
||||||
DockNode ID=0x0000000A Parent=0x00000002 SizeRef=634,329 Selected=0xC1986EE2
|
DockNode ID=0x0000000C Parent=0x0000000A SizeRef=710,259 Selected=0xC1986EE2
|
||||||
DockNode ID=0x00000004 Parent=0xFC02A410 SizeRef=1902,334 Selected=0x4F89F0DC
|
DockNode ID=0x00000006 Parent=0x123F8F08 SizeRef=1910,301 Selected=0x4F89F0DC
|
||||||
)";
|
)";
|
||||||
|
|
||||||
Settings::Settings(const std::string& path)
|
Settings::Settings(const std::string& path)
|
||||||
|
|||||||
@@ -153,8 +153,8 @@ namespace anm2ed
|
|||||||
/* Symbol / Name / String / Type / Default */ \
|
/* Symbol / Name / String / Type / Default */ \
|
||||||
X(SHORTCUT_CENTER_VIEW, shortcutCenterView, "Center View", STRING, "Home") \
|
X(SHORTCUT_CENTER_VIEW, shortcutCenterView, "Center View", STRING, "Home") \
|
||||||
X(SHORTCUT_FIT, shortcutFit, "Fit", STRING, "F") \
|
X(SHORTCUT_FIT, shortcutFit, "Fit", STRING, "F") \
|
||||||
X(SHORTCUT_ZOOM_IN, shortcutZoomIn, "Zoom In", STRING, "Ctrl++") \
|
X(SHORTCUT_ZOOM_IN, shortcutZoomIn, "Zoom In", STRING, "Ctrl+Equal") \
|
||||||
X(SHORTCUT_ZOOM_OUT, shortcutZoomOut, "Zoom Out", STRING, "Ctrl+-") \
|
X(SHORTCUT_ZOOM_OUT, shortcutZoomOut, "Zoom Out", STRING, "Ctrl+Minus") \
|
||||||
X(SHORTCUT_PLAY_PAUSE, shortcutPlayPause, "Play/Pause", STRING, "Space") \
|
X(SHORTCUT_PLAY_PAUSE, shortcutPlayPause, "Play/Pause", STRING, "Space") \
|
||||||
X(SHORTCUT_ONIONSKIN, shortcutOnionskin, "Onionskin", STRING, "O") \
|
X(SHORTCUT_ONIONSKIN, shortcutOnionskin, "Onionskin", STRING, "O") \
|
||||||
X(SHORTCUT_NEW, shortcutNew, "New", STRING, "Ctrl+N") \
|
X(SHORTCUT_NEW, shortcutNew, "New", STRING, "Ctrl+N") \
|
||||||
|
|||||||
@@ -4,10 +4,7 @@ using namespace anm2ed::snapshots;
|
|||||||
|
|
||||||
namespace anm2ed
|
namespace anm2ed
|
||||||
{
|
{
|
||||||
bool SnapshotStack::is_empty()
|
bool SnapshotStack::is_empty() { return top == 0; }
|
||||||
{
|
|
||||||
return top == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SnapshotStack::push(const Snapshot& snapshot)
|
void SnapshotStack::push(const Snapshot& snapshot)
|
||||||
{
|
{
|
||||||
@@ -26,10 +23,7 @@ namespace anm2ed
|
|||||||
return &snapshots[--top];
|
return &snapshots[--top];
|
||||||
}
|
}
|
||||||
|
|
||||||
void SnapshotStack::clear()
|
void SnapshotStack::clear() { top = 0; }
|
||||||
{
|
|
||||||
top = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Snapshots::push(const Snapshot& snapshot)
|
void Snapshots::push(const Snapshot& snapshot)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ namespace anm2ed
|
|||||||
Storage null{};
|
Storage null{};
|
||||||
Storage sound{};
|
Storage sound{};
|
||||||
Storage spritesheet{};
|
Storage spritesheet{};
|
||||||
|
Storage items{};
|
||||||
|
std::map<int, Storage> frames{};
|
||||||
std::string message = snapshots::ACTION;
|
std::string message = snapshots::ACTION;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
161
src/socket.cpp
Normal file
161
src/socket.cpp
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
#include "socket.h"
|
||||||
|
|
||||||
|
namespace anm2ed
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
struct WSAInitializer
|
||||||
|
{
|
||||||
|
WSAInitializer()
|
||||||
|
{
|
||||||
|
WSADATA data{};
|
||||||
|
WSAStartup(MAKEWORD(2, 2), &data);
|
||||||
|
}
|
||||||
|
~WSAInitializer() { WSACleanup(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
WSAInitializer initializer{};
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Socket::Socket() : handle(SOCKET_INVALID), role(CLIENT) {}
|
||||||
|
|
||||||
|
Socket::Socket(Socket&& other) noexcept : handle(other.handle), role(other.role) { other.handle = SOCKET_INVALID; }
|
||||||
|
|
||||||
|
Socket& Socket::operator=(Socket&& other) noexcept
|
||||||
|
{
|
||||||
|
if (this != &other)
|
||||||
|
{
|
||||||
|
close();
|
||||||
|
handle = other.handle;
|
||||||
|
role = other.role;
|
||||||
|
other.handle = SOCKET_INVALID;
|
||||||
|
}
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Socket::~Socket() { close(); }
|
||||||
|
|
||||||
|
bool Socket::open(SocketRole newRole)
|
||||||
|
{
|
||||||
|
close();
|
||||||
|
role = newRole;
|
||||||
|
|
||||||
|
handle = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||||
|
|
||||||
|
if (!is_valid()) return false;
|
||||||
|
|
||||||
|
if (role == SERVER)
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
BOOL opt = TRUE;
|
||||||
|
#else
|
||||||
|
int opt = 1;
|
||||||
|
#endif
|
||||||
|
::setsockopt(handle, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<const char*>(&opt), sizeof(opt));
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Socket::bind(const SocketAddress& address)
|
||||||
|
{
|
||||||
|
if (!is_valid()) return false;
|
||||||
|
|
||||||
|
sockaddr_in addr{};
|
||||||
|
addr.sin_family = AF_INET;
|
||||||
|
addr.sin_port = htons(address.port);
|
||||||
|
|
||||||
|
if (address.host.empty())
|
||||||
|
addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (::inet_pton(AF_INET, address.host.c_str(), &addr.sin_addr) <= 0) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ::bind(handle, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Socket::listen()
|
||||||
|
{
|
||||||
|
if (!is_valid()) return false;
|
||||||
|
return ::listen(handle, SOMAXCONN) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Socket Socket::accept()
|
||||||
|
{
|
||||||
|
Socket client{};
|
||||||
|
if (!is_valid()) return client;
|
||||||
|
|
||||||
|
auto accepted = ::accept(handle, nullptr, nullptr);
|
||||||
|
if (accepted == SOCKET_INVALID) return client;
|
||||||
|
|
||||||
|
client.close();
|
||||||
|
client.handle = accepted;
|
||||||
|
client.role = CLIENT;
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Socket::connect(const SocketAddress& address)
|
||||||
|
{
|
||||||
|
if (!is_valid()) return false;
|
||||||
|
|
||||||
|
sockaddr_in addr{};
|
||||||
|
addr.sin_family = AF_INET;
|
||||||
|
addr.sin_port = htons(address.port);
|
||||||
|
|
||||||
|
if (::inet_pton(AF_INET, address.host.c_str(), &addr.sin_addr) <= 0) return false;
|
||||||
|
|
||||||
|
return ::connect(handle, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Socket::send(const void* data, size_t size)
|
||||||
|
{
|
||||||
|
if (!is_valid() || !data || size == 0) return false;
|
||||||
|
|
||||||
|
auto bytes = reinterpret_cast<const char*>(data);
|
||||||
|
size_t totalSent = 0;
|
||||||
|
|
||||||
|
while (totalSent < size)
|
||||||
|
{
|
||||||
|
auto sent = ::send(handle, bytes + totalSent, static_cast<int>(size - totalSent), 0);
|
||||||
|
if (sent <= 0) return false;
|
||||||
|
totalSent += static_cast<size_t>(sent);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Socket::receive(void* buffer, size_t size)
|
||||||
|
{
|
||||||
|
if (!is_valid() || !buffer || size == 0) return false;
|
||||||
|
|
||||||
|
auto* bytes = reinterpret_cast<char*>(buffer);
|
||||||
|
size_t totalReceived = 0;
|
||||||
|
|
||||||
|
while (totalReceived < size)
|
||||||
|
{
|
||||||
|
auto received = ::recv(handle, bytes + totalReceived, static_cast<int>(size - totalReceived), 0);
|
||||||
|
if (received <= 0) return false;
|
||||||
|
totalReceived += static_cast<size_t>(received);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Socket::close()
|
||||||
|
{
|
||||||
|
if (!is_valid()) return;
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
::closesocket(handle);
|
||||||
|
#else
|
||||||
|
::close(handle);
|
||||||
|
#endif
|
||||||
|
handle = SOCKET_INVALID;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Socket::is_valid() const { return handle != SOCKET_INVALID; }
|
||||||
|
}
|
||||||
61
src/socket.h
Normal file
61
src/socket.h
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <winsock2.h>
|
||||||
|
#include <ws2tcpip.h>
|
||||||
|
using socket_handle = SOCKET;
|
||||||
|
constexpr socket_handle SOCKET_INVALID = INVALID_SOCKET;
|
||||||
|
#else
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
using socket_handle = int;
|
||||||
|
constexpr socket_handle SOCKET_INVALID = -1;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace anm2ed
|
||||||
|
{
|
||||||
|
enum SocketRole
|
||||||
|
{
|
||||||
|
SERVER,
|
||||||
|
CLIENT
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SocketAddress
|
||||||
|
{
|
||||||
|
std::string host{};
|
||||||
|
unsigned short port{};
|
||||||
|
};
|
||||||
|
|
||||||
|
class Socket
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
socket_handle handle;
|
||||||
|
SocketRole role{};
|
||||||
|
|
||||||
|
public:
|
||||||
|
Socket();
|
||||||
|
~Socket();
|
||||||
|
|
||||||
|
Socket(const Socket&) = delete;
|
||||||
|
Socket& operator=(const Socket&) = delete;
|
||||||
|
Socket(Socket&& other) noexcept;
|
||||||
|
Socket& operator=(Socket&& other) noexcept;
|
||||||
|
|
||||||
|
bool open(SocketRole role);
|
||||||
|
bool bind(const SocketAddress&);
|
||||||
|
bool listen();
|
||||||
|
Socket accept();
|
||||||
|
bool connect(const SocketAddress&);
|
||||||
|
|
||||||
|
bool send(const void*, size_t);
|
||||||
|
bool receive(void*, size_t);
|
||||||
|
|
||||||
|
void close();
|
||||||
|
bool is_valid() const;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -52,7 +52,10 @@ namespace anm2ed
|
|||||||
{
|
{
|
||||||
auto droppedFile = event.drop.data;
|
auto droppedFile = event.drop.data;
|
||||||
if (filesystem::path_is_extension(droppedFile, "anm2"))
|
if (filesystem::path_is_extension(droppedFile, "anm2"))
|
||||||
|
{
|
||||||
manager.open(std::string(droppedFile));
|
manager.open(std::string(droppedFile));
|
||||||
|
SDL_FlashWindow(window, SDL_FLASH_UNTIL_FOCUSED);
|
||||||
|
}
|
||||||
else if (filesystem::path_is_extension(droppedFile, "png"))
|
else if (filesystem::path_is_extension(droppedFile, "png"))
|
||||||
{
|
{
|
||||||
if (auto document = manager.get())
|
if (auto document = manager.get())
|
||||||
|
|||||||
@@ -8,14 +8,37 @@
|
|||||||
|
|
||||||
namespace anm2ed::util::filesystem
|
namespace anm2ed::util::filesystem
|
||||||
{
|
{
|
||||||
std::string path_preferences_get()
|
std::string path_pref_get(const char* org, const char* app)
|
||||||
{
|
{
|
||||||
char* preferencesPath = SDL_GetPrefPath("", "anm2ed");
|
auto path = SDL_GetPrefPath(org, app);
|
||||||
std::string preferencesPathString = preferencesPath;
|
std::string string = path;
|
||||||
SDL_free(preferencesPath);
|
SDL_free(path);
|
||||||
return preferencesPathString;
|
return string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string path_preferences_get() { return path_pref_get(nullptr, "anm2ed"); }
|
||||||
|
std::string path_base_get() { return std::string(SDL_GetBasePath()); }
|
||||||
|
std::string path_executable_get() { return std::filesystem::path(path_base_get()) / "anm2ed"; }
|
||||||
|
|
||||||
|
#ifdef __unix__
|
||||||
|
std::string path_application_get()
|
||||||
|
{
|
||||||
|
return std::filesystem::path(path_pref_get(nullptr, "applications")) / "anm2ed.desktop";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string path_mime_get()
|
||||||
|
{
|
||||||
|
return std::filesystem::path(path_pref_get(nullptr, "mime/application")) / "x-anm2+xml.xml";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string path_icon_get() { return std::filesystem::path(path_preferences_get()) / "anm2ed.png"; }
|
||||||
|
std::string path_icon_file_get()
|
||||||
|
{
|
||||||
|
return std::filesystem::path(path_preferences_get()) / "application-x-anm2+xml.png";
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
bool path_is_exist(const std::string& path)
|
bool path_is_exist(const std::string& path)
|
||||||
{
|
{
|
||||||
std::error_code errorCode;
|
std::error_code errorCode;
|
||||||
@@ -49,8 +72,5 @@ namespace anm2ed::util::filesystem
|
|||||||
std::filesystem::current_path(path);
|
std::filesystem::current_path(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
WorkingDirectory::~WorkingDirectory()
|
WorkingDirectory::~WorkingDirectory() { std::filesystem::current_path(previous); }
|
||||||
{
|
|
||||||
std::filesystem::current_path(previous);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -5,9 +5,21 @@
|
|||||||
|
|
||||||
namespace anm2ed::util::filesystem
|
namespace anm2ed::util::filesystem
|
||||||
{
|
{
|
||||||
|
#ifdef __unix__
|
||||||
|
std::string path_application_get();
|
||||||
|
std::string path_mime_get();
|
||||||
|
std::string path_icon_get();
|
||||||
|
std::string path_icon_file_get();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
std::string path_pref_get();
|
||||||
std::string path_preferences_get();
|
std::string path_preferences_get();
|
||||||
|
std::string path_base_get();
|
||||||
|
std::string path_executable_get();
|
||||||
|
|
||||||
bool path_is_exist(const std::string&);
|
bool path_is_exist(const std::string&);
|
||||||
bool path_is_extension(const std::string&, const std::string&);
|
bool path_is_extension(const std::string&, const std::string&);
|
||||||
|
|
||||||
std::filesystem::path path_lower_case_backslash_handle(std::filesystem::path&);
|
std::filesystem::path path_lower_case_backslash_handle(std::filesystem::path&);
|
||||||
|
|
||||||
class WorkingDirectory
|
class WorkingDirectory
|
||||||
|
|||||||
Reference in New Issue
Block a user