backend-infra-engineer: Release v0.3.2 snapshot

This commit is contained in:
scawful
2025-10-17 12:10:25 -04:00
parent 4371618a9b
commit 3d71417f62
857 changed files with 174954 additions and 45626 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,278 @@
// AppDelegate.mm
// Must define before any ImGui includes (needed by imgui_test_engine via editor_manager.h)
#ifndef IMGUI_DEFINE_MATH_OPERATORS
#define IMGUI_DEFINE_MATH_OPERATORS
#endif
#import "app/platform/app_delegate.h"
#import "app/controller.h"
#import "util/file_util.h"
#import "app/editor/editor.h"
#import "app/rom.h"
#include <span>
#include <vector>
using std::span;
#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];
// Disable automatic UI state persistence to prevent crashes
// macOS NSPersistentUIManager can crash if state gets corrupted
[[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"NSQuitAlwaysKeepsWindows"];
}
- (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::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 "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,123 @@
// Windows and Linux implementation of FileDialogWrapper using nativefiledialog-extended
#include "util/file_util.h"
#include <nfd.h>
#include <filesystem>
#include <vector>
#include <string>
namespace yaze {
namespace util {
std::string FileDialogWrapper::ShowOpenFileDialog() {
nfdchar_t* outPath = nullptr;
nfdfilteritem_t filterItem[2] = {{"ROM Files", "sfc,smc"}, {"All Files", "*"}};
nfdresult_t result = NFD_OpenDialog(&outPath, filterItem, 2, nullptr);
if (result == NFD_OKAY) {
std::string path(outPath);
NFD_FreePath(outPath);
return path;
}
return "";
}
std::string FileDialogWrapper::ShowOpenFolderDialog() {
nfdchar_t* outPath = nullptr;
nfdresult_t result = NFD_PickFolder(&outPath, nullptr);
if (result == NFD_OKAY) {
std::string path(outPath);
NFD_FreePath(outPath);
return path;
}
return "";
}
std::string FileDialogWrapper::ShowSaveFileDialog(const std::string& default_name,
const std::string& default_extension) {
nfdchar_t* outPath = nullptr;
nfdfilteritem_t filterItem[1] = {{default_extension.empty() ? "All Files" : default_extension.c_str(),
default_extension.empty() ? "*" : default_extension.c_str()}};
nfdresult_t result = NFD_SaveDialog(&outPath,
default_extension.empty() ? nullptr : filterItem,
default_extension.empty() ? 0 : 1,
nullptr,
default_name.c_str());
if (result == NFD_OKAY) {
std::string path(outPath);
NFD_FreePath(outPath);
return path;
}
return "";
}
std::vector<std::string> FileDialogWrapper::GetSubdirectoriesInFolder(
const std::string& folder_path) {
std::vector<std::string> subdirs;
try {
for (const auto& entry : std::filesystem::directory_iterator(folder_path)) {
if (entry.is_directory()) {
subdirs.push_back(entry.path().string());
}
}
} catch (...) {
// Return empty vector on error
}
return subdirs;
}
std::vector<std::string> FileDialogWrapper::GetFilesInFolder(
const std::string& folder_path) {
std::vector<std::string> files;
try {
for (const auto& entry : std::filesystem::directory_iterator(folder_path)) {
if (entry.is_regular_file()) {
files.push_back(entry.path().string());
}
}
} catch (...) {
// Return empty vector on error
}
return files;
}
// Delegate to main implementations
std::string FileDialogWrapper::ShowOpenFileDialogNFD() {
return ShowOpenFileDialog();
}
std::string FileDialogWrapper::ShowOpenFileDialogBespoke() {
return ShowOpenFileDialog();
}
std::string FileDialogWrapper::ShowSaveFileDialogNFD(const std::string& default_name,
const std::string& default_extension) {
return ShowSaveFileDialog(default_name, default_extension);
}
std::string FileDialogWrapper::ShowSaveFileDialogBespoke(const std::string& default_name,
const std::string& default_extension) {
return ShowSaveFileDialog(default_name, default_extension);
}
std::string FileDialogWrapper::ShowOpenFolderDialogNFD() {
return ShowOpenFolderDialog();
}
std::string FileDialogWrapper::ShowOpenFolderDialogBespoke() {
return ShowOpenFolderDialog();
}
} // namespace util
} // namespace yaze

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/core/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/core/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

117
src/app/platform/timing.h Normal file
View File

@@ -0,0 +1,117 @@
#ifndef YAZE_APP_CORE_TIMING_H
#define YAZE_APP_CORE_TIMING_H
#include <SDL.h>
#include <cstdint>
namespace yaze {
/**
* @class TimingManager
* @brief Provides accurate timing for animations and frame pacing
*
* This class solves the issue where ImGui::GetIO().DeltaTime only updates
* when events are processed (mouse movement, etc). It uses SDL's performance
* counter to provide accurate timing regardless of input events.
*/
class TimingManager {
public:
static TimingManager& Get() {
static TimingManager instance;
return instance;
}
/**
* @brief Update the timing manager (call once per frame)
* @return The delta time since last update in seconds
*/
float Update() {
uint64_t current_time = SDL_GetPerformanceCounter();
float delta_time = 0.0f;
if (last_time_ > 0) {
delta_time = (current_time - last_time_) / static_cast<float>(frequency_);
// Clamp delta time to prevent huge jumps (e.g., when debugging)
if (delta_time > 0.1f) {
delta_time = 0.1f;
}
accumulated_time_ += delta_time;
frame_count_++;
// Update FPS counter once per second
if (accumulated_time_ >= 1.0f) {
fps_ = static_cast<float>(frame_count_) / accumulated_time_;
frame_count_ = 0;
accumulated_time_ = 0.0f;
}
}
last_time_ = current_time;
last_delta_time_ = delta_time;
return delta_time;
}
/**
* @brief Get the last frame's delta time in seconds
*/
float GetDeltaTime() const {
return last_delta_time_;
}
/**
* @brief Get current FPS
*/
float GetFPS() const {
return fps_;
}
/**
* @brief Get total elapsed time since first update
*/
float GetElapsedTime() const {
if (last_time_ == 0) return 0.0f;
uint64_t current_time = SDL_GetPerformanceCounter();
return (current_time - first_time_) / static_cast<float>(frequency_);
}
/**
* @brief Reset the timing state
*/
void Reset() {
last_time_ = 0;
first_time_ = SDL_GetPerformanceCounter();
accumulated_time_ = 0.0f;
frame_count_ = 0;
fps_ = 0.0f;
last_delta_time_ = 0.0f;
}
private:
TimingManager() {
frequency_ = SDL_GetPerformanceFrequency();
first_time_ = SDL_GetPerformanceCounter();
last_time_ = 0;
accumulated_time_ = 0.0f;
frame_count_ = 0;
fps_ = 0.0f;
last_delta_time_ = 0.0f;
}
uint64_t frequency_;
uint64_t first_time_;
uint64_t last_time_;
float accumulated_time_;
uint32_t frame_count_;
float fps_;
float last_delta_time_;
TimingManager(const TimingManager&) = delete;
TimingManager& operator=(const TimingManager&) = delete;
};
} // namespace yaze
#endif // YAZE_APP_CORE_TIMING_H

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::Controller *controller;
@end
#endif
#else
#ifdef __OBJC__
@interface AppViewController : UIViewController <MTKViewDelegate>
@property(nonatomic) yaze::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

View File

@@ -0,0 +1 @@
IDI_ICON1 ICON DISCARDABLE "yaze.ico"

237
src/app/platform/window.cc Normal file
View File

@@ -0,0 +1,237 @@
#include "app/platform/window.h"
#include <filesystem>
#include "absl/status/status.h"
#include "absl/strings/str_format.h"
#include "app/platform/font_loader.h"
#include "util/sdl_deleter.h"
#include "util/log.h"
#include "app/gfx/resource/arena.h"
#include "app/gui/core/style.h"
#include "imgui/backends/imgui_impl_sdl2.h"
#include "imgui/backends/imgui_impl_sdlrenderer2.h"
#include "imgui/imgui.h"
namespace {
// Custom ImGui assertion handler to prevent crashes
void ImGuiAssertionHandler(const char* expr, const char* file, int line,
const char* msg) {
// Log the assertion instead of crashing
LOG_ERROR("ImGui", "Assertion failed: %s\nFile: %s:%d\nMessage: %s",
expr, file, line, msg ? msg : "");
// Try to recover by resetting ImGui state
static int error_count = 0;
error_count++;
if (error_count > 5) {
LOG_ERROR("ImGui", "Too many assertions, resetting workspace settings...");
// Backup and reset imgui.ini
try {
if (std::filesystem::exists("imgui.ini")) {
std::filesystem::copy("imgui.ini", "imgui.ini.backup",
std::filesystem::copy_options::overwrite_existing);
std::filesystem::remove("imgui.ini");
LOG_INFO("ImGui", "Workspace settings reset. Backup saved to imgui.ini.backup");
}
} catch (const std::exception& e) {
LOG_ERROR("ImGui", "Failed to reset workspace: %s", e.what());
}
error_count = 0; // Reset counter
}
// Don't abort - let the program continue
// The assertion is logged and workspace can be reset if needed
}
} // namespace
namespace yaze {
namespace core {
// Global flag for window resize state (used by emulator to pause)
bool g_window_is_resizing = false;
absl::Status CreateWindow(Window& window, gfx::IRenderer* renderer, int flags) {
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER) != 0) {
return absl::InternalError(
absl::StrFormat("SDL_Init: %s\n", SDL_GetError()));
}
SDL_DisplayMode display_mode;
SDL_GetCurrentDisplayMode(0, &display_mode);
int screen_width = display_mode.w * 0.8;
int screen_height = display_mode.h * 0.8;
window.window_ = std::unique_ptr<SDL_Window, util::SDL_Deleter>(
SDL_CreateWindow("Yet Another Zelda3 Editor", SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED, screen_width, screen_height,
flags),
util::SDL_Deleter());
if (window.window_ == nullptr) {
return absl::InternalError(
absl::StrFormat("SDL_CreateWindow: %s\n", SDL_GetError()));
}
// Only initialize renderer if one is provided and not already initialized
if (renderer && !renderer->GetBackendRenderer()) {
if (!renderer->Initialize(window.window_.get())) {
return absl::InternalError("Failed to initialize renderer");
}
}
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
// Set custom assertion handler to prevent crashes
#ifdef IMGUI_DISABLE_DEFAULT_ASSERT_HANDLER
ImGui::SetAssertHandler(ImGuiAssertionHandler);
#else
// For release builds, assertions are already disabled
LOG_INFO("Window", "ImGui assertions are disabled in this build");
#endif
// Initialize ImGuiTestEngine after ImGui context is created
#if defined(YAZE_ENABLE_IMGUI_TEST_ENGINE) && YAZE_ENABLE_IMGUI_TEST_ENGINE
test::TestManager::Get().InitializeUITesting();
#endif
// Initialize ImGui backends if renderer is provided
if (renderer) {
SDL_Renderer* sdl_renderer = static_cast<SDL_Renderer*>(renderer->GetBackendRenderer());
ImGui_ImplSDL2_InitForSDLRenderer(window.window_.get(), sdl_renderer);
ImGui_ImplSDLRenderer2_Init(sdl_renderer);
}
RETURN_IF_ERROR(LoadPackageFonts());
// Apply original YAZE colors as fallback, then try to load theme system
gui::ColorsYaze();
// Audio is now handled by IAudioBackend in Emulator class
// Keep legacy buffer allocation for backwards compatibility
if (window.audio_device_ == 0) {
const int audio_frequency = 48000;
const size_t buffer_size = (audio_frequency / 50) * 2; // 1920 int16_t for stereo PAL
// CRITICAL FIX: Allocate buffer as ARRAY, not single value
// Use new[] with shared_ptr custom deleter for proper array allocation
window.audio_buffer_ = std::shared_ptr<int16_t>(
new int16_t[buffer_size],
std::default_delete<int16_t[]>());
// Note: Actual audio device is created by Emulator's IAudioBackend
// This maintains compatibility with existing code paths
LOG_INFO("Window", "Audio buffer allocated: %zu int16_t samples (backend in Emulator)",
buffer_size);
}
return absl::OkStatus();
}
absl::Status ShutdownWindow(Window& window) {
SDL_PauseAudioDevice(window.audio_device_, 1);
SDL_CloseAudioDevice(window.audio_device_);
// Stop test engine WHILE ImGui context is still valid
#ifdef YAZE_ENABLE_IMGUI_TEST_ENGINE
test::TestManager::Get().StopUITesting();
#endif
// TODO: BAD FIX, SLOW SHUTDOWN TAKES TOO LONG NOW
// CRITICAL FIX: Shutdown graphics arena FIRST
// This ensures all textures are destroyed while renderer is still valid
LOG_INFO("Window", "Shutting down graphics arena...");
gfx::Arena::Get().Shutdown();
// Shutdown ImGui implementations (after Arena but before context)
LOG_INFO("Window", "Shutting down ImGui implementations...");
ImGui_ImplSDL2_Shutdown();
ImGui_ImplSDLRenderer2_Shutdown();
// Destroy ImGui context
LOG_INFO("Window", "Destroying ImGui context...");
ImGui::DestroyContext();
// NOW destroy test engine context (after ImGui context is destroyed)
#ifdef YAZE_ENABLE_IMGUI_TEST_ENGINE
test::TestManager::Get().DestroyUITestingContext();
#endif
// Finally destroy window
LOG_INFO("Window", "Destroying window...");
SDL_DestroyWindow(window.window_.get());
LOG_INFO("Window", "Shutting down SDL...");
SDL_Quit();
LOG_INFO("Window", "Shutdown complete");
return absl::OkStatus();
}
absl::Status HandleEvents(Window& window) {
SDL_Event event;
ImGuiIO& io = ImGui::GetIO();
// Protect SDL_PollEvent from crashing the app
// macOS NSPersistentUIManager corruption can crash during event polling
while (SDL_PollEvent(&event)) {
ImGui_ImplSDL2_ProcessEvent(&event);
switch (event.type) {
case SDL_KEYDOWN:
case SDL_KEYUP: {
io.KeyShift = ((SDL_GetModState() & KMOD_SHIFT) != 0);
io.KeyCtrl = ((SDL_GetModState() & KMOD_CTRL) != 0);
io.KeyAlt = ((SDL_GetModState() & KMOD_ALT) != 0);
io.KeySuper = ((SDL_GetModState() & KMOD_GUI) != 0);
break;
}
case SDL_WINDOWEVENT:
switch (event.window.event) {
case SDL_WINDOWEVENT_CLOSE:
window.active_ = false;
break;
case SDL_WINDOWEVENT_SIZE_CHANGED:
case SDL_WINDOWEVENT_RESIZED:
// Update display size for both resize and size_changed events
io.DisplaySize.x = static_cast<float>(event.window.data1);
io.DisplaySize.y = static_cast<float>(event.window.data2);
core::g_window_is_resizing = true;
break;
case SDL_WINDOWEVENT_MINIMIZED:
case SDL_WINDOWEVENT_HIDDEN:
// Window is minimized/hidden
g_window_is_resizing = false;
break;
case SDL_WINDOWEVENT_RESTORED:
case SDL_WINDOWEVENT_SHOWN:
case SDL_WINDOWEVENT_EXPOSED:
// Window is restored - clear resize flag
g_window_is_resizing = false;
break;
}
break;
}
}
int mouseX;
int mouseY;
const int buttons = SDL_GetMouseState(&mouseX, &mouseY);
io.DeltaTime = 1.0f / 60.0f;
io.MousePos = ImVec2(static_cast<float>(mouseX), static_cast<float>(mouseY));
io.MouseDown[0] = buttons & SDL_BUTTON(SDL_BUTTON_LEFT);
io.MouseDown[1] = buttons & SDL_BUTTON(SDL_BUTTON_RIGHT);
io.MouseDown[2] = buttons & SDL_BUTTON(SDL_BUTTON_MIDDLE);
int wheel = 0;
io.MouseWheel = static_cast<float>(wheel);
return absl::OkStatus();
}
} // namespace core
} // namespace yaze

33
src/app/platform/window.h Normal file
View File

@@ -0,0 +1,33 @@
#ifndef YAZE_CORE_WINDOW_H_
#define YAZE_CORE_WINDOW_H_
#include <SDL.h>
#include <memory>
#include "absl/status/status.h"
#include "absl/strings/str_format.h"
#include "util/sdl_deleter.h"
#include "app/gfx/core/bitmap.h"
#include "app/gfx/backend/irenderer.h"
namespace yaze {
namespace core {
struct Window {
std::shared_ptr<SDL_Window> window_;
SDL_AudioDeviceID audio_device_;
std::shared_ptr<int16_t> audio_buffer_;
bool active_ = true;
};
// Legacy CreateWindow (deprecated - use Controller::OnEntry instead)
// Kept for backward compatibility with test code
absl::Status CreateWindow(Window& window, gfx::IRenderer* renderer = nullptr,
int flags = SDL_WINDOW_RESIZABLE);
absl::Status HandleEvents(Window &window);
absl::Status ShutdownWindow(Window &window);
} // namespace core
} // namespace yaze
#endif // YAZE_CORE_WINDOW_H_