more minor polish there and here

This commit is contained in:
2025-11-11 16:07:02 -05:00
parent 2a671e2623
commit d9a05947c0
20 changed files with 221 additions and 81 deletions

2
.vscode/launch.json vendored
View File

@@ -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",

View File

@@ -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)

View File

@@ -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;
} }
} }

View File

@@ -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);
} }

View File

@@ -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"

View File

@@ -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;

View File

@@ -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)

View File

@@ -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;
} }

View File

@@ -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")};

View File

@@ -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;

View File

@@ -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"

View File

@@ -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();
} }
} }

View File

@@ -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.");

View File

@@ -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);
} }
} }

View File

@@ -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();

View File

@@ -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);
} }

View File

@@ -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)
{ {

View File

@@ -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>

View File

@@ -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) \

View File

@@ -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);
} }