#include "taskbar.h" #include #include #include #include #include #include #include #include #ifdef _MSC_VER #ifndef __attribute__ #define __attribute__(x) #endif #ifndef restrict #define restrict __restrict #endif #endif #include #include "math_.h" #include "render.h" #include "shader.h" #include "types.h" #include "icon.h" #include "toast.h" using namespace anm2ed::resource; using namespace anm2ed::types; using namespace anm2ed::canvas; using namespace anm2ed::util; using namespace glm; namespace anm2ed::imgui { 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("File")) { if (ImGui::MenuItem("New", settings.shortcutNew.c_str())) dialog.file_save(dialog::ANM2_NEW); if (ImGui::MenuItem("Open", settings.shortcutOpen.c_str())) dialog.file_open(dialog::ANM2_NEW); if (ImGui::BeginMenu("Open Recent", !manager.recentFiles.empty())) { for (auto [i, file] : std::views::enumerate(manager.recentFiles)) { auto label = std::format(FILE_LABEL_FORMAT, file.filename().string(), file.string()); ImGui::PushID(i); if (ImGui::MenuItem(label.c_str())) manager.open(file); ImGui::PopID(); } if (!manager.recentFiles.empty()) if (ImGui::MenuItem("Clear List")) manager.recent_files_clear(); ImGui::EndMenu(); } 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)) dialog.file_save(dialog::ANM2_SAVE); if (ImGui::MenuItem("Explore XML Location", nullptr, false, document)) dialog.file_explorer_open(document->directory_get()); ImGui::Separator(); if (ImGui::MenuItem("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("Wizard")) { if (ImGui::MenuItem("Generate Animation From Grid", nullptr, false, item && document->reference.itemType == anm2::LAYER)) generatePopup.open(); ImGui::Separator(); if (ImGui::MenuItem("Render Animation", nullptr, false, animation)) renderPopup.open(); ImGui::EndMenu(); } if (ImGui::BeginMenu("Playback")) { ImGui::MenuItem("Always Loop", nullptr, &settings.playbackIsLoop); ImGui::SetItemTooltip("%s", "Animations will always loop during playback, even if looping isn't set."); ImGui::MenuItem("Clamp", nullptr, &settings.playbackIsClamp); ImGui::SetItemTooltip("%s", "Operations will always be clamped to within the animation's bounds.\nFor example, " "dragging the playhead, or triggers."); ImGui::EndMenu(); } if (ImGui::BeginMenu("Window")) { for (auto [i, member] : std::views::enumerate(WINDOW_MEMBERS)) ImGui::MenuItem(WINDOW_STRINGS[i], nullptr, &(settings.*member)); ImGui::EndMenu(); } if (ImGui::BeginMenu("Settings")) { if (ImGui::MenuItem("Configure")) { editSettings = settings; configurePopup.open(); } ImGui::EndMenu(); } if (ImGui::BeginMenu("Help")) { if (ImGui::MenuItem("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.generateDelay; auto& zoom = settings.generateZoom; auto& zoomStep = settings.viewZoomStep; auto childSize = ImVec2(row_widget_width_get(2), size_without_footer_get().y); if (ImGui::BeginChild("##Options Child", childSize, ImGuiChildFlags_Borders)) { ImGui::InputInt2("Start Position", value_ptr(startPosition)); ImGui::InputInt2("Frame Size", value_ptr(size)); ImGui::InputInt2("Pivot", value_ptr(pivot)); ImGui::InputInt("Rows", &rows, STEP, STEP_FAST); ImGui::InputInt("Columns", &columns, STEP, STEP_FAST); input_int_range("Count", count, anm2::FRAME_NUM_MIN, rows * columns); ImGui::InputInt("Delay", &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(backgroundColor); 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("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, "Generate Animation from Grid", Document::FRAMES, generate_from_grid()); generatePopup.close(); } ImGui::SameLine(); if (ImGui::Button("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("Display")) { if (ImGui::BeginChild("##Tab Child", childSize, true)) { input_float_range("UI Scale", editSettings.uiScale, 0.5f, 2.0f, 0.25f, 0.25f, "%.2f"); ImGui::SetItemTooltip("Change the scale of the UI."); ImGui::Checkbox("Vsync", &editSettings.isVsync); ImGui::SetItemTooltip("Toggle vertical sync; synchronizes program update rate with monitor refresh rate."); } ImGui::EndChild(); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("File")) { if (ImGui::BeginChild("##Tab Child", childSize, true)) { ImGui::SeparatorText("Autosave"); ImGui::Checkbox("Enabled", &editSettings.fileIsAutosave); ImGui::SetItemTooltip("Enables autosaving of documents."); ImGui::BeginDisabled(!editSettings.fileIsAutosave); input_int_range("Time (minutes)", editSettings.fileAutosaveTime, 0, 10); ImGui::SetItemTooltip("If changed, will autosave documents using this interval."); 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::EndTabItem(); } if (ImGui::BeginTabItem("Input")) { if (ImGui::BeginChild("##Tab Child", childSize, true)) { ImGui::SeparatorText("Keyboard"); input_float_range("Repeat Delay (seconds)", editSettings.keyboardRepeatDelay, 0.05f, 1.0f, 0.05f, 0.05f, "%.2f"); ImGui::SetItemTooltip("Set how often, after repeating begins, key inputs will be fired."); input_float_range("Repeat Rate (seconds)", editSettings.keyboardRepeatRate, 0.005f, 1.0f, 0.005f, 0.005f, "%.3f"); ImGui::SetItemTooltip("Set how often, after repeating begins, key inputs will be fired."); ImGui::SeparatorText("Zoom"); 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::EndChild(); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Shortcuts")) { ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2()); if (ImGui::BeginChild("##Tab Child", childSize, true)) { if (ImGui::BeginTable("Shortcuts", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_ScrollY)) { ImGui::TableSetupScrollFreeze(0, 1); ImGui::TableSetupColumn("Shortcut"); ImGui::TableSetupColumn("Value"); 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(SHORTCUT_STRINGS[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 (auto& key : KEY_MAP | std::views::values) { 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("Save", widgetSize)) { settings = editSettings; manager.chords_set(settings); configurePopup.close(); } ImGui::SetItemTooltip("Use the configured settings."); ImGui::SameLine(); if (ImGui::Button("Use Default Settings", widgetSize)) editSettings = Settings(); ImGui::SetItemTooltip("Reset the settings to their defaults."); ImGui::SameLine(); if (ImGui::Button("Close", widgetSize)) configurePopup.close(); ImGui::SetItemTooltip("Close without updating settings."); ImGui::EndPopup(); } renderPopup.trigger(); if (ImGui::BeginPopupModal(renderPopup.label, &renderPopup.isOpen, ImGuiWindowFlags_NoResize)) { auto& playback = document->playback; 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& isRange = manager.isRecordingRange; auto widgetSize = widget_size_with_row_get(2); auto dialogType = type == render::PNGS ? dialog::PNG_DIRECTORY_SET : type == render::GIF ? dialog::GIF_PATH_SET : type == render::WEBM ? dialog::WEBM_PATH_SET : dialog::NONE; auto replace_extension = [&]() { path = std::filesystem::path(path).replace_extension(render::EXTENSIONS[type]); }; auto range_to_length = [&]() { start = 0; end = animation->frameNum; }; if (renderPopup.isJustOpened) { replace_extension(); if (!isRange) range_to_length(); } 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("FFmpeg Path", &ffmpegPath); ImGui::SetItemTooltip("Set the path where the FFmpeg installation is located.\nFFmpeg is required to render " "animations.\nhttps://ffmpeg.org"); 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(); input_text_string(type == render::PNGS ? "Directory" : "Path", &path); ImGui::SetItemTooltip("Set the output path or directory for the animation."); dialog.set_string_to_selected_path(path, dialogType); if (ImGui::Combo("Type", &type, render::STRINGS, render::COUNT)) replace_extension(); ImGui::SetItemTooltip("Set the type of the output."); if (type == render::PNGS) { ImGui::Separator(); input_text_string("Format", &format); ImGui::SetItemTooltip( "For outputted images, each image will use this format.\n{} represents the index of each image."); } ImGui::Separator(); if (ImGui::Checkbox("Custom Range", &isRange)) if (!isRange) range_to_length(); ImGui::SetItemTooltip("Toggle using a custom range for the animation."); if (isRange) { input_int_range("Start", start, 0, animation->frameNum - 1); ImGui::SetItemTooltip("Set the starting time of the animation."); input_int_range("End", end, start + 1, animation->frameNum); ImGui::SetItemTooltip("Set the ending time of the animation."); } ImGui::Separator(); ImGui::Checkbox("Raw", &isRaw); ImGui::SetItemTooltip("Record only the raw animation; i.e., only its layers, to its bounds."); if (isRaw) { input_float_range("Scale", scale, 1.0f, 100.0f, STEP, STEP_FAST, "%.1fx"); ImGui::SetItemTooltip("Set the output scale of the animation."); } ImGui::Separator(); ImGui::Checkbox("Sound", &settings.timelineIsSound); ImGui::SetItemTooltip("Toggle sounds playing with triggers.\nBind sounds to events in the Events window.\nThe " "output animation will use the played sounds."); ImGui::Separator(); if (ImGui::Button("Render", widgetSize)) { bool canStart = true; auto warn_and_close = [&](const std::string& message) { toasts.warning(message); 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(); if (ImGui::Button("Cancel", widgetSize)) renderPopup.close(); ImGui::EndPopup(); } renderPopup.end(); aboutPopup.trigger(); if (ImGui::BeginPopupModal(aboutPopup.label, &aboutPopup.isOpen, ImGuiWindowFlags_NoResize)) { struct Credit { const char* string{}; font::Type font{font::REGULAR}; }; struct ScrollingCredit { int index{}; float offset{}; }; struct CreditsState { std::vector active{}; float spawnTimer{1.0f}; int nextIndex{}; }; static constexpr auto ANM2ED_LABEL = "Anm2Ed"; static constexpr auto VERSION_LABEL = "Version 2.0"; static constexpr auto CREDIT_DELAY = 1.0f; static constexpr auto CREDIT_SCROLL_SPEED = 25.0f; static constexpr Credit CREDITS[] = { {"Anm2Ed", font::BOLD}, {"License: GPLv3"}, {""}, {"Designer", font::BOLD}, {"Shweet"}, {""}, {"Additional Help", font::BOLD}, {"im-tem"}, {""}, {"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"}, {""}, {"libxm"}, {"https://github.com/Artefact2/libxm"}, {"License: WTFPL"}, {""}, {"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)); static CreditsState creditsState{}; auto credits_reset = [&]() { resources.music.play(true); creditsState = {}; creditsState.spawnTimer = CREDIT_DELAY; }; if (aboutPopup.isJustOpened) credits_reset(); auto size = ImGui::GetContentRegionAvail(); ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE_LARGE); ImGui::SetCursorPosX((size.x - ImGui::CalcTextSize(ANM2ED_LABEL).x) / 2); ImGui::Text(ANM2ED_LABEL); ImGui::SetCursorPosX((size.x - ImGui::CalcTextSize(VERSION_LABEL).x) / 2); ImGui::Text(VERSION_LABEL); ImGui::PopFont(); auto creditRegionPos = ImGui::GetCursorScreenPos(); auto creditRegionSize = ImGui::GetContentRegionAvail(); if (creditRegionSize.y > 0.0f && creditRegionSize.x > 0.0f) { 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)font::SIZE)); 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)font::SIZE; auto baseColor = ImGui::GetStyleColorVec4(ImGuiCol_Text); auto fadeSpan = (float)font::SIZE * 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 + font::SIZE < 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)font::SIZE, 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 + font::SIZE); 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, (float)font::SIZE, ImVec2(xPos, yPos), ImGui::GetColorU32(color), credit.string); ++it; } drawList->PopClipRect(); } 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(); 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(); 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; } }