mirror of
https://github.com/BanjoRecomp/BanjoRecomp
synced 2026-08-16 12:44:35 -04:00
Update codebase with changes for Zelda 64: Recompiled 1.2
This commit is contained in:
+30
-11
@@ -25,6 +25,7 @@
|
||||
#include "banjo_config.h"
|
||||
#include "banjo_sound.h"
|
||||
#include "banjo_render.h"
|
||||
#include "banjo_support.h"
|
||||
#include "banjo_game.h"
|
||||
#include "recomp_data.h"
|
||||
#include "ovl_patches.hpp"
|
||||
@@ -45,14 +46,15 @@
|
||||
|
||||
#include "../../lib/rt64/src/contrib/stb/stb_image.h"
|
||||
|
||||
const std::string version_string = "0.0.1";
|
||||
const std::string version_string = "0.1.0";
|
||||
|
||||
template<typename... Ts>
|
||||
void exit_error(const char* str, Ts ...args) {
|
||||
// TODO pop up an error
|
||||
((void)fprintf(stderr, str, args), ...);
|
||||
assert(false);
|
||||
std::quick_exit(EXIT_FAILURE);
|
||||
|
||||
ultramodern::error_handling::quick_exit(__FILE__, __LINE__, __FUNCTION__);
|
||||
}
|
||||
|
||||
ultramodern::gfx_callbacks_t::gfx_data_t create_gfx() {
|
||||
@@ -126,11 +128,13 @@ SDL_Window* window;
|
||||
ultramodern::renderer::WindowHandle create_window(ultramodern::gfx_callbacks_t::gfx_data_t) {
|
||||
uint32_t flags = SDL_WINDOW_RESIZABLE;
|
||||
|
||||
#if defined(RT64_SDL_WINDOW_VULKAN)
|
||||
#if defined(__APPLE__)
|
||||
flags |= SDL_WINDOW_METAL;
|
||||
#elif defined(RT64_SDL_WINDOW_VULKAN)
|
||||
flags |= SDL_WINDOW_VULKAN;
|
||||
#endif
|
||||
|
||||
window = SDL_CreateWindow("Banjo: Recompiled", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1600, 960, flags);
|
||||
window = SDL_CreateWindow("Zelda 64: Recompiled", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1600, 960, flags);
|
||||
#if defined(__linux__)
|
||||
SetImageAsIcon("icons/512.png",window);
|
||||
if (ultramodern::renderer::get_graphics_config().wm_option == ultramodern::renderer::WindowMode::Fullscreen) { // TODO: Remove once RT64 gets native fullscreen support on Linux
|
||||
@@ -152,6 +156,9 @@ ultramodern::renderer::WindowHandle create_window(ultramodern::gfx_callbacks_t::
|
||||
return ultramodern::renderer::WindowHandle{ wmInfo.info.win.window, GetCurrentThreadId() };
|
||||
#elif defined(__linux__) || defined(__ANDROID__)
|
||||
return ultramodern::renderer::WindowHandle{ window };
|
||||
#elif defined(__APPLE__)
|
||||
SDL_MetalView view = SDL_Metal_CreateView(window);
|
||||
return ultramodern::renderer::WindowHandle{ wmInfo.info.cocoa.window, SDL_Metal_GetLayer(view) };
|
||||
#else
|
||||
static_assert(false && "Unimplemented");
|
||||
#endif
|
||||
@@ -503,15 +510,17 @@ void release_preload(PreloadContext& context) {
|
||||
#endif
|
||||
|
||||
void enable_texture_pack(recomp::mods::ModContext& context, const recomp::mods::ModHandle& mod) {
|
||||
(void)context;
|
||||
banjo::renderer::enable_texture_pack(mod);
|
||||
banjo::renderer::enable_texture_pack(context, mod);
|
||||
}
|
||||
|
||||
void disable_texture_pack(recomp::mods::ModContext& context, const recomp::mods::ModHandle& mod) {
|
||||
(void)context;
|
||||
void disable_texture_pack(recomp::mods::ModContext&, const recomp::mods::ModHandle& mod) {
|
||||
banjo::renderer::disable_texture_pack(mod);
|
||||
}
|
||||
|
||||
void reorder_texture_pack(recomp::mods::ModContext&) {
|
||||
banjo::renderer::trigger_texture_pack_update();
|
||||
}
|
||||
|
||||
#define REGISTER_FUNC(name) recomp::overlays::register_base_export(#name, name)
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
@@ -555,14 +564,22 @@ int main(int argc, char** argv) {
|
||||
// Force wasapi on Windows, as there seems to be some issue with sample queueing with directsound currently.
|
||||
SDL_setenv("SDL_AUDIODRIVER", "wasapi", true);
|
||||
#endif
|
||||
//printf("Current dir: %ls\n", std::filesystem::current_path().c_str());
|
||||
|
||||
#if defined(__linux__) && defined(RECOMP_FLATPAK)
|
||||
// When using Flatpak, applications tend to launch from the home directory by default.
|
||||
// Mods might use the current working directory to store the data, so we switch it to a directory
|
||||
// with persistent data storage and write permissions under Flatpak to ensure it works.
|
||||
std::error_code ec;
|
||||
std::filesystem::current_path("/var/data", ec);
|
||||
#endif
|
||||
|
||||
// Initialize SDL audio and set the output frequency.
|
||||
SDL_InitSubSystem(SDL_INIT_AUDIO);
|
||||
reset_audio(48000);
|
||||
|
||||
// Source controller mappings file
|
||||
if (SDL_GameControllerAddMappingsFromFile("gamecontrollerdb.txt") < 0) {
|
||||
std::u8string controller_db_path = (banjo::get_program_path() / "recompcontrollerdb.txt").u8string();
|
||||
if (SDL_GameControllerAddMappingsFromFile(reinterpret_cast<const char *>(controller_db_path.c_str())) < 0) {
|
||||
fprintf(stderr, "Failed to load controller mappings: %s\n", SDL_GetError());
|
||||
}
|
||||
|
||||
@@ -584,10 +601,11 @@ int main(int argc, char** argv) {
|
||||
REGISTER_FUNC(recomp_get_inverted_axes);
|
||||
REGISTER_FUNC(recomp_get_analog_inverted_axes);
|
||||
recompui::register_ui_exports();
|
||||
recomputil::register_data_api_exports();
|
||||
|
||||
banjo::register_bk_overlays();
|
||||
banjo::register_bk_patches();
|
||||
recomp::init_extended_actor_data();
|
||||
recomputil::init_extended_actor_data();
|
||||
banjo::load_config();
|
||||
|
||||
recomp::rsp::callbacks_t rsp_callbacks{
|
||||
@@ -636,6 +654,7 @@ int main(int argc, char** argv) {
|
||||
.allow_runtime_toggle = true,
|
||||
.on_enabled = enable_texture_pack,
|
||||
.on_disabled = disable_texture_pack,
|
||||
.on_reordered = reorder_texture_pack,
|
||||
};
|
||||
auto texture_pack_content_type_id = recomp::mods::register_mod_content_type(texture_pack_content_type);
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
#include <memory>
|
||||
#include <cstring>
|
||||
#include <variant>
|
||||
#include <algorithm>
|
||||
|
||||
#define HLSL_CPU
|
||||
#include "hle/rt64_application.h"
|
||||
#include "rt64_render_hooks.h"
|
||||
#include "overloaded.h"
|
||||
|
||||
#include "ultramodern/ultramodern.hpp"
|
||||
#include "ultramodern/config.hpp"
|
||||
@@ -13,12 +15,6 @@
|
||||
#include "recomp_ui.h"
|
||||
#include "concurrentqueue.h"
|
||||
|
||||
// Helper class for variant visiting.
|
||||
template<class... Ts>
|
||||
struct overloaded : Ts... { using Ts::operator()...; };
|
||||
template<class... Ts>
|
||||
overloaded(Ts...) -> overloaded<Ts...>;
|
||||
|
||||
static RT64::UserConfiguration::Antialiasing device_max_msaa = RT64::UserConfiguration::Antialiasing::None;
|
||||
static bool sample_positions_supported = false;
|
||||
static bool high_precision_fb_enabled = false;
|
||||
@@ -27,14 +23,25 @@ static uint8_t DMEM[0x1000];
|
||||
static uint8_t IMEM[0x1000];
|
||||
|
||||
struct TexturePackEnableAction {
|
||||
std::filesystem::path path;
|
||||
std::string mod_id;
|
||||
};
|
||||
|
||||
struct TexturePackDisableAction {
|
||||
std::filesystem::path path;
|
||||
std::string mod_id;
|
||||
};
|
||||
|
||||
using TexturePackAction = std::variant<TexturePackEnableAction, TexturePackDisableAction>;
|
||||
struct TexturePackSecondaryEnableAction {
|
||||
std::string mod_id;
|
||||
};
|
||||
|
||||
struct TexturePackSecondaryDisableAction {
|
||||
std::string mod_id;
|
||||
};
|
||||
|
||||
struct TexturePackUpdateAction {
|
||||
};
|
||||
|
||||
using TexturePackAction = std::variant<TexturePackEnableAction, TexturePackDisableAction, TexturePackSecondaryEnableAction, TexturePackSecondaryDisableAction, TexturePackUpdateAction>;
|
||||
|
||||
static moodycamel::ConcurrentQueue<TexturePackAction> texture_pack_action_queue;
|
||||
|
||||
@@ -171,6 +178,7 @@ void set_application_user_config(RT64::Application* application, const ultramode
|
||||
application->userConfig.refreshRate = to_rt64(config.rr_option);
|
||||
application->userConfig.refreshRateTarget = config.rr_manual_value;
|
||||
application->userConfig.internalColorFormat = to_rt64(config.hpfb_option);
|
||||
application->userConfig.displayBuffering = RT64::UserConfiguration::DisplayBuffering::Triple;
|
||||
}
|
||||
|
||||
ultramodern::renderer::SetupResult map_setup_result(RT64::Application::SetupResult rt64_result) {
|
||||
@@ -192,6 +200,23 @@ ultramodern::renderer::SetupResult map_setup_result(RT64::Application::SetupResu
|
||||
std::exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
ultramodern::renderer::GraphicsApi map_graphics_api(RT64::UserConfiguration::GraphicsAPI api) {
|
||||
switch (api) {
|
||||
case RT64::UserConfiguration::GraphicsAPI::D3D12:
|
||||
return ultramodern::renderer::GraphicsApi::D3D12;
|
||||
case RT64::UserConfiguration::GraphicsAPI::Vulkan:
|
||||
return ultramodern::renderer::GraphicsApi::Vulkan;
|
||||
case RT64::UserConfiguration::GraphicsAPI::Metal:
|
||||
return ultramodern::renderer::GraphicsApi::Metal;
|
||||
case RT64::UserConfiguration::GraphicsAPI::Automatic:
|
||||
return ultramodern::renderer::GraphicsApi::Auto;
|
||||
}
|
||||
|
||||
fprintf(stderr, "Unhandled `RT64::UserConfiguration::GraphicsAPI` ?\n");
|
||||
assert(false);
|
||||
std::exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
banjo::renderer::RT64Context::RT64Context(uint8_t* rdram, ultramodern::renderer::WindowHandle window_handle, bool debug) {
|
||||
static unsigned char dummy_rom_header[0x40];
|
||||
recompui::set_render_hooks();
|
||||
@@ -263,9 +288,11 @@ banjo::renderer::RT64Context::RT64Context(uint8_t* rdram, ultramodern::renderer:
|
||||
case ultramodern::renderer::GraphicsApi::Vulkan:
|
||||
app->userConfig.graphicsAPI = RT64::UserConfiguration::GraphicsAPI::Vulkan;
|
||||
break;
|
||||
default:
|
||||
case ultramodern::renderer::GraphicsApi::Metal:
|
||||
app->userConfig.graphicsAPI = RT64::UserConfiguration::GraphicsAPI::Metal;
|
||||
break;
|
||||
case ultramodern::renderer::GraphicsApi::Auto:
|
||||
// Don't override if auto is selected.
|
||||
app->userConfig.graphicsAPI = RT64::UserConfiguration::GraphicsAPI::Automatic;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -275,6 +302,8 @@ banjo::renderer::RT64Context::RT64Context(uint8_t* rdram, ultramodern::renderer:
|
||||
thread_id = window_handle.thread_id;
|
||||
#endif
|
||||
setup_result = map_setup_result(app->setup(thread_id));
|
||||
// Get the API that RT64 chose.
|
||||
chosen_api = map_graphics_api(app->chosenGraphicsAPI);
|
||||
if (setup_result != ultramodern::renderer::SetupResult::Success) {
|
||||
app = nullptr;
|
||||
return;
|
||||
@@ -303,32 +332,7 @@ banjo::renderer::RT64Context::RT64Context(uint8_t* rdram, ultramodern::renderer:
|
||||
banjo::renderer::RT64Context::~RT64Context() = default;
|
||||
|
||||
void banjo::renderer::RT64Context::send_dl(const OSTask* task) {
|
||||
bool packs_disabled = false;
|
||||
TexturePackAction cur_action;
|
||||
while (texture_pack_action_queue.try_dequeue(cur_action)) {
|
||||
std::visit(overloaded{
|
||||
[&](TexturePackDisableAction& to_disable) {
|
||||
enabled_texture_packs.erase(to_disable.path);
|
||||
packs_disabled = true;
|
||||
},
|
||||
[&](TexturePackEnableAction& to_enable) {
|
||||
enabled_texture_packs.insert(to_enable.path);
|
||||
// Load the pack now if no packs have been disabled.
|
||||
if (!packs_disabled) {
|
||||
app->textureCache->loadReplacementDirectory(to_enable.path);
|
||||
}
|
||||
}
|
||||
}, cur_action);
|
||||
}
|
||||
|
||||
// If any packs were disabled, unload all packs and load all the active ones.
|
||||
if (packs_disabled) {
|
||||
app->textureCache->clearReplacementDirectories();
|
||||
for (const std::filesystem::path& cur_pack_path : enabled_texture_packs) {
|
||||
app->textureCache->loadReplacementDirectory(cur_pack_path);
|
||||
}
|
||||
}
|
||||
|
||||
check_texture_pack_actions();
|
||||
app->state->rsp->reset();
|
||||
app->interpreter->loadUCodeGBI(task->t.ucode & 0x3FFFFFF, task->t.ucode_data & 0x3FFFFFF, true);
|
||||
app->processDisplayLists(app->core.RDRAM, task->t.data_ptr & 0x3FFFFFF, 0, true);
|
||||
@@ -394,6 +398,66 @@ float banjo::renderer::RT64Context::get_resolution_scale() const {
|
||||
}
|
||||
}
|
||||
|
||||
void banjo::renderer::RT64Context::check_texture_pack_actions() {
|
||||
bool packs_changed = false;
|
||||
TexturePackAction cur_action;
|
||||
while (texture_pack_action_queue.try_dequeue(cur_action)) {
|
||||
std::visit(overloaded{
|
||||
[&](TexturePackDisableAction &to_disable) {
|
||||
enabled_texture_packs.erase(to_disable.mod_id);
|
||||
packs_changed = true;
|
||||
},
|
||||
[&](TexturePackEnableAction &to_enable) {
|
||||
enabled_texture_packs.insert(to_enable.mod_id);
|
||||
packs_changed = true;
|
||||
},
|
||||
[&](TexturePackSecondaryDisableAction &to_override_disable) {
|
||||
secondary_disabled_texture_packs.insert(to_override_disable.mod_id);
|
||||
packs_changed = true;
|
||||
},
|
||||
[&](TexturePackSecondaryEnableAction &to_override_enable) {
|
||||
secondary_disabled_texture_packs.erase(to_override_enable.mod_id);
|
||||
packs_changed = true;
|
||||
},
|
||||
[&](TexturePackUpdateAction &) {
|
||||
packs_changed = true;
|
||||
}
|
||||
}, cur_action);
|
||||
}
|
||||
|
||||
// If any packs were disabled, unload all packs and load all the active ones.
|
||||
if (packs_changed) {
|
||||
// Sort the enabled texture packs in reverse order so that earlier ones override later ones.
|
||||
std::vector<std::string> sorted_texture_packs{};
|
||||
sorted_texture_packs.reserve(enabled_texture_packs.size());
|
||||
for (const std::string& mod : enabled_texture_packs) {
|
||||
if (!secondary_disabled_texture_packs.contains(mod)) {
|
||||
sorted_texture_packs.emplace_back(mod);
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(sorted_texture_packs.begin(), sorted_texture_packs.end(),
|
||||
[](const std::string& lhs, const std::string& rhs) {
|
||||
return recomp::mods::get_mod_order_index(lhs) > recomp::mods::get_mod_order_index(rhs);
|
||||
}
|
||||
);
|
||||
|
||||
// Build the path list from the sorted mod list.
|
||||
std::vector<RT64::ReplacementDirectory> replacement_directories;
|
||||
replacement_directories.reserve(enabled_texture_packs.size());
|
||||
for (const std::string &mod_id : sorted_texture_packs) {
|
||||
replacement_directories.emplace_back(RT64::ReplacementDirectory(recomp::mods::get_mod_filename(mod_id)));
|
||||
}
|
||||
|
||||
if (!replacement_directories.empty()) {
|
||||
app->textureCache->loadReplacementDirectories(replacement_directories);
|
||||
}
|
||||
else {
|
||||
app->textureCache->clearReplacementDirectories();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RT64::UserConfiguration::Antialiasing banjo::renderer::RT64MaxMSAA() {
|
||||
return device_max_msaa;
|
||||
}
|
||||
@@ -410,10 +474,72 @@ bool banjo::renderer::RT64HighPrecisionFBEnabled() {
|
||||
return high_precision_fb_enabled;
|
||||
}
|
||||
|
||||
void banjo::renderer::enable_texture_pack(const recomp::mods::ModHandle& mod) {
|
||||
texture_pack_action_queue.enqueue(TexturePackEnableAction{mod.manifest.mod_root_path});
|
||||
void banjo::renderer::trigger_texture_pack_update() {
|
||||
texture_pack_action_queue.enqueue(TexturePackUpdateAction{});
|
||||
}
|
||||
|
||||
void banjo::renderer::enable_texture_pack(const recomp::mods::ModContext& context, const recomp::mods::ModHandle& mod) {
|
||||
texture_pack_action_queue.enqueue(TexturePackEnableAction{mod.manifest.mod_id});
|
||||
|
||||
// Check for the texture pack enabled config option.
|
||||
const recomp::mods::ConfigSchema& config_schema = context.get_mod_config_schema(mod.manifest.mod_id);
|
||||
auto find_it = config_schema.options_by_id.find(banjo::renderer::special_option_texture_pack_enabled);
|
||||
if (find_it != config_schema.options_by_id.end()) {
|
||||
const recomp::mods::ConfigOption& config_option = config_schema.options[find_it->second];
|
||||
|
||||
if (is_texture_pack_enable_config_option(config_option, false)) {
|
||||
recomp::mods::ConfigValueVariant value_variant = context.get_mod_config_value(mod.manifest.mod_id, config_option.id);
|
||||
uint32_t value;
|
||||
if (uint32_t* value_ptr = std::get_if<uint32_t>(&value_variant)) {
|
||||
value = *value_ptr;
|
||||
}
|
||||
else {
|
||||
value = 0;
|
||||
}
|
||||
|
||||
if (value) {
|
||||
banjo::renderer::secondary_enable_texture_pack(mod.manifest.mod_id);
|
||||
}
|
||||
else {
|
||||
banjo::renderer::secondary_disable_texture_pack(mod.manifest.mod_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void banjo::renderer::disable_texture_pack(const recomp::mods::ModHandle& mod) {
|
||||
texture_pack_action_queue.enqueue(TexturePackDisableAction{mod.manifest.mod_root_path});
|
||||
texture_pack_action_queue.enqueue(TexturePackDisableAction{mod.manifest.mod_id});
|
||||
}
|
||||
|
||||
void banjo::renderer::secondary_enable_texture_pack(const std::string& mod_id) {
|
||||
texture_pack_action_queue.enqueue(TexturePackSecondaryEnableAction{mod_id});
|
||||
}
|
||||
|
||||
void banjo::renderer::secondary_disable_texture_pack(const std::string& mod_id) {
|
||||
texture_pack_action_queue.enqueue(TexturePackSecondaryDisableAction{mod_id});
|
||||
}
|
||||
|
||||
|
||||
// HD texture enable option. Must be an enum with two options.
|
||||
// The first option is treated as disabled and the second option is treated as enabled.
|
||||
bool banjo::renderer::is_texture_pack_enable_config_option(const recomp::mods::ConfigOption& option, bool show_errors) {
|
||||
if (option.id == banjo::renderer::special_option_texture_pack_enabled) {
|
||||
if (option.type != recomp::mods::ConfigOptionType::Enum) {
|
||||
if (show_errors) {
|
||||
recompui::message_box(("Mod has the special config option id for enabling an HD texture pack (\"" + banjo::renderer::special_option_texture_pack_enabled + "\"), but the config option is not an enum.").c_str());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const recomp::mods::ConfigOptionEnum &option_enum = std::get<recomp::mods::ConfigOptionEnum>(option.variant);
|
||||
if (option_enum.options.size() != 2) {
|
||||
if (show_errors) {
|
||||
recompui::message_box(("Mod has the special config option id for enabling an HD texture pack (\"" + banjo::renderer::special_option_texture_pack_enabled + "\"), but the config option doesn't have exactly 2 values.").c_str());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
#include "banjo_support.h"
|
||||
#include <SDL.h>
|
||||
#include "nfd.h"
|
||||
#include "RmlUi/Core.h"
|
||||
|
||||
namespace banjo {
|
||||
// MARK: - Internal Helpers
|
||||
void perform_file_dialog_operation(const std::function<void(bool, const std::filesystem::path&)>& callback) {
|
||||
nfdnchar_t* native_path = nullptr;
|
||||
nfdresult_t result = NFD_OpenDialogN(&native_path, nullptr, 0, nullptr);
|
||||
|
||||
bool success = (result == NFD_OKAY);
|
||||
std::filesystem::path path;
|
||||
|
||||
if (success) {
|
||||
path = std::filesystem::path{native_path};
|
||||
NFD_FreePathN(native_path);
|
||||
}
|
||||
|
||||
callback(success, path);
|
||||
}
|
||||
|
||||
void perform_file_dialog_operation_multiple(const std::function<void(bool, const std::list<std::filesystem::path>&)>& callback) {
|
||||
const nfdpathset_t* native_paths = nullptr;
|
||||
nfdresult_t result = NFD_OpenDialogMultipleN(&native_paths, nullptr, 0, nullptr);
|
||||
|
||||
bool success = (result == NFD_OKAY);
|
||||
std::list<std::filesystem::path> paths;
|
||||
nfdpathsetsize_t count = 0;
|
||||
|
||||
if (success) {
|
||||
NFD_PathSet_GetCount(native_paths, &count);
|
||||
for (nfdpathsetsize_t i = 0; i < count; i++) {
|
||||
nfdnchar_t* cur_path = nullptr;
|
||||
nfdresult_t cur_result = NFD_PathSet_GetPathN(native_paths, i, &cur_path);
|
||||
if (cur_result == NFD_OKAY) {
|
||||
paths.emplace_back(std::filesystem::path{cur_path});
|
||||
}
|
||||
}
|
||||
NFD_PathSet_Free(native_paths);
|
||||
}
|
||||
|
||||
callback(success, paths);
|
||||
}
|
||||
|
||||
// MARK: - Public API
|
||||
|
||||
std::filesystem::path get_program_path() {
|
||||
#if defined(__APPLE__)
|
||||
return get_bundle_resource_directory();
|
||||
#elif defined(__linux__) && defined(RECOMP_FLATPAK)
|
||||
return "/app/bin";
|
||||
#else
|
||||
return "";
|
||||
#endif
|
||||
}
|
||||
|
||||
std::filesystem::path get_asset_path(const char* asset) {
|
||||
return get_program_path() / "assets" / asset;
|
||||
}
|
||||
|
||||
void open_file_dialog(std::function<void(bool success, const std::filesystem::path& path)> callback) {
|
||||
#ifdef __APPLE__
|
||||
dispatch_on_ui_thread([callback]() {
|
||||
perform_file_dialog_operation(callback);
|
||||
});
|
||||
#else
|
||||
perform_file_dialog_operation(callback);
|
||||
#endif
|
||||
}
|
||||
|
||||
void open_file_dialog_multiple(std::function<void(bool success, const std::list<std::filesystem::path>& paths)> callback) {
|
||||
#ifdef __APPLE__
|
||||
dispatch_on_ui_thread([callback]() {
|
||||
perform_file_dialog_operation_multiple(callback);
|
||||
});
|
||||
#else
|
||||
perform_file_dialog_operation_multiple(callback);
|
||||
#endif
|
||||
}
|
||||
|
||||
void show_error_message_box(const char *title, const char *message) {
|
||||
#ifdef __APPLE__
|
||||
std::string title_copy(title);
|
||||
std::string message_copy(message);
|
||||
|
||||
dispatch_on_ui_thread([title_copy, message_copy] {
|
||||
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, title_copy.c_str(), message_copy.c_str(), nullptr);
|
||||
});
|
||||
#else
|
||||
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, title, message, nullptr);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user