imgui-frontend-engineer: add iOS platform scaffolding
This commit is contained in:
@@ -3,6 +3,10 @@
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <TargetConditionals.h>
|
||||
#endif
|
||||
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "util/file_util.h"
|
||||
|
||||
@@ -16,6 +20,12 @@ std::vector<std::filesystem::path> AssetLoader::GetSearchPaths(
|
||||
// macOS bundle resource paths
|
||||
std::string bundle_root = yaze::util::GetBundleResourcePath();
|
||||
|
||||
#if TARGET_OS_IOS == 1
|
||||
// iOS app bundle resources live at the root.
|
||||
search_paths.push_back(std::filesystem::path(bundle_root) / "assets" /
|
||||
relative_path);
|
||||
search_paths.push_back(std::filesystem::path(bundle_root) / relative_path);
|
||||
#else
|
||||
// Try Contents/Resources first (standard bundle location)
|
||||
search_paths.push_back(std::filesystem::path(bundle_root) / "Contents" /
|
||||
"Resources" / relative_path);
|
||||
@@ -29,6 +39,7 @@ std::vector<std::filesystem::path> AssetLoader::GetSearchPaths(
|
||||
".." / "assets" / relative_path);
|
||||
search_paths.push_back(std::filesystem::path(bundle_root) / ".." / ".." /
|
||||
".." / ".." / "assets" / relative_path);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Standard relative paths (works for all platforms)
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
#include "util/file_util.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "core/features.h"
|
||||
#include "util/platform_paths.h"
|
||||
|
||||
#if defined(YAZE_ENABLE_NFD) && YAZE_ENABLE_NFD
|
||||
#include <nfd.h>
|
||||
@@ -19,45 +22,276 @@
|
||||
|
||||
#if TARGET_IPHONE_SIMULATOR == 1 || TARGET_OS_IPHONE == 1
|
||||
/* iOS in Xcode simulator */
|
||||
#import <dispatch/dispatch.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
|
||||
|
||||
#include "app/platform/app_delegate.h"
|
||||
|
||||
namespace {
|
||||
static std::string selectedFile;
|
||||
@interface AppDelegate : UIResponder <UIApplicationDelegate, UIDocumentPickerDelegate>
|
||||
@end
|
||||
|
||||
void ShowOpenFileDialogImpl(void (^completionHandler)(std::string)) {
|
||||
AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
|
||||
[appDelegate PresentDocumentPickerWithCompletionHandler:^(NSString *filePath) {
|
||||
selectedFile = std::string([filePath UTF8String]);
|
||||
completionHandler(selectedFile);
|
||||
}];
|
||||
@interface AppDelegate (FileDialog)
|
||||
- (void)PresentDocumentPickerWithCompletionHandler:
|
||||
(void (^)(NSString *selectedFile))completionHandler
|
||||
allowedTypes:(NSArray<UTType*> *)allowedTypes;
|
||||
@end
|
||||
|
||||
namespace {
|
||||
std::string TrimCopy(const std::string& input) {
|
||||
const auto start = input.find_first_not_of(" \t\n\r");
|
||||
if (start == std::string::npos) {
|
||||
return "";
|
||||
}
|
||||
const auto end = input.find_last_not_of(" \t\n\r");
|
||||
return input.substr(start, end - start + 1);
|
||||
}
|
||||
|
||||
std::string ShowOpenFileDialogSync() {
|
||||
__block std::string result;
|
||||
std::vector<std::string> SplitFilterSpec(const std::string& spec) {
|
||||
std::vector<std::string> tokens;
|
||||
std::string current;
|
||||
for (char ch : spec) {
|
||||
if (ch == ',') {
|
||||
std::string trimmed = TrimCopy(current);
|
||||
if (!trimmed.empty() && trimmed[0] == '.') {
|
||||
trimmed.erase(0, 1);
|
||||
}
|
||||
if (!trimmed.empty()) {
|
||||
tokens.push_back(trimmed);
|
||||
}
|
||||
current.clear();
|
||||
} else {
|
||||
current.push_back(ch);
|
||||
}
|
||||
}
|
||||
std::string trimmed = TrimCopy(current);
|
||||
if (!trimmed.empty() && trimmed[0] == '.') {
|
||||
trimmed.erase(0, 1);
|
||||
}
|
||||
if (!trimmed.empty()) {
|
||||
tokens.push_back(trimmed);
|
||||
}
|
||||
return tokens;
|
||||
}
|
||||
|
||||
ShowOpenFileDialogImpl(^(std::string filePath) {
|
||||
result = filePath;
|
||||
});
|
||||
NSArray<UTType*>* BuildAllowedTypes(const yaze::util::FileDialogOptions& options) {
|
||||
if (options.filters.empty()) {
|
||||
return @[ UTTypeData ];
|
||||
}
|
||||
|
||||
bool allow_all = false;
|
||||
NSMutableArray<UTType*>* types = [NSMutableArray array];
|
||||
|
||||
for (const auto& filter : options.filters) {
|
||||
const std::string spec = TrimCopy(filter.spec);
|
||||
if (spec.empty() || spec == "*") {
|
||||
allow_all = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const auto& token : SplitFilterSpec(spec)) {
|
||||
if (token == "*") {
|
||||
allow_all = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
NSString* ext = [NSString stringWithUTF8String:token.c_str()];
|
||||
UTType* type = [UTType typeWithFilenameExtension:ext];
|
||||
if (!type) {
|
||||
NSString* identifier = [NSString stringWithUTF8String:token.c_str()];
|
||||
type = [UTType typeWithIdentifier:identifier];
|
||||
}
|
||||
if (type) {
|
||||
[types addObject:type];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (allow_all || [types count] == 0) {
|
||||
return @[ UTTypeData ];
|
||||
}
|
||||
|
||||
return types;
|
||||
}
|
||||
|
||||
std::filesystem::path ResolveDocumentsPath() {
|
||||
auto docs_result = yaze::util::PlatformPaths::GetUserDocumentsDirectory();
|
||||
if (docs_result.ok()) {
|
||||
return *docs_result;
|
||||
}
|
||||
auto temp_result = yaze::util::PlatformPaths::GetTempDirectory();
|
||||
if (temp_result.ok()) {
|
||||
return *temp_result;
|
||||
}
|
||||
std::error_code ec;
|
||||
auto cwd = std::filesystem::current_path(ec);
|
||||
if (!ec) {
|
||||
return cwd;
|
||||
}
|
||||
return std::filesystem::path(".");
|
||||
}
|
||||
|
||||
std::string NormalizeExtension(const std::string& ext) {
|
||||
if (ext.empty()) {
|
||||
return "";
|
||||
}
|
||||
if (ext.front() == '.') {
|
||||
return ext;
|
||||
}
|
||||
return "." + ext;
|
||||
}
|
||||
|
||||
std::string BuildSaveFilename(const std::string& default_name,
|
||||
const std::string& default_extension) {
|
||||
std::string name = default_name.empty() ? "yaze_output" : default_name;
|
||||
const std::string normalized_ext = NormalizeExtension(default_extension);
|
||||
if (!normalized_ext.empty()) {
|
||||
auto dot_pos = name.find_last_of('.');
|
||||
if (dot_pos == std::string::npos || dot_pos == 0) {
|
||||
name += normalized_ext;
|
||||
}
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
void ShowOpenFileDialogImpl(NSArray<UTType*>* allowed_types,
|
||||
void (^completionHandler)(std::string)) {
|
||||
AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
|
||||
if (!appDelegate) {
|
||||
completionHandler("");
|
||||
return;
|
||||
}
|
||||
[appDelegate PresentDocumentPickerWithCompletionHandler:^(NSString *filePath) {
|
||||
completionHandler(std::string([filePath UTF8String]));
|
||||
}
|
||||
allowedTypes:allowed_types];
|
||||
}
|
||||
|
||||
std::string ShowOpenFileDialogSync(
|
||||
const yaze::util::FileDialogOptions& options) {
|
||||
__block std::string result;
|
||||
__block bool done = false;
|
||||
NSArray<UTType*>* allowed_types = BuildAllowedTypes(options);
|
||||
|
||||
auto present_picker = ^{
|
||||
ShowOpenFileDialogImpl(allowed_types, ^(std::string filePath) {
|
||||
result = filePath;
|
||||
done = true;
|
||||
});
|
||||
};
|
||||
|
||||
if ([NSThread isMainThread]) {
|
||||
present_picker();
|
||||
// Run a nested loop to keep UI responsive while waiting on selection.
|
||||
while (!done) {
|
||||
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
|
||||
beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.01]];
|
||||
}
|
||||
} else {
|
||||
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
ShowOpenFileDialogImpl(allowed_types, ^(std::string filePath) {
|
||||
result = filePath;
|
||||
dispatch_semaphore_signal(semaphore);
|
||||
});
|
||||
});
|
||||
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
std::string yaze::util::FileDialogWrapper::ShowOpenFileDialog() { return ShowOpenFileDialogSync(); }
|
||||
std::string yaze::util::FileDialogWrapper::ShowOpenFileDialog(
|
||||
const FileDialogOptions& options) {
|
||||
return ShowOpenFileDialogSync(options);
|
||||
}
|
||||
|
||||
std::string yaze::util::FileDialogWrapper::ShowOpenFolderDialog() { return ""; }
|
||||
std::string yaze::util::FileDialogWrapper::ShowOpenFileDialog() {
|
||||
return ShowOpenFileDialog(FileDialogOptions{});
|
||||
}
|
||||
|
||||
std::string yaze::util::FileDialogWrapper::ShowOpenFileDialogNFD() {
|
||||
return ShowOpenFileDialog(FileDialogOptions{});
|
||||
}
|
||||
|
||||
std::string yaze::util::FileDialogWrapper::ShowOpenFileDialogBespoke() {
|
||||
return ShowOpenFileDialog(FileDialogOptions{});
|
||||
}
|
||||
|
||||
void yaze::util::FileDialogWrapper::ShowOpenFileDialogAsync(
|
||||
const FileDialogOptions& options,
|
||||
std::function<void(const std::string&)> callback) {
|
||||
if (!callback) {
|
||||
return;
|
||||
}
|
||||
NSArray<UTType*>* allowed_types = BuildAllowedTypes(options);
|
||||
auto callback_ptr =
|
||||
std::make_shared<std::function<void(const std::string&)>>(
|
||||
std::move(callback));
|
||||
|
||||
auto present_picker = ^{
|
||||
ShowOpenFileDialogImpl(allowed_types, ^(std::string filePath) {
|
||||
(*callback_ptr)(filePath);
|
||||
});
|
||||
};
|
||||
|
||||
if ([NSThread isMainThread]) {
|
||||
present_picker();
|
||||
} else {
|
||||
dispatch_async(dispatch_get_main_queue(), present_picker);
|
||||
}
|
||||
}
|
||||
|
||||
std::string yaze::util::FileDialogWrapper::ShowOpenFolderDialog() {
|
||||
return ResolveDocumentsPath().string();
|
||||
}
|
||||
|
||||
std::string yaze::util::FileDialogWrapper::ShowSaveFileDialog(
|
||||
const std::string& default_name, const std::string& default_extension) {
|
||||
const auto base_dir = ResolveDocumentsPath();
|
||||
const std::string filename = BuildSaveFilename(default_name, default_extension);
|
||||
return (base_dir / filename).string();
|
||||
}
|
||||
|
||||
std::string yaze::util::FileDialogWrapper::ShowSaveFileDialogNFD(
|
||||
const std::string& default_name, const std::string& default_extension) {
|
||||
return ShowSaveFileDialog(default_name, default_extension);
|
||||
}
|
||||
|
||||
std::string yaze::util::FileDialogWrapper::ShowSaveFileDialogBespoke(
|
||||
const std::string& default_name, const std::string& default_extension) {
|
||||
return ShowSaveFileDialog(default_name, default_extension);
|
||||
}
|
||||
|
||||
std::vector<std::string> yaze::util::FileDialogWrapper::GetFilesInFolder(
|
||||
const std::string &folder) {
|
||||
return {};
|
||||
std::vector<std::string> files;
|
||||
std::error_code ec;
|
||||
for (const auto& entry : std::filesystem::directory_iterator(folder, ec)) {
|
||||
if (ec) {
|
||||
break;
|
||||
}
|
||||
if (entry.is_regular_file()) {
|
||||
files.push_back(entry.path().string());
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
std::vector<std::string> yaze::util::FileDialogWrapper::GetSubdirectoriesInFolder(
|
||||
const std::string &folder) {
|
||||
return {};
|
||||
std::vector<std::string> directories;
|
||||
std::error_code ec;
|
||||
for (const auto& entry : std::filesystem::directory_iterator(folder, ec)) {
|
||||
if (ec) {
|
||||
break;
|
||||
}
|
||||
if (entry.is_directory()) {
|
||||
directories.push_back(entry.path().string());
|
||||
}
|
||||
}
|
||||
return directories;
|
||||
}
|
||||
|
||||
std::string yaze::util::GetBundleResourcePath() {
|
||||
@@ -73,11 +307,91 @@ std::string yaze::util::GetBundleResourcePath() {
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
|
||||
|
||||
std::string yaze::util::FileDialogWrapper::ShowOpenFileDialogBespoke() {
|
||||
namespace {
|
||||
std::string TrimCopy(const std::string& input) {
|
||||
const auto start = input.find_first_not_of(" \t\n\r");
|
||||
if (start == std::string::npos) {
|
||||
return "";
|
||||
}
|
||||
const auto end = input.find_last_not_of(" \t\n\r");
|
||||
return input.substr(start, end - start + 1);
|
||||
}
|
||||
|
||||
std::vector<std::string> SplitFilterSpec(const std::string& spec) {
|
||||
std::vector<std::string> tokens;
|
||||
std::string current;
|
||||
for (char ch : spec) {
|
||||
if (ch == ',') {
|
||||
std::string trimmed = TrimCopy(current);
|
||||
if (!trimmed.empty() && trimmed[0] == '.') {
|
||||
trimmed.erase(0, 1);
|
||||
}
|
||||
if (!trimmed.empty()) {
|
||||
tokens.push_back(trimmed);
|
||||
}
|
||||
current.clear();
|
||||
} else {
|
||||
current.push_back(ch);
|
||||
}
|
||||
}
|
||||
std::string trimmed = TrimCopy(current);
|
||||
if (!trimmed.empty() && trimmed[0] == '.') {
|
||||
trimmed.erase(0, 1);
|
||||
}
|
||||
if (!trimmed.empty()) {
|
||||
tokens.push_back(trimmed);
|
||||
}
|
||||
return tokens;
|
||||
}
|
||||
|
||||
std::vector<std::string> CollectExtensions(
|
||||
const yaze::util::FileDialogOptions& options, bool* allow_all) {
|
||||
std::vector<std::string> extensions;
|
||||
if (!allow_all) {
|
||||
return extensions;
|
||||
}
|
||||
*allow_all = false;
|
||||
|
||||
for (const auto& filter : options.filters) {
|
||||
const std::string spec = TrimCopy(filter.spec);
|
||||
if (spec.empty() || spec == "*") {
|
||||
*allow_all = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const auto& token : SplitFilterSpec(spec)) {
|
||||
if (token == "*") {
|
||||
*allow_all = true;
|
||||
} else {
|
||||
extensions.push_back(token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return extensions;
|
||||
}
|
||||
|
||||
std::string ShowOpenFileDialogBespokeWithOptions(
|
||||
const yaze::util::FileDialogOptions& options) {
|
||||
NSOpenPanel* openPanel = [NSOpenPanel openPanel];
|
||||
[openPanel setCanChooseFiles:YES];
|
||||
[openPanel setCanChooseDirectories:NO];
|
||||
[openPanel setAllowsMultipleSelection:NO];
|
||||
|
||||
bool allow_all = false;
|
||||
std::vector<std::string> extensions = CollectExtensions(options, &allow_all);
|
||||
if (allow_all || extensions.empty()) {
|
||||
[openPanel setAllowedFileTypes:nil];
|
||||
} else {
|
||||
NSMutableArray<NSString*>* allowed_types = [NSMutableArray array];
|
||||
for (const auto& extension : extensions) {
|
||||
NSString* ext = [NSString stringWithUTF8String:extension.c_str()];
|
||||
if (ext) {
|
||||
[allowed_types addObject:ext];
|
||||
}
|
||||
}
|
||||
[openPanel setAllowedFileTypes:allowed_types];
|
||||
}
|
||||
|
||||
if ([openPanel runModal] == NSModalResponseOK) {
|
||||
NSURL* url = [[openPanel URLs] objectAtIndex:0];
|
||||
@@ -88,6 +402,70 @@ std::string yaze::util::FileDialogWrapper::ShowOpenFileDialogBespoke() {
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string ShowOpenFileDialogNFDWithOptions(
|
||||
const yaze::util::FileDialogOptions& options) {
|
||||
#if defined(YAZE_ENABLE_NFD) && YAZE_ENABLE_NFD
|
||||
NFD_Init();
|
||||
nfdu8char_t* out_path = NULL;
|
||||
const nfdu8filteritem_t* filter_list = nullptr;
|
||||
size_t filter_count = 0;
|
||||
std::vector<nfdu8filteritem_t> filter_items;
|
||||
std::vector<std::string> filter_names;
|
||||
std::vector<std::string> filter_specs;
|
||||
|
||||
if (!options.filters.empty()) {
|
||||
filter_items.reserve(options.filters.size());
|
||||
filter_names.reserve(options.filters.size());
|
||||
filter_specs.reserve(options.filters.size());
|
||||
|
||||
for (const auto& filter : options.filters) {
|
||||
std::string label = filter.label.empty() ? "Files" : filter.label;
|
||||
std::string spec = filter.spec.empty() ? "*" : filter.spec;
|
||||
filter_names.push_back(label);
|
||||
filter_specs.push_back(spec);
|
||||
filter_items.push_back(
|
||||
{filter_names.back().c_str(), filter_specs.back().c_str()});
|
||||
}
|
||||
|
||||
filter_list = filter_items.data();
|
||||
filter_count = filter_items.size();
|
||||
}
|
||||
|
||||
nfdopendialogu8args_t args = {0};
|
||||
args.filterList = filter_list;
|
||||
args.filterCount = filter_count;
|
||||
|
||||
nfdresult_t result = NFD_OpenDialogU8_With(&out_path, &args);
|
||||
if (result == NFD_OKAY) {
|
||||
std::string file_path(out_path);
|
||||
NFD_FreePath(out_path);
|
||||
NFD_Quit();
|
||||
return file_path;
|
||||
} else if (result == NFD_CANCEL) {
|
||||
NFD_Quit();
|
||||
return "";
|
||||
}
|
||||
NFD_Quit();
|
||||
return "";
|
||||
#else
|
||||
return ShowOpenFileDialogBespokeWithOptions(options);
|
||||
#endif
|
||||
}
|
||||
} // namespace
|
||||
|
||||
std::string yaze::util::FileDialogWrapper::ShowOpenFileDialogBespoke() {
|
||||
return ShowOpenFileDialogBespokeWithOptions(FileDialogOptions{});
|
||||
}
|
||||
|
||||
void yaze::util::FileDialogWrapper::ShowOpenFileDialogAsync(
|
||||
const FileDialogOptions& options,
|
||||
std::function<void(const std::string&)> callback) {
|
||||
if (!callback) {
|
||||
return;
|
||||
}
|
||||
callback(ShowOpenFileDialog(options));
|
||||
}
|
||||
|
||||
std::string yaze::util::FileDialogWrapper::ShowSaveFileDialogBespoke(const std::string& default_name,
|
||||
const std::string& default_extension) {
|
||||
NSSavePanel* savePanel = [NSSavePanel savePanel];
|
||||
@@ -111,12 +489,16 @@ std::string yaze::util::FileDialogWrapper::ShowSaveFileDialogBespoke(const std::
|
||||
}
|
||||
|
||||
// Global feature flag-based dispatch methods
|
||||
std::string yaze::util::FileDialogWrapper::ShowOpenFileDialog() {
|
||||
std::string yaze::util::FileDialogWrapper::ShowOpenFileDialog(
|
||||
const FileDialogOptions& options) {
|
||||
if (core::FeatureFlags::get().kUseNativeFileDialog) {
|
||||
return ShowOpenFileDialogNFD();
|
||||
} else {
|
||||
return ShowOpenFileDialogBespoke();
|
||||
return ShowOpenFileDialogNFDWithOptions(options);
|
||||
}
|
||||
return ShowOpenFileDialogBespokeWithOptions(options);
|
||||
}
|
||||
|
||||
std::string yaze::util::FileDialogWrapper::ShowOpenFileDialog() {
|
||||
return ShowOpenFileDialog(FileDialogOptions{});
|
||||
}
|
||||
|
||||
std::string yaze::util::FileDialogWrapper::ShowOpenFolderDialog() {
|
||||
@@ -138,30 +520,7 @@ std::string yaze::util::FileDialogWrapper::ShowSaveFileDialog(const std::string&
|
||||
|
||||
// NFD implementation for macOS (fallback to bespoke if NFD not available)
|
||||
std::string yaze::util::FileDialogWrapper::ShowOpenFileDialogNFD() {
|
||||
#if defined(YAZE_ENABLE_NFD) && YAZE_ENABLE_NFD
|
||||
NFD_Init();
|
||||
nfdu8char_t *out_path = NULL;
|
||||
nfdu8filteritem_t filters[1] = {{"Rom File", "sfc,smc"}};
|
||||
nfdopendialogu8args_t args = {0};
|
||||
args.filterList = filters;
|
||||
args.filterCount = 1;
|
||||
|
||||
nfdresult_t result = NFD_OpenDialogU8_With(&out_path, &args);
|
||||
if (result == NFD_OKAY) {
|
||||
std::string file_path(out_path);
|
||||
NFD_FreePath(out_path);
|
||||
NFD_Quit();
|
||||
return file_path;
|
||||
} else if (result == NFD_CANCEL) {
|
||||
NFD_Quit();
|
||||
return "";
|
||||
}
|
||||
NFD_Quit();
|
||||
return "";
|
||||
#else
|
||||
// NFD not compiled in, use bespoke
|
||||
return ShowOpenFileDialogBespoke();
|
||||
#endif
|
||||
return ShowOpenFileDialogNFDWithOptions(FileDialogOptions{});
|
||||
}
|
||||
|
||||
std::string yaze::util::FileDialogWrapper::ShowOpenFolderDialogNFD() {
|
||||
@@ -300,4 +659,4 @@ std::string yaze::util::GetBundleResourcePath() {
|
||||
// Unsupported platform
|
||||
#endif // TARGET_OS_MAC
|
||||
|
||||
#endif // __APPLE__ && __MACH__
|
||||
#endif // __APPLE__ && __MACH__
|
||||
|
||||
@@ -11,11 +11,35 @@
|
||||
namespace yaze {
|
||||
namespace util {
|
||||
|
||||
std::string FileDialogWrapper::ShowOpenFileDialog() {
|
||||
std::string FileDialogWrapper::ShowOpenFileDialog(
|
||||
const FileDialogOptions& options) {
|
||||
nfdchar_t* outPath = nullptr;
|
||||
nfdfilteritem_t filterItem[2] = {{"ROM Files", "sfc,smc"},
|
||||
{"All Files", "*"}};
|
||||
nfdresult_t result = NFD_OpenDialog(&outPath, filterItem, 2, nullptr);
|
||||
const nfdfilteritem_t* filter_list = nullptr;
|
||||
size_t filter_count = 0;
|
||||
std::vector<nfdfilteritem_t> filter_items;
|
||||
std::vector<std::string> filter_names;
|
||||
std::vector<std::string> filter_specs;
|
||||
|
||||
if (!options.filters.empty()) {
|
||||
filter_items.reserve(options.filters.size());
|
||||
filter_names.reserve(options.filters.size());
|
||||
filter_specs.reserve(options.filters.size());
|
||||
|
||||
for (const auto& filter : options.filters) {
|
||||
std::string label = filter.label.empty() ? "Files" : filter.label;
|
||||
std::string spec = filter.spec.empty() ? "*" : filter.spec;
|
||||
filter_names.push_back(label);
|
||||
filter_specs.push_back(spec);
|
||||
filter_items.push_back(
|
||||
{filter_names.back().c_str(), filter_specs.back().c_str()});
|
||||
}
|
||||
|
||||
filter_list = filter_items.data();
|
||||
filter_count = filter_items.size();
|
||||
}
|
||||
|
||||
nfdresult_t result =
|
||||
NFD_OpenDialog(&outPath, filter_list, filter_count, nullptr);
|
||||
|
||||
if (result == NFD_OKAY) {
|
||||
std::string path(outPath);
|
||||
@@ -26,6 +50,19 @@ std::string FileDialogWrapper::ShowOpenFileDialog() {
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string FileDialogWrapper::ShowOpenFileDialog() {
|
||||
return ShowOpenFileDialog(FileDialogOptions{});
|
||||
}
|
||||
|
||||
void FileDialogWrapper::ShowOpenFileDialogAsync(
|
||||
const FileDialogOptions& options,
|
||||
std::function<void(const std::string&)> callback) {
|
||||
if (!callback) {
|
||||
return;
|
||||
}
|
||||
callback(ShowOpenFileDialog(options));
|
||||
}
|
||||
|
||||
std::string FileDialogWrapper::ShowOpenFolderDialog() {
|
||||
nfdchar_t* outPath = nullptr;
|
||||
nfdresult_t result = NFD_PickFolder(&outPath, nullptr);
|
||||
|
||||
@@ -13,6 +13,12 @@ namespace util {
|
||||
// Web implementation of FileDialogWrapper
|
||||
// Triggers the existing file input element in the HTML
|
||||
|
||||
std::string FileDialogWrapper::ShowOpenFileDialog(
|
||||
const FileDialogOptions& options) {
|
||||
(void)options;
|
||||
return ShowOpenFileDialog();
|
||||
}
|
||||
|
||||
std::string FileDialogWrapper::ShowOpenFileDialog() {
|
||||
#ifdef __EMSCRIPTEN__
|
||||
// Trigger the existing file input element
|
||||
@@ -33,6 +39,15 @@ std::string FileDialogWrapper::ShowOpenFileDialog() {
|
||||
#endif
|
||||
}
|
||||
|
||||
void FileDialogWrapper::ShowOpenFileDialogAsync(
|
||||
const FileDialogOptions& options,
|
||||
std::function<void(const std::string&)> callback) {
|
||||
if (!callback) {
|
||||
return;
|
||||
}
|
||||
callback(ShowOpenFileDialog(options));
|
||||
}
|
||||
|
||||
std::string FileDialogWrapper::ShowOpenFolderDialog() {
|
||||
// Folder picking not supported on web in the same way
|
||||
return "";
|
||||
@@ -57,4 +72,3 @@ std::vector<std::string> FileDialogWrapper::GetFilesInFolder(
|
||||
|
||||
} // namespace util
|
||||
} // namespace yaze
|
||||
|
||||
|
||||
@@ -31,8 +31,17 @@ namespace {
|
||||
std::string SetFontPath(const std::string& font_path) {
|
||||
#ifdef __APPLE__
|
||||
#if TARGET_OS_IOS == 1
|
||||
const std::string kBundlePath = util::GetBundleResourcePath();
|
||||
return kBundlePath + font_path;
|
||||
const std::string bundle_root = util::GetBundleResourcePath();
|
||||
std::string bundle_path =
|
||||
absl::StrCat(bundle_root, "assets/font/", font_path);
|
||||
if (std::filesystem::exists(bundle_path)) {
|
||||
return bundle_path;
|
||||
}
|
||||
bundle_path = absl::StrCat(bundle_root, font_path);
|
||||
if (std::filesystem::exists(bundle_path)) {
|
||||
return bundle_path;
|
||||
}
|
||||
return absl::StrCat("assets/font/", font_path);
|
||||
#else
|
||||
std::string bundle_path = absl::StrCat(
|
||||
util::GetBundleResourcePath(), "Contents/Resources/font/", font_path);
|
||||
|
||||
35
src/app/platform/ios/ios_host.h
Normal file
35
src/app/platform/ios/ios_host.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "app/application.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace ios {
|
||||
|
||||
struct IOSHostConfig {
|
||||
AppConfig app_config;
|
||||
bool auto_start = true;
|
||||
};
|
||||
|
||||
class IOSHost {
|
||||
public:
|
||||
IOSHost() = default;
|
||||
~IOSHost();
|
||||
|
||||
absl::Status Initialize(const IOSHostConfig& config);
|
||||
void Tick();
|
||||
void Shutdown();
|
||||
|
||||
void SetMetalView(void* view);
|
||||
void* GetMetalView() const;
|
||||
|
||||
private:
|
||||
IOSHostConfig config_{};
|
||||
void* metal_view_ = nullptr;
|
||||
bool initialized_ = false;
|
||||
};
|
||||
|
||||
} // namespace ios
|
||||
} // namespace yaze
|
||||
61
src/app/platform/ios/ios_host.mm
Normal file
61
src/app/platform/ios/ios_host.mm
Normal file
@@ -0,0 +1,61 @@
|
||||
#include "app/platform/ios/ios_host.h"
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#include <TargetConditionals.h>
|
||||
#endif
|
||||
|
||||
#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
|
||||
#import <MetalKit/MetalKit.h>
|
||||
#endif
|
||||
|
||||
#include "app/platform/ios/ios_platform_state.h"
|
||||
#include "util/log.h"
|
||||
|
||||
namespace yaze::ios {
|
||||
|
||||
IOSHost::~IOSHost() {
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
absl::Status IOSHost::Initialize(const IOSHostConfig& config) {
|
||||
config_ = config;
|
||||
|
||||
#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
|
||||
if (!metal_view_) {
|
||||
return absl::FailedPreconditionError("Metal view not attached");
|
||||
}
|
||||
|
||||
Application::Instance().Initialize(config_.app_config);
|
||||
initialized_ = true;
|
||||
LOG_INFO("IOSHost", "Initialized iOS host (stub)");
|
||||
return absl::OkStatus();
|
||||
#else
|
||||
return absl::FailedPreconditionError("IOSHost only available on iOS");
|
||||
#endif
|
||||
}
|
||||
|
||||
void IOSHost::Tick() {
|
||||
if (!initialized_) {
|
||||
return;
|
||||
}
|
||||
Application::Instance().Tick();
|
||||
}
|
||||
|
||||
void IOSHost::Shutdown() {
|
||||
if (!initialized_) {
|
||||
return;
|
||||
}
|
||||
Application::Instance().Shutdown();
|
||||
initialized_ = false;
|
||||
}
|
||||
|
||||
void IOSHost::SetMetalView(void* view) {
|
||||
metal_view_ = view;
|
||||
platform::ios::SetMetalView(view);
|
||||
}
|
||||
|
||||
void* IOSHost::GetMetalView() const {
|
||||
return metal_view_;
|
||||
}
|
||||
|
||||
} // namespace yaze::ios
|
||||
22
src/app/platform/ios/ios_platform_state.h
Normal file
22
src/app/platform/ios/ios_platform_state.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
namespace yaze {
|
||||
namespace platform {
|
||||
namespace ios {
|
||||
|
||||
struct SafeAreaInsets {
|
||||
float left = 0.0f;
|
||||
float right = 0.0f;
|
||||
float top = 0.0f;
|
||||
float bottom = 0.0f;
|
||||
};
|
||||
|
||||
void SetMetalView(void* view);
|
||||
void* GetMetalView();
|
||||
|
||||
void SetSafeAreaInsets(float left, float right, float top, float bottom);
|
||||
SafeAreaInsets GetSafeAreaInsets();
|
||||
|
||||
} // namespace ios
|
||||
} // namespace platform
|
||||
} // namespace yaze
|
||||
30
src/app/platform/ios/ios_platform_state.mm
Normal file
30
src/app/platform/ios/ios_platform_state.mm
Normal file
@@ -0,0 +1,30 @@
|
||||
#include "app/platform/ios/ios_platform_state.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace platform {
|
||||
namespace ios {
|
||||
|
||||
namespace {
|
||||
void* g_metal_view = nullptr;
|
||||
SafeAreaInsets g_safe_area_insets = {};
|
||||
} // namespace
|
||||
|
||||
void SetMetalView(void* view) {
|
||||
g_metal_view = view;
|
||||
}
|
||||
|
||||
void* GetMetalView() {
|
||||
return g_metal_view;
|
||||
}
|
||||
|
||||
void SetSafeAreaInsets(float left, float right, float top, float bottom) {
|
||||
g_safe_area_insets = {left, right, top, bottom};
|
||||
}
|
||||
|
||||
SafeAreaInsets GetSafeAreaInsets() {
|
||||
return g_safe_area_insets;
|
||||
}
|
||||
|
||||
} // namespace ios
|
||||
} // namespace platform
|
||||
} // namespace yaze
|
||||
56
src/app/platform/ios/ios_window_backend.h
Normal file
56
src/app/platform/ios/ios_window_backend.h
Normal file
@@ -0,0 +1,56 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "app/platform/iwindow.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace platform {
|
||||
|
||||
class IOSWindowBackend final : public IWindowBackend {
|
||||
public:
|
||||
IOSWindowBackend() = default;
|
||||
~IOSWindowBackend() override = default;
|
||||
|
||||
absl::Status Initialize(const WindowConfig& config) override;
|
||||
absl::Status Shutdown() override;
|
||||
bool IsInitialized() const override;
|
||||
|
||||
bool PollEvent(WindowEvent& out_event) override;
|
||||
void ProcessNativeEvent(void* native_event) override;
|
||||
|
||||
WindowStatus GetStatus() const override;
|
||||
bool IsActive() const override;
|
||||
void SetActive(bool active) override;
|
||||
void GetSize(int* width, int* height) const override;
|
||||
void SetSize(int width, int height) override;
|
||||
std::string GetTitle() const override;
|
||||
void SetTitle(const std::string& title) override;
|
||||
|
||||
bool InitializeRenderer(gfx::IRenderer* renderer) override;
|
||||
SDL_Window* GetNativeWindow() override;
|
||||
|
||||
absl::Status InitializeImGui(gfx::IRenderer* renderer) override;
|
||||
void ShutdownImGui() override;
|
||||
void NewImGuiFrame() override;
|
||||
void RenderImGui(gfx::IRenderer* renderer) override;
|
||||
|
||||
uint32_t GetAudioDevice() const override;
|
||||
std::shared_ptr<int16_t> GetAudioBuffer() const override;
|
||||
|
||||
std::string GetBackendName() const override;
|
||||
int GetSDLVersion() const override;
|
||||
|
||||
private:
|
||||
bool initialized_ = false;
|
||||
bool imgui_initialized_ = false;
|
||||
WindowStatus status_{};
|
||||
std::string title_;
|
||||
void* metal_view_ = nullptr;
|
||||
void* command_queue_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace platform
|
||||
} // namespace yaze
|
||||
351
src/app/platform/ios/ios_window_backend.mm
Normal file
351
src/app/platform/ios/ios_window_backend.mm
Normal file
@@ -0,0 +1,351 @@
|
||||
#include "app/platform/ios/ios_window_backend.h"
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#include <TargetConditionals.h>
|
||||
#endif
|
||||
|
||||
#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
|
||||
#import <CoreFoundation/CoreFoundation.h>
|
||||
#import <Metal/Metal.h>
|
||||
#import <MetalKit/MetalKit.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "app/gfx/backend/metal_renderer.h"
|
||||
#include "app/gui/core/style.h"
|
||||
#include "app/platform/font_loader.h"
|
||||
#include "app/platform/ios/ios_platform_state.h"
|
||||
#include "imgui/backends/imgui_impl_metal.h"
|
||||
#include "imgui/imgui.h"
|
||||
#include "util/log.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace platform {
|
||||
|
||||
namespace {
|
||||
#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
|
||||
UIEdgeInsets GetSafeAreaInsets(MTKView* view) {
|
||||
if (!view) {
|
||||
return UIEdgeInsetsZero;
|
||||
}
|
||||
if (@available(iOS 11.0, *)) {
|
||||
return view.safeAreaInsets;
|
||||
}
|
||||
return UIEdgeInsetsZero;
|
||||
}
|
||||
|
||||
void ApplyTouchStyle(MTKView* view) {
|
||||
ImGuiStyle& style = ImGui::GetStyle();
|
||||
const float frame_height = ImGui::GetFrameHeight();
|
||||
const float target_height = std::max(44.0f, frame_height);
|
||||
const float touch_extra =
|
||||
std::clamp((target_height - frame_height) * 0.5f, 0.0f, 16.0f);
|
||||
style.TouchExtraPadding = ImVec2(touch_extra, touch_extra);
|
||||
|
||||
const float font_size = ImGui::GetFontSize();
|
||||
if (font_size > 0.0f) {
|
||||
style.ScrollbarSize = std::max(style.ScrollbarSize, font_size * 1.1f);
|
||||
style.GrabMinSize = std::max(style.GrabMinSize, font_size * 0.9f);
|
||||
style.FramePadding.x = std::max(style.FramePadding.x, font_size * 0.55f);
|
||||
style.FramePadding.y = std::max(style.FramePadding.y, font_size * 0.35f);
|
||||
style.ItemSpacing.x = std::max(style.ItemSpacing.x, font_size * 0.45f);
|
||||
style.ItemSpacing.y = std::max(style.ItemSpacing.y, font_size * 0.35f);
|
||||
}
|
||||
|
||||
const UIEdgeInsets insets = GetSafeAreaInsets(view);
|
||||
const float safe_x = std::max(insets.left, insets.right);
|
||||
const float safe_y = std::max(insets.top, insets.bottom);
|
||||
style.DisplaySafeAreaPadding = ImVec2(safe_x, safe_y);
|
||||
ios::SetSafeAreaInsets(insets.left, insets.right, insets.top,
|
||||
insets.bottom);
|
||||
}
|
||||
#endif
|
||||
} // namespace
|
||||
|
||||
absl::Status IOSWindowBackend::Initialize(const WindowConfig& config) {
|
||||
#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
|
||||
metal_view_ = ios::GetMetalView();
|
||||
if (!metal_view_) {
|
||||
return absl::FailedPreconditionError("Metal view not set");
|
||||
}
|
||||
|
||||
title_ = config.title;
|
||||
status_.is_active = true;
|
||||
status_.is_focused = true;
|
||||
status_.is_fullscreen = config.fullscreen;
|
||||
|
||||
auto* view = static_cast<MTKView*>(metal_view_);
|
||||
status_.width = static_cast<int>(view.bounds.size.width);
|
||||
status_.height = static_cast<int>(view.bounds.size.height);
|
||||
|
||||
initialized_ = true;
|
||||
return absl::OkStatus();
|
||||
#else
|
||||
(void)config;
|
||||
return absl::FailedPreconditionError(
|
||||
"IOSWindowBackend is only available on iOS");
|
||||
#endif
|
||||
}
|
||||
|
||||
absl::Status IOSWindowBackend::Shutdown() {
|
||||
ShutdownImGui();
|
||||
|
||||
#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
|
||||
if (command_queue_) {
|
||||
CFRelease(command_queue_);
|
||||
command_queue_ = nullptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
metal_view_ = nullptr;
|
||||
initialized_ = false;
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
bool IOSWindowBackend::IsInitialized() const {
|
||||
return initialized_;
|
||||
}
|
||||
|
||||
bool IOSWindowBackend::PollEvent(WindowEvent& out_event) {
|
||||
out_event = WindowEvent{};
|
||||
return false;
|
||||
}
|
||||
|
||||
void IOSWindowBackend::ProcessNativeEvent(void* native_event) {
|
||||
(void)native_event;
|
||||
}
|
||||
|
||||
WindowStatus IOSWindowBackend::GetStatus() const {
|
||||
WindowStatus status = status_;
|
||||
#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
|
||||
if (metal_view_) {
|
||||
auto* view = static_cast<MTKView*>(metal_view_);
|
||||
status.width = static_cast<int>(view.bounds.size.width);
|
||||
status.height = static_cast<int>(view.bounds.size.height);
|
||||
}
|
||||
#endif
|
||||
return status;
|
||||
}
|
||||
|
||||
bool IOSWindowBackend::IsActive() const {
|
||||
return status_.is_active;
|
||||
}
|
||||
|
||||
void IOSWindowBackend::SetActive(bool active) {
|
||||
status_.is_active = active;
|
||||
}
|
||||
|
||||
void IOSWindowBackend::GetSize(int* width, int* height) const {
|
||||
#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
|
||||
if (metal_view_) {
|
||||
auto* view = static_cast<MTKView*>(metal_view_);
|
||||
if (width) {
|
||||
*width = static_cast<int>(view.bounds.size.width);
|
||||
}
|
||||
if (height) {
|
||||
*height = static_cast<int>(view.bounds.size.height);
|
||||
}
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (width) {
|
||||
*width = 0;
|
||||
}
|
||||
if (height) {
|
||||
*height = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void IOSWindowBackend::SetSize(int width, int height) {
|
||||
#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
|
||||
if (metal_view_) {
|
||||
auto* view = static_cast<MTKView*>(metal_view_);
|
||||
view.drawableSize = CGSizeMake(width, height);
|
||||
}
|
||||
#else
|
||||
(void)width;
|
||||
(void)height;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string IOSWindowBackend::GetTitle() const {
|
||||
return title_;
|
||||
}
|
||||
|
||||
void IOSWindowBackend::SetTitle(const std::string& title) {
|
||||
title_ = title;
|
||||
}
|
||||
|
||||
bool IOSWindowBackend::InitializeRenderer(gfx::IRenderer* renderer) {
|
||||
if (!renderer || !metal_view_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (renderer->GetBackendRenderer()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto* metal_renderer = dynamic_cast<gfx::MetalRenderer*>(renderer);
|
||||
if (metal_renderer) {
|
||||
metal_renderer->SetMetalView(metal_view_);
|
||||
} else {
|
||||
LOG_WARN("IOSWindowBackend", "Non-Metal renderer selected on iOS");
|
||||
}
|
||||
|
||||
return renderer->Initialize(nullptr);
|
||||
}
|
||||
|
||||
SDL_Window* IOSWindowBackend::GetNativeWindow() {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
absl::Status IOSWindowBackend::InitializeImGui(gfx::IRenderer* renderer) {
|
||||
if (imgui_initialized_) {
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
if (!renderer) {
|
||||
return absl::InvalidArgumentError("Renderer is null");
|
||||
}
|
||||
|
||||
#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
|
||||
if (!metal_view_) {
|
||||
return absl::FailedPreconditionError("Metal view not set");
|
||||
}
|
||||
|
||||
auto* view = static_cast<MTKView*>(metal_view_);
|
||||
id<MTLDevice> device = view.device;
|
||||
if (!device) {
|
||||
device = MTLCreateSystemDefaultDevice();
|
||||
view.device = device;
|
||||
}
|
||||
|
||||
if (!device) {
|
||||
return absl::InternalError("Failed to create Metal device");
|
||||
}
|
||||
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
|
||||
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
|
||||
io.ConfigFlags |= ImGuiConfigFlags_IsTouchScreen;
|
||||
|
||||
if (!ImGui_ImplMetal_Init(device)) {
|
||||
return absl::InternalError("ImGui_ImplMetal_Init failed");
|
||||
}
|
||||
|
||||
auto font_status = LoadPackageFonts();
|
||||
if (!font_status.ok()) {
|
||||
ImGui_ImplMetal_Shutdown();
|
||||
ImGui::DestroyContext();
|
||||
return font_status;
|
||||
}
|
||||
gui::ColorsYaze();
|
||||
#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
|
||||
ApplyTouchStyle(view);
|
||||
#endif
|
||||
|
||||
if (!command_queue_) {
|
||||
id<MTLCommandQueue> queue = [device newCommandQueue];
|
||||
command_queue_ = (__bridge_retained void*)queue;
|
||||
}
|
||||
|
||||
imgui_initialized_ = true;
|
||||
LOG_INFO("IOSWindowBackend", "ImGui initialized with Metal backend");
|
||||
return absl::OkStatus();
|
||||
#else
|
||||
return absl::FailedPreconditionError(
|
||||
"IOSWindowBackend is only available on iOS");
|
||||
#endif
|
||||
}
|
||||
|
||||
void IOSWindowBackend::ShutdownImGui() {
|
||||
if (!imgui_initialized_) {
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui_ImplMetal_Shutdown();
|
||||
ImGui::DestroyContext();
|
||||
|
||||
imgui_initialized_ = false;
|
||||
}
|
||||
|
||||
void IOSWindowBackend::NewImGuiFrame() {
|
||||
if (!imgui_initialized_) {
|
||||
return;
|
||||
}
|
||||
|
||||
#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
|
||||
auto* view = static_cast<MTKView*>(metal_view_);
|
||||
if (!view) {
|
||||
return;
|
||||
}
|
||||
|
||||
ApplyTouchStyle(view);
|
||||
|
||||
auto* render_pass = view.currentRenderPassDescriptor;
|
||||
if (!render_pass) {
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui_ImplMetal_NewFrame(render_pass);
|
||||
#endif
|
||||
}
|
||||
|
||||
void IOSWindowBackend::RenderImGui(gfx::IRenderer* renderer) {
|
||||
if (!imgui_initialized_) {
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui::Render();
|
||||
|
||||
#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
|
||||
(void)renderer;
|
||||
auto* view = static_cast<MTKView*>(metal_view_);
|
||||
if (!view || !view.currentDrawable) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* render_pass = view.currentRenderPassDescriptor;
|
||||
if (!render_pass || !command_queue_) {
|
||||
return;
|
||||
}
|
||||
|
||||
id<MTLCommandQueue> queue =
|
||||
(__bridge id<MTLCommandQueue>)command_queue_;
|
||||
id<MTLCommandBuffer> command_buffer = [queue commandBuffer];
|
||||
id<MTLRenderCommandEncoder> encoder =
|
||||
[command_buffer renderCommandEncoderWithDescriptor:render_pass];
|
||||
|
||||
ImGui_ImplMetal_RenderDrawData(ImGui::GetDrawData(), command_buffer, encoder);
|
||||
[encoder endEncoding];
|
||||
[command_buffer presentDrawable:view.currentDrawable];
|
||||
[command_buffer commit];
|
||||
#else
|
||||
(void)renderer;
|
||||
#endif
|
||||
}
|
||||
|
||||
uint32_t IOSWindowBackend::GetAudioDevice() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::shared_ptr<int16_t> IOSWindowBackend::GetAudioBuffer() const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string IOSWindowBackend::GetBackendName() const {
|
||||
return "iOS-Metal";
|
||||
}
|
||||
|
||||
int IOSWindowBackend::GetSDLVersion() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace platform
|
||||
} // namespace yaze
|
||||
@@ -272,6 +272,7 @@ class IWindowBackend {
|
||||
enum class WindowBackendType {
|
||||
SDL2,
|
||||
SDL3,
|
||||
IOS,
|
||||
Auto // Automatically select based on availability
|
||||
};
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#endif
|
||||
#else
|
||||
#ifdef __OBJC__
|
||||
@interface AppViewController : UIViewController <MTKViewDelegate>
|
||||
@interface AppViewController : UIViewController <MTKViewDelegate, UIGestureRecognizerDelegate>
|
||||
@property(nonatomic) yaze::Controller *controller;
|
||||
@property(nonatomic) UIHoverGestureRecognizer *hoverGestureRecognizer;
|
||||
@property(nonatomic) UIPinchGestureRecognizer *pinchRecognizer;
|
||||
|
||||
@@ -5,6 +5,14 @@
|
||||
#include "app/platform/sdl2_window_backend.h"
|
||||
#include "util/log.h"
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#include <TargetConditionals.h>
|
||||
#endif
|
||||
|
||||
#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
|
||||
#include "app/platform/ios/ios_window_backend.h"
|
||||
#endif
|
||||
|
||||
#ifdef YAZE_USE_SDL3
|
||||
#include "app/platform/sdl3_window_backend.h"
|
||||
#endif
|
||||
@@ -33,6 +41,15 @@ std::unique_ptr<IWindowBackend> WindowBackendFactory::Create(
|
||||
return std::make_unique<SDL2WindowBackend>();
|
||||
#endif
|
||||
|
||||
case WindowBackendType::IOS:
|
||||
#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
|
||||
return std::make_unique<IOSWindowBackend>();
|
||||
#else
|
||||
LOG_WARN("WindowBackendFactory",
|
||||
"iOS backend requested on non-iOS platform");
|
||||
return nullptr;
|
||||
#endif
|
||||
|
||||
case WindowBackendType::Auto:
|
||||
default:
|
||||
return Create(GetDefaultType());
|
||||
@@ -40,6 +57,9 @@ std::unique_ptr<IWindowBackend> WindowBackendFactory::Create(
|
||||
}
|
||||
|
||||
WindowBackendType WindowBackendFactory::GetDefaultType() {
|
||||
#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
|
||||
return WindowBackendType::IOS;
|
||||
#endif
|
||||
#ifdef YAZE_USE_SDL3
|
||||
return WindowBackendType::SDL3;
|
||||
#else
|
||||
@@ -66,6 +86,13 @@ bool WindowBackendFactory::IsAvailable(WindowBackendType type) {
|
||||
case WindowBackendType::Auto:
|
||||
return true; // Auto always available
|
||||
|
||||
case WindowBackendType::IOS:
|
||||
#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user