#pragma once // MassBuilderSaveTool // Copyright (C) 2021-2024 Guillaume Jacquemin // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include #include #include #include #include #include #ifdef SAVETOOL_DEBUG_BUILD #include #endif #include #include #include #include #include #include #include #include "../Managers/MassManager.h" #include "../Managers/ProfileManager.h" #include "../ToastQueue/ToastQueue.h" #include "../UpdateChecker/UpdateChecker.h" #ifdef SAVETOOL_DEBUG_BUILD #define tw CORRADE_TWEAKABLE #endif using namespace Corrade; using namespace Containers::Literals; using namespace Magnum; namespace mbst { class Application: public Platform::Sdl2Application, public efsw::FileWatchListener { public: explicit Application(const Arguments& arguments); ~Application() override; void handleFileAction(efsw::WatchID watch_id, const std::string& dir, const std::string& filename, efsw::Action action, std::string old_filename = "") override; private: // Events void drawEvent() override; void viewportEvent(ViewportEvent& event) override; void keyPressEvent(KeyEvent& event) override; void keyReleaseEvent(KeyEvent& event) override; void mousePressEvent(MouseEvent& event) override; void mouseReleaseEvent(MouseEvent& event) override; void mouseMoveEvent(MouseMoveEvent& event) override; void mouseScrollEvent(MouseScrollEvent& event) override; void textInputEvent(TextInputEvent& event) override; void anyEvent(SDL_Event& event) override; enum InitStatus: std::int32_t { InitSuccess, ProfileManagerFailure }; void initEvent(SDL_Event& event); void updateCheckEvent(SDL_Event& event); enum FileEventType: std::int32_t { FileAdded = efsw::Action::Add, FileDeleted = efsw::Action::Delete, FileModified = efsw::Action::Modified, FileMoved = efsw::Action::Moved, StagedUpdate = 1 << 3 }; void fileUpdateEvent(SDL_Event& event); // Initialisation methods void initialiseConfiguration(); void initialiseGui(); void initialiseManager(); void initialiseMassManager(); void initialiseFileWatcher(); // GUI-related methods void drawImGui(); void drawGui(); void drawMainMenu(); void drawDisclaimer(); void drawInitialisation(); void drawProfileManager(); void drawBackupListPopup(); void drawBackupRestorePopup(std::size_t backup_index); void drawBackupDeletePopup(std::size_t backup_index); void drawBackupProfilePopup(std::size_t profile_index); void drawDeleteProfilePopup(std::size_t profile_index); void drawManager(); bool drawIntEditPopup(int* value_to_edit, int max); bool drawRenamePopup(Containers::ArrayView name_view); void drawGeneralInfo(); void drawResearchInventory(); void drawMaterialRow(Containers::StringView name, std::int32_t tier, GameData::MaterialID id); void drawUnavailableMaterialRow(Containers::StringView name, std::int32_t tier); void drawMassManager(); auto drawDeleteMassPopup(int mass_index) -> ImGuiID; auto drawDeleteStagedMassPopup(Containers::StringView filename) -> ImGuiID; void drawMassViewer(); void drawFrameInfo(); void drawJointSliders(); void drawFrameStyles(); void drawEyeColourPicker(); void drawCustomFrameStyles(); void drawArmour(); void drawBLAttachment(); void drawCustomArmourStyles(); void drawWeapons(); void drawWeaponCategory(Containers::StringView name, Containers::ArrayView weapons_view, bool& dirty, Containers::StringView payload_type, Containers::StringView payload_tooltip); void drawWeaponEditor(GameObjects::Weapon& weapon); void drawGlobalStyles(); void drawTuning(); void drawDecalEditor(GameObjects::Decal& decal); void drawAccessoryEditor(GameObjects::Accessory& accessory, Containers::ArrayView style_view); auto getStyleName(std::int32_t id, Containers::ArrayView view) -> Containers::StringView; enum DCSResult { DCS_Fail, DCS_ResetStyle, DCS_Save }; auto drawCustomStyle(GameObjects::CustomStyle& style) -> DCSResult; void drawAbout(); void drawGameState(); // Convenience wrappers over ImGui stuff void drawHelpMarker(Containers::StringView text, float wrap_pos = 0.0f); void drawTooltip(Containers::StringView text, float wrap_pos = 0.0f); bool drawCheckbox(Containers::StringView label, bool value); template bool drawUnsafeWidget(Functor func, Args... args) { GameState game_state = _gameState; // Copying the value to reduce the risk of a data race. ImGui::BeginDisabled(game_state != GameState::NotRunning); bool result = func(std::forward(args)...); ImGui::EndDisabled(); return result; } // Obviously, should only be used with ImGui widgets that return a bool. // Also, func should be a lambda if there are any default arguments, like ImGui::Button(), etc... template void drawUnsafeText(const char* text, Args... args) { // Alternative to the above, for ImGui::Text*() variants. if(_gameState != GameState::NotRunning) { ImGui::TextDisabled(text, std::forward(args)...); } else { ImGui::Text(text, std::forward(args)...); } } template void drawAlignedText(Containers::StringView text, Args... args) { ImGui::AlignTextToFramePadding(); ImGui::Text(text.data(), std::forward(args)...); } void openUri(Containers::StringView uri); void checkGameState(); void checkForUpdates(); Utility::Resource _rs{"assets"_s}; // GUI-related members ImGuiIntegration::Context _imgui{NoCreate}; enum class UiState: uint8_t { Disclaimer, Initialising, ProfileManager, MainManager, MassViewer } _uiState{UiState::Disclaimer}; bool _aboutPopup = false; #ifdef SAVETOOL_DEBUG_BUILD bool _demoWindow = false; bool _styleEditor = false; bool _metricsWindow = false; #endif ToastQueue _queue; std::thread _initThread; std::thread _updateThread; std::uint32_t _initEventId; std::uint32_t _updateEventId; std::uint32_t _fileEventId; Containers::String _lastError; enum class GameState : std::uint8_t { Unknown, NotRunning, Running } _gameState{GameState::Unknown}; SDL_TimerID _gameCheckTimerId = 0; Containers::Pointer _profileManager; GameObjects::Profile* _currentProfile = nullptr; Containers::Pointer _massManager; GameObjects::Mass* _currentMass = nullptr; GameObjects::Weapon* _currentWeapon = nullptr; Containers::Pointer _fileWatcher; enum watchID { SaveDir = 0, StagingDir = 1 }; Containers::StaticArray<2, efsw::WatchID> _watchIDs; Containers::Optional _checker{Containers::NullOpt}; std::mutex _checkerMutex; bool _modifiedBySaveTool = false; bool _jointsDirty = false; bool _stylesDirty = false; bool _eyeFlareDirty = false; bool _meleeDirty = false; bool _shieldsDirty = false; bool _bShootersDirty = false; bool _eShootersDirty = false; bool _bLaunchersDirty = false; bool _eLaunchersDirty = false; Containers::Optional _selectedArmourSlot{Containers::NullOpt}; Containers::StaticArray<38, std::int32_t> _selectedArmourDecals{ValueInit}; Containers::StaticArray<38, std::int32_t> _selectedArmourAccessories{ValueInit}; std::uint32_t _selectedBLPlacement = 0; std::int32_t _selectedWeaponPart = 0; std::int32_t _selectedWeaponDecal = 0; std::int32_t _selectedWeaponAccessory = 0; Timeline _timeline; }; }