more minor polish there and here
This commit is contained in:
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
@@ -2,7 +2,7 @@
|
|||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"configurations": [
|
"configurations": [
|
||||||
{
|
{
|
||||||
"name": "Debug ANM2Ed",
|
"name": "Debug",
|
||||||
"type": "cppdbg",
|
"type": "cppdbg",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "${workspaceFolder}/build/anm2ed",
|
"program": "${workspaceFolder}/build/anm2ed",
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ set(SDL_SENSOR OFF CACHE BOOL "" FORCE)
|
|||||||
set(SDL_HIDAPI OFF CACHE BOOL "" FORCE)
|
set(SDL_HIDAPI OFF CACHE BOOL "" FORCE)
|
||||||
set(SDL_CAMERA OFF CACHE BOOL "" FORCE)
|
set(SDL_CAMERA OFF CACHE BOOL "" FORCE)
|
||||||
set(SDL_TRAY OFF CACHE BOOL "" FORCE)
|
set(SDL_TRAY OFF CACHE BOOL "" FORCE)
|
||||||
|
set(SDL_VULKAN OFF CACHE BOOL "" FORCE)
|
||||||
add_subdirectory(external/SDL EXCLUDE_FROM_ALL)
|
add_subdirectory(external/SDL EXCLUDE_FROM_ALL)
|
||||||
|
|
||||||
set(SDLMIXER_DEPS_SHARED OFF CACHE BOOL "" FORCE)
|
set(SDLMIXER_DEPS_SHARED OFF CACHE BOOL "" FORCE)
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ using namespace glm;
|
|||||||
|
|
||||||
namespace anm2ed::anm2
|
namespace anm2ed::anm2
|
||||||
{
|
{
|
||||||
Anm2::Anm2() { info.createdOn = time::get("%d-%B-%Y %I:%M:%S"); }
|
Anm2::Anm2() { info.createdOn = time::get("%m/%d/%Y %I:%M:%S %p"); }
|
||||||
|
|
||||||
Anm2::Anm2(const std::string& path, std::string* errorString)
|
Anm2::Anm2(const std::string& path, std::string* errorString)
|
||||||
{
|
{
|
||||||
@@ -74,4 +74,4 @@ namespace anm2ed::anm2
|
|||||||
if (vector::in_bounds(item->frames, frameIndex)) return &item->frames[frameIndex];
|
if (vector::in_bounds(item->frames, frameIndex)) return &item->frames[frameIndex];
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,10 +54,13 @@ namespace anm2ed::anm2
|
|||||||
event.serialize(document, eventsElement, id);
|
event.serialize(document, eventsElement, id);
|
||||||
element->InsertEndChild(eventsElement);
|
element->InsertEndChild(eventsElement);
|
||||||
|
|
||||||
auto soundsElement = document.NewElement("Sounds");
|
if (!sounds.empty())
|
||||||
for (auto& [id, sound] : sounds)
|
{
|
||||||
sound.serialize(document, soundsElement, id);
|
auto soundsElement = document.NewElement("Sounds");
|
||||||
element->InsertEndChild(soundsElement);
|
for (auto& [id, sound] : sounds)
|
||||||
|
sound.serialize(document, soundsElement, id);
|
||||||
|
element->InsertEndChild(soundsElement);
|
||||||
|
}
|
||||||
|
|
||||||
parent->InsertEndChild(element);
|
parent->InsertEndChild(element);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
#include "anm2_type.h"
|
|
||||||
#include "event.h"
|
#include "event.h"
|
||||||
#include "layer.h"
|
#include "layer.h"
|
||||||
#include "null.h"
|
#include "null.h"
|
||||||
|
|||||||
@@ -240,25 +240,25 @@ namespace anm2ed::anm2
|
|||||||
{
|
{
|
||||||
if (!vector::in_bounds(frames, index)) return;
|
if (!vector::in_bounds(frames, index)) return;
|
||||||
|
|
||||||
Frame& frame = frames[index];
|
auto original = frames[index];
|
||||||
if (frame.duration == FRAME_DURATION_MIN) return;
|
if (original.duration == FRAME_DURATION_MIN) return;
|
||||||
|
|
||||||
Frame frameNext = vector::in_bounds(frames, index + 1) ? frames[index + 1] : frame;
|
auto nextFrame = vector::in_bounds(frames, index + 1) ? frames[index + 1] : original;
|
||||||
|
|
||||||
int duration{};
|
int duration{};
|
||||||
int i = index;
|
int i = index;
|
||||||
|
|
||||||
while (duration < frame.duration)
|
while (duration < original.duration)
|
||||||
{
|
{
|
||||||
Frame baked = frame;
|
Frame baked = original;
|
||||||
float interpolation = (float)duration / frame.duration;
|
float interpolation = (float)duration / original.duration;
|
||||||
baked.duration = std::min(interval, frame.duration - duration);
|
baked.duration = std::min(interval, original.duration - duration);
|
||||||
baked.isInterpolated = (i == index) ? frame.isInterpolated : false;
|
baked.isInterpolated = (i == index) ? original.isInterpolated : false;
|
||||||
baked.rotation = glm::mix(frame.rotation, frameNext.rotation, interpolation);
|
baked.rotation = glm::mix(original.rotation, nextFrame.rotation, interpolation);
|
||||||
baked.position = glm::mix(frame.position, frameNext.position, interpolation);
|
baked.position = glm::mix(original.position, nextFrame.position, interpolation);
|
||||||
baked.scale = glm::mix(frame.scale, frameNext.scale, interpolation);
|
baked.scale = glm::mix(original.scale, nextFrame.scale, interpolation);
|
||||||
baked.colorOffset = glm::mix(frame.colorOffset, frameNext.colorOffset, interpolation);
|
baked.colorOffset = glm::mix(original.colorOffset, nextFrame.colorOffset, interpolation);
|
||||||
baked.tint = glm::mix(frame.tint, frameNext.tint, interpolation);
|
baked.tint = glm::mix(original.tint, nextFrame.tint, interpolation);
|
||||||
if (isRoundScale) baked.scale = vec2(ivec2(baked.scale));
|
if (isRoundScale) baked.scale = vec2(ivec2(baked.scale));
|
||||||
if (isRoundRotation) baked.rotation = (int)baked.rotation;
|
if (isRoundRotation) baked.rotation = (int)baked.rotation;
|
||||||
|
|
||||||
|
|||||||
@@ -2,14 +2,12 @@
|
|||||||
|
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "filesystem_.h"
|
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "toast.h"
|
#include "toast.h"
|
||||||
|
|
||||||
using namespace anm2ed::anm2;
|
using namespace anm2ed::anm2;
|
||||||
using namespace anm2ed::imgui;
|
using namespace anm2ed::imgui;
|
||||||
using namespace anm2ed::types;
|
using namespace anm2ed::types;
|
||||||
using namespace anm2ed::util;
|
|
||||||
|
|
||||||
using namespace glm;
|
using namespace glm;
|
||||||
|
|
||||||
@@ -17,10 +15,11 @@ namespace anm2ed
|
|||||||
{
|
{
|
||||||
Document::Document(const std::string& path, bool isNew, std::string* errorString)
|
Document::Document(const std::string& path, bool isNew, std::string* errorString)
|
||||||
{
|
{
|
||||||
if (!filesystem::path_is_exist(path)) return;
|
|
||||||
|
|
||||||
if (isNew)
|
if (isNew)
|
||||||
|
{
|
||||||
anm2 = anm2::Anm2();
|
anm2 = anm2::Anm2();
|
||||||
|
if (!save(path)) return;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
anm2 = Anm2(path, errorString);
|
anm2 = Anm2(path, errorString);
|
||||||
@@ -38,7 +37,7 @@ namespace anm2ed
|
|||||||
merge(current.merge), event(current.event), layer(current.layer), null(current.null), sound(current.sound),
|
merge(current.merge), event(current.event), layer(current.layer), null(current.null), sound(current.sound),
|
||||||
spritesheet(current.spritesheet), frames(current.frames), message(current.message),
|
spritesheet(current.spritesheet), frames(current.frames), message(current.message),
|
||||||
previewZoom(other.previewZoom), previewPan(other.previewPan), editorPan(other.editorPan),
|
previewZoom(other.previewZoom), previewPan(other.previewPan), editorPan(other.editorPan),
|
||||||
editorZoom(other.editorZoom), overlayIndex(other.overlayIndex), saveHash(other.saveHash),
|
editorZoom(other.editorZoom), overlayIndex(other.overlayIndex), hash(other.hash), saveHash(other.saveHash),
|
||||||
autosaveHash(other.autosaveHash), lastAutosaveTime(other.lastAutosaveTime), isOpen(other.isOpen),
|
autosaveHash(other.autosaveHash), lastAutosaveTime(other.lastAutosaveTime), isOpen(other.isOpen),
|
||||||
isForceDirty(other.isForceDirty), isAnimationPreviewSet(other.isAnimationPreviewSet),
|
isForceDirty(other.isForceDirty), isAnimationPreviewSet(other.isAnimationPreviewSet),
|
||||||
isSpritesheetEditorSet(other.isSpritesheetEditorSet)
|
isSpritesheetEditorSet(other.isSpritesheetEditorSet)
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
#include "icon.h"
|
#include "icon.h"
|
||||||
|
#include "toast.h"
|
||||||
|
|
||||||
using namespace anm2ed::resource;
|
using namespace anm2ed::resource;
|
||||||
using namespace anm2ed::types;
|
using namespace anm2ed::types;
|
||||||
@@ -60,7 +61,12 @@ namespace anm2ed::imgui
|
|||||||
ImGui::EndMenu();
|
ImGui::EndMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui::MenuItem("Save", settings.shortcutSave.c_str(), false, document)) manager.save();
|
if (ImGui::MenuItem("Save", settings.shortcutSave.c_str(), false, document))
|
||||||
|
{
|
||||||
|
if (settings.fileIsWarnOverwrite) overwritePopup.open();
|
||||||
|
manager.save();
|
||||||
|
}
|
||||||
|
|
||||||
if (ImGui::MenuItem("Save As", settings.shortcutSaveAs.c_str(), false, document))
|
if (ImGui::MenuItem("Save As", settings.shortcutSaveAs.c_str(), false, document))
|
||||||
dialog.file_save(dialog::ANM2_SAVE);
|
dialog.file_save(dialog::ANM2_SAVE);
|
||||||
if (ImGui::MenuItem("Explore XML Location", nullptr, false, document))
|
if (ImGui::MenuItem("Explore XML Location", nullptr, false, document))
|
||||||
@@ -275,6 +281,11 @@ namespace anm2ed::imgui
|
|||||||
input_int_range("Time (minutes)", editSettings.fileAutosaveTime, 0, 10);
|
input_int_range("Time (minutes)", editSettings.fileAutosaveTime, 0, 10);
|
||||||
ImGui::SetItemTooltip("If changed, will autosave documents using this interval.");
|
ImGui::SetItemTooltip("If changed, will autosave documents using this interval.");
|
||||||
ImGui::EndDisabled();
|
ImGui::EndDisabled();
|
||||||
|
|
||||||
|
ImGui::SeparatorText("Options");
|
||||||
|
|
||||||
|
ImGui::Checkbox("Overwrite Warning", &editSettings.fileIsWarnOverwrite);
|
||||||
|
ImGui::SetItemTooltip("A warning will be shown when saving a file.");
|
||||||
}
|
}
|
||||||
ImGui::EndChild();
|
ImGui::EndChild();
|
||||||
|
|
||||||
@@ -297,7 +308,7 @@ namespace anm2ed::imgui
|
|||||||
|
|
||||||
ImGui::SeparatorText("Zoom");
|
ImGui::SeparatorText("Zoom");
|
||||||
|
|
||||||
input_float_range("Step", editSettings.viewZoomStep, 10.0f, 250.0f, 10.0f, 10.0f, "%.0f");
|
input_float_range("Step", editSettings.viewZoomStep, 10.0f, 250.0f, 10.0f, 10.0f, "%.0f%%");
|
||||||
ImGui::SetItemTooltip("When zooming in/out with mouse or shortcut, this value will be used.");
|
ImGui::SetItemTooltip("When zooming in/out with mouse or shortcut, this value will be used.");
|
||||||
}
|
}
|
||||||
ImGui::EndChild();
|
ImGui::EndChild();
|
||||||
@@ -494,11 +505,77 @@ namespace anm2ed::imgui
|
|||||||
|
|
||||||
if (ImGui::Button("Render", widgetSize))
|
if (ImGui::Button("Render", widgetSize))
|
||||||
{
|
{
|
||||||
manager.isRecordingStart = true;
|
bool canStart = true;
|
||||||
playback.time = start;
|
auto warn_and_close = [&](const std::string& message)
|
||||||
playback.isPlaying = true;
|
{
|
||||||
renderPopup.close();
|
toasts.warning(message);
|
||||||
manager.progressPopup.open();
|
renderPopup.close();
|
||||||
|
canStart = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto ffmpegPathValid = [&]() -> bool
|
||||||
|
{
|
||||||
|
if (ffmpegPath.empty()) return false;
|
||||||
|
std::error_code ec{};
|
||||||
|
std::filesystem::path ffmpeg(ffmpegPath);
|
||||||
|
if (!std::filesystem::exists(ffmpeg, ec) || !std::filesystem::is_regular_file(ffmpeg, ec)) return false;
|
||||||
|
#ifdef _WIN32
|
||||||
|
auto ext = ffmpeg.extension().string();
|
||||||
|
std::transform(ext.begin(), ext.end(), ext.begin(), [](unsigned char c) { return (char)std::tolower(c); });
|
||||||
|
if (ext != ".exe") return false;
|
||||||
|
return true;
|
||||||
|
#else
|
||||||
|
auto permMask = std::filesystem::status(ffmpeg, ec).permissions();
|
||||||
|
using std::filesystem::perms;
|
||||||
|
if (permMask == perms::unknown) return true;
|
||||||
|
auto has_exec = [&](perms p)
|
||||||
|
{
|
||||||
|
return (perms::none != (p & perms::owner_exec)) || (perms::none != (p & perms::group_exec)) ||
|
||||||
|
(perms::none != (p & perms::others_exec));
|
||||||
|
};
|
||||||
|
return has_exec(permMask);
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!ffmpegPathValid()) warn_and_close("Invalid FFmpeg executable. Please set a valid FFmpeg path.");
|
||||||
|
|
||||||
|
if (canStart)
|
||||||
|
{
|
||||||
|
std::error_code ec{};
|
||||||
|
if (type == render::PNGS)
|
||||||
|
{
|
||||||
|
if (path.empty())
|
||||||
|
warn_and_close("Select an output directory for PNG exports.");
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::filesystem::path directory(path);
|
||||||
|
if (!std::filesystem::exists(directory, ec) || !std::filesystem::is_directory(directory, ec))
|
||||||
|
warn_and_close("PNG exports require a valid directory.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::filesystem::path output(path);
|
||||||
|
auto parent = output.parent_path();
|
||||||
|
auto parentInvalid =
|
||||||
|
!parent.empty() && (!std::filesystem::exists(parent, ec) || !std::filesystem::is_directory(parent, ec));
|
||||||
|
if (path.empty() || std::filesystem::is_directory(output, ec) || parentInvalid)
|
||||||
|
{
|
||||||
|
output = std::filesystem::path("output").replace_extension(render::EXTENSIONS[type]);
|
||||||
|
path = output.string();
|
||||||
|
warn_and_close(std::format("Invalid output file. Using default path: {}", path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canStart)
|
||||||
|
{
|
||||||
|
manager.isRecordingStart = true;
|
||||||
|
playback.time = start;
|
||||||
|
playback.isPlaying = true;
|
||||||
|
renderPopup.close();
|
||||||
|
manager.progressPopup.open();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
@@ -705,13 +782,38 @@ namespace anm2ed::imgui
|
|||||||
ImGui::EndPopup();
|
ImGui::EndPopup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
overwritePopup.trigger();
|
||||||
|
if (ImGui::BeginPopupModal(overwritePopup.label, &overwritePopup.isOpen, ImGuiWindowFlags_NoResize))
|
||||||
|
{
|
||||||
|
|
||||||
|
ImGui::Text("Are you sure? This will overwrite the existing file.");
|
||||||
|
|
||||||
|
auto widgetSize = widget_size_with_row_get(2);
|
||||||
|
|
||||||
|
if (ImGui::Button("Yes", widgetSize))
|
||||||
|
{
|
||||||
|
manager.save();
|
||||||
|
overwritePopup.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::SameLine();
|
||||||
|
|
||||||
|
if (ImGui::Button("No", widgetSize)) overwritePopup.close();
|
||||||
|
|
||||||
|
ImGui::EndPopup();
|
||||||
|
}
|
||||||
|
|
||||||
if (resources.music.is_playing() && !aboutPopup.isOpen) resources.music.stop();
|
if (resources.music.is_playing() && !aboutPopup.isOpen) resources.music.stop();
|
||||||
|
|
||||||
aboutPopup.end();
|
aboutPopup.end();
|
||||||
|
|
||||||
if (shortcut(manager.chords[SHORTCUT_NEW], shortcut::GLOBAL)) dialog.file_save(dialog::ANM2_NEW);
|
if (shortcut(manager.chords[SHORTCUT_NEW], shortcut::GLOBAL)) dialog.file_save(dialog::ANM2_NEW);
|
||||||
if (shortcut(manager.chords[SHORTCUT_OPEN], shortcut::GLOBAL)) dialog.file_open(dialog::ANM2_OPEN);
|
if (shortcut(manager.chords[SHORTCUT_OPEN], shortcut::GLOBAL)) dialog.file_open(dialog::ANM2_OPEN);
|
||||||
if (shortcut(manager.chords[SHORTCUT_SAVE], shortcut::GLOBAL)) manager.save();
|
if (shortcut(manager.chords[SHORTCUT_SAVE], shortcut::GLOBAL))
|
||||||
|
{
|
||||||
|
if (settings.fileIsWarnOverwrite) overwritePopup.open();
|
||||||
|
manager.save();
|
||||||
|
}
|
||||||
if (shortcut(manager.chords[SHORTCUT_SAVE_AS], shortcut::GLOBAL)) dialog.file_save(dialog::ANM2_SAVE);
|
if (shortcut(manager.chords[SHORTCUT_SAVE_AS], shortcut::GLOBAL)) dialog.file_save(dialog::ANM2_SAVE);
|
||||||
if (shortcut(manager.chords[SHORTCUT_EXIT], shortcut::GLOBAL)) isQuitting = true;
|
if (shortcut(manager.chords[SHORTCUT_EXIT], shortcut::GLOBAL)) isQuitting = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ namespace anm2ed::imgui
|
|||||||
Canvas generate;
|
Canvas generate;
|
||||||
float generateTime{};
|
float generateTime{};
|
||||||
PopupHelper generatePopup{PopupHelper("Generate Animation from Grid")};
|
PopupHelper generatePopup{PopupHelper("Generate Animation from Grid")};
|
||||||
|
PopupHelper overwritePopup{PopupHelper("Overwrite File", imgui::POPUP_SMALL_NO_HEIGHT)};
|
||||||
PopupHelper renderPopup{PopupHelper("Render Animation", imgui::POPUP_SMALL_NO_HEIGHT)};
|
PopupHelper renderPopup{PopupHelper("Render Animation", imgui::POPUP_SMALL_NO_HEIGHT)};
|
||||||
PopupHelper configurePopup{PopupHelper("Configure")};
|
PopupHelper configurePopup{PopupHelper("Configure")};
|
||||||
PopupHelper aboutPopup{PopupHelper("About")};
|
PopupHelper aboutPopup{PopupHelper("About")};
|
||||||
|
|||||||
@@ -48,7 +48,8 @@ namespace anm2ed::imgui
|
|||||||
{
|
{
|
||||||
if (auto trigger = animation->triggers.frame_generate(playback.time, anm2::TRIGGER);
|
if (auto trigger = animation->triggers.frame_generate(playback.time, anm2::TRIGGER);
|
||||||
trigger.is_visible(anm2::TRIGGER))
|
trigger.is_visible(anm2::TRIGGER))
|
||||||
if (anm2.content.sounds.contains(trigger.soundID)) anm2.content.sounds[trigger.soundID].audio.play(mixer);
|
if (anm2.content.sounds.contains(trigger.soundID))
|
||||||
|
anm2.content.sounds[trigger.soundID].audio.play(false, mixer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,7 +133,7 @@ namespace anm2ed::imgui
|
|||||||
{
|
{
|
||||||
if (auto rect = animation->rect(isRootTransform); rect != vec4(-1.0f))
|
if (auto rect = animation->rect(isRootTransform); rect != vec4(-1.0f))
|
||||||
{
|
{
|
||||||
size_set(vec2(rect.w, rect.z) * scale);
|
size_set(vec2(rect.z, rect.w) * scale);
|
||||||
set_to_rect(zoom, pan, rect);
|
set_to_rect(zoom, pan, rect);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -583,6 +584,8 @@ namespace anm2ed::imgui
|
|||||||
|
|
||||||
ImGui::ProgressBar(progress);
|
ImGui::ProgressBar(progress);
|
||||||
|
|
||||||
|
ImGui::Text("Once recording is complete, rendering may take some time.\nPlease be patient...");
|
||||||
|
|
||||||
if (ImGui::Button("Cancel", ImVec2(ImGui::GetContentRegionAvail().x, 0)))
|
if (ImGui::Button("Cancel", ImVec2(ImGui::GetContentRegionAvail().x, 0)))
|
||||||
{
|
{
|
||||||
playback.isPlaying = false;
|
playback.isPlaying = false;
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
#include "audio_stream.h"
|
#include "audio_stream.h"
|
||||||
#include "canvas.h"
|
#include "canvas.h"
|
||||||
#include "manager.h"
|
#include "manager.h"
|
||||||
#include "render.h"
|
|
||||||
#include "resources.h"
|
#include "resources.h"
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
|
|
||||||
|
|||||||
@@ -106,22 +106,26 @@ namespace anm2ed::imgui
|
|||||||
|
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing);
|
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing);
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, style.WindowPadding);
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, style.WindowPadding);
|
||||||
|
auto viewport = ImGui::GetMainViewport();
|
||||||
|
auto textureSize = texture.size.x * texture.size.y > (viewport->Size.x * viewport->Size.y) * 0.5f
|
||||||
|
? to_vec2(viewport->Size) * 0.5f
|
||||||
|
: vec2(texture.size);
|
||||||
|
auto aspectRatio = (float)texture.size.x / texture.size.y;
|
||||||
|
|
||||||
|
if (textureSize.x / textureSize.y > aspectRatio)
|
||||||
|
textureSize.x = textureSize.y * aspectRatio;
|
||||||
|
else
|
||||||
|
textureSize.y = textureSize.x / aspectRatio;
|
||||||
|
|
||||||
|
auto textWidth = ImGui::CalcTextSize(path).x;
|
||||||
|
auto tooltipPadding = style.WindowPadding.x * 4.0f;
|
||||||
|
auto minWidth = textureSize.x + style.ItemSpacing.x + textWidth + tooltipPadding;
|
||||||
|
ImGui::SetNextWindowSize(ImVec2(minWidth, 0), ImGuiCond_Appearing);
|
||||||
|
|
||||||
if (ImGui::BeginItemTooltip())
|
if (ImGui::BeginItemTooltip())
|
||||||
{
|
{
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2());
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2());
|
||||||
|
|
||||||
auto viewport = ImGui::GetMainViewport();
|
|
||||||
|
|
||||||
auto textureSize = texture.size.x * texture.size.y > (viewport->Size.x * viewport->Size.y) * 0.5f
|
|
||||||
? to_vec2(viewport->Size) * 0.5f
|
|
||||||
: vec2(texture.size);
|
|
||||||
auto aspectRatio = (float)texture.size.x / texture.size.y;
|
|
||||||
|
|
||||||
if (textureSize.x / textureSize.y > aspectRatio)
|
|
||||||
textureSize.x = textureSize.y * aspectRatio;
|
|
||||||
else
|
|
||||||
textureSize.y = textureSize.x / aspectRatio;
|
|
||||||
|
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2());
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2());
|
||||||
if (ImGui::BeginChild("##Spritesheet Tooltip Image Child", to_imvec2(textureSize),
|
if (ImGui::BeginChild("##Spritesheet Tooltip Image Child", to_imvec2(textureSize),
|
||||||
ImGuiChildFlags_Borders))
|
ImGuiChildFlags_Borders))
|
||||||
@@ -148,7 +152,7 @@ namespace anm2ed::imgui
|
|||||||
ImGui::PopStyleVar(2);
|
ImGui::PopStyleVar(2);
|
||||||
|
|
||||||
auto imageSize = to_imvec2(vec2(spritesheetChildSize.y));
|
auto imageSize = to_imvec2(vec2(spritesheetChildSize.y));
|
||||||
auto aspectRatio = (float)texture.size.x / texture.size.y;
|
aspectRatio = (float)texture.size.x / texture.size.y;
|
||||||
|
|
||||||
if (imageSize.x / imageSize.y > aspectRatio)
|
if (imageSize.x / imageSize.y > aspectRatio)
|
||||||
imageSize.x = imageSize.y * aspectRatio;
|
imageSize.x = imageSize.y * aspectRatio;
|
||||||
@@ -284,4 +288,4 @@ namespace anm2ed::imgui
|
|||||||
}
|
}
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -176,7 +176,17 @@ namespace anm2ed::imgui
|
|||||||
ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4());
|
ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4());
|
||||||
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4());
|
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4());
|
||||||
if (ImGui::Selectable("##Item Button", false, ImGuiSelectableFlags_None, itemSize))
|
if (ImGui::Selectable("##Item Button", false, ImGuiSelectableFlags_None, itemSize))
|
||||||
|
{
|
||||||
|
if (type == anm2::LAYER)
|
||||||
|
{
|
||||||
|
document.spritesheet.reference = anm2.content.layers[id].spritesheetID;
|
||||||
|
document.layer.selection = {id};
|
||||||
|
}
|
||||||
|
else if (type == anm2::NULL_)
|
||||||
|
document.null.selection = {id};
|
||||||
|
|
||||||
reference = {reference.animationIndex, type, id};
|
reference = {reference.animationIndex, type, id};
|
||||||
|
}
|
||||||
ImGui::PopStyleColor(3);
|
ImGui::PopStyleColor(3);
|
||||||
if (ImGui::IsItemHovered())
|
if (ImGui::IsItemHovered())
|
||||||
{
|
{
|
||||||
@@ -993,10 +1003,11 @@ namespace anm2ed::imgui
|
|||||||
|
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
|
|
||||||
|
ImGui::BeginDisabled(!animation || animation->frameNum == animation->length());
|
||||||
if (ImGui::Button("Fit Animation Length", widgetSize))
|
if (ImGui::Button("Fit Animation Length", widgetSize))
|
||||||
DOCUMENT_EDIT(document, "Fit Animation Length", Document::ANIMATIONS,
|
DOCUMENT_EDIT(document, "Fit Animation Length", Document::ANIMATIONS, animation->fit_length());
|
||||||
animation->frameNum = animation->length());
|
|
||||||
ImGui::SetItemTooltip("The animation length will be set to the effective length of the animation.");
|
ImGui::SetItemTooltip("The animation length will be set to the effective length of the animation.");
|
||||||
|
ImGui::EndDisabled();
|
||||||
|
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
|
|
||||||
@@ -1030,7 +1041,7 @@ namespace anm2ed::imgui
|
|||||||
auto createdBy = anm2.info.createdBy;
|
auto createdBy = anm2.info.createdBy;
|
||||||
ImGui::SetNextItemWidth(widgetSize.x);
|
ImGui::SetNextItemWidth(widgetSize.x);
|
||||||
if (input_text_string("Author", &createdBy))
|
if (input_text_string("Author", &createdBy))
|
||||||
DOCUMENT_EDIT(document, "FPS", Document::ANIMATIONS, anm2.info.createdBy = createdBy);
|
DOCUMENT_EDIT(document, "Author", Document::ANIMATIONS, anm2.info.createdBy = createdBy);
|
||||||
ImGui::SetItemTooltip("Set the author of the document.");
|
ImGui::SetItemTooltip("Set the author of the document.");
|
||||||
|
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
@@ -1239,9 +1250,10 @@ namespace anm2ed::imgui
|
|||||||
|
|
||||||
if (ImGui::Button("Bake", widgetSize))
|
if (ImGui::Button("Bake", widgetSize))
|
||||||
{
|
{
|
||||||
if (auto itemPtr = document.item_get())
|
if (auto item = document.item_get())
|
||||||
DOCUMENT_EDIT(document, "Bake Frames", Document::FRAMES,
|
for (auto i : frames.selection | std::views::reverse)
|
||||||
itemPtr->frames_bake(reference.frameIndex, interval, isRoundScale, isRoundRotation));
|
DOCUMENT_EDIT(document, "Bake Frames", Document::FRAMES,
|
||||||
|
item->frames_bake(i, interval, isRoundScale, isRoundRotation));
|
||||||
bakePopup.close();
|
bakePopup.close();
|
||||||
}
|
}
|
||||||
ImGui::SetItemTooltip("Bake the selected frame(s) with the options selected.");
|
ImGui::SetItemTooltip("Bake the selected frame(s) with the options selected.");
|
||||||
|
|||||||
@@ -39,15 +39,7 @@ namespace anm2ed
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isRecent)
|
if (isRecent) recent_file_add(path);
|
||||||
{
|
|
||||||
recentFiles.erase(std::remove(recentFiles.begin(), recentFiles.end(), path), recentFiles.end());
|
|
||||||
recentFiles.insert(recentFiles.begin(), path);
|
|
||||||
|
|
||||||
if (recentFiles.size() > RECENT_LIMIT) recentFiles.resize(RECENT_LIMIT);
|
|
||||||
|
|
||||||
recent_files_write();
|
|
||||||
}
|
|
||||||
|
|
||||||
selected = (int)documents.size() - 1;
|
selected = (int)documents.size() - 1;
|
||||||
pendingSelected = selected;
|
pendingSelected = selected;
|
||||||
@@ -63,6 +55,7 @@ namespace anm2ed
|
|||||||
std::string errorString{};
|
std::string errorString{};
|
||||||
document->path = !path.empty() ? path : document->path.string();
|
document->path = !path.empty() ? path : document->path.string();
|
||||||
document->save(document->path, &errorString);
|
document->save(document->path, &errorString);
|
||||||
|
recent_file_add(document->path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,6 +163,22 @@ namespace anm2ed
|
|||||||
nullPropertiesPopup.close();
|
nullPropertiesPopup.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Manager::recent_file_add(const std::string& path)
|
||||||
|
{
|
||||||
|
if (path.empty()) return;
|
||||||
|
std::error_code ec{};
|
||||||
|
if (!std::filesystem::exists(path, ec))
|
||||||
|
{
|
||||||
|
logger.warning(std::format("Skipping missing recent file: {}", path));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
recentFiles.erase(std::remove(recentFiles.begin(), recentFiles.end(), path), recentFiles.end());
|
||||||
|
recentFiles.insert(recentFiles.begin(), path);
|
||||||
|
if (recentFiles.size() > RECENT_LIMIT) recentFiles.resize(RECENT_LIMIT);
|
||||||
|
recent_files_write();
|
||||||
|
}
|
||||||
|
|
||||||
void Manager::recent_files_load()
|
void Manager::recent_files_load()
|
||||||
{
|
{
|
||||||
auto path = recent_files_path_get();
|
auto path = recent_files_path_get();
|
||||||
@@ -189,6 +198,12 @@ namespace anm2ed
|
|||||||
{
|
{
|
||||||
if (line.empty()) continue;
|
if (line.empty()) continue;
|
||||||
if (std::find(recentFiles.begin(), recentFiles.end(), line) != recentFiles.end()) continue;
|
if (std::find(recentFiles.begin(), recentFiles.end(), line) != recentFiles.end()) continue;
|
||||||
|
std::error_code ec{};
|
||||||
|
if (!std::filesystem::exists(line, ec))
|
||||||
|
{
|
||||||
|
logger.warning(std::format("Skipping missing recent file: {}", line));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
recentFiles.emplace_back(line);
|
recentFiles.emplace_back(line);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ namespace anm2ed
|
|||||||
void recent_files_load();
|
void recent_files_load();
|
||||||
void recent_files_write();
|
void recent_files_write();
|
||||||
void recent_files_clear();
|
void recent_files_clear();
|
||||||
|
void recent_file_add(const std::string&);
|
||||||
|
|
||||||
void autosave_files_load();
|
void autosave_files_load();
|
||||||
void autosave_files_open();
|
void autosave_files_open();
|
||||||
|
|||||||
@@ -34,6 +34,6 @@ namespace anm2ed::render
|
|||||||
|
|
||||||
namespace anm2ed
|
namespace anm2ed
|
||||||
{
|
{
|
||||||
bool animation_render(const std::string&, const std::string&, std::vector<resource::Texture>, AudioStream,
|
bool animation_render(const std::string&, const std::string&, std::vector<resource::Texture>&, AudioStream&,
|
||||||
render::Type, glm::ivec2, int);
|
render::Type, glm::ivec2, int);
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,10 @@
|
|||||||
#include "audio_stream.h"
|
#include "audio_stream.h"
|
||||||
|
|
||||||
|
#if defined(__clang__) || defined(__GNUC__)
|
||||||
|
#pragma GCC diagnostic push
|
||||||
|
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace anm2ed
|
namespace anm2ed
|
||||||
{
|
{
|
||||||
void AudioStream::callback(void* userData, MIX_Mixer* mixer, const SDL_AudioSpec* spec, float* pcm, int samples)
|
void AudioStream::callback(void* userData, MIX_Mixer* mixer, const SDL_AudioSpec* spec, float* pcm, int samples)
|
||||||
@@ -8,15 +13,9 @@ namespace anm2ed
|
|||||||
self->stream.insert(self->stream.end(), pcm, pcm + samples);
|
self->stream.insert(self->stream.end(), pcm, pcm + samples);
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioStream::AudioStream(MIX_Mixer* mixer)
|
AudioStream::AudioStream(MIX_Mixer* mixer) { MIX_GetMixerFormat(mixer, &spec); }
|
||||||
{
|
|
||||||
MIX_GetMixerFormat(mixer, &spec);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioStream::capture_begin(MIX_Mixer* mixer)
|
void AudioStream::capture_begin(MIX_Mixer* mixer) { MIX_SetPostMixCallback(mixer, callback, this); }
|
||||||
{
|
|
||||||
MIX_SetPostMixCallback(mixer, callback, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioStream::capture_end(MIX_Mixer* mixer)
|
void AudioStream::capture_end(MIX_Mixer* mixer)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -66,12 +66,12 @@ namespace anm2ed::resource::icon
|
|||||||
)";
|
)";
|
||||||
|
|
||||||
constexpr auto SHOW_RECT_DATA = R"(
|
constexpr auto SHOW_RECT_DATA = R"(
|
||||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <rect x="4" y="4" width="16" height="16" rx="0.5" stroke="#FFF" stroke-width="2" stroke-linejoin="round"/> <path d="M12 9.5v5M9.5 12h5" stroke="#FFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg>
|
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <rect x="4" y="4" width="16" height="16" rx="0.5" stroke="#FFF" stroke-width="2" stroke-linejoin="round"/> <circle cx="12" cy="12" r="1" fill="#FFF" stroke="none"/> </svg>
|
||||||
)";
|
)";
|
||||||
|
|
||||||
constexpr auto HIDE_RECT_DATA = R"(
|
constexpr auto HIDE_RECT_DATA = R"(
|
||||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <rect x="4" y="4" width="16" height="16" rx="0.5" stroke="#FFF" stroke-width="2" stroke-linejoin="round"/> <path d="M12 9.5v5M9.5 12h5" stroke="#FFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> <path d="M2 2L22 22" stroke="#FFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg>
|
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <rect x="4" y="4" width="16" height="16" rx="0.5" stroke="#FFF" stroke-width="2" stroke-linejoin="round"/> <circle cx="12" cy="12" r="1" fill="#FFF" stroke="none"/> <path d="M2 2L22 22" stroke="#FFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </svg>
|
||||||
)";
|
)";
|
||||||
|
|
||||||
constexpr auto ANIMATION_DATA = R"(
|
constexpr auto ANIMATION_DATA = R"(
|
||||||
<svg viewBox="0 0 24 24" fill="#FFF" xmlns="http://www.w3.org/2000/svg"><path d="M5.99807 7L8.30747 3H11.9981L9.68867 7H5.99807ZM11.9981 7L14.3075 3H17.9981L15.6887 7H11.9981ZM17.9981 7L20.3075 3H21.0082C21.556 3 22 3.44495 22 3.9934V20.0066C22 20.5552 21.5447 21 21.0082 21H2.9918C2.44405 21 2 20.5551 2 20.0066V3.9934C2 3.44476 2.45531 3 2.9918 3H5.99807L4 6.46076V19H20V7H17.9981Z"/></svg>
|
<svg viewBox="0 0 24 24" fill="#FFF" xmlns="http://www.w3.org/2000/svg"><path d="M5.99807 7L8.30747 3H11.9981L9.68867 7H5.99807ZM11.9981 7L14.3075 3H17.9981L15.6887 7H11.9981ZM17.9981 7L20.3075 3H21.0082C21.556 3 22 3.44495 22 3.9934V20.0066C22 20.5552 21.5447 21 21.0082 21H2.9918C2.44405 21 2 20.5551 2 20.0066V3.9934C2 3.44476 2.45531 3 2.9918 3H5.99807L4 6.46076V19H20V7H17.9981Z"/></svg>
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ namespace anm2ed
|
|||||||
\
|
\
|
||||||
X(FILE_IS_AUTOSAVE, fileIsAutosave, "Autosave", BOOL, true) \
|
X(FILE_IS_AUTOSAVE, fileIsAutosave, "Autosave", BOOL, true) \
|
||||||
X(FILE_AUTOSAVE_TIME, fileAutosaveTime, "Autosave Time", INT, 1) \
|
X(FILE_AUTOSAVE_TIME, fileAutosaveTime, "Autosave Time", INT, 1) \
|
||||||
|
X(FILE_IS_WARN_OVERWRITE, fileIsWarnOverwrite, "Warn on Overwrite", BOOL, true) \
|
||||||
\
|
\
|
||||||
X(KEYBOARD_REPEAT_DELAY, keyboardRepeatDelay, "Repeat Delay", FLOAT, 0.300f) \
|
X(KEYBOARD_REPEAT_DELAY, keyboardRepeatDelay, "Repeat Delay", FLOAT, 0.300f) \
|
||||||
X(KEYBOARD_REPEAT_RATE, keyboardRepeatRate, "Repeat Rate", FLOAT, 0.050f) \
|
X(KEYBOARD_REPEAT_RATE, keyboardRepeatRate, "Repeat Rate", FLOAT, 0.050f) \
|
||||||
|
|||||||
@@ -32,7 +32,8 @@ namespace anm2ed::util::filesystem
|
|||||||
std::filesystem::path path_lower_case_backslash_handle(std::filesystem::path& path)
|
std::filesystem::path path_lower_case_backslash_handle(std::filesystem::path& path)
|
||||||
{
|
{
|
||||||
if (path_is_exist(path)) return path;
|
if (path_is_exist(path)) return path;
|
||||||
if (path_is_exist(string::backslash_replace(path))) return path;
|
path = string::backslash_replace(path);
|
||||||
|
if (path_is_exist(path)) return path;
|
||||||
return string::to_lower(path);
|
return string::to_lower(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user