epic: refactor SDL2_Renderer usage to IRenderer and queued texture rendering

- Updated the testing guide to clarify the testing framework's organization and execution methods, improving user understanding.
- Refactored CMakeLists to include new platform-specific files, ensuring proper integration of the rendering backend.
- Modified main application files to utilize the new IRenderer interface, enhancing flexibility in rendering operations.
- Implemented deferred texture management in various components, allowing for more efficient graphics handling and improved performance.
- Introduced new methods for texture creation and updates, streamlining the rendering process across the application.
- Enhanced logging and error handling in the rendering pipeline to facilitate better debugging and diagnostics.
This commit is contained in:
scawful
2025-10-07 17:15:11 -04:00
parent 9e6f538520
commit 6c331f1fd0
101 changed files with 1401 additions and 2677 deletions

View File

@@ -0,0 +1,64 @@
#ifndef YAZE_APP_PLATFORM_APP_DELEGATE_H
#define YAZE_APP_PLATFORM_APP_DELEGATE_H
#if defined(__APPLE__) && defined(__MACH__)
/* Apple OSX and iOS (Darwin). */
#import <CoreText/CoreText.h>
#include <TargetConditionals.h>
#if TARGET_IPHONE_SIMULATOR == 1 || TARGET_OS_IPHONE == 1
/* iOS in Xcode simulator */
#import <PencilKit/PencilKit.h>
#import <UIKit/UIKit.h>
@interface AppDelegate : UIResponder <UIApplicationDelegate,
UIDocumentPickerDelegate,
UITabBarControllerDelegate,
PKCanvasViewDelegate>
@property(strong, nonatomic) UIWindow *window;
@property UIDocumentPickerViewController *documentPicker;
@property(nonatomic, copy) void (^completionHandler)(NSString *selectedFile);
- (void)PresentDocumentPickerWithCompletionHandler:
(void (^)(NSString *selectedFile))completionHandler;
// TODO: Setup a tab bar controller for multiple yaze instances
@property(nonatomic) UITabBarController *tabBarController;
// TODO: Setup a font picker for the text editor and display settings
@property(nonatomic) UIFontPickerViewController *fontPicker;
// TODO: Setup the pencil kit for drawing
@property PKToolPicker *toolPicker;
@property PKCanvasView *canvasView;
// TODO: Setup the file manager for file operations
@property NSFileManager *fileManager;
@end
#elif TARGET_OS_MAC == 1
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Initialize the Cocoa application.
*/
void yaze_initialize_cocoa();
/**
* @brief Run the Cocoa application delegate.
*/
int yaze_run_cocoa_app_delegate(const char *filename);
#ifdef __cplusplus
} // extern "C"
#endif
#endif // TARGET_OS_MAC
#endif // defined(__APPLE__) && defined(__MACH__)
#endif // YAZE_APP_PLATFORM_APP_DELEGATE_H

View File

@@ -0,0 +1,264 @@
// AppDelegate.mm
#import "app/platform/app_delegate.h"
#import "app/core/controller.h"
#import "util/file_util.h"
#import "app/editor/editor.h"
#import "app/rom.h"
#if defined(__APPLE__) && defined(__MACH__)
/* Apple OSX and iOS (Darwin). */
#include <TargetConditionals.h>
#import <CoreText/CoreText.h>
#if TARGET_IPHONE_SIMULATOR == 1 || TARGET_OS_IPHONE == 1
/* iOS in Xcode simulator */
#elif TARGET_OS_MAC == 1
/* macOS */
#import <Cocoa/Cocoa.h>
@interface AppDelegate : NSObject <NSApplicationDelegate>
- (void)setupMenus;
// - (void)changeApplicationIcon;
@end
@implementation AppDelegate
// - (void)changeApplicationIcon {
// NSImage *newIcon = [NSImage imageNamed:@"newIcon"];
// [NSApp setApplicationIconImage:newIcon];
// }
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
[self setupMenus];
}
- (void)setupMenus {
NSMenu *mainMenu = [NSApp mainMenu];
NSMenuItem *fileMenuItem = [mainMenu itemWithTitle:@"File"];
if (!fileMenuItem) {
NSMenu *fileMenu = [[NSMenu alloc] initWithTitle:@"File"];
fileMenuItem = [[NSMenuItem alloc] initWithTitle:@"File" action:nil keyEquivalent:@""];
[fileMenuItem setSubmenu:fileMenu];
NSMenuItem *openItem = [[NSMenuItem alloc] initWithTitle:@"Open"
action:@selector(openFileAction:)
keyEquivalent:@"o"];
[fileMenu addItem:openItem];
// Open Recent
NSMenu *openRecentMenu = [[NSMenu alloc] initWithTitle:@"Open Recent"];
NSMenuItem *openRecentMenuItem = [[NSMenuItem alloc] initWithTitle:@"Open Recent"
action:nil
keyEquivalent:@""];
[openRecentMenuItem setSubmenu:openRecentMenu];
[fileMenu addItem:openRecentMenuItem];
// Add a separator
[fileMenu addItem:[NSMenuItem separatorItem]];
// Save
NSMenuItem *saveItem = [[NSMenuItem alloc] initWithTitle:@"Save" action:nil keyEquivalent:@"s"];
[fileMenu addItem:saveItem];
// Separator
[fileMenu addItem:[NSMenuItem separatorItem]];
// Options submenu
NSMenu *optionsMenu = [[NSMenu alloc] initWithTitle:@"Options"];
NSMenuItem *optionsMenuItem = [[NSMenuItem alloc] initWithTitle:@"Options"
action:nil
keyEquivalent:@""];
[optionsMenuItem setSubmenu:optionsMenu];
// Flag checkmark field
NSMenuItem *flagItem = [[NSMenuItem alloc] initWithTitle:@"Flag"
action:@selector(toggleFlagAction:)
keyEquivalent:@""];
[flagItem setTarget:self];
[flagItem setState:NSControlStateValueOff];
[optionsMenu addItem:flagItem];
[fileMenu addItem:optionsMenuItem];
[mainMenu insertItem:fileMenuItem atIndex:1];
}
NSMenuItem *editMenuItem = [mainMenu itemWithTitle:@"Edit"];
if (!editMenuItem) {
NSMenu *editMenu = [[NSMenu alloc] initWithTitle:@"Edit"];
editMenuItem = [[NSMenuItem alloc] initWithTitle:@"Edit" action:nil keyEquivalent:@""];
[editMenuItem setSubmenu:editMenu];
NSMenuItem *undoItem = [[NSMenuItem alloc] initWithTitle:@"Undo" action:nil keyEquivalent:@"z"];
[editMenu addItem:undoItem];
NSMenuItem *redoItem = [[NSMenuItem alloc] initWithTitle:@"Redo" action:nil keyEquivalent:@"Z"];
[editMenu addItem:redoItem];
// Add a separator
[editMenu addItem:[NSMenuItem separatorItem]];
NSMenuItem *cutItem = [[NSMenuItem alloc] initWithTitle:@"Cut"
action:@selector(cutAction:)
keyEquivalent:@"x"];
[editMenu addItem:cutItem];
NSMenuItem *copyItem = [[NSMenuItem alloc] initWithTitle:@"Copy" action:nil keyEquivalent:@"c"];
[editMenu addItem:copyItem];
NSMenuItem *pasteItem = [[NSMenuItem alloc] initWithTitle:@"Paste"
action:nil
keyEquivalent:@"v"];
[editMenu addItem:pasteItem];
// Add a separator
[editMenu addItem:[NSMenuItem separatorItem]];
NSMenuItem *selectAllItem = [[NSMenuItem alloc] initWithTitle:@"Select All"
action:nil
keyEquivalent:@"a"];
[editMenu addItem:selectAllItem];
[mainMenu insertItem:editMenuItem atIndex:2];
}
NSMenuItem *viewMenuItem = [mainMenu itemWithTitle:@"View"];
if (!viewMenuItem) {
NSMenu *viewMenu = [[NSMenu alloc] initWithTitle:@"View"];
viewMenuItem = [[NSMenuItem alloc] initWithTitle:@"View" action:nil keyEquivalent:@""];
[viewMenuItem setSubmenu:viewMenu];
// Emulator view button
NSMenuItem *emulatorViewItem = [[NSMenuItem alloc] initWithTitle:@"Emulator View"
action:nil
keyEquivalent:@"1"];
[viewMenu addItem:emulatorViewItem];
// Hex Editor View
NSMenuItem *hexEditorViewItem = [[NSMenuItem alloc] initWithTitle:@"Hex Editor View"
action:nil
keyEquivalent:@"2"];
[viewMenu addItem:hexEditorViewItem];
// Disassembly view button
NSMenuItem *disassemblyViewItem = [[NSMenuItem alloc] initWithTitle:@"Disassembly View"
action:nil
keyEquivalent:@"3"];
[viewMenu addItem:disassemblyViewItem];
// Memory view button
NSMenuItem *memoryViewItem = [[NSMenuItem alloc] initWithTitle:@"Memory View"
action:nil
keyEquivalent:@"4"];
[viewMenu addItem:memoryViewItem];
// Add a separator
[viewMenu addItem:[NSMenuItem separatorItem]];
// Toggle fullscreen button
NSMenuItem *toggleFullscreenItem = [[NSMenuItem alloc] initWithTitle:@"Toggle Fullscreen"
action:nil
keyEquivalent:@"f"];
[viewMenu addItem:toggleFullscreenItem];
[mainMenu insertItem:viewMenuItem atIndex:3];
}
NSMenuItem *helpMenuItem = [mainMenu itemWithTitle:@"Help"];
if (!helpMenuItem) {
NSMenu *helpMenu = [[NSMenu alloc] initWithTitle:@"Help"];
helpMenuItem = [[NSMenuItem alloc] initWithTitle:@"Help" action:nil keyEquivalent:@""];
[helpMenuItem setSubmenu:helpMenu];
// URL to online documentation
NSMenuItem *documentationItem = [[NSMenuItem alloc] initWithTitle:@"Documentation"
action:nil
keyEquivalent:@"?"];
[helpMenu addItem:documentationItem];
[mainMenu insertItem:helpMenuItem atIndex:4];
}
}
// Action method for the New menu item
- (void)newFileAction:(id)sender {
NSLog(@"New File action triggered");
}
- (void)toggleFlagAction:(id)sender {
NSMenuItem *flagItem = (NSMenuItem *)sender;
if ([flagItem state] == NSControlStateValueOff) {
[flagItem setState:NSControlStateValueOn];
} else {
[flagItem setState:NSControlStateValueOff];
}
}
- (void)openFileAction:(id)sender {
// TODO: Re-implmenent this without the SharedRom singleton
// if (!yaze::SharedRom::shared_rom_
// ->LoadFromFile(yaze::util::FileDialogWrapper::ShowOpenFileDialog())
// .ok()) {
// NSAlert *alert = [[NSAlert alloc] init];
// [alert setMessageText:@"Error"];
// [alert setInformativeText:@"Failed to load file."];
// [alert addButtonWithTitle:@"OK"];
// [alert runModal];
// }
}
- (void)cutAction:(id)sender {
// TODO: Implement
}
- (void)openRecentFileAction:(id)sender {
NSLog(@"Open Recent File action triggered");
}
@end
extern "C" void yaze_initialize_cococa() {
@autoreleasepool {
AppDelegate *delegate = [[AppDelegate alloc] init];
[NSApplication sharedApplication];
[NSApp setDelegate:delegate];
[NSApp finishLaunching];
}
}
extern "C" int yaze_run_cocoa_app_delegate(const char *filename) {
yaze_initialize_cococa();
auto controller = std::make_unique<yaze::core::Controller>();
EXIT_IF_ERROR(controller->OnEntry(filename));
while (controller->IsActive()) {
@autoreleasepool {
controller->OnInput();
if (auto status = controller->OnLoad(); !status.ok()) {
NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText:@"Error"];
[alert setInformativeText:[NSString stringWithUTF8String:status.message().data()]];
[alert addButtonWithTitle:@"OK"];
[alert runModal];
break;
}
controller->DoRender();
}
}
controller->OnExit();
return EXIT_SUCCESS;
}
#endif
#endif

View File

@@ -0,0 +1,94 @@
#include "app/platform/asset_loader.h"
#include <fstream>
#include <sstream>
#include "absl/strings/str_format.h"
#include "util/file_util.h"
namespace yaze {
std::vector<std::filesystem::path> AssetLoader::GetSearchPaths(const std::string& relative_path) {
std::vector<std::filesystem::path> search_paths;
#ifdef __APPLE__
// macOS bundle resource paths
std::string bundle_root = yaze::util::GetBundleResourcePath();
// Try Contents/Resources first (standard bundle location)
search_paths.push_back(std::filesystem::path(bundle_root) / "Contents" / "Resources" / relative_path);
// Try without Contents (if app is at root)
search_paths.push_back(std::filesystem::path(bundle_root) / "Resources" / relative_path);
// Development paths (when running from build dir)
search_paths.push_back(std::filesystem::path(bundle_root) / ".." / ".." / ".." / "assets" / relative_path);
search_paths.push_back(std::filesystem::path(bundle_root) / ".." / ".." / ".." / ".." / "assets" / relative_path);
#endif
// Standard relative paths (works for all platforms)
search_paths.push_back(std::filesystem::path("assets") / relative_path);
search_paths.push_back(std::filesystem::path("../assets") / relative_path);
search_paths.push_back(std::filesystem::path("../../assets") / relative_path);
search_paths.push_back(std::filesystem::path("../../../assets") / relative_path);
search_paths.push_back(std::filesystem::path("../../../../assets") / relative_path);
// Build directory paths
search_paths.push_back(std::filesystem::path("build/assets") / relative_path);
search_paths.push_back(std::filesystem::path("../build/assets") / relative_path);
return search_paths;
}
absl::StatusOr<std::filesystem::path> AssetLoader::FindAssetFile(const std::string& relative_path) {
auto search_paths = GetSearchPaths(relative_path);
for (const auto& path : search_paths) {
if (std::filesystem::exists(path)) {
return path;
}
}
// Debug: Print searched paths
std::string searched_paths;
for (const auto& path : search_paths) {
searched_paths += "\n - " + path.string();
}
return absl::NotFoundError(
absl::StrFormat("Asset file not found: %s\nSearched paths:%s",
relative_path, searched_paths));
}
absl::StatusOr<std::string> AssetLoader::LoadTextFile(const std::string& relative_path) {
auto path_result = FindAssetFile(relative_path);
if (!path_result.ok()) {
return path_result.status();
}
const auto& path = *path_result;
std::ifstream file(path);
if (!file.is_open()) {
return absl::InternalError(
absl::StrFormat("Failed to open file: %s", path.string()));
}
std::stringstream buffer;
buffer << file.rdbuf();
std::string content = buffer.str();
if (content.empty()) {
return absl::InternalError(
absl::StrFormat("File is empty: %s", path.string()));
}
return content;
}
bool AssetLoader::AssetExists(const std::string& relative_path) {
return FindAssetFile(relative_path).ok();
}
} // namespace yaze

View File

@@ -0,0 +1,57 @@
#ifndef YAZE_APP_PLATFORM_ASSET_LOADER_H_
#define YAZE_APP_PLATFORM_ASSET_LOADER_H_
#include <filesystem>
#include <string>
#include <vector>
#include "absl/status/statusor.h"
namespace yaze {
/**
* @class AssetLoader
* @brief Cross-platform asset file loading utility
*
* Handles platform-specific paths for loading assets from:
* - macOS bundle resources
* - Windows relative paths
* - Linux relative paths
* - Development build directories
*/
class AssetLoader {
public:
/**
* Load a text file from the assets directory
* @param relative_path Path relative to assets/ (e.g., "agent/system_prompt.txt")
* @return File contents or error
*/
static absl::StatusOr<std::string> LoadTextFile(const std::string& relative_path);
/**
* Find an asset file by trying multiple platform-specific paths
* @param relative_path Path relative to assets/
* @return Full path to file or error
*/
static absl::StatusOr<std::filesystem::path> FindAssetFile(const std::string& relative_path);
/**
* Get list of search paths for a given asset
* @param relative_path Path relative to assets/
* @return Vector of paths to try in order
*/
static std::vector<std::filesystem::path> GetSearchPaths(const std::string& relative_path);
/**
* Check if an asset file exists
* @param relative_path Path relative to assets/
* @return true if file exists in any search path
*/
static bool AssetExists(const std::string& relative_path);
};
} // namespace yaze
#endif // YAZE_APP_PLATFORM_ASSET_LOADER_H_

View File

@@ -0,0 +1,303 @@
#include "util/file_util.h"
#include <iostream>
#include <string>
#include <vector>
#include "app/core/features.h"
#if defined(YAZE_ENABLE_NFD) && YAZE_ENABLE_NFD
#include <nfd.h>
#endif
#if defined(__APPLE__) && defined(__MACH__)
/* Apple OSX and iOS (Darwin). */
#include <Foundation/Foundation.h>
#include <TargetConditionals.h>
#import <CoreText/CoreText.h>
#if TARGET_IPHONE_SIMULATOR == 1 || TARGET_OS_IPHONE == 1
/* iOS in Xcode simulator */
#import <UIKit/UIKit.h>
#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
#include "app/platform/app_delegate.h"
namespace {
static std::string selectedFile;
void ShowOpenFileDialogImpl(void (^completionHandler)(std::string)) {
AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
[appDelegate PresentDocumentPickerWithCompletionHandler:^(NSString *filePath) {
selectedFile = std::string([filePath UTF8String]);
completionHandler(selectedFile);
}];
}
std::string ShowOpenFileDialogSync() {
__block std::string result;
ShowOpenFileDialogImpl(^(std::string filePath) {
result = filePath;
});
return result;
}
} // namespace
std::string yaze::util::FileDialogWrapper::ShowOpenFileDialog() { return ShowOpenFileDialogSync(); }
std::string yaze::util::FileDialogWrapper::ShowOpenFolderDialog() { return ""; }
std::vector<std::string> yaze::util::FileDialogWrapper::GetFilesInFolder(
const std::string &folder) {
return {};
}
std::vector<std::string> yaze::util::FileDialogWrapper::GetSubdirectoriesInFolder(
const std::string &folder) {
return {};
}
std::string yaze::util::GetBundleResourcePath() {
NSBundle* bundle = [NSBundle mainBundle];
NSString* resourceDirectoryPath = [bundle bundlePath];
NSString* path = [resourceDirectoryPath stringByAppendingString:@"/"];
return [path UTF8String];
}
#elif TARGET_OS_MAC == 1
/* macOS */
#import <Cocoa/Cocoa.h>
#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
std::string yaze::util::FileDialogWrapper::ShowOpenFileDialogBespoke() {
NSOpenPanel* openPanel = [NSOpenPanel openPanel];
[openPanel setCanChooseFiles:YES];
[openPanel setCanChooseDirectories:NO];
[openPanel setAllowsMultipleSelection:NO];
if ([openPanel runModal] == NSModalResponseOK) {
NSURL* url = [[openPanel URLs] objectAtIndex:0];
NSString* path = [url path];
return std::string([path UTF8String]);
}
return "";
}
std::string yaze::util::FileDialogWrapper::ShowSaveFileDialogBespoke(const std::string& default_name,
const std::string& default_extension) {
NSSavePanel* savePanel = [NSSavePanel savePanel];
if (!default_name.empty()) {
[savePanel setNameFieldStringValue:[NSString stringWithUTF8String:default_name.c_str()]];
}
if (!default_extension.empty()) {
NSString* ext = [NSString stringWithUTF8String:default_extension.c_str()];
[savePanel setAllowedFileTypes:@[ext]];
}
if ([savePanel runModal] == NSModalResponseOK) {
NSURL* url = [savePanel URL];
NSString* path = [url path];
return std::string([path UTF8String]);
}
return "";
}
// Global feature flag-based dispatch methods
std::string yaze::util::FileDialogWrapper::ShowOpenFileDialog() {
if (core::FeatureFlags::get().kUseNativeFileDialog) {
return ShowOpenFileDialogNFD();
} else {
return ShowOpenFileDialogBespoke();
}
}
std::string yaze::util::FileDialogWrapper::ShowOpenFolderDialog() {
if (core::FeatureFlags::get().kUseNativeFileDialog) {
return ShowOpenFolderDialogNFD();
} else {
return ShowOpenFolderDialogBespoke();
}
}
std::string yaze::util::FileDialogWrapper::ShowSaveFileDialog(const std::string& default_name,
const std::string& default_extension) {
if (core::FeatureFlags::get().kUseNativeFileDialog) {
return ShowSaveFileDialogNFD(default_name, default_extension);
} else {
return ShowSaveFileDialogBespoke(default_name, default_extension);
}
}
// 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
}
std::string yaze::util::FileDialogWrapper::ShowOpenFolderDialogNFD() {
#if defined(YAZE_ENABLE_NFD) && YAZE_ENABLE_NFD
NFD_Init();
nfdu8char_t *out_path = NULL;
nfdresult_t result = NFD_PickFolderU8(&out_path, NULL);
if (result == NFD_OKAY) {
std::string folder_path(out_path);
NFD_FreePath(out_path);
NFD_Quit();
return folder_path;
} else if (result == NFD_CANCEL) {
NFD_Quit();
return "";
}
NFD_Quit();
return "";
#else
// NFD not compiled in, use bespoke
return ShowOpenFolderDialogBespoke();
#endif
}
std::string yaze::util::FileDialogWrapper::ShowSaveFileDialogNFD(const std::string& default_name,
const std::string& default_extension) {
#if defined(YAZE_ENABLE_NFD) && YAZE_ENABLE_NFD
NFD_Init();
nfdu8char_t *out_path = NULL;
nfdsavedialogu8args_t args = {0};
if (!default_extension.empty()) {
// Create filter for the save dialog
static nfdu8filteritem_t filters[3] = {
{"Theme File", "theme"},
{"Project File", "yaze"},
{"ROM File", "sfc,smc"}
};
if (default_extension == "theme") {
args.filterList = &filters[0];
args.filterCount = 1;
} else if (default_extension == "yaze") {
args.filterList = &filters[1];
args.filterCount = 1;
} else if (default_extension == "sfc" || default_extension == "smc") {
args.filterList = &filters[2];
args.filterCount = 1;
}
}
if (!default_name.empty()) {
args.defaultName = default_name.c_str();
}
nfdresult_t result = NFD_SaveDialogU8_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 ShowSaveFileDialogBespoke(default_name, default_extension);
#endif
}
std::string yaze::util::FileDialogWrapper::ShowOpenFolderDialogBespoke() {
NSOpenPanel* openPanel = [NSOpenPanel openPanel];
[openPanel setCanChooseFiles:NO];
[openPanel setCanChooseDirectories:YES];
[openPanel setAllowsMultipleSelection:NO];
if ([openPanel runModal] == NSModalResponseOK) {
NSURL* url = [[openPanel URLs] objectAtIndex:0];
NSString* path = [url path];
return std::string([path UTF8String]);
}
return "";
}
std::vector<std::string> yaze::util::FileDialogWrapper::GetFilesInFolder(
const std::string& folder) {
std::vector<std::string> filenames;
NSFileManager* fileManager = [NSFileManager defaultManager];
NSDirectoryEnumerator* enumerator =
[fileManager enumeratorAtPath:[NSString stringWithUTF8String:folder.c_str()]];
NSString* file;
while (file = [enumerator nextObject]) {
if ([file hasPrefix:@"."]) {
continue;
}
filenames.push_back(std::string([file UTF8String]));
}
return filenames;
}
std::vector<std::string> yaze::util::FileDialogWrapper::GetSubdirectoriesInFolder(
const std::string& folder) {
std::vector<std::string> subdirectories;
NSFileManager* fileManager = [NSFileManager defaultManager];
NSDirectoryEnumerator* enumerator =
[fileManager enumeratorAtPath:[NSString stringWithUTF8String:folder.c_str()]];
NSString* file;
while (file = [enumerator nextObject]) {
if ([file hasPrefix:@"."]) {
continue;
}
BOOL isDirectory;
NSString* path =
[NSString stringWithFormat:@"%@/%@", [NSString stringWithUTF8String:folder.c_str()], file];
[fileManager fileExistsAtPath:path isDirectory:&isDirectory];
if (isDirectory) {
subdirectories.push_back(std::string([file UTF8String]));
}
}
return subdirectories;
}
std::string yaze::util::GetBundleResourcePath() {
NSBundle* bundle = [NSBundle mainBundle];
NSString* resourceDirectoryPath = [bundle bundlePath];
NSString* path = [resourceDirectoryPath stringByAppendingString:@"/"];
return [path UTF8String];
}
#else
// Unsupported platform
#endif // TARGET_OS_MAC
#endif // __APPLE__ && __MACH__

View File

@@ -0,0 +1,140 @@
#include "app/platform/font_loader.h"
#include <filesystem>
#include <string>
#include <vector>
#include <cstring>
#include "absl/status/status.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "util/file_util.h"
#include "app/gui/icons.h"
#include "imgui/imgui.h"
#include "util/macro.h"
namespace yaze {
static const char* KARLA_REGULAR = "Karla-Regular.ttf";
static const char* ROBOTO_MEDIUM = "Roboto-Medium.ttf";
static const char* COUSINE_REGULAR = "Cousine-Regular.ttf";
static const char* DROID_SANS = "DroidSans.ttf";
static const char* NOTO_SANS_JP = "NotoSansJP.ttf";
static const char* IBM_PLEX_JP = "IBMPlexSansJP-Bold.ttf";
static const float FONT_SIZE_DEFAULT = 16.0F;
static const float FONT_SIZE_DROID_SANS = 18.0F;
static const float ICON_FONT_SIZE = 18.0F;
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;
#else
return absl::StrCat(util::GetBundleResourcePath(), "Contents/Resources/font/",
font_path);
#endif
#else
return absl::StrCat("assets/font/", font_path);
#endif
}
absl::Status LoadFont(const FontConfig& font_config) {
ImGuiIO& imgui_io = ImGui::GetIO();
std::string actual_font_path = SetFontPath(font_config.font_path);
// Check if the file exists with std library first, since ImGui IO will assert
// if the file does not exist
if (!std::filesystem::exists(actual_font_path)) {
return absl::InternalError(
absl::StrFormat("Font file %s does not exist", actual_font_path));
}
if (!imgui_io.Fonts->AddFontFromFileTTF(actual_font_path.data(),
font_config.font_size)) {
return absl::InternalError(
absl::StrFormat("Failed to load font from %s", actual_font_path));
}
return absl::OkStatus();
}
absl::Status AddIconFont(const FontConfig& /*config*/) {
static const ImWchar icons_ranges[] = {ICON_MIN_MD, 0xf900, 0};
ImFontConfig icons_config{};
icons_config.MergeMode = true;
icons_config.GlyphOffset.y = 5.0F;
icons_config.GlyphMinAdvanceX = 13.0F;
icons_config.PixelSnapH = true;
std::string icon_font_path = SetFontPath(FONT_ICON_FILE_NAME_MD);
ImGuiIO& imgui_io = ImGui::GetIO();
if (!imgui_io.Fonts->AddFontFromFileTTF(icon_font_path.c_str(), ICON_FONT_SIZE,
&icons_config, icons_ranges)) {
return absl::InternalError("Failed to add icon fonts");
}
return absl::OkStatus();
}
absl::Status AddJapaneseFont(const FontConfig& /*config*/) {
ImFontConfig japanese_font_config{};
japanese_font_config.MergeMode = true;
japanese_font_config.GlyphOffset.y = 5.0F;
japanese_font_config.GlyphMinAdvanceX = 13.0F;
japanese_font_config.PixelSnapH = true;
std::string japanese_font_path = SetFontPath(NOTO_SANS_JP);
ImGuiIO& imgui_io = ImGui::GetIO();
if (!imgui_io.Fonts->AddFontFromFileTTF(japanese_font_path.data(), ICON_FONT_SIZE,
&japanese_font_config,
imgui_io.Fonts->GetGlyphRangesJapanese())) {
return absl::InternalError("Failed to add Japanese fonts");
}
return absl::OkStatus();
}
} // namespace
absl::Status LoadPackageFonts() {
if (font_registry.fonts.empty()) {
// Initialize the font names and sizes with proper ImFontConfig initialization
font_registry.fonts = {
FontConfig{KARLA_REGULAR, FONT_SIZE_DEFAULT, {}, {}},
FontConfig{ROBOTO_MEDIUM, FONT_SIZE_DEFAULT, {}, {}},
FontConfig{COUSINE_REGULAR, FONT_SIZE_DEFAULT, {}, {}},
FontConfig{IBM_PLEX_JP, FONT_SIZE_DEFAULT, {}, {}},
FontConfig{DROID_SANS, FONT_SIZE_DROID_SANS, {}, {}},
};
}
// Load fonts with associated icon and Japanese merges
for (const auto& font_config : font_registry.fonts) {
RETURN_IF_ERROR(LoadFont(font_config));
RETURN_IF_ERROR(AddIconFont(font_config));
RETURN_IF_ERROR(AddJapaneseFont(font_config));
}
return absl::OkStatus();
}
absl::Status ReloadPackageFont(const FontConfig& config) {
ImGuiIO& imgui_io = ImGui::GetIO();
std::string actual_font_path = SetFontPath(config.font_path);
if (!imgui_io.Fonts->AddFontFromFileTTF(actual_font_path.data(),
config.font_size)) {
return absl::InternalError(
absl::StrFormat("Failed to load font from %s", actual_font_path));
}
RETURN_IF_ERROR(AddIconFont(config));
RETURN_IF_ERROR(AddJapaneseFont(config));
return absl::OkStatus();
}
#ifdef __linux__
void LoadSystemFonts() {
// Load Linux System Fonts into ImGui
// System font loading is now handled by NFD (Native File Dialog)
// This function is kept for compatibility but does nothing
}
#endif
} // namespace yaze

View File

@@ -0,0 +1,34 @@
#ifndef YAZE_APP_PLATFORM_FONTLOADER_H
#define YAZE_APP_PLATFORM_FONTLOADER_H
#include <vector>
#include "absl/status/status.h"
#include "imgui/imgui.h"
namespace yaze {
struct FontConfig {
const char* font_path;
float font_size;
ImFontConfig im_font_config;
ImFontConfig jp_conf_config;
};
struct FontState {
std::vector<FontConfig> fonts;
};
static FontState font_registry;
absl::Status LoadPackageFonts();
absl::Status ReloadPackageFont(const FontConfig& config);
void LoadSystemFonts();
} // namespace yaze
#endif // YAZE_APP_PLATFORM_FONTLOADER_H

View File

@@ -0,0 +1,59 @@
#include "app/platform/font_loader.h"
#import <CoreText/CoreText.h>
#include <TargetConditionals.h>
#include "app/gui/icons.h"
#include "imgui/imgui.h"
#if TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1
/* iOS */
void yaze::LoadSystemFonts() {}
#elif TARGET_OS_MAC == 1
/* macOS */
#import <Cocoa/Cocoa.h>
void yaze::LoadSystemFonts() {
NSArray *fontNames = @[ @"Helvetica", @"Times New Roman", @"Courier", @"Arial", @"Verdana" ];
for (NSString *fontName in fontNames) {
NSFont *font = [NSFont fontWithName:fontName size:14.0];
if (!font) {
NSLog(@"Font not found: %@", fontName);
continue;
}
CTFontDescriptorRef fontDescriptor =
CTFontDescriptorCreateWithNameAndSize((CFStringRef)font.fontName, font.pointSize);
CFURLRef fontURL = (CFURLRef)CTFontDescriptorCopyAttribute(fontDescriptor, kCTFontURLAttribute);
NSString *fontPath = [(NSURL *)fontURL path];
CFRelease(fontDescriptor);
if (fontPath != nil && [[NSFileManager defaultManager] isReadableFileAtPath:fontPath]) {
// Load the font into ImGui
ImGuiIO &io = ImGui::GetIO();
ImFontConfig icons_config;
icons_config.MergeMode = true;
icons_config.GlyphOffset.y = 5.0f;
icons_config.GlyphMinAdvanceX = 13.0f;
icons_config.PixelSnapH = true;
static const ImWchar icons_ranges[] = {ICON_MIN_MD, 0xf900, 0};
static const float ICON_FONT_SIZE = 18.0f;
ImFont *imFont = io.Fonts->AddFontFromFileTTF([fontPath UTF8String], 14.0f);
if (!imFont) {
NSLog(@"Failed to load font: %@", fontPath);
}
io.Fonts->AddFontFromFileTTF(FONT_ICON_FILE_NAME_MD, ICON_FONT_SIZE, &icons_config,
icons_ranges);
} else {
NSLog(@"Font file not accessible: %@", fontPath);
}
if (fontURL) {
CFRelease(fontURL);
}
}
}
#endif

View File

@@ -0,0 +1,35 @@
#ifndef YAZE_APP_CORE_PLATFORM_VIEW_CONTROLLER_H
#define YAZE_APP_CORE_PLATFORM_VIEW_CONTROLLER_H
#ifdef __APPLE__
#include <TargetConditionals.h>
#if TARGET_OS_OSX
#ifdef __OBJC__
@interface AppViewController : NSViewController <NSWindowDelegate>
@property(nonatomic) yaze::core::Controller *controller;
@end
#endif
#else
#ifdef __OBJC__
@interface AppViewController : UIViewController <MTKViewDelegate>
@property(nonatomic) yaze::core::Controller *controller;
@property(nonatomic) UIHoverGestureRecognizer *hoverGestureRecognizer;
@property(nonatomic) UIPinchGestureRecognizer *pinchRecognizer;
@property(nonatomic) UISwipeGestureRecognizer *swipeRecognizer;
@property(nonatomic) UILongPressGestureRecognizer *longPressRecognizer;
@end
#endif
#endif
#ifdef __OBJC__
@interface AppViewController () <MTKViewDelegate>
@property(nonatomic, readonly) MTKView *mtkView;
@property(nonatomic, strong) id<MTLDevice> device;
@property(nonatomic, strong) id<MTLCommandQueue> commandQueue;
@end
#endif
#endif // __APPLE__
#endif // YAZE_APP_CORE_PLATFORM_VIEW_CONTROLLER_H