Refactor + render animation tweaks + updated frame properties + bug fixes

This commit is contained in:
2025-12-17 23:02:00 -05:00
parent b4b4fe3714
commit 119bbc4081
63 changed files with 1964 additions and 1701 deletions
+191
View File
@@ -0,0 +1,191 @@
#include "about.h"
#include <cmath>
#include <imgui.h>
#include <vector>
using namespace anm2ed::resource;
namespace anm2ed::imgui::wizard
{
static constexpr auto CREDIT_DELAY = 1.0f;
static constexpr auto CREDIT_SCROLL_SPEED = 25.0f;
static constexpr About::Credit CREDITS[] = {
{"Anm2Ed", font::BOLD},
{"License: GPLv3"},
{""},
{"Designer", font::BOLD},
{"Shweet"},
{""},
{"Additional Help", font::BOLD},
{"im-tem"},
{""},
{"Localization", font::BOLD},
{"Gabriel Asencio (Spanish (Latin America))"},
{"ExtremeThreat (Russian)"},
{"CxRedix (Chinese)"},
{"sawalk/사왈이 (Korean)"},
{""},
{"Based on the work of:", font::BOLD},
{"Adrian Gavrilita"},
{"Simon Parzer"},
{"Matt Kapuszczak"},
{""},
{"XM Music", font::BOLD},
{"Drozerix"},
{"\"Keygen Wraith\""},
{"https://modarchive.org/module.php?207854"},
{"License: CC0"},
{""},
{"Libraries", font::BOLD},
{"Dear ImGui"},
{"https://github.com/ocornut/imgui"},
{"License: MIT"},
{""},
{"SDL"},
{"https://github.com/libsdl-org/SDL"},
{"License: zlib"},
{""},
{"SDL_mixer"},
{"https://github.com/libsdl-org/SDL_mixer"},
{"License: zlib"},
{""},
{"tinyxml2"},
{"https://github.com/leethomason/tinyxml2"},
{"License: zlib"},
{""},
{"glm"},
{"https://github.com/g-truc/glm"},
{"License: MIT"},
{""},
{"lunasvg"},
{"https://github.com/sammycage/lunasvg"},
{"License: MIT"},
{""},
{"Icons", font::BOLD},
{"Remix Icons"},
{"remixicon.com"},
{"License: Apache"},
{""},
{"Font", font::BOLD},
{"Noto Sans"},
{"https://fonts.google.com/noto/specimen/Noto+Sans"},
{"License: OFL"},
{""},
{"Special Thanks", font::BOLD},
{"Edmund McMillen"},
{"Florian Himsl"},
{"Tyrone Rodriguez"},
{"The-Vinh Truong (_kilburn)"},
{"Isaac Reflashed team"},
{"Everyone who waited patiently for this to be finished"},
{"Everyone else who has worked on The Binding of Isaac!"},
{""},
{""},
{""},
{""},
{""},
{""},
{""},
{""},
{""},
{"enjoy the jams :)"},
{""},
{""},
{""},
{""},
{""},
{""},
{""},
{""},
{""},
{""},
};
static constexpr auto CREDIT_COUNT = (int)(sizeof(CREDITS) / sizeof(About::Credit));
void About::reset(Resources& resources)
{
resources.music_track().play(true);
creditsState = {};
creditsState.spawnTimer = CREDIT_DELAY;
}
void About::update(Resources& resources)
{
auto size = ImGui::GetContentRegionAvail();
auto applicationLabel = localize.get(LABEL_APPLICATION_NAME);
auto versionLabel = localize.get(LABEL_APPLICATION_VERSION);
ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE_LARGE);
ImGui::SetCursorPosX((size.x - ImGui::CalcTextSize(applicationLabel).x) / 2);
ImGui::TextUnformatted(applicationLabel);
ImGui::SetCursorPosX((size.x - ImGui::CalcTextSize(versionLabel).x) / 2);
ImGui::TextUnformatted(versionLabel);
ImGui::PopFont();
auto creditRegionPos = ImGui::GetCursorScreenPos();
auto creditRegionSize = ImGui::GetContentRegionAvail();
if (creditRegionSize.y > 0.0f && creditRegionSize.x > 0.0f)
{
auto fontSize = ImGui::GetFontSize();
auto drawList = ImGui::GetWindowDrawList();
auto clipMax = ImVec2(creditRegionPos.x + creditRegionSize.x, creditRegionPos.y + creditRegionSize.y);
drawList->PushClipRect(creditRegionPos, clipMax, true);
auto delta = ImGui::GetIO().DeltaTime;
creditsState.spawnTimer -= delta;
auto maxVisible = std::max(1, (int)std::floor(creditRegionSize.y / (float)fontSize));
while (creditsState.active.size() < (size_t)maxVisible && creditsState.spawnTimer <= 0.0f)
{
creditsState.active.push_back({creditsState.nextIndex, 0.0f});
creditsState.nextIndex = (creditsState.nextIndex + 1) % CREDIT_COUNT;
creditsState.spawnTimer += CREDIT_DELAY;
}
auto baseY = clipMax.y - (float)fontSize;
auto& baseColor = ImGui::GetStyleColorVec4(ImGuiCol_Text);
auto fadeSpan = (float)fontSize * 2.0f;
for (auto it = creditsState.active.begin(); it != creditsState.active.end();)
{
it->offset += CREDIT_SCROLL_SPEED * delta;
auto yPos = baseY - it->offset;
if (yPos + fontSize < creditRegionPos.y)
{
it = creditsState.active.erase(it);
continue;
}
auto& credit = CREDITS[it->index];
auto fontPtr = resources.fonts[credit.font].get();
auto textSize = fontPtr->CalcTextSizeA((float)fontSize, FLT_MAX, 0.0f, credit.string);
auto xPos = creditRegionPos.x + (creditRegionSize.x - textSize.x) * 0.5f;
auto alpha = 1.0f;
auto topDist = yPos - creditRegionPos.y;
if (topDist < fadeSpan) alpha *= std::clamp(topDist / fadeSpan, 0.0f, 1.0f);
auto bottomDist = (creditRegionPos.y + creditRegionSize.y) - (yPos + fontSize);
if (bottomDist < fadeSpan) alpha *= std::clamp(bottomDist / fadeSpan, 0.0f, 1.0f);
if (alpha <= 0.0f)
{
++it;
continue;
}
auto color = baseColor;
color.w *= alpha;
drawList->AddText(fontPtr, fontSize, ImVec2(xPos, yPos), ImGui::GetColorU32(color), credit.string);
++it;
}
drawList->PopClipRect();
}
}
}
+36
View File
@@ -0,0 +1,36 @@
#pragma once
#include "../../resources.h"
namespace anm2ed::imgui::wizard
{
class About
{
public:
struct Credit
{
const char* string{};
resource::font::Type font{resource::font::REGULAR};
};
struct ScrollingCredit
{
int index{};
float offset{};
};
struct CreditsState
{
std::vector<ScrollingCredit> active{};
float spawnTimer{1.0f};
int nextIndex{};
};
int creditsIndex{};
CreditsState creditsState{};
void reset(Resources& resources);
void update(Resources& resources);
};
}
@@ -0,0 +1,270 @@
#include "change_all_frame_properties.h"
#include <algorithm>
#include <cmath>
#include <string>
#include "math_.h"
using namespace anm2ed::util::math;
using namespace glm;
namespace anm2ed::imgui::wizard
{
void ChangeAllFrameProperties::update(Document& document, Settings& settings)
{
isChanged = false;
auto& frames = document.frames.selection;
auto& isCropX = settings.changeIsCropX;
auto& isCropY = settings.changeIsCropY;
auto& isSizeX = settings.changeIsSizeX;
auto& isSizeY = settings.changeIsSizeY;
auto& isPositionX = settings.changeIsPositionX;
auto& isPositionY = settings.changeIsPositionY;
auto& isPivotX = settings.changeIsPivotX;
auto& isPivotY = settings.changeIsPivotY;
auto& isScaleX = settings.changeIsScaleX;
auto& isScaleY = settings.changeIsScaleY;
auto& isRotation = settings.changeIsRotation;
auto& isDuration = settings.changeIsDuration;
auto& isTintR = settings.changeIsTintR;
auto& isTintG = settings.changeIsTintG;
auto& isTintB = settings.changeIsTintB;
auto& isTintA = settings.changeIsTintA;
auto& isColorOffsetR = settings.changeIsColorOffsetR;
auto& isColorOffsetG = settings.changeIsColorOffsetG;
auto& isColorOffsetB = settings.changeIsColorOffsetB;
auto& isVisibleSet = settings.changeIsVisibleSet;
auto& isInterpolatedSet = settings.changeIsInterpolatedSet;
auto& crop = settings.changeCrop;
auto& size = settings.changeSize;
auto& position = settings.changePosition;
auto& pivot = settings.changePivot;
auto& scale = settings.changeScale;
auto& rotation = settings.changeRotation;
auto& duration = settings.changeDuration;
auto& tint = settings.changeTint;
auto& colorOffset = settings.changeColorOffset;
auto& isVisible = settings.changeIsVisible;
auto& isInterpolated = settings.changeIsInterpolated;
#define PROPERTIES_WIDGET(body, checkboxLabel, isEnabled) \
ImGui::Checkbox(checkboxLabel, &isEnabled); \
ImGui::SameLine(); \
ImGui::BeginDisabled(!isEnabled); \
body; \
ImGui::EndDisabled();
auto bool_value = [&](const char* checkboxLabel, const char* valueLabel, bool& isEnabled, bool& value)
{ PROPERTIES_WIDGET(ImGui::Checkbox(valueLabel, &value), checkboxLabel, isEnabled) };
auto color3_value = [&](const char* checkboxRLabel, const char* checkboxGLabel, const char* checkboxBLabel,
const char* valueRLabel, const char* valueGLabel, const char* valueBLabel,
const char* label, bool& isREnabled, bool& isGEnabled, bool& isBEnabled, vec3& value)
{
auto style = ImGui::GetStyle();
auto width = (ImGui::CalcItemWidth() - (ImGui::GetFrameHeightWithSpacing() * 2) - (style.ItemSpacing.x * 2) -
ImGui::GetFrameHeight()) /
3;
ivec3 valueAlt = {float_to_uint8(value.r), float_to_uint8(value.g), float_to_uint8(value.b)};
ImGui::PushItemWidth(width);
PROPERTIES_WIDGET(ImGui::DragInt(valueRLabel, &valueAlt.r, DRAG_SPEED, 0, 255, "R:%d"), checkboxRLabel,
isREnabled);
ImGui::SameLine();
PROPERTIES_WIDGET(ImGui::DragInt(valueGLabel, &valueAlt.g, DRAG_SPEED, 0, 255, "G:%d"), checkboxGLabel,
isGEnabled);
ImGui::SameLine();
PROPERTIES_WIDGET(ImGui::DragInt(valueBLabel, &valueAlt.b, DRAG_SPEED, 0, 255, "B:%d"), checkboxBLabel,
isBEnabled);
ImGui::PopItemWidth();
ImGui::SameLine();
value = vec3(uint8_to_float(valueAlt.r), uint8_to_float(valueAlt.g), uint8_to_float(valueAlt.b));
ImVec4 buttonColor = {isREnabled ? value.r : 0, isGEnabled ? value.g : 0, isBEnabled ? value.b : 0, 1};
ImGui::ColorButton(label, buttonColor);
ImGui::SameLine();
ImGui::TextUnformatted(label);
};
auto color4_value = [&](const char* checkboxRLabel, const char* checkboxGLabel, const char* checkboxBLabel,
const char* checkboxALabel, const char* valueRLabel, const char* valueGLabel,
const char* valueBLabel, const char* valueALabel, const char* label, bool& isREnabled,
bool& isGEnabled, bool& isBEnabled, bool& isAEnabled, vec4& value)
{
auto style = ImGui::GetStyle();
auto width = (ImGui::CalcItemWidth() - (ImGui::GetFrameHeightWithSpacing() * 3) - (style.ItemSpacing.x * 3) -
ImGui::GetFrameHeight()) /
4;
ivec4 valueAlt = {float_to_uint8(value.r), float_to_uint8(value.g), float_to_uint8(value.b),
float_to_uint8(value.a)};
ImGui::PushItemWidth(width);
PROPERTIES_WIDGET(ImGui::DragInt(valueRLabel, &valueAlt.r, DRAG_SPEED, 0, 255, "R:%d"), checkboxRLabel,
isREnabled);
ImGui::SameLine();
PROPERTIES_WIDGET(ImGui::DragInt(valueGLabel, &valueAlt.g, DRAG_SPEED, 0, 255, "G:%d"), checkboxGLabel,
isGEnabled);
ImGui::SameLine();
PROPERTIES_WIDGET(ImGui::DragInt(valueBLabel, &valueAlt.b, DRAG_SPEED, 0, 255, "B:%d"), checkboxBLabel,
isBEnabled);
ImGui::SameLine();
PROPERTIES_WIDGET(ImGui::DragInt(valueALabel, &valueAlt.a, DRAG_SPEED, 0, 255, "A:%d"), checkboxALabel,
isAEnabled);
ImGui::PopItemWidth();
ImGui::SameLine();
value = vec4(uint8_to_float(valueAlt.r), uint8_to_float(valueAlt.g), uint8_to_float(valueAlt.b),
uint8_to_float(valueAlt.a));
ImVec4 buttonColor = {isREnabled ? value.r : 0, isGEnabled ? value.g : 0, isBEnabled ? value.b : 0,
isAEnabled ? value.a : 1};
ImGui::ColorButton(label, buttonColor);
ImGui::SameLine();
ImGui::TextUnformatted(label);
};
auto float_value = [&](const char* checkboxLabel, const char* valueLabel, bool& isEnabled, float& value)
{
PROPERTIES_WIDGET(ImGui::DragFloat(valueLabel, &value, DRAG_SPEED, 0.0f, 0.0f, float_format_get(value)),
checkboxLabel, isEnabled);
};
auto float2_value = [&](const char* checkboxXLabel, const char* checkboxYLabel, const char* valueXLabel,
const char* valueYLabel, bool& isXEnabled, bool& isYEnabled, vec2& value)
{
auto style = ImGui::GetStyle();
auto width = (ImGui::CalcItemWidth() - ImGui::GetFrameHeightWithSpacing() - style.ItemSpacing.x) / 2;
ImGui::PushItemWidth(width);
PROPERTIES_WIDGET(ImGui::DragFloat(valueXLabel, &value.x, DRAG_SPEED, 0.0f, 0.0f, float_format_get(value.x)),
checkboxXLabel, isXEnabled);
ImGui::SameLine();
PROPERTIES_WIDGET(ImGui::DragFloat(valueYLabel, &value.y, DRAG_SPEED, 0.0f, 0.0f, float_format_get(value.y)),
checkboxYLabel, isYEnabled);
ImGui::PopItemWidth();
};
auto duration_value = [&](const char* checkboxLabel, const char* valueLabel, bool& isEnabled, int& value)
{
PROPERTIES_WIDGET(
input_int_range(valueLabel, value, anm2::FRAME_DURATION_MIN, anm2::FRAME_DURATION_MAX, STEP, STEP_FAST),
checkboxLabel, isEnabled);
};
#undef PROPERTIES_WIDGET
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImGui::GetStyle().ItemInnerSpacing);
float2_value("##Is Crop X", "##Is Crop Y", "##Crop X", localize.get(BASIC_CROP), isCropX, isCropY, crop);
float2_value("##Is Size X", "##Is Size Y", "##Size X", localize.get(BASIC_SIZE), isSizeX, isSizeY, size);
float2_value("##Is Position X", "##Is Position Y", "##Position X", localize.get(BASIC_POSITION), isPositionX,
isPositionY, position);
float2_value("##Is Pivot X", "##Is Pivot Y", "##Pivot X", localize.get(BASIC_PIVOT), isPivotX, isPivotY, pivot);
float2_value("##Is Scale X", "##Is Scale Y", "##Scale X", localize.get(BASIC_SCALE), isScaleX, isScaleY, scale);
float_value("##Is Rotation", localize.get(BASIC_ROTATION), isRotation, rotation);
duration_value("##Is Duration", localize.get(BASIC_DURATION), isDuration, duration);
color4_value("##Is Tint R", "##Is Tint G", "##Is Tint B", "##Is Tint A", "##Tint R", "##Tint G", "##Tint B",
"##Tint A", localize.get(BASIC_TINT), isTintR, isTintG, isTintB, isTintA, tint);
color3_value("##Is Color Offset R", "##Is Color Offset G", "##Is Color Offset B", "##Color Offset R",
"##Color Offset B", "##Color Offset G", localize.get(BASIC_COLOR_OFFSET), isColorOffsetR,
isColorOffsetG, isColorOffsetB, colorOffset);
bool_value("##Is Visible", localize.get(BASIC_VISIBLE), isVisibleSet, isVisible);
ImGui::SameLine();
bool_value("##Is Interpolated", localize.get(BASIC_INTERPOLATED), isInterpolatedSet, isInterpolated);
ImGui::PopStyleVar();
auto frame_change = [&](anm2::ChangeType type)
{
anm2::FrameChange frameChange;
if (isCropX) frameChange.cropX = crop.x;
if (isCropY) frameChange.cropY = crop.y;
if (isSizeX) frameChange.sizeX = size.x;
if (isSizeY) frameChange.sizeY = size.y;
if (isPositionX) frameChange.positionX = position.x;
if (isPositionY) frameChange.positionY = position.y;
if (isPivotX) frameChange.pivotX = pivot.x;
if (isPivotY) frameChange.pivotY = pivot.y;
if (isScaleX) frameChange.scaleX = scale.x;
if (isScaleY) frameChange.scaleY = scale.y;
if (isRotation) frameChange.rotation = std::make_optional(rotation);
if (isDuration) frameChange.duration = std::make_optional(duration);
if (isTintR) frameChange.tintR = tint.r;
if (isTintG) frameChange.tintG = tint.g;
if (isTintB) frameChange.tintB = tint.b;
if (isTintA) frameChange.tintA = tint.a;
if (isColorOffsetR) frameChange.colorOffsetR = colorOffset.r;
if (isColorOffsetG) frameChange.colorOffsetG = colorOffset.g;
if (isColorOffsetB) frameChange.colorOffsetB = colorOffset.b;
if (isVisibleSet) frameChange.isVisible = std::make_optional(isVisible);
if (isInterpolatedSet) frameChange.isInterpolated = std::make_optional(isInterpolated);
DOCUMENT_EDIT(document, localize.get(EDIT_CHANGE_FRAME_PROPERTIES), Document::FRAMES,
document.item_get()->frames_change(frameChange, type, *frames.begin(), (int)frames.size()));
isChanged = true;
};
ImGui::Separator();
bool isAnyProperty = isCropX || isCropY || isSizeX || isSizeY || isPositionX || isPositionY || isPivotX ||
isPivotY || isScaleX || isScaleY || isRotation || isDuration || isTintR || isTintG ||
isTintB || isTintA || isColorOffsetR || isColorOffsetG || isColorOffsetB || isVisibleSet ||
isInterpolatedSet;
auto rowWidgetSize = widget_size_with_row_get(5);
ImGui::BeginDisabled(!isAnyProperty);
if (ImGui::Button(localize.get(LABEL_ADJUST), rowWidgetSize)) frame_change(anm2::ADJUST);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_ADJUST));
ImGui::SameLine();
if (ImGui::Button(localize.get(BASIC_ADD), rowWidgetSize)) frame_change(anm2::ADD);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_ADD_VALUES));
ImGui::SameLine();
if (ImGui::Button(localize.get(LABEL_SUBTRACT), rowWidgetSize)) frame_change(anm2::SUBTRACT);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_SUBTRACT_VALUES));
ImGui::SameLine();
if (ImGui::Button(localize.get(LABEL_MULTIPLY), rowWidgetSize)) frame_change(anm2::MULTIPLY);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_MULTIPLY_VALUES));
ImGui::SameLine();
if (ImGui::Button(localize.get(LABEL_DIVIDE), rowWidgetSize)) frame_change(anm2::DIVIDE);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_DIVIDE_VALUES));
ImGui::EndDisabled();
}
}
@@ -0,0 +1,15 @@
#pragma once
#include "document.h"
#include "settings.h"
namespace anm2ed::imgui::wizard
{
class ChangeAllFrameProperties
{
public:
bool isChanged{};
void update(Document&, Settings&);
};
}
+201
View File
@@ -0,0 +1,201 @@
#include "configure.h"
#include "imgui_.h"
using namespace anm2ed::types;
namespace anm2ed::imgui::wizard
{
void Configure::reset(Settings& settings) { temporary = settings; }
void Configure::update(Manager& manager, Settings& settings)
{
isSet = false;
auto childSize = size_without_footer_get(2);
if (ImGui::BeginTabBar("##Configure Tabs"))
{
if (ImGui::BeginTabItem(localize.get(LABEL_DISPLAY)))
{
if (ImGui::BeginChild("##Tab Child", childSize, true))
{
ImGui::SeparatorText(localize.get(LABEL_WINDOW_MENU));
input_float_range(localize.get(LABEL_UI_SCALE), temporary.uiScale, 0.5f, 2.0f, 0.25f, 0.25f, "%.2f");
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_UI_SCALE));
ImGui::Checkbox(localize.get(LABEL_VSYNC), &temporary.isVsync);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_VSYNC));
ImGui::SeparatorText(localize.get(LABEL_LOCALIZATION));
ImGui::Combo(localize.get(LABEL_LANGUAGE), &temporary.language, LANGUAGE_STRINGS, LANGUAGE_COUNT);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_LANGUAGE));
ImGui::SeparatorText(localize.get(LABEL_THEME));
for (int i = 0; i < theme::COUNT; i++)
{
ImGui::RadioButton(localize.get(theme::STRINGS[i]), &temporary.theme, i);
ImGui::SameLine();
}
}
ImGui::EndChild();
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem(localize.get(LABEL_FILE_MENU)))
{
if (ImGui::BeginChild("##Tab Child", childSize, true))
{
ImGui::SeparatorText(localize.get(LABEL_AUTOSAVE));
ImGui::Checkbox(localize.get(BASIC_ENABLED), &temporary.fileIsAutosave);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_AUTOSAVE_ENABLED));
ImGui::BeginDisabled(!temporary.fileIsAutosave);
input_int_range(localize.get(LABEL_TIME_MINUTES), temporary.fileAutosaveTime, 0, 10);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_AUTOSAVE_INTERVAL));
ImGui::EndDisabled();
ImGui::SeparatorText(localize.get(LABEL_SNAPSHOTS));
input_int_range(localize.get(LABEL_STACK_SIZE), temporary.fileSnapshotStackSize, 0, 100);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_STACK_SIZE));
ImGui::SeparatorText(localize.get(LABEL_OPTIONS));
ImGui::Checkbox(localize.get(LABEL_OVERWRITE_WARNING), &temporary.fileIsWarnOverwrite);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_OVERWRITE_WARNING));
}
ImGui::EndChild();
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem(localize.get(LABEL_INPUT)))
{
if (ImGui::BeginChild("##Tab Child", childSize, true))
{
ImGui::SeparatorText(localize.get(LABEL_KEYBOARD));
input_float_range(localize.get(LABEL_REPEAT_DELAY), temporary.keyboardRepeatDelay, 0.05f, 1.0f, 0.05f, 0.05f,
"%.2f");
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_REPEAT_DELAY));
input_float_range(localize.get(LABEL_REPEAT_RATE), temporary.keyboardRepeatRate, 0.005f, 1.0f, 0.005f, 0.005f,
"%.3f");
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_REPEAT_DELAY));
ImGui::SeparatorText(localize.get(LABEL_ZOOM));
input_float_range(localize.get(LABEL_ZOOM_STEP), temporary.inputZoomStep, 10.0f, 250.0f, 10.0f, 10.0f,
"%.0f%%");
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_ZOOM_STEP));
ImGui::SeparatorText(localize.get(LABEL_TOOL));
ImGui::Checkbox(localize.get(LABEL_MOVE_TOOL_SNAP), &temporary.inputIsMoveToolSnapToMouse);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_MOVE_TOOL_SNAP));
}
ImGui::EndChild();
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem(localize.get(LABEL_SHORTCUTS_TAB)))
{
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2());
if (ImGui::BeginChild("##Tab Child", childSize, true))
{
if (ImGui::BeginTable(localize.get(LABEL_SHORTCUTS_TAB), 2,
ImGuiTableFlags_Borders | ImGuiTableFlags_ScrollY))
{
ImGui::TableSetupScrollFreeze(0, 1);
ImGui::TableSetupColumn(localize.get(LABEL_SHORTCUT_COLUMN));
ImGui::TableSetupColumn(localize.get(LABEL_VALUE_COLUMN));
ImGui::TableHeadersRow();
for (int i = 0; i < SHORTCUT_COUNT; ++i)
{
bool isSelected = selectedShortcut == i;
ShortcutMember member = SHORTCUT_MEMBERS[i];
std::string* settingString = &(temporary.*member);
std::string chordString = isSelected ? "" : *settingString;
ImGui::PushID(i);
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted(localize.get(::anm2ed::SHORTCUT_STRING_TYPES[i]));
ImGui::TableSetColumnIndex(1);
if (ImGui::Selectable(chordString.c_str(), isSelected)) selectedShortcut = i;
ImGui::PopID();
if (isSelected)
{
ImGuiKeyChord chord{ImGuiKey_None};
if (ImGui::IsKeyDown(ImGuiMod_Ctrl)) chord |= ImGuiMod_Ctrl;
if (ImGui::IsKeyDown(ImGuiMod_Shift)) chord |= ImGuiMod_Shift;
if (ImGui::IsKeyDown(ImGuiMod_Alt)) chord |= ImGuiMod_Alt;
if (ImGui::IsKeyDown(ImGuiMod_Super)) chord |= ImGuiMod_Super;
for (const auto& entry : KEY_MAP)
{
auto key = entry.second;
if (ImGui::IsKeyPressed(key))
{
chord |= key;
*settingString = chord_to_string(chord);
selectedShortcut = -1;
break;
}
}
}
}
ImGui::EndTable();
}
ImGui::EndChild();
ImGui::PopStyleVar();
ImGui::EndTabItem();
}
}
ImGui::EndTabBar();
}
auto widgetSize = widget_size_with_row_get(3);
if (ImGui::Button(localize.get(BASIC_SAVE), widgetSize))
{
settings = temporary;
ImGui::GetIO().KeyRepeatDelay = settings.keyboardRepeatDelay;
ImGui::GetIO().KeyRepeatRate = settings.keyboardRepeatRate;
ImGui::GetStyle().FontScaleMain = settings.uiScale;
SnapshotStack::max_size_set(settings.fileSnapshotStackSize);
imgui::theme_set((theme::Type)settings.theme);
localize.language = (Language)settings.language;
manager.chords_set(settings);
for (auto& document : manager.documents)
document.snapshots.apply_limit();
isSet = true;
}
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_SETTINGS_SAVE));
ImGui::SameLine();
if (ImGui::Button(localize.get(LABEL_USE_DEFAULT_SETTINGS), widgetSize)) temporary = Settings();
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_USE_DEFAULT_SETTINGS));
ImGui::SameLine();
if (ImGui::Button(localize.get(LABEL_CLOSE), widgetSize)) isSet = true;
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_CLOSE_SETTINGS));
}
}
+18
View File
@@ -0,0 +1,18 @@
#pragma once
#include "manager.h"
namespace anm2ed::imgui::wizard
{
class Configure
{
Settings temporary{};
int selectedShortcut{-1};
public:
bool isSet{};
void reset(Settings&);
void update(Manager&, Settings&);
};
}
@@ -0,0 +1,115 @@
#include "generate_animation_from_grid.h"
#include "math_.h"
#include "types.h"
using namespace anm2ed::types;
using namespace anm2ed::util;
using namespace glm;
namespace anm2ed::imgui::wizard
{
GenerateAnimationFromGrid::GenerateAnimationFromGrid() : Canvas(vec2()) {}
void GenerateAnimationFromGrid::update(Document& document, Resources& resources, Settings& settings)
{
isEnd = false;
auto& startPosition = settings.generateStartPosition;
auto& size = settings.generateSize;
auto& pivot = settings.generatePivot;
auto& rows = settings.generateRows;
auto& columns = settings.generateColumns;
auto& count = settings.generateCount;
auto& delay = settings.generateDuration;
auto& zoom = settings.generateZoom;
auto& zoomStep = settings.inputZoomStep;
auto childSize = ImVec2(row_widget_width_get(2), size_without_footer_get().y);
if (ImGui::BeginChild("##Options Child", childSize, ImGuiChildFlags_Borders))
{
ImGui::InputInt2(localize.get(LABEL_GENERATE_START_POSITION), value_ptr(startPosition));
ImGui::InputInt2(localize.get(LABEL_GENERATE_FRAME_SIZE), value_ptr(size));
ImGui::InputInt2(localize.get(BASIC_PIVOT), value_ptr(pivot));
ImGui::InputInt(localize.get(LABEL_GENERATE_ROWS), &rows, STEP, STEP_FAST);
ImGui::InputInt(localize.get(LABEL_GENERATE_COLUMNS), &columns, STEP, STEP_FAST);
input_int_range(localize.get(LABEL_GENERATE_COUNT), count, anm2::FRAME_NUM_MIN, rows * columns);
ImGui::InputInt(localize.get(BASIC_DURATION), &delay, STEP, STEP_FAST);
}
ImGui::EndChild();
ImGui::SameLine();
if (ImGui::BeginChild("##Preview Child", childSize, ImGuiChildFlags_Borders))
{
auto& backgroundColor = settings.previewBackgroundColor;
auto& shaderTexture = resources.shaders[resource::shader::TEXTURE];
auto previewSize = ImVec2(ImGui::GetContentRegionAvail().x, size_without_footer_get(2).y);
bind();
size_set(to_vec2(previewSize));
viewport_set();
clear(vec4(backgroundColor, 1.0f));
if (document.reference.itemType == anm2::LAYER)
{
auto& texture =
document.anm2.content.spritesheets[document.anm2.content.layers[document.reference.itemID].spritesheetID]
.texture;
auto index = std::clamp((int)(time * (count - 1)), 0, (count - 1));
auto row = index / columns;
auto column = index % columns;
auto crop = startPosition + ivec2(size.x * column, size.y * row);
auto uvMin = (vec2(crop) + vec2(0.5f)) / vec2(texture.size);
auto uvMax = (vec2(crop) + vec2(size) - vec2(0.5f)) / vec2(texture.size);
mat4 transform = transform_get(zoom) * math::quad_model_get(size, {}, pivot);
texture_render(shaderTexture, texture.id, transform, vec4(1.0f), {},
math::uv_vertices_get(uvMin, uvMax).data());
}
unbind();
ImGui::Image(texture, previewSize);
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
ImGui::SliderFloat("##Time", &time, 0.0f, 1.0f, "");
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
ImGui::InputFloat("##Zoom", &zoom, zoomStep, zoomStep, "%.0f%%");
zoom = glm::clamp(zoom, ZOOM_MIN, ZOOM_MAX);
}
ImGui::EndChild();
auto widgetSize = widget_size_with_row_get(2);
if (ImGui::Button(localize.get(LABEL_GENERATE), widgetSize))
{
auto generate_from_grid = [&]()
{
auto item = document.item_get();
auto animation = document.animation_get();
if (item && animation)
{
item->frames_generate_from_grid(startPosition, size, pivot, columns, count, delay);
animation->frameNum = animation->length();
}
};
DOCUMENT_EDIT(document, localize.get(EDIT_GENERATE_ANIMATION_FROM_GRID), Document::FRAMES, generate_from_grid());
isEnd = true;
}
ImGui::SameLine();
if (ImGui::Button(localize.get(BASIC_CANCEL), widgetSize)) isEnd = true;
}
}
@@ -0,0 +1,20 @@
#pragma once
#include "canvas.h"
#include "document.h"
#include "resources.h"
#include "settings.h"
namespace anm2ed::imgui::wizard
{
class GenerateAnimationFromGrid : public Canvas
{
float time{};
public:
bool isEnd{};
GenerateAnimationFromGrid();
void update(Document&, Resources&, Settings&);
};
}
+290
View File
@@ -0,0 +1,290 @@
#include "render_animation.h"
#include <ranges>
#include "log.h"
#include "path_.h"
#include "process_.h"
#include "toast.h"
#include <ranges>
using namespace anm2ed::resource;
using namespace anm2ed::util;
namespace anm2ed::imgui::wizard
{
void RenderAnimation::range_to_animation_set(Manager& manager, Document& document)
{
if (auto animation = document.animation_get())
{
manager.recordingStart = 0;
manager.recordingEnd = animation->frameNum - 1;
}
}
void RenderAnimation::range_to_frames_set(Manager& manager, Document& document)
{
auto& frames = document.frames.selection;
if (!frames.empty())
{
if (auto item = document.item_get())
{
int duration{};
for (auto [i, frame] : std::views::enumerate(item->frames))
{
if ((int)i == *frames.begin()) manager.recordingStart = duration;
if ((int)i == *frames.rbegin()) manager.recordingEnd = duration + frame.duration - 1;
duration += frame.duration;
}
}
}
}
void RenderAnimation::reset(Manager& manager, Document& document, Settings& settings)
{
if (!manager.isRecordingRange) range_to_animation_set(manager, document);
settings.renderPath.replace_extension(render::EXTENSIONS[settings.renderType]);
}
void RenderAnimation::update(Manager& manager, Document& document, Resources& resources, Settings& settings,
Dialog& dialog)
{
isEnd = false;
auto animation = document.animation_get();
if (!animation) return;
auto& ffmpegPath = settings.renderFFmpegPath;
auto& path = settings.renderPath;
auto& format = settings.renderFormat;
auto& scale = settings.renderScale;
auto& isRaw = settings.renderIsRawAnimation;
auto& type = settings.renderType;
auto& start = manager.recordingStart;
auto& end = manager.recordingEnd;
auto& rows = settings.renderRows;
auto& columns = settings.renderColumns;
auto& isRange = manager.isRecordingRange;
auto& frames = document.frames.selection;
auto& reference = document.reference;
auto& frameNum = animation->frameNum;
auto widgetSize = widget_size_with_row_get(2);
auto dialogType = type == render::PNGS ? Dialog::PNG_DIRECTORY_SET
: type == render::SPRITESHEET ? Dialog::PNG_PATH_SET
: type == render::GIF ? Dialog::GIF_PATH_SET
: type == render::WEBM ? Dialog::WEBM_PATH_SET
: Dialog::NONE;
if (ImGui::ImageButton("##FFmpeg Path Set", resources.icons[icon::FOLDER].id, icon_size_get()))
dialog.file_open(Dialog::FFMPEG_PATH_SET);
ImGui::SameLine();
input_text_path(localize.get(LABEL_FFMPEG_PATH), &ffmpegPath);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_FFMPEG_PATH));
if (dialog.is_selected(Dialog::FFMPEG_PATH_SET))
{
ffmpegPath = path::to_utf8(dialog.path);
dialog.reset();
}
if (ImGui::ImageButton("##Path Set", resources.icons[icon::FOLDER].id, icon_size_get()))
{
if (dialogType == Dialog::PNG_DIRECTORY_SET)
dialog.folder_open(dialogType);
else
dialog.file_save(dialogType);
}
ImGui::SameLine();
auto pathLabel = type == render::PNGS ? LABEL_OUTPUT_DIRECTORY : LABEL_OUTPUT_PATH;
input_text_path(localize.get(pathLabel), &path);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_OUTPUT_PATH));
if (dialog.is_selected(dialogType))
{
path = path::to_utf8(dialog.path);
dialog.reset();
}
if (ImGui::Combo(localize.get(LABEL_TYPE), &type, render::STRINGS, render::COUNT))
path.replace_extension(render::EXTENSIONS[type]);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_RENDER_TYPE));
if (type == render::PNGS || type == render::SPRITESHEET) ImGui::Separator();
if (type == render::PNGS)
{
input_text_path(localize.get(LABEL_FORMAT), &format);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_FORMAT));
}
else if (type == render::SPRITESHEET)
{
input_int_range(localize.get(LABEL_GENERATE_ROWS), rows, 1, frameNum - 1);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_ROWS));
input_int_range(localize.get(LABEL_GENERATE_COLUMNS), columns, 1, frameNum - 1);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_COLUMNS));
}
ImGui::Separator();
ImGui::Checkbox(localize.get(LABEL_CUSTOM_RANGE), &isRange);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_CUSTOM_RANGE));
ImGui::SameLine();
ImGui::BeginDisabled(frames.empty() || reference.itemID == anm2::TRIGGER);
if (ImGui::Button(localize.get(LABEL_TO_SELECTED_FRAMES))) range_to_frames_set(manager, document);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_TO_SELECTED_FRAMES));
ImGui::EndDisabled();
ImGui::SameLine();
if (ImGui::Button(localize.get(LABEL_TO_ANIMATION_RANGE))) range_to_animation_set(manager, document);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_TO_ANIMATION_RANGE));
ImGui::BeginDisabled(!isRange);
{
input_int_range(localize.get(LABEL_START), start, 0, frameNum - 1);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_START));
input_int_range(localize.get(LABEL_END), end, start, frameNum - 1);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_END));
}
ImGui::EndDisabled();
ImGui::Separator();
ImGui::Checkbox(localize.get(LABEL_RAW), &isRaw);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_RAW));
ImGui::BeginDisabled(!isRaw);
{
input_float_range(localize.get(BASIC_SCALE), scale, 1.0f, 100.0f, STEP, STEP_FAST, "%.1fx");
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_SCALE_OUTPUT));
}
ImGui::EndDisabled();
ImGui::Separator();
ImGui::Checkbox(localize.get(LABEL_SOUND), &settings.timelineIsSound);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_SOUND));
ImGui::Separator();
if (ImGui::Button(localize.get(LABEL_RENDER), widgetSize))
{
path.replace_extension(render::EXTENSIONS[type]);
auto render_is_valid = [&]()
{
auto ffmpeg_is_valid = [&]()
{
if (!path::is_executable(ffmpegPath)) return false;
auto testCommand = ffmpegPath.string() + " -version";
Process process(testCommand.c_str(), "r");
auto result = process.output_get_and_close();
if (!result.contains("ffmpeg version")) return false;
return true;
};
auto ffmpeg_valid_check = [&]()
{
if (!ffmpeg_is_valid())
{
toasts.push(localize.get(TOAST_INVALID_FFMPEG));
logger.error(localize.get(TOAST_INVALID_FFMPEG, anm2ed::ENGLISH));
return false;
}
return true;
};
auto png_format_valid_check = [&]()
{
if (!format.string().contains("{}"))
{
toasts.push(localize.get(TOAST_PNG_FORMAT_INVALID));
logger.error(localize.get(TOAST_PNG_FORMAT_INVALID, anm2ed::ENGLISH));
}
return true;
};
auto path_valid_check = [&]()
{
if (path.empty())
{
toasts.push(localize.get(TOAST_RENDER_PATH_EMPTY));
logger.error(localize.get(TOAST_RENDER_PATH_EMPTY, anm2ed::ENGLISH));
return false;
}
return true;
};
auto png_directory_valid_check = [&]()
{
if (!path::ensure_directory(path))
{
toasts.push(localize.get(TOAST_PNG_DIRECTORY_INVALID));
logger.error(localize.get(TOAST_PNG_DIRECTORY_INVALID, anm2ed::ENGLISH));
return false;
}
return true;
};
auto spritesheet_valid_check = [&]()
{
if (rows <= 0 && columns <= 0)
{
toasts.push(localize.get(TOAST_RENDER_PATH_EMPTY));
logger.error(localize.get(TOAST_RENDER_PATH_EMPTY, anm2ed::ENGLISH));
return false;
}
return true;
};
if (!path_valid_check()) return false;
switch (type)
{
case render::PNGS:
if (!png_directory_valid_check()) return false;
if (!png_format_valid_check()) return false;
format.replace_extension(render::EXTENSIONS[render::SPRITESHEET]);
break;
case render::SPRITESHEET:
if (!spritesheet_valid_check()) return false;
break;
case render::GIF:
case render::WEBM:
case render::MP4:
if (!ffmpeg_valid_check()) return false;
break;
default:
return false;
break;
}
return true;
};
if (render_is_valid())
{
manager.isRecordingStart = true;
manager.progressPopup.open();
}
isEnd = true;
}
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_RENDER_BUTTON));
ImGui::SameLine();
if (ImGui::Button(localize.get(BASIC_CANCEL), widgetSize)) isEnd = true;
}
}
+20
View File
@@ -0,0 +1,20 @@
#pragma once
#include "dialog.h"
#include "manager.h"
#include "resources.h"
namespace anm2ed::imgui::wizard
{
class RenderAnimation
{
public:
bool isEnd{};
void range_to_frames_set(Manager&, Document&);
void range_to_animation_set(Manager&, Document&);
void reset(Manager&, Document&, Settings&);
void update(Manager&, Document&, Resources&, Settings&, Dialog&);
};
}