mirror of
https://github.com/Zelda64Recomp/Zelda64Recomp.git
synced 2025-10-30 08:03:03 +00:00
Finish drag and drop mod installation, disable mod refresh button and code mod toggle when game starts
This commit is contained in:
parent
75c3669961
commit
11c84659cf
14 changed files with 474 additions and 144 deletions
|
|
@ -52,6 +52,7 @@ namespace recompui {
|
|||
bool is_context_capturing_input();
|
||||
bool is_context_capturing_mouse();
|
||||
bool is_any_context_shown();
|
||||
ContextId try_close_current_context();
|
||||
|
||||
ContextId get_launcher_context_id();
|
||||
ContextId get_config_context_id();
|
||||
|
|
@ -79,19 +80,35 @@ namespace recompui {
|
|||
};
|
||||
|
||||
void init_prompt_context();
|
||||
void open_prompt(
|
||||
const std::string& headerText,
|
||||
const std::string& contentText,
|
||||
const std::string& confirmLabelText,
|
||||
const std::string& cancelLabelText,
|
||||
std::function<void()> confirmCb,
|
||||
std::function<void()> cancelCb,
|
||||
ButtonVariant _confirmVariant = ButtonVariant::Success,
|
||||
ButtonVariant _cancelVariant = ButtonVariant::Error,
|
||||
bool _focusOnCancel = true,
|
||||
const std::string& _returnElementId = ""
|
||||
void open_choice_prompt(
|
||||
const std::string& header_text,
|
||||
const std::string& content_text,
|
||||
const std::string& confirm_label_text,
|
||||
const std::string& cancel_label_text,
|
||||
std::function<void()> confirm_action,
|
||||
std::function<void()> cancel_action,
|
||||
ButtonVariant confirm_variant = ButtonVariant::Success,
|
||||
ButtonVariant cancel_variant = ButtonVariant::Error,
|
||||
bool focus_on_cancel = true,
|
||||
const std::string& return_element_id = ""
|
||||
);
|
||||
void open_info_prompt(
|
||||
const std::string& header_text,
|
||||
const std::string& content_text,
|
||||
const std::string& okay_label_text,
|
||||
std::function<void()> okay_action,
|
||||
ButtonVariant okay_variant = ButtonVariant::Error,
|
||||
const std::string& return_element_id = ""
|
||||
);
|
||||
void open_notification(
|
||||
const std::string& header_text,
|
||||
const std::string& content_text,
|
||||
const std::string& return_element_id = ""
|
||||
);
|
||||
void close_prompt();
|
||||
bool is_prompt_open();
|
||||
void update_mod_list();
|
||||
void process_game_started();
|
||||
|
||||
void apply_color_hack();
|
||||
void get_window_size(int& width, int& height);
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit ac153080f5200a08488d6d329812bf53bdab03b2
|
||||
Subproject commit db1b1a10822c6f7aa9d8bd4f930e820f11daed3e
|
||||
|
|
@ -296,6 +296,7 @@ bool sdl_event_filter(void* userdata, SDL_Event* event) {
|
|||
|
||||
void recomp::handle_events() {
|
||||
SDL_Event cur_event;
|
||||
static bool started = false;
|
||||
static bool exited = false;
|
||||
while (SDL_PollEvent(&cur_event) && !exited) {
|
||||
exited = sdl_event_filter(nullptr, &cur_event);
|
||||
|
|
@ -312,6 +313,11 @@ void recomp::handle_events() {
|
|||
SDL_ShowCursor(cursor_visible ? SDL_ENABLE : SDL_DISABLE);
|
||||
SDL_SetRelativeMouseMode(cursor_locked ? SDL_TRUE : SDL_FALSE);
|
||||
}
|
||||
|
||||
if (!started && ultramodern::is_game_started()) {
|
||||
started = true;
|
||||
recompui::process_game_started();
|
||||
}
|
||||
}
|
||||
|
||||
constexpr SDL_GameControllerButton SDL_CONTROLLER_BUTTON_SOUTH = SDL_CONTROLLER_BUTTON_A;
|
||||
|
|
|
|||
|
|
@ -344,6 +344,15 @@ void recompui::ContextId::close() {
|
|||
}
|
||||
}
|
||||
|
||||
recompui::ContextId recompui::try_close_current_context() {
|
||||
if (opened_context_id != ContextId::null()) {
|
||||
ContextId prev_context = opened_context_id;
|
||||
opened_context_id.close();
|
||||
return prev_context;
|
||||
}
|
||||
return ContextId::null();
|
||||
}
|
||||
|
||||
void recompui::ContextId::process_updates() {
|
||||
// Ensure a context is currently opened by this thread.
|
||||
if (opened_context_id == ContextId::null()) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#include "RmlUi/Core/StringUtilities.h"
|
||||
|
||||
#include "recomp_ui.h"
|
||||
#include "ui_element.h"
|
||||
#include "../core/ui_context.h"
|
||||
|
||||
|
|
@ -134,6 +135,7 @@ void Element::handle_event(const Event& event) {
|
|||
}
|
||||
|
||||
void Element::ProcessEvent(Rml::Event &event) {
|
||||
ContextId prev_context = recompui::try_close_current_context();
|
||||
ContextId context = ContextId::null();
|
||||
Rml::ElementDocument* doc = event.GetTargetElement()->GetOwnerDocument();
|
||||
if (doc != nullptr) {
|
||||
|
|
@ -198,6 +200,10 @@ void Element::ProcessEvent(Rml::Event &event) {
|
|||
if (context != ContextId::null() && did_open) {
|
||||
context.close();
|
||||
}
|
||||
|
||||
if (prev_context != ContextId::null()) {
|
||||
prev_context.open();
|
||||
}
|
||||
}
|
||||
|
||||
void Element::set_attribute(const Rml::String &attribute_key, const Rml::String &attribute_value) {
|
||||
|
|
|
|||
|
|
@ -165,7 +165,7 @@ void apply_graphics_config(void) {
|
|||
|
||||
void close_config_menu() {
|
||||
if (ultramodern::renderer::get_graphics_config() != new_options) {
|
||||
recompui::open_prompt(
|
||||
recompui::open_choice_prompt(
|
||||
"Graphics options have changed",
|
||||
"Would you like to apply or discard the changes?",
|
||||
"Apply",
|
||||
|
|
@ -192,7 +192,7 @@ void close_config_menu() {
|
|||
}
|
||||
|
||||
void zelda64::open_quit_game_prompt() {
|
||||
recompui::open_prompt(
|
||||
recompui::open_choice_prompt(
|
||||
"Are you sure you want to quit?",
|
||||
"Any progress since your last save will be lost.",
|
||||
"Quit",
|
||||
|
|
|
|||
|
|
@ -75,6 +75,10 @@ ModDetailsPanel::ModDetailsPanel(Element *parent) : Element(parent) {
|
|||
ModDetailsPanel::~ModDetailsPanel() {
|
||||
}
|
||||
|
||||
void ModDetailsPanel::disable_toggle() {
|
||||
enable_toggle->set_enabled(false);
|
||||
}
|
||||
|
||||
void ModDetailsPanel::set_mod_details(const recomp::mods::ModDetails& details, const std::string &thumbnail, bool toggle_checked, bool toggle_enabled, bool toggle_label_visible, bool configure_enabled) {
|
||||
cur_details = details;
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ public:
|
|||
void set_mod_details(const recomp::mods::ModDetails& details, const std::string &thumbnail, bool toggle_checked, bool toggle_enabled, bool toggle_label_visible, bool configure_enabled);
|
||||
void set_mod_toggled_callback(std::function<void(bool)> callback);
|
||||
void set_mod_configure_pressed_callback(std::function<void()> callback);
|
||||
void disable_toggle();
|
||||
private:
|
||||
recomp::mods::ModDetails cur_details;
|
||||
Container *thumbnail_container = nullptr;
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ namespace recompui {
|
|||
installation.mod_id = manifest.mod_id;
|
||||
installation.display_name = manifest.display_name;
|
||||
installation.mod_version = manifest.version;
|
||||
installation.mod_files.emplace_back(target_path);
|
||||
installation.mod_file = target_path;
|
||||
}
|
||||
}
|
||||
else if (file_path.extension() == ".rtz") {
|
||||
|
|
@ -44,36 +44,43 @@ namespace recompui {
|
|||
if (exists) {
|
||||
installation.mod_id = std::string((const char *)(target_path.stem().u8string().c_str()));
|
||||
installation.display_name = installation.mod_id;
|
||||
installation.mod_version = recomp::Version();
|
||||
installation.mod_files.emplace_back(target_path);
|
||||
installation.mod_version = recomp::Version(0, 0, 0);
|
||||
installation.mod_file = target_path;
|
||||
}
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
if (exists) {
|
||||
std::filesystem::copy(file_path, target_path, ec);
|
||||
std::filesystem::copy(file_path, target_write_path, ec);
|
||||
if (ec) {
|
||||
result.error_messages.emplace_back(std::format("Unable to copy to {}.", target_write_path.string()));
|
||||
result.error_messages.emplace_back(std::format("Unable to install {} to mod directory.", file_path.filename().string()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
result.error_messages.emplace_back(std::format("Unable to install {} as it does not seem to be a mod.", file_path.string()));
|
||||
result.error_messages.emplace_back(std::format("{} is not a mod.", file_path.string()));
|
||||
std::filesystem::remove(target_write_path, ec);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const std::filesystem::path &path : installation.mod_files) {
|
||||
if (std::filesystem::exists(path, ec)) {
|
||||
installation.needs_overwrite_confirmation = true;
|
||||
break;
|
||||
if (std::filesystem::exists(installation.mod_file, ec)) {
|
||||
installation.needs_overwrite_confirmation = true;
|
||||
}
|
||||
if (!installation.needs_overwrite_confirmation) {
|
||||
// This check isn't really needed as additional_files will be empty for a single mod installation,
|
||||
// but it's good to have in case this logic ever changes.
|
||||
for (const std::filesystem::path &path : installation.additional_files) {
|
||||
if (std::filesystem::exists(path, ec)) {
|
||||
installation.needs_overwrite_confirmation = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.pending_installations.emplace_back(installation);
|
||||
}
|
||||
|
||||
void start_package_mod_installation(recomp::mods::ZipModFileHandle &file_handle, std::function<void(std::filesystem::path, size_t, size_t)> progress_callback, ModInstaller::Result &result) {
|
||||
void start_package_mod_installation(const std::filesystem::path &path, recomp::mods::ZipModFileHandle &file_handle, std::function<void(std::filesystem::path, size_t, size_t)> progress_callback, ModInstaller::Result &result) {
|
||||
std::error_code ec;
|
||||
char filename[1024];
|
||||
std::filesystem::path mods_directory = recomp::mods::get_mods_directory();
|
||||
|
|
@ -81,6 +88,7 @@ namespace recompui {
|
|||
mz_uint num_files = mz_zip_reader_get_num_files(file_handle.archive.get());
|
||||
std::list<std::filesystem::path> dynamic_lib_files;
|
||||
std::list<ModInstaller::Installation>::iterator first_nrm_iterator = result.pending_installations.end();
|
||||
bool found_mod = false;
|
||||
for (mz_uint i = 0; i < num_files; i++) {
|
||||
mz_uint filename_length = mz_zip_reader_get_filename(zip_archive, i, filename, sizeof(filename));
|
||||
if (filename_length == 0) {
|
||||
|
|
@ -89,40 +97,42 @@ namespace recompui {
|
|||
|
||||
std::filesystem::path target_path = mods_directory / std::u8string_view((const char8_t *)(filename));
|
||||
if ((target_path.extension() == ".rtz") || (target_path.extension() == ".nrm")) {
|
||||
found_mod = true;
|
||||
ModInstaller::Installation installation;
|
||||
std::filesystem::path target_write_path = target_path.u8string() + NewExtension;
|
||||
std::ofstream output_stream(target_write_path, std::ios::binary);
|
||||
if (!output_stream.is_open()) {
|
||||
result.error_messages.emplace_back(std::format("Unable to open {} for writing.", target_write_path.string()));
|
||||
result.error_messages.emplace_back(std::format("Unable to write to mod directory."));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!mz_zip_reader_extract_to_callback(zip_archive, i, &zip_write_func, &output_stream, 0)) {
|
||||
output_stream.close();
|
||||
std::filesystem::remove(target_write_path, ec);
|
||||
result.error_messages.emplace_back(std::format("Unable to extract to {}.", target_write_path.string()));
|
||||
result.error_messages.emplace_back(std::format("Failed to install {} to mod directory.", path.filename().string()));
|
||||
continue;
|
||||
}
|
||||
|
||||
output_stream.close();
|
||||
if (output_stream.bad()) {
|
||||
std::filesystem::remove(target_write_path, ec);
|
||||
result.error_messages.emplace_back(std::format("Unable to write to {}.", target_write_path.string()));
|
||||
result.error_messages.emplace_back(std::format("Failed to install {} to mod directory.", path.filename().string()));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try to load the extracted file as a mod file handle.
|
||||
recomp::mods::ModOpenError open_error;
|
||||
recomp::mods::ZipModFileHandle extracted_file_handle(target_write_path, open_error);
|
||||
std::unique_ptr<recomp::mods::ZipModFileHandle> extracted_file_handle = std::make_unique<recomp::mods::ZipModFileHandle>(target_write_path, open_error);
|
||||
if (open_error != recomp::mods::ModOpenError::Good) {
|
||||
result.error_messages.emplace_back(std::format("Unable to open {}.", target_write_path.string()));
|
||||
result.error_messages.emplace_back(std::format("Invalid mod ({}) in {}.", target_path.filename().string(), path.filename().string()));
|
||||
extracted_file_handle.reset();
|
||||
std::filesystem::remove(target_write_path, ec);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for the existence of the manifest file.
|
||||
bool exists = false;
|
||||
std::vector<char> manifest_bytes = extracted_file_handle.read_file(ManifestFilename, exists);
|
||||
std::vector<char> manifest_bytes = extracted_file_handle->read_file(ManifestFilename, exists);
|
||||
if (exists) {
|
||||
// Parse the manifest file to check for its validity.
|
||||
std::string error;
|
||||
|
|
@ -134,31 +144,39 @@ namespace recompui {
|
|||
installation.mod_id = manifest.mod_id;
|
||||
installation.display_name = manifest.display_name;
|
||||
installation.mod_version = manifest.version;
|
||||
installation.mod_files.emplace_back(target_path);
|
||||
installation.mod_file = target_path;
|
||||
}
|
||||
}
|
||||
else if (target_path.extension() == ".rtz") {
|
||||
// When it's an rtz file, check if the texture database file exists.
|
||||
exists = mz_zip_reader_locate_file(extracted_file_handle.archive.get(), TextureDatabaseFilename, nullptr, 0) >= 0;
|
||||
exists = mz_zip_reader_locate_file(extracted_file_handle->archive.get(), TextureDatabaseFilename, nullptr, 0) >= 0;
|
||||
|
||||
if (exists) {
|
||||
installation.mod_id = std::string((const char *)(target_path.stem().u8string().c_str()));
|
||||
installation.display_name = installation.mod_id;
|
||||
installation.mod_version = recomp::Version();
|
||||
installation.mod_files.emplace_back(target_path);
|
||||
installation.mod_file = target_path;
|
||||
}
|
||||
}
|
||||
|
||||
if (!exists) {
|
||||
result.error_messages.emplace_back(std::format("Unable to install {} as it does not seem to be a mod.", target_path.filename().string()));
|
||||
result.error_messages.emplace_back(std::format("Invalid mod ({}) in {}.", target_path.filename().string(), path.filename().string()));
|
||||
extracted_file_handle.reset();
|
||||
std::filesystem::remove(target_write_path, ec);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const std::filesystem::path &path : installation.mod_files) {
|
||||
if (std::filesystem::exists(path, ec)) {
|
||||
installation.needs_overwrite_confirmation = true;
|
||||
break;
|
||||
if (std::filesystem::exists(installation.mod_file, ec)) {
|
||||
installation.needs_overwrite_confirmation = true;
|
||||
}
|
||||
if (!installation.needs_overwrite_confirmation) {
|
||||
// This check isn't really needed as additional_files will be empty at this point,
|
||||
// but it's good to have in case this logic ever changes.
|
||||
for (const std::filesystem::path &path : installation.additional_files) {
|
||||
if (std::filesystem::exists(path, ec)) {
|
||||
installation.needs_overwrite_confirmation = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -181,21 +199,21 @@ namespace recompui {
|
|||
std::filesystem::path target_write_path = target_path.u8string() + NewExtension;
|
||||
std::ofstream output_stream(target_write_path, std::ios::binary);
|
||||
if (!output_stream.is_open()) {
|
||||
result.error_messages.emplace_back(std::format("Unable to open {} for writing.", target_write_path.string()));
|
||||
result.error_messages.emplace_back(std::format("Failed to install {} to mod directory.", path.filename().string()));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!mz_zip_reader_extract_to_callback(zip_archive, i, &zip_write_func, &output_stream, 0)) {
|
||||
output_stream.close();
|
||||
std::filesystem::remove(target_write_path, ec);
|
||||
result.error_messages.emplace_back(std::format("Unable to extract to {}.", target_write_path.string()));
|
||||
result.error_messages.emplace_back(std::format("Failed to install {} to mod directory.", path.filename().string()));
|
||||
continue;
|
||||
}
|
||||
|
||||
output_stream.close();
|
||||
if (output_stream.bad()) {
|
||||
std::filesystem::remove(target_write_path, ec);
|
||||
result.error_messages.emplace_back(std::format("Unable to write to {}.", target_write_path.string()));
|
||||
result.error_messages.emplace_back(std::format("Failed to install {} to mod directory.", path.filename().string()));
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -207,14 +225,11 @@ namespace recompui {
|
|||
if (first_nrm_iterator != result.pending_installations.end()) {
|
||||
// Associate all these files to the first mod that is found.
|
||||
for (const std::filesystem::path &path : dynamic_lib_files) {
|
||||
first_nrm_iterator->mod_files.emplace_back(path);
|
||||
first_nrm_iterator->additional_files.emplace_back(path);
|
||||
|
||||
// Run verification against for overwrite confirmations.
|
||||
for (const std::filesystem::path &path : first_nrm_iterator->mod_files) {
|
||||
if (std::filesystem::exists(path, ec)) {
|
||||
first_nrm_iterator->needs_overwrite_confirmation = true;
|
||||
break;
|
||||
}
|
||||
if (std::filesystem::exists(path, ec)) {
|
||||
first_nrm_iterator->needs_overwrite_confirmation = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -226,8 +241,43 @@ namespace recompui {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!found_mod) {
|
||||
result.error_messages.emplace_back(std::format("No mods found in {}.", path.filename().string()));
|
||||
}
|
||||
}
|
||||
|
||||
void remove_and_rename(std::vector<std::string>& error_messages, const std::filesystem::path& path) {
|
||||
std::error_code ec;
|
||||
std::filesystem::path old_path(path.u8string() + OldExtension);
|
||||
std::filesystem::path new_path(path.u8string() + NewExtension);
|
||||
|
||||
// Rename the current path to a temporary old path, but only if the current path already exists.
|
||||
if (std::filesystem::exists(path, ec)) {
|
||||
std::filesystem::remove(old_path, ec);
|
||||
std::filesystem::rename(path, old_path, ec);
|
||||
if (ec) {
|
||||
// If it fails, remove the new path.
|
||||
std::filesystem::remove(new_path, ec);
|
||||
error_messages.emplace_back(std::format("Unable to rename {}.", path.filename().string()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Rename the new path to the current path.
|
||||
std::filesystem::rename(new_path, path, ec);
|
||||
if (ec) {
|
||||
// If it fails, remove the new path and also restore the temporary old path to the current path.
|
||||
std::filesystem::remove(new_path, ec);
|
||||
std::filesystem::rename(old_path, path, ec);
|
||||
error_messages.emplace_back(std::format("Unable to rename {}.", path.filename().string()));
|
||||
return;
|
||||
}
|
||||
|
||||
// If nothing failed, just remove the temporary old path.
|
||||
std::filesystem::remove(old_path, ec);
|
||||
};
|
||||
|
||||
void ModInstaller::start_mod_installation(const std::list<std::filesystem::path> &file_paths, std::function<void(std::filesystem::path, size_t, size_t)> progress_callback, Result &result) {
|
||||
result = Result();
|
||||
|
||||
|
|
@ -235,64 +285,46 @@ namespace recompui {
|
|||
recomp::mods::ModOpenError open_error;
|
||||
recomp::mods::ZipModFileHandle file_handle(path, open_error);
|
||||
if (open_error != recomp::mods::ModOpenError::Good) {
|
||||
result.error_messages = { std::format("File %s is not a valid container.", path.string()) };
|
||||
result.error_messages.emplace_back(std::format("{} is not a valid zip or mod.", path.filename().string()));
|
||||
continue;
|
||||
}
|
||||
|
||||
// First we verify if the container itself isn't a mod already.
|
||||
// TODO hook into the runtime's container registration to check the extension instead of using hardcoded values.
|
||||
if ((path.extension() == ".rtz") || (path.extension() == ".nrm")) {
|
||||
start_single_mod_installation(path, file_handle, progress_callback, result);
|
||||
}
|
||||
else {
|
||||
// Scan the container for compatible mods instead. This is the case for packages made by users or how they're tipically uploaded to Thunderstore.
|
||||
start_package_mod_installation(file_handle, progress_callback, result);
|
||||
start_package_mod_installation(path, file_handle, progress_callback, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ModInstaller::finish_mod_installation(const std::unordered_set<std::string> &confirmed_overwrites, Result &result) {
|
||||
result.error_messages.clear();
|
||||
void ModInstaller::cancel_mod_installation(const Result &result, std::vector<std::string>& error_messages) {
|
||||
error_messages.clear();
|
||||
|
||||
std::error_code ec;
|
||||
// Delete all the files that were extracted for all mods.
|
||||
for (const Installation &installation : result.pending_installations) {
|
||||
std::filesystem::path new_path(installation.mod_file.u8string() + NewExtension);
|
||||
std::filesystem::remove(new_path, ec);
|
||||
for (const std::filesystem::path &path : installation.additional_files) {
|
||||
std::filesystem::path new_path(path.u8string() + NewExtension);
|
||||
std::filesystem::remove(new_path, ec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ModInstaller::finish_mod_installation(const Result &result, std::vector<std::string>& error_messages) {
|
||||
error_messages.clear();
|
||||
|
||||
std::error_code ec;
|
||||
for (const Installation &installation : result.pending_installations) {
|
||||
if (installation.needs_overwrite_confirmation && !confirmed_overwrites.contains(installation.mod_id)) {
|
||||
// If the user hasn't confirmed this overwrite, simply delete all the files that were extracted for this mod.
|
||||
for (const std::filesystem::path &path : installation.mod_files) {
|
||||
std::filesystem::path new_path(path.u8string() + NewExtension);
|
||||
std::filesystem::remove(new_path, ec);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const std::filesystem::path &path : installation.mod_files) {
|
||||
std::filesystem::path old_path(path.u8string() + OldExtension);
|
||||
std::filesystem::path new_path(path.u8string() + NewExtension);
|
||||
|
||||
// Rename the current path to a temporary old path, but only if the current path already exists.
|
||||
if (std::filesystem::exists(path, ec)) {
|
||||
std::filesystem::remove(old_path, ec);
|
||||
std::filesystem::rename(path, old_path, ec);
|
||||
if (ec) {
|
||||
// If it fails, remove the new path.
|
||||
std::filesystem::remove(new_path, ec);
|
||||
result.error_messages.emplace_back(std::format("Unable to rename {}.", path.string()));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Rename the new path to the current path.
|
||||
std::filesystem::rename(new_path, path, ec);
|
||||
if (ec) {
|
||||
// If it fails, remove the new path and also restore the temporary old path to the current path.
|
||||
std::filesystem::remove(new_path, ec);
|
||||
std::filesystem::rename(old_path, path, ec);
|
||||
result.error_messages.emplace_back(std::format("Unable to rename {}.", path.string()));
|
||||
continue;
|
||||
}
|
||||
|
||||
// If nothing failed, just remove the temporary old path.
|
||||
std::filesystem::remove(old_path, ec);
|
||||
// Overwrite the mod files.
|
||||
remove_and_rename(error_messages, installation.mod_file);
|
||||
for (const std::filesystem::path &path : installation.additional_files) {
|
||||
remove_and_rename(error_messages, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@
|
|||
#include <librecomp/game.hpp>
|
||||
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
namespace recompui {
|
||||
struct ModInstaller {
|
||||
|
|
@ -11,17 +13,28 @@ namespace recompui {
|
|||
std::string mod_id;
|
||||
std::string display_name;
|
||||
recomp::Version mod_version;
|
||||
std::list<std::filesystem::path> mod_files;
|
||||
std::filesystem::path mod_file;
|
||||
std::list<std::filesystem::path> additional_files;
|
||||
bool needs_overwrite_confirmation = false;
|
||||
};
|
||||
|
||||
struct Confirmation {
|
||||
std::string old_display_name;
|
||||
std::string new_display_name;
|
||||
std::string old_mod_id;
|
||||
std::string new_mod_id;
|
||||
recomp::Version old_version;
|
||||
recomp::Version new_version;
|
||||
};
|
||||
|
||||
struct Result {
|
||||
std::list<std::string> error_messages;
|
||||
std::list<Installation> pending_installations;
|
||||
};
|
||||
|
||||
static void start_mod_installation(const std::list<std::filesystem::path> &file_paths, std::function<void(std::filesystem::path, size_t, size_t)> progress_callback, Result &result);
|
||||
static void finish_mod_installation(const std::unordered_set<std::string> &confirmed_overwrites, Result &result);
|
||||
static void cancel_mod_installation(const Result& result, std::vector<std::string>& errors);
|
||||
static void finish_mod_installation(const Result &result, std::vector<std::string>& errors);
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -212,7 +212,7 @@ void ModMenu::refresh_mods() {
|
|||
}
|
||||
|
||||
recomp::mods::scan_mods();
|
||||
mod_details = recomp::mods::get_mod_details(game_mod_id);
|
||||
mod_details = recomp::mods::get_all_mod_details(game_mod_id);
|
||||
create_mod_list();
|
||||
}
|
||||
|
||||
|
|
@ -336,7 +336,7 @@ void ModMenu::mod_dragged(uint32_t mod_index, EventDrag drag) {
|
|||
|
||||
// Re-order the mods and update all the details on the menu.
|
||||
recomp::mods::set_mod_index(game_mod_id, mod_details[mod_index].mod_id, mod_drag_target_index);
|
||||
mod_details = recomp::mods::get_mod_details(game_mod_id);
|
||||
mod_details = recomp::mods::get_all_mod_details(game_mod_id);
|
||||
for (size_t i = 0; i < mod_entry_buttons.size(); i++) {
|
||||
mod_entry_buttons[i]->set_mod_details(mod_details[i]);
|
||||
mod_entry_buttons[i]->set_mod_thumbnail(generate_thumbnail_src_for_mod(mod_details[i].mod_id));
|
||||
|
|
@ -471,6 +471,25 @@ void ModMenu::create_mod_list() {
|
|||
}
|
||||
}
|
||||
|
||||
void ModMenu::process_event(const Event &e) {
|
||||
if (e.type == EventType::Update) {
|
||||
if (mods_dirty) {
|
||||
refresh_mods();
|
||||
mods_dirty = false;
|
||||
}
|
||||
if (ultramodern::is_game_started()) {
|
||||
refresh_button->set_enabled(false);
|
||||
}
|
||||
if (active_mod_index != -1) {
|
||||
bool auto_enabled = recomp::mods::is_mod_auto_enabled(mod_details[active_mod_index].mod_id);
|
||||
bool toggle_enabled = !auto_enabled && (mod_details[active_mod_index].runtime_toggleable || !ultramodern::is_game_started());
|
||||
if (!toggle_enabled) {
|
||||
mod_details_panel->disable_toggle();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ModMenu::ModMenu(Element *parent) : Element(parent) {
|
||||
game_mod_id = "mm";
|
||||
|
||||
|
|
@ -526,7 +545,7 @@ ModMenu::ModMenu(Element *parent) : Element(parent) {
|
|||
footer_container->set_border_bottom_right_radius(16.0f);
|
||||
{
|
||||
refresh_button = context.create_element<Button>(footer_container, "Refresh", recompui::ButtonStyle::Primary);
|
||||
refresh_button->add_pressed_callback(std::bind(&ModMenu::refresh_mods, this));
|
||||
refresh_button->add_pressed_callback([this](){ recomp::mods::scan_mods(); this->refresh_mods(); });
|
||||
|
||||
context.create_element<Label>(footer_container, "⚠ UNDER CONSTRUCTION ⚠", LabelStyle::Small);
|
||||
|
||||
|
|
@ -540,6 +559,7 @@ ModMenu::ModMenu(Element *parent) : Element(parent) {
|
|||
mod_entry_floating_view->set_position(Position::Absolute);
|
||||
mod_entry_floating_view->set_selected(true);
|
||||
|
||||
recomp::mods::scan_mods();
|
||||
refresh_mods();
|
||||
|
||||
context.close();
|
||||
|
|
@ -560,6 +580,35 @@ ModMenu::~ModMenu() {
|
|||
|
||||
// Placeholder class until the rest of the UI refactor is finished.
|
||||
|
||||
recompui::ModMenu* mod_menu;
|
||||
|
||||
void recompui::update_mod_list() {
|
||||
if (mod_menu) {
|
||||
recompui::ContextId ui_context = recompui::get_config_context_id();
|
||||
bool opened = ui_context.open_if_not_already();
|
||||
|
||||
mod_menu->set_mods_dirty();
|
||||
mod_menu->queue_update();
|
||||
|
||||
if (opened) {
|
||||
ui_context.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void recompui::process_game_started() {
|
||||
if (mod_menu) {
|
||||
recompui::ContextId ui_context = recompui::get_config_context_id();
|
||||
bool opened = ui_context.open_if_not_already();
|
||||
|
||||
mod_menu->queue_update();
|
||||
|
||||
if (opened) {
|
||||
ui_context.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ElementModMenu::ElementModMenu(const Rml::String &tag) : Rml::Element(tag) {
|
||||
SetProperty("width", "100%");
|
||||
SetProperty("height", "100%");
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ class ModMenu : public Element {
|
|||
public:
|
||||
ModMenu(Element *parent);
|
||||
virtual ~ModMenu();
|
||||
void set_mods_dirty() { mods_dirty = true; }
|
||||
private:
|
||||
void refresh_mods();
|
||||
void open_mods_folder();
|
||||
|
|
@ -76,6 +77,7 @@ private:
|
|||
void mod_string_option_changed(const std::string &id, const std::string &value);
|
||||
void mod_number_option_changed(const std::string &id, double value);
|
||||
void create_mod_list();
|
||||
void process_event(const Event &e) override;
|
||||
|
||||
Container *body_container = nullptr;
|
||||
Container *list_container = nullptr;
|
||||
|
|
@ -96,6 +98,7 @@ private:
|
|||
std::vector<recomp::mods::ModDetails> mod_details{};
|
||||
std::unordered_set<std::string> loaded_thumbnails;
|
||||
std::string game_mod_id;
|
||||
bool mods_dirty = false;
|
||||
|
||||
ConfigSubMenu *config_sub_menu;
|
||||
};
|
||||
|
|
@ -104,8 +107,6 @@ class ElementModMenu : public Rml::Element {
|
|||
public:
|
||||
ElementModMenu(const Rml::String& tag);
|
||||
virtual ~ElementModMenu();
|
||||
private:
|
||||
ModMenu *mod_menu;
|
||||
};
|
||||
|
||||
} // namespace recompui
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ struct {
|
|||
recompui::ContextId ui_context;
|
||||
recompui::Label* prompt_header;
|
||||
recompui::Label* prompt_label;
|
||||
recompui::Element* prompt_controls;
|
||||
recompui::Button* confirm_button;
|
||||
recompui::Button* cancel_button;
|
||||
std::function<void()> confirm_action;
|
||||
|
|
@ -95,26 +96,26 @@ void recompui::init_prompt_context() {
|
|||
prompt_content->set_border_color(Color{ 255, 255, 255, 51 });
|
||||
prompt_content->set_background_color(Color{ 8, 7, 13, 229 });
|
||||
|
||||
prompt_state.prompt_header = context.create_element<Label>(prompt_content, "Graphics options have changed", LabelStyle::Large);
|
||||
prompt_state.prompt_header = context.create_element<Label>(prompt_content, "", LabelStyle::Large);
|
||||
prompt_state.prompt_header->set_margin(24, Unit::Dp);
|
||||
|
||||
prompt_state.prompt_label = context.create_element<Label>(prompt_content, "Would you like to apply or discard these changes?", LabelStyle::Small);
|
||||
prompt_state.prompt_label = context.create_element<Label>(prompt_content, "", LabelStyle::Small);
|
||||
prompt_state.prompt_label->set_margin(24, Unit::Dp);
|
||||
prompt_state.prompt_label->set_margin_top(0);
|
||||
|
||||
Element* prompt_controls = context.create_element<Element>(prompt_content);
|
||||
prompt_state.prompt_controls = context.create_element<Element>(prompt_content);
|
||||
|
||||
prompt_controls->set_display(Display::Flex);
|
||||
prompt_controls->set_flex_direction(FlexDirection::Row);
|
||||
prompt_controls->set_justify_content(JustifyContent::Center);
|
||||
prompt_controls->set_padding_top(24, Unit::Dp);
|
||||
prompt_controls->set_padding_bottom(24, Unit::Dp);
|
||||
prompt_controls->set_padding_left(12, Unit::Dp);
|
||||
prompt_controls->set_padding_right(12, Unit::Dp);
|
||||
prompt_controls->set_border_top_width(1.1, Unit::Dp);
|
||||
prompt_controls->set_border_top_color({ 255, 255, 255, 25 });
|
||||
prompt_state.prompt_controls->set_display(Display::Flex);
|
||||
prompt_state.prompt_controls->set_flex_direction(FlexDirection::Row);
|
||||
prompt_state.prompt_controls->set_justify_content(JustifyContent::Center);
|
||||
prompt_state.prompt_controls->set_padding_top(24, Unit::Dp);
|
||||
prompt_state.prompt_controls->set_padding_bottom(24, Unit::Dp);
|
||||
prompt_state.prompt_controls->set_padding_left(12, Unit::Dp);
|
||||
prompt_state.prompt_controls->set_padding_right(12, Unit::Dp);
|
||||
prompt_state.prompt_controls->set_border_top_width(1.1, Unit::Dp);
|
||||
prompt_state.prompt_controls->set_border_top_color({ 255, 255, 255, 25 });
|
||||
|
||||
prompt_state.confirm_button = context.create_element<Button>(prompt_controls, "", ButtonStyle::Primary);
|
||||
prompt_state.confirm_button = context.create_element<Button>(prompt_state.prompt_controls, "", ButtonStyle::Primary);
|
||||
prompt_state.confirm_button->set_min_width(185.0f, Unit::Dp);
|
||||
prompt_state.confirm_button->set_margin_top(0);
|
||||
prompt_state.confirm_button->set_margin_bottom(0);
|
||||
|
|
@ -131,7 +132,7 @@ void recompui::init_prompt_context() {
|
|||
confirm_hover_style->set_background_color(Color{ 69, 208, 67, 76 });
|
||||
confirm_hover_style->set_color(Color{ 242, 242, 242, 255 });
|
||||
|
||||
prompt_state.cancel_button = context.create_element<Button>(prompt_controls, "", ButtonStyle::Primary);
|
||||
prompt_state.cancel_button = context.create_element<Button>(prompt_state.prompt_controls, "", ButtonStyle::Primary);
|
||||
prompt_state.cancel_button->set_min_width(185.0f, Unit::Dp);
|
||||
prompt_state.cancel_button->set_margin_top(0);
|
||||
prompt_state.cancel_button->set_margin_bottom(0);
|
||||
|
|
@ -197,47 +198,133 @@ void style_button(recompui::Button* button, recompui::ButtonVariant variant) {
|
|||
disabled_style->set_color(disabled_color);
|
||||
}
|
||||
|
||||
void recompui::open_prompt(
|
||||
const std::string& headerText,
|
||||
const std::string& contentText,
|
||||
const std::string& confirmLabelText,
|
||||
const std::string& cancelLabelText,
|
||||
std::function<void()> confirmCb,
|
||||
std::function<void()> cancelCb,
|
||||
ButtonVariant _confirmVariant,
|
||||
ButtonVariant _cancelVariant,
|
||||
bool _focusOnCancel,
|
||||
const std::string& _returnElementId
|
||||
) {
|
||||
std::lock_guard lock{ prompt_state.mutex };
|
||||
|
||||
prompt_state.ui_context.open();
|
||||
|
||||
prompt_state.prompt_header->set_text(headerText);
|
||||
prompt_state.prompt_label->set_text(contentText);
|
||||
prompt_state.confirm_button->set_text(confirmLabelText);
|
||||
prompt_state.cancel_button->set_text(cancelLabelText);
|
||||
prompt_state.confirm_action = confirmCb;
|
||||
prompt_state.cancel_action = cancelCb;
|
||||
prompt_state.return_element_id = _returnElementId;
|
||||
|
||||
style_button(prompt_state.confirm_button, _confirmVariant);
|
||||
style_button(prompt_state.cancel_button, _cancelVariant);
|
||||
|
||||
if (_focusOnCancel) {
|
||||
// Must be called while prompt_state.mutex is locked.
|
||||
void show_prompt(std::function<void()>& prev_cancel_action, bool focus_on_cancel) {
|
||||
if (!recompui::is_context_shown(prompt_state.ui_context)) {
|
||||
recompui::show_context(prompt_state.ui_context, "");
|
||||
}
|
||||
else {
|
||||
// Call the previous cancel action to effectively close the previous prompt.
|
||||
if (prev_cancel_action) {
|
||||
prev_cancel_action();
|
||||
}
|
||||
}
|
||||
|
||||
if (focus_on_cancel) {
|
||||
// TODO nav: focus cancel button
|
||||
}
|
||||
else {
|
||||
// TODO nav: focus confirm button
|
||||
}
|
||||
}
|
||||
|
||||
void recompui::open_choice_prompt(
|
||||
const std::string& header_text,
|
||||
const std::string& content_text,
|
||||
const std::string& confirm_label_text,
|
||||
const std::string& cancel_label_text,
|
||||
std::function<void()> confirm_action,
|
||||
std::function<void()> cancel_action,
|
||||
ButtonVariant confirm_variant,
|
||||
ButtonVariant cancel_variant,
|
||||
bool focus_on_cancel,
|
||||
const std::string& return_element_id
|
||||
) {
|
||||
std::lock_guard lock{ prompt_state.mutex };
|
||||
|
||||
std::function<void()> prev_cancel_action = std::move(prompt_state.cancel_action);
|
||||
|
||||
prompt_state.ui_context.open();
|
||||
|
||||
prompt_state.prompt_header->set_text(header_text);
|
||||
prompt_state.prompt_label->set_text(content_text);
|
||||
prompt_state.prompt_controls->set_display(Display::Flex);
|
||||
prompt_state.confirm_button->set_display(Display::Block);
|
||||
prompt_state.cancel_button->set_display(Display::Block);
|
||||
prompt_state.confirm_button->set_text(confirm_label_text);
|
||||
prompt_state.cancel_button->set_text(cancel_label_text);
|
||||
prompt_state.confirm_action = confirm_action;
|
||||
prompt_state.cancel_action = cancel_action;
|
||||
prompt_state.return_element_id = return_element_id;
|
||||
|
||||
style_button(prompt_state.confirm_button, confirm_variant);
|
||||
style_button(prompt_state.cancel_button, cancel_variant);
|
||||
|
||||
prompt_state.ui_context.close();
|
||||
|
||||
if (!recompui::is_context_shown(prompt_state.ui_context)) {
|
||||
recompui::show_context(prompt_state.ui_context, "");
|
||||
show_prompt(prev_cancel_action, focus_on_cancel);
|
||||
}
|
||||
|
||||
void recompui::open_info_prompt(
|
||||
const std::string& header_text,
|
||||
const std::string& content_text,
|
||||
const std::string& okay_label_text,
|
||||
std::function<void()> okay_action,
|
||||
ButtonVariant okay_variant,
|
||||
const std::string& return_element_id
|
||||
) {
|
||||
std::lock_guard lock{ prompt_state.mutex };
|
||||
|
||||
std::function<void()> prev_cancel_action = std::move(prompt_state.cancel_action);
|
||||
|
||||
prompt_state.ui_context.open();
|
||||
|
||||
prompt_state.prompt_header->set_text(header_text);
|
||||
prompt_state.prompt_label->set_text(content_text);
|
||||
prompt_state.prompt_controls->set_display(Display::Flex);
|
||||
prompt_state.confirm_button->set_display(Display::None);
|
||||
prompt_state.cancel_button->set_display(Display::Block);
|
||||
prompt_state.cancel_button->set_text(okay_label_text);
|
||||
prompt_state.confirm_action = {};
|
||||
prompt_state.cancel_action = okay_action;
|
||||
prompt_state.return_element_id = return_element_id;
|
||||
|
||||
style_button(prompt_state.cancel_button, okay_variant);
|
||||
|
||||
prompt_state.ui_context.close();
|
||||
|
||||
show_prompt(prev_cancel_action, true);
|
||||
}
|
||||
|
||||
void recompui::open_notification(
|
||||
const std::string& header_text,
|
||||
const std::string& content_text,
|
||||
const std::string& return_element_id
|
||||
) {
|
||||
std::lock_guard lock{ prompt_state.mutex };
|
||||
|
||||
std::function<void()> prev_cancel_action = std::move(prompt_state.cancel_action);
|
||||
|
||||
prompt_state.ui_context.open();
|
||||
|
||||
prompt_state.prompt_header->set_text(header_text);
|
||||
prompt_state.prompt_label->set_text(content_text);
|
||||
prompt_state.prompt_controls->set_display(Display::None);
|
||||
prompt_state.confirm_button->set_display(Display::None);
|
||||
prompt_state.cancel_button->set_display(Display::None);
|
||||
prompt_state.confirm_action = {};
|
||||
prompt_state.cancel_action = {};
|
||||
prompt_state.return_element_id = return_element_id;
|
||||
|
||||
prompt_state.ui_context.close();
|
||||
|
||||
show_prompt(prev_cancel_action, false);
|
||||
}
|
||||
|
||||
void recompui::close_prompt() {
|
||||
std::lock_guard lock{ prompt_state.mutex };
|
||||
|
||||
if (recompui::is_context_shown(prompt_state.ui_context)) {
|
||||
if (prompt_state.cancel_action) {
|
||||
prompt_state.cancel_action();
|
||||
}
|
||||
|
||||
recompui::hide_context(prompt_state.ui_context);
|
||||
}
|
||||
}
|
||||
|
||||
bool recompui::is_prompt_open() {
|
||||
return false;
|
||||
std::lock_guard lock{ prompt_state.mutex };
|
||||
|
||||
return recompui::is_context_shown(prompt_state.ui_context);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -837,8 +837,113 @@ void recompui::release_image(const std::string &src) {
|
|||
}
|
||||
|
||||
void recompui::drop_files(const std::list<std::filesystem::path> &file_list) {
|
||||
// Prevent mod installation after the game has started.
|
||||
if (ultramodern::is_game_started()) {
|
||||
return;
|
||||
}
|
||||
|
||||
recompui::open_notification("Installing Mods", "Please Wait");
|
||||
// TODO: Needs a progress callback and a prompt for every mod that needs to be confirmed to be overwritten.
|
||||
// TODO: Run this on a background thread and use the callbacks to advance the state instead of blocking.
|
||||
ModInstaller::Result result;
|
||||
ModInstaller::start_mod_installation(file_list, nullptr, result);
|
||||
ModInstaller::finish_mod_installation({}, result);
|
||||
}
|
||||
|
||||
recompui::close_prompt();
|
||||
|
||||
if (!result.error_messages.empty()) {
|
||||
std::string error_label = std::accumulate(result.error_messages.begin(), result.error_messages.end(), std::string{},
|
||||
[](const std::string &lhs, const std::string &rhs)
|
||||
{
|
||||
return lhs.empty() ? rhs : lhs + '\n' + rhs;
|
||||
});
|
||||
|
||||
recompui::open_info_prompt("Error Installing Mods", error_label, "OK", {}, recompui::ButtonVariant::Tertiary);
|
||||
std::vector<std::string> dummy_error_messages{};
|
||||
ModInstaller::cancel_mod_installation(result, dummy_error_messages);
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<ModInstaller::Confirmation> confirmations{};
|
||||
|
||||
for (const ModInstaller::Installation& pending_install : result.pending_installations) {
|
||||
if (pending_install.needs_overwrite_confirmation) {
|
||||
// Get the mod details for the current mod at this file path.
|
||||
std::string old_mod_id = recomp::mods::get_mod_id_from_filename(pending_install.mod_file.filename());
|
||||
std::optional<recomp::mods::ModDetails> old_mod_details = {};
|
||||
|
||||
if (!old_mod_id.empty()) {
|
||||
old_mod_details = recomp::mods::get_details_for_mod(old_mod_id);
|
||||
}
|
||||
|
||||
if (old_mod_details) {
|
||||
confirmations.emplace_back(ModInstaller::Confirmation {
|
||||
.old_display_name = old_mod_details->display_name,
|
||||
.new_display_name = pending_install.display_name,
|
||||
.old_mod_id = old_mod_details->mod_id,
|
||||
.new_mod_id = pending_install.mod_id,
|
||||
.old_version = old_mod_details->version,
|
||||
.new_version = pending_install.mod_version
|
||||
});
|
||||
}
|
||||
else {
|
||||
confirmations.emplace_back(ModInstaller::Confirmation {
|
||||
.old_display_name = "?",
|
||||
.new_display_name = pending_install.display_name,
|
||||
.old_mod_id = "",
|
||||
.new_mod_id = pending_install.mod_id,
|
||||
.old_version = recomp::Version{0, 0, 0, ""},
|
||||
.new_version = pending_install.mod_version
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (confirmations.empty()) {
|
||||
std::vector<std::string> error_messages{};
|
||||
ModInstaller::finish_mod_installation(result, error_messages);
|
||||
// TODO show errors
|
||||
}
|
||||
else {
|
||||
std::string prompt_text = std::accumulate(confirmations.begin(), confirmations.end(), std::string{},
|
||||
[](const std::string &cur_text, const ModInstaller::Confirmation &confirmation)
|
||||
{
|
||||
std::string new_text{};
|
||||
if (confirmation.old_display_name == confirmation.new_display_name) {
|
||||
new_text = confirmation.old_display_name + " (" + confirmation.old_version.to_string() + " -> " + confirmation.new_version.to_string() + ")";
|
||||
}
|
||||
else {
|
||||
new_text =
|
||||
confirmation.old_display_name + " (" + confirmation.old_version.to_string() + ") -> " +
|
||||
confirmation.new_display_name + " (" + confirmation.new_version.to_string() + ")";
|
||||
}
|
||||
return cur_text.empty() ? new_text : cur_text + '\n' + new_text;
|
||||
});
|
||||
|
||||
// open prompt where confirm finishes the mod installation with the overwritten files
|
||||
recompui::open_choice_prompt("Overwrite Mods?",
|
||||
prompt_text,
|
||||
"Overwrite",
|
||||
"Cancel",
|
||||
[result]() {
|
||||
std::vector<std::string> error_messages{};
|
||||
recomp::mods::close_mods();
|
||||
ModInstaller::finish_mod_installation(result, error_messages);
|
||||
recomp::mods::scan_mods();
|
||||
ContextId old_context = recompui::get_current_context();
|
||||
old_context.close();
|
||||
recompui::update_mod_list();
|
||||
old_context.open();
|
||||
// TODO show errors
|
||||
},
|
||||
[result]() {
|
||||
std::vector<std::string> error_messages{};
|
||||
ModInstaller::cancel_mod_installation(result, error_messages);
|
||||
// TODO show errors
|
||||
},
|
||||
recompui::ButtonVariant::Success,
|
||||
recompui::ButtonVariant::Error,
|
||||
true,
|
||||
""
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue