Files
anm2ed/src/imgui/taskbar.cpp
2025-11-30 22:25:57 -05:00

991 lines
33 KiB
C++

#include "taskbar.h"
#include <algorithm>
#include <array>
#include <cfloat>
#include <cmath>
#include <cstddef>
#include <filesystem>
#include <format>
#include <system_error>
#include <tuple>
#include <vector>
#include <imgui/imgui.h>
#include "log.h"
#include "math_.h"
#include "render.h"
#include "shader.h"
#include "snapshots.h"
#include "types.h"
#include "icon.h"
#include "toast.h"
#include "strings.h"
using namespace anm2ed::resource;
using namespace anm2ed::types;
using namespace anm2ed::canvas;
using namespace anm2ed::util;
using namespace glm;
namespace anm2ed::imgui
{
namespace
{
#ifndef _WIN32
constexpr auto EXEC_PERMS =
std::filesystem::perms::owner_exec | std::filesystem::perms::group_exec | std::filesystem::perms::others_exec;
#endif
bool ffmpeg_is_executable(const std::string& pathString)
{
if (pathString.empty()) return false;
std::error_code ec{};
auto status = std::filesystem::status(pathString, ec);
if (ec || !std::filesystem::is_regular_file(status)) return false;
#ifndef _WIN32
if ((status.permissions() & EXEC_PERMS) == std::filesystem::perms::none) return false;
#endif
return true;
}
bool png_directory_ensure(const std::string& directory)
{
if (directory.empty())
{
toasts.push(localize.get(TOAST_PNG_DIRECTORY_NOT_SET));
logger.warning(localize.get(TOAST_PNG_DIRECTORY_NOT_SET, anm2ed::ENGLISH));
return false;
}
std::error_code ec{};
auto pathValue = std::filesystem::path(directory);
auto exists = std::filesystem::exists(pathValue, ec);
if (ec)
{
auto errorMessage = ec.message();
toasts.push(std::vformat(localize.get(TOAST_PNG_DIRECTORY_ACCESS_ERROR),
std::make_format_args(directory, errorMessage)));
logger.error(std::vformat(localize.get(TOAST_PNG_DIRECTORY_ACCESS_ERROR, anm2ed::ENGLISH),
std::make_format_args(directory, errorMessage)));
return false;
}
if (exists)
{
if (!std::filesystem::is_directory(pathValue, ec) || ec)
{
toasts.push(std::vformat(localize.get(TOAST_PNG_DIRECTORY_NOT_DIRECTORY), std::make_format_args(directory)));
logger.warning(std::vformat(localize.get(TOAST_PNG_DIRECTORY_NOT_DIRECTORY, anm2ed::ENGLISH),
std::make_format_args(directory)));
return false;
}
return true;
}
if (!std::filesystem::create_directories(pathValue, ec) || ec)
{
auto errorMessage = ec.message();
toasts.push(std::vformat(localize.get(TOAST_PNG_DIRECTORY_CREATE_ERROR),
std::make_format_args(directory, errorMessage)));
logger.error(std::vformat(localize.get(TOAST_PNG_DIRECTORY_CREATE_ERROR, anm2ed::ENGLISH),
std::make_format_args(directory, errorMessage)));
return false;
}
return true;
}
}
static constexpr auto CREDIT_DELAY = 1.0f;
static constexpr auto CREDIT_SCROLL_SPEED = 25.0f;
struct Credit
{
const char* string{};
font::Type font{font::REGULAR};
};
struct ScrollingCredit
{
int index{};
float offset{};
};
struct CreditsState
{
std::vector<ScrollingCredit> active{};
float spawnTimer{1.0f};
int nextIndex{};
};
static constexpr Credit CREDITS[] = {
{"Anm2Ed", font::BOLD},
{"License: GPLv3"},
{""},
{"Designer", font::BOLD},
{"Shweet"},
{""},
{"Additional Help", font::BOLD},
{"im-tem"},
{""},
{"Localization", font::BOLD},
{"ExtremeThreat (Russian)"},
{"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)"},
{"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(Credit));
Taskbar::Taskbar() : generate(vec2()) {}
void Taskbar::update(Manager& manager, Settings& settings, Resources& resources, Dialog& dialog, bool& isQuitting)
{
auto document = manager.get();
auto animation = document ? document->animation_get() : nullptr;
auto item = document ? document->item_get() : nullptr;
if (ImGui::BeginMainMenuBar())
{
height = ImGui::GetWindowSize().y;
if (ImGui::BeginMenu(localize.get(LABEL_FILE_MENU)))
{
if (ImGui::MenuItem(localize.get(BASIC_NEW), settings.shortcutNew.c_str())) dialog.file_save(dialog::ANM2_NEW);
if (ImGui::MenuItem(localize.get(BASIC_OPEN), settings.shortcutOpen.c_str()))
dialog.file_open(dialog::ANM2_OPEN);
auto recentFiles = manager.recent_files_ordered();
if (ImGui::BeginMenu(localize.get(LABEL_OPEN_RECENT), !recentFiles.empty()))
{
for (std::size_t index = 0; index < recentFiles.size(); ++index)
{
const auto& file = recentFiles[index];
auto label = std::format(FILE_LABEL_FORMAT, file.filename().string(), file.string());
ImGui::PushID((int)index);
if (ImGui::MenuItem(label.c_str())) manager.open(file.string());
ImGui::PopID();
}
if (!recentFiles.empty())
if (ImGui::MenuItem(localize.get(LABEL_CLEAR_LIST))) manager.recent_files_clear();
ImGui::EndMenu();
}
if (ImGui::MenuItem(localize.get(BASIC_SAVE), settings.shortcutSave.c_str(), false, document))
{
if (settings.fileIsWarnOverwrite)
overwritePopup.open();
else
manager.save(document->path);
}
if (ImGui::MenuItem(localize.get(LABEL_SAVE_AS), settings.shortcutSaveAs.c_str(), false, document))
dialog.file_save(dialog::ANM2_SAVE);
if (ImGui::MenuItem(localize.get(LABEL_EXPLORE_XML_LOCATION), nullptr, false, document))
dialog.file_explorer_open(document->directory_get().string());
ImGui::Separator();
if (ImGui::MenuItem(localize.get(LABEL_EXIT), settings.shortcutExit.c_str())) isQuitting = true;
ImGui::EndMenu();
}
if (dialog.is_selected(dialog::ANM2_NEW))
{
manager.new_(dialog.path);
dialog.reset();
}
if (dialog.is_selected(dialog::ANM2_OPEN))
{
manager.open(dialog.path);
dialog.reset();
}
if (dialog.is_selected(dialog::ANM2_SAVE))
{
manager.save(dialog.path);
dialog.reset();
}
if (ImGui::BeginMenu(localize.get(LABEL_WIZARD_MENU)))
{
if (ImGui::MenuItem(localize.get(LABEL_TASKBAR_GENERATE_ANIMATION_FROM_GRID), nullptr, false,
item && document->reference.itemType == anm2::LAYER))
generatePopup.open();
ImGui::Separator();
if (ImGui::MenuItem(localize.get(LABEL_TASKBAR_RENDER_ANIMATION), nullptr, false, animation))
renderPopup.open();
ImGui::EndMenu();
}
if (ImGui::BeginMenu(localize.get(LABEL_PLAYBACK_MENU)))
{
ImGui::MenuItem(localize.get(LABEL_PLAYBACK_ALWAYS_LOOP), nullptr, &settings.playbackIsLoop);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_PLAYBACK_ALWAYS_LOOP));
ImGui::MenuItem(localize.get(LABEL_CLAMP), nullptr, &settings.playbackIsClamp);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_PLAYBACK_CLAMP));
ImGui::EndMenu();
}
if (ImGui::BeginMenu(localize.get(LABEL_WINDOW_MENU)))
{
for (std::size_t index = 0; index < WINDOW_COUNT; ++index)
{
auto member = WINDOW_MEMBERS[index];
ImGui::MenuItem(localize.get(::anm2ed::WINDOW_STRING_TYPES[index]), nullptr, &(settings.*member));
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu(localize.get(LABEL_SETTINGS_MENU)))
{
if (ImGui::MenuItem(localize.get(LABEL_TASKBAR_CONFIGURE)))
{
editSettings = settings;
configurePopup.open();
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu(localize.get(LABEL_HELP_MENU)))
{
if (ImGui::MenuItem(localize.get(LABEL_TASKBAR_ABOUT))) aboutPopup.open();
ImGui::EndMenu();
}
ImGui::EndMainMenuBar();
}
generatePopup.trigger();
if (ImGui::BeginPopupModal(generatePopup.label(), &generatePopup.isOpen, ImGuiWindowFlags_NoResize))
{
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& time = generateTime;
auto& shaderTexture = resources.shaders[resource::shader::TEXTURE];
auto previewSize = ImVec2(ImGui::GetContentRegionAvail().x, size_without_footer_get(2).y);
generate.size_set(to_vec2(previewSize));
generate.bind();
generate.viewport_set();
generate.clear(vec4(backgroundColor, 1.0f));
if (document && 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 = generate.transform_get(zoom) * math::quad_model_get(size, {}, pivot);
generate.texture_render(shaderTexture, texture.id, transform, vec4(1.0f), {},
math::uv_vertices_get(uvMin, uvMax).data());
}
generate.unbind();
ImGui::Image(generate.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 = [&]()
{
item->frames_generate_from_grid(startPosition, size, pivot, columns, count, delay);
animation->frameNum = animation->length();
};
DOCUMENT_EDIT_PTR(document, localize.get(EDIT_GENERATE_ANIMATION_FROM_GRID), Document::FRAMES,
generate_from_grid());
generatePopup.close();
}
ImGui::SameLine();
if (ImGui::Button(localize.get(BASIC_CANCEL), widgetSize)) generatePopup.close();
ImGui::EndPopup();
}
configurePopup.trigger();
if (ImGui::BeginPopupModal(configurePopup.label(), &configurePopup.isOpen, ImGuiWindowFlags_NoResize))
{
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), editSettings.uiScale, 0.5f, 2.0f, 0.25f, 0.25f, "%.2f");
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_UI_SCALE));
ImGui::Checkbox(localize.get(LABEL_VSYNC), &editSettings.isVsync);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_VSYNC));
ImGui::SeparatorText(localize.get(LABEL_LOCALIZATION));
ImGui::Combo(localize.get(LABEL_LANGUAGE), &editSettings.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]), &editSettings.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), &editSettings.fileIsAutosave);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_AUTOSAVE_ENABLED));
ImGui::BeginDisabled(!editSettings.fileIsAutosave);
input_int_range(localize.get(LABEL_TIME_MINUTES), editSettings.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), editSettings.fileSnapshotStackSize, 0, 100);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_STACK_SIZE));
ImGui::SeparatorText(localize.get(LABEL_OPTIONS));
ImGui::Checkbox(localize.get(LABEL_OVERWRITE_WARNING), &editSettings.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), editSettings.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), editSettings.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), editSettings.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), &editSettings.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 = &(editSettings.*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 = editSettings;
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();
configurePopup.close();
}
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_SETTINGS_SAVE));
ImGui::SameLine();
if (ImGui::Button(localize.get(LABEL_USE_DEFAULT_SETTINGS), widgetSize)) editSettings = Settings();
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_USE_DEFAULT_SETTINGS));
ImGui::SameLine();
if (ImGui::Button(localize.get(LABEL_CLOSE), widgetSize)) configurePopup.close();
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_CLOSE_SETTINGS));
ImGui::EndPopup();
}
renderPopup.trigger();
if (ImGui::BeginPopupModal(renderPopup.label(), &renderPopup.isOpen, ImGuiWindowFlags_NoResize))
{
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;
int length = std::max(1, end - start + 1);
auto range_to_frames_set = [&]()
{
if (auto item = document->item_get())
{
int duration{};
for (std::size_t index = 0; index < item->frames.size(); ++index)
{
const auto& frame = item->frames[index];
if ((int)index == *frames.begin())
start = duration;
else if ((int)index == *frames.rbegin())
{
end = duration;
break;
}
duration += frame.duration;
}
}
};
auto range_to_animation_set = [&]()
{
start = 0;
end = animation->frameNum - 1;
};
auto range_set = [&]()
{
if (!isRange) range_to_animation_set();
length = std::max(1, end - (start + 1));
};
auto rows_columns_set = [&]()
{
auto framesNeeded = std::max(1, length);
int bestRows = 1;
int bestColumns = framesNeeded;
auto bestScore = std::make_tuple(bestColumns - bestRows, bestColumns * bestRows - framesNeeded, -bestColumns);
for (int candidateRows = 1; candidateRows <= framesNeeded; ++candidateRows)
{
int candidateColumns = (framesNeeded + candidateRows - 1) / candidateRows;
if (candidateColumns < candidateRows) break;
auto candidateScore = std::make_tuple(candidateColumns - candidateRows,
candidateColumns * candidateRows - framesNeeded, -candidateColumns);
if (candidateScore < bestScore)
{
bestScore = candidateScore;
bestRows = candidateRows;
bestColumns = candidateColumns;
}
}
rows = bestRows;
columns = bestColumns;
};
auto replace_extension = [&]()
{ path = std::filesystem::path(path).replace_extension(render::EXTENSIONS[type]).string(); };
auto render_set = [&]()
{
replace_extension();
range_set();
rows_columns_set();
};
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 (renderPopup.isJustOpened) render_set();
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_string(localize.get(LABEL_FFMPEG_PATH), &ffmpegPath);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_FFMPEG_PATH));
dialog.set_string_to_selected_path(ffmpegPath, dialog::FFMPEG_PATH_SET);
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_string(localize.get(pathLabel), &path);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_OUTPUT_PATH));
dialog.set_string_to_selected_path(path, dialogType);
if (ImGui::Combo(localize.get(LABEL_TYPE), &type, render::STRINGS, render::COUNT)) render_set();
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_RENDER_TYPE));
if (type == render::PNGS || type == render::SPRITESHEET) ImGui::Separator();
if (type == render::PNGS)
{
if (input_text_string(localize.get(LABEL_FORMAT), &format))
format = std::filesystem::path(format).replace_extension(".png").string();
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_FORMAT));
}
else if (type == render::SPRITESHEET)
{
input_int_range(localize.get(LABEL_GENERATE_ROWS), rows, 1, length);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_ROWS));
input_int_range(localize.get(LABEL_GENERATE_COLUMNS), columns, 1, length);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_COLUMNS));
if (ImGui::Button(localize.get(LABEL_SET_TO_RECOMMENDED))) rows_columns_set();
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_SET_TO_RECOMMENDED));
}
ImGui::Separator();
if (ImGui::Checkbox(localize.get(LABEL_CUSTOM_RANGE), &isRange))
{
range_set();
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_CUSTOM_RANGE));
}
ImGui::SameLine();
ImGui::BeginDisabled(frames.empty());
if (ImGui::Button(localize.get(LABEL_TO_SELECTED_FRAMES))) range_to_frames_set();
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();
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_TO_ANIMATION_RANGE));
ImGui::BeginDisabled(!isRange);
{
input_int_range(localize.get(LABEL_START), start, 0, animation->frameNum);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_START));
input_int_range(localize.get(LABEL_END), end, start, animation->frameNum);
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))
{
bool isRender = true;
if (!ffmpeg_is_executable(ffmpegPath))
{
toasts.push(localize.get(TOAST_INVALID_FFMPEG_PATH));
logger.error(localize.get(TOAST_INVALID_FFMPEG_PATH, anm2ed::ENGLISH));
isRender = false;
}
if (isRender && type == render::PNGS) isRender = png_directory_ensure(path);
if (isRender)
{
manager.isRecordingStart = true;
manager.progressPopup.open();
}
renderPopup.close();
}
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_RENDER_BUTTON));
ImGui::SameLine();
if (ImGui::Button(localize.get(BASIC_CANCEL), widgetSize)) renderPopup.close();
ImGui::EndPopup();
}
renderPopup.end();
aboutPopup.trigger();
if (ImGui::BeginPopupModal(aboutPopup.label(), &aboutPopup.isOpen, ImGuiWindowFlags_NoResize))
{
static CreditsState creditsState{};
auto credits_reset = [&]()
{
resources.music_track().play(true);
creditsState = {};
creditsState.spawnTimer = CREDIT_DELAY;
};
if (aboutPopup.isJustOpened) credits_reset();
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;
const 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;
}
const 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();
}
ImGui::EndPopup();
}
if (auto* music = resources.music_track_if_loaded())
if (music->is_playing() && !aboutPopup.isOpen) music->stop();
overwritePopup.trigger();
if (ImGui::BeginPopupModal(overwritePopup.label(), &overwritePopup.isOpen, ImGuiWindowFlags_NoResize))
{
ImGui::TextUnformatted(localize.get(LABEL_OVERWRITE_CONFIRMATION));
auto widgetSize = widget_size_with_row_get(2);
if (ImGui::Button(localize.get(BASIC_YES), widgetSize))
{
manager.save();
overwritePopup.close();
}
ImGui::SameLine();
if (ImGui::Button(localize.get(BASIC_NO), widgetSize)) overwritePopup.close();
ImGui::EndPopup();
}
aboutPopup.end();
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_SAVE], shortcut::GLOBAL))
{
if (settings.fileIsWarnOverwrite)
overwritePopup.open();
else
manager.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;
}
}