summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/mac/CMakeLists.txt17
-rw-r--r--app/mac/controller-adapter-gamepad.hh16
-rw-r--r--app/mac/controller-adapter-gamepad.mm5
-rw-r--r--app/mac/controller-adapter-keyboard.hh14
-rw-r--r--app/mac/controller-adapter-keyboard.mm44
-rw-r--r--app/mac/controller-adapter.hh11
-rw-r--r--app/mac/main.mm24
-rw-r--r--app/mac/scene.hh12
-rw-r--r--app/mac/scene.mm109
-rw-r--r--app/mac/view-controller.hh17
-rw-r--r--app/mac/view-controller.mm46
11 files changed, 200 insertions, 115 deletions
diff --git a/app/mac/CMakeLists.txt b/app/mac/CMakeLists.txt
index a4f25c9..d6ea464 100644
--- a/app/mac/CMakeLists.txt
+++ b/app/mac/CMakeLists.txt
@@ -1,7 +1,8 @@
add_executable(nes_mac)
-find_library(FRAMEWORK_COCOA Cocoa REQUIRED)
-find_library(FRAMEWORK_SPRITEKIT SpriteKit REQUIRED)
+find_library(FRAMEWORK_Cocoa Cocoa REQUIRED)
+find_library(FRAMEWORK_SpriteKit SpriteKit REQUIRED)
+find_library(FRAMEWORK_GameController GameController REQUIRED)
target_include_directories(nes_mac PRIVATE .)
target_link_libraries(
@@ -9,12 +10,18 @@ target_link_libraries(
PRIVATE
nes::options
nes::nes
- ${FRAMEWORK_COCOA}
- ${FRAMEWORK_SPRITEKIT})
+ ${FRAMEWORK_Cocoa}
+ ${FRAMEWORK_SpriteKit}
+ ${FRAMEWORK_GameController})
target_sources(
nes_mac
PRIVATE
main.mm
scene.hh
- scene.mm) \ No newline at end of file
+ scene.mm
+ view-controller.hh
+ view-controller.mm
+ controller-adapter.hh
+ controller-adapter-keyboard.hh
+ controller-adapter-keyboard.mm) \ No newline at end of file
diff --git a/app/mac/controller-adapter-gamepad.hh b/app/mac/controller-adapter-gamepad.hh
new file mode 100644
index 0000000..14bfeae
--- /dev/null
+++ b/app/mac/controller-adapter-gamepad.hh
@@ -0,0 +1,16 @@
+//
+// Created by Hannes Schulze on 29.01.25.
+//
+
+#ifndef NES_CONTROLLER_ADAPTER_GAMEPAD_HH
+#define NES_CONTROLLER_ADAPTER_GAMEPAD_HH
+
+
+
+class ControllerAdapterGamepad {
+
+};
+
+
+
+#endif //NES_CONTROLLER_ADAPTER_GAMEPAD_HH
diff --git a/app/mac/controller-adapter-gamepad.mm b/app/mac/controller-adapter-gamepad.mm
new file mode 100644
index 0000000..132fa85
--- /dev/null
+++ b/app/mac/controller-adapter-gamepad.mm
@@ -0,0 +1,5 @@
+//
+// Created by Hannes Schulze on 29.01.25.
+//
+
+#include "controller-adapter-gamepad.hh"
diff --git a/app/mac/controller-adapter-keyboard.hh b/app/mac/controller-adapter-keyboard.hh
new file mode 100644
index 0000000..d91f069
--- /dev/null
+++ b/app/mac/controller-adapter-keyboard.hh
@@ -0,0 +1,14 @@
+#pragma once
+
+#import "controller-adapter.hh"
+#import <GameController/GameController.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface ControllerAdapterKeyboard : NSObject <ControllerAdapter>
+
+@property (readonly, nullable) GCKeyboard* keyboard;
+
+@end
+
+NS_ASSUME_NONNULL_END \ No newline at end of file
diff --git a/app/mac/controller-adapter-keyboard.mm b/app/mac/controller-adapter-keyboard.mm
new file mode 100644
index 0000000..ad3dda8
--- /dev/null
+++ b/app/mac/controller-adapter-keyboard.mm
@@ -0,0 +1,44 @@
+#import "controller-adapter-keyboard.hh"
+
+@implementation ControllerAdapterKeyboard
+
+- (instancetype)init
+{
+ self = [super init];
+ if (self)
+ {
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(handleKeyboardConnected:)
+ name:GCKeyboardDidConnectNotification
+ object:nil];
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+}
+
+- (void)handleKeyboardConnected:(NSNotification*)notification
+{
+ _keyboard = [GCKeyboard coalescedKeyboard];
+}
+
+- (nes::button_mask)readPressedButtons
+{
+ auto profile = [[self keyboard] keyboardInput];
+
+ auto res = nes::button_mask{};
+ if ([[profile buttonForKeyCode:GCKeyCodeUpArrow] isPressed]) { res.add(nes::buttons::up); }
+ if ([[profile buttonForKeyCode:GCKeyCodeDownArrow] isPressed]) { res.add(nes::buttons::down); }
+ if ([[profile buttonForKeyCode:GCKeyCodeLeftArrow] isPressed]) { res.add(nes::buttons::left); }
+ if ([[profile buttonForKeyCode:GCKeyCodeRightArrow] isPressed]) { res.add(nes::buttons::right); }
+ if ([[profile buttonForKeyCode:GCKeyCodeKeyZ] isPressed]) { res.add(nes::buttons::a); }
+ if ([[profile buttonForKeyCode:GCKeyCodeKeyX] isPressed]) { res.add(nes::buttons::b); }
+ if ([[profile buttonForKeyCode:GCKeyCodeReturnOrEnter] isPressed]) { res.add(nes::buttons::start); }
+ if ([[profile buttonForKeyCode:GCKeyCodeSpacebar] isPressed]) { res.add(nes::buttons::select); }
+ return res;
+}
+
+@end \ No newline at end of file
diff --git a/app/mac/controller-adapter.hh b/app/mac/controller-adapter.hh
new file mode 100644
index 0000000..d0fc33a
--- /dev/null
+++ b/app/mac/controller-adapter.hh
@@ -0,0 +1,11 @@
+#pragma once
+
+#import "nes/util/button-mask.hh"
+#import <Foundation/Foundation.h>
+
+/// Adapts inputs from a GCPhysicalInputProfile to a NES button mask.
+@protocol ControllerAdapter <NSObject>
+
+- (nes::button_mask)readPressedButtons;
+
+@end \ No newline at end of file
diff --git a/app/mac/main.mm b/app/mac/main.mm
index 5ca7c5c..4e96cea 100644
--- a/app/mac/main.mm
+++ b/app/mac/main.mm
@@ -1,8 +1,7 @@
-#import "scene.hh"
+#import "view-controller.hh"
#import <iostream>
#import <Cocoa/Cocoa.h>
-#import <SpriteKit/SpriteKit.h>
int main(int argc, char** argv)
{
@@ -34,23 +33,12 @@ int main(int argc, char** argv)
[mainMenu addItem:item];
[app setMainMenu:mainMenu];
- auto scene = [[Scene alloc] initWithFilePath:filePath];
- auto minSize = [scene size];
-
- auto view = [[SKView alloc] init];
- [view setIgnoresSiblingOrder:YES];
- [view setShowsFPS:YES];
- [view presentScene:scene];
-
- auto window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0., 0., minSize.width * 2, minSize.height * 2)
- styleMask:(NSWindowStyleMaskTitled | NSWindowStyleMaskClosable |
- NSWindowMiniaturizeButton | NSWindowStyleMaskResizable)
- backing:NSBackingStoreBuffered
- defer:false];
+ auto viewController = [[ViewController alloc] initWithFilePath:filePath];
+ auto window = [NSWindow windowWithContentViewController:viewController];
[window setTitle:@"NES"];
- [window setContentAspectRatio:minSize];
- [window setContentMinSize:minSize];
- [window setContentView:view];
+ [window setContentAspectRatio:[viewController minimumDisplaySize]];
+ [window setContentMinSize:[viewController minimumDisplaySize]];
+ [window setContentSize:[viewController preferredDisplaySize]];
[window makeKeyAndOrderFront:nil];
[app activateIgnoringOtherApps:YES];
diff --git a/app/mac/scene.hh b/app/mac/scene.hh
index 1660e66..ed56f2f 100644
--- a/app/mac/scene.hh
+++ b/app/mac/scene.hh
@@ -1,12 +1,22 @@
#pragma once
+#import "controller-adapter.hh"
#import <SpriteKit/SpriteKit.h>
+NS_ASSUME_NONNULL_BEGIN
+
@interface Scene : SKScene
- (instancetype)initWithFilePath:(NSString*)filePath NS_DESIGNATED_INITIALIZER;
+ (instancetype)sceneWithSize:(CGSize)size NS_UNAVAILABLE;
- (instancetype)initWithSize:(CGSize)size NS_UNAVAILABLE;
+- (instancetype)initWithCoder:(NSCoder*)coder NS_UNAVAILABLE;
+- (instancetype)init NS_UNAVAILABLE;
+
+@property (nonatomic, nullable) id<ControllerAdapter> primaryController;
+@property (nonatomic, nullable) id<ControllerAdapter> secondaryController;
+
+@end
-@end \ No newline at end of file
+NS_ASSUME_NONNULL_END \ No newline at end of file
diff --git a/app/mac/scene.mm b/app/mac/scene.mm
index b71f9f6..0d50a7e 100644
--- a/app/mac/scene.mm
+++ b/app/mac/scene.mm
@@ -1,7 +1,6 @@
#import "scene.hh"
#import "nes/nes.hh"
#import "nes/util/display.hh"
-#import "nes/util/rgb.hh"
#import <memory>
#import <optional>
#import <iostream>
@@ -32,13 +31,10 @@ namespace
buffer_back_ = buffer_front_;
buffer_front_ = tmp;
bytes_back_ = static_cast<std::uint8_t*>([buffer_back_ mutableBytes]);
- }
- auto actually_switch_buffers() -> void
- {
- auto texture = [SKTexture textureWithData:buffer_front_ size:CGSizeMake(width, height) flipped:YES];
- [texture setFilteringMode:SKTextureFilteringNearest];
- [node_ setTexture:texture];
+ auto texture = [SKTexture textureWithData:buffer_front_ size:CGSizeMake(width, height) flipped:YES];
+ [texture setFilteringMode:SKTextureFilteringNearest];
+ [node_ setTexture:texture];
}
virtual auto set(unsigned x, unsigned y, nes::rgb value) -> void override
@@ -71,9 +67,9 @@ namespace
@implementation Scene
{
- std::unique_ptr<display_impl> display_;
- std::unique_ptr<nes::nes> nes_;
- std::optional<NSTimeInterval> last_time_;
+ std::unique_ptr<display_impl> _display;
+ std::unique_ptr<nes::nes> _nes;
+ std::optional<NSTimeInterval> _lastTimestamp;
}
- (instancetype)initWithFilePath:(NSString*)filePath
@@ -95,97 +91,28 @@ namespace
std::abort();
}
- self->display_ = std::make_unique<display_impl>(node);
- self->nes_ = std::make_unique<nes::nes>(std::move(cartridge), *self->display_);
+ _display = std::make_unique<display_impl>(node);
+ _nes = std::make_unique<nes::nes>(std::move(cartridge), *self->_display);
}
return self;
}
-- (void)keyDown:(NSEvent*)event
-{
- auto& controller_1 = self->nes_->ref_controller_1();
-
- switch ([event keyCode])
- {
- case 0x7e: // up arrow
- controller_1.ref_pressed().add(nes::buttons::up);
- break;
- case 0x7d: // down arrow
- controller_1.ref_pressed().add(nes::buttons::down);
- break;
- case 0x7b: // left arrow
- controller_1.ref_pressed().add(nes::buttons::left);
- break;
- case 0x7c: // right arrow
- controller_1.ref_pressed().add(nes::buttons::right);
- break;
- case 0x6: // "Z"
- controller_1.ref_pressed().add(nes::buttons::a);
- break;
- case 0x7: // "X"
- controller_1.ref_pressed().add(nes::buttons::b);
- break;
- case 0x24: // return
- controller_1.ref_pressed().add(nes::buttons::start);
- break;
- case 0x31: // space
- controller_1.ref_pressed().add(nes::buttons::select);
- break;
- default:
- [super keyDown:event];
- break;
- }
-}
-
-- (void)keyUp:(NSEvent*)event
+- (void)update:(NSTimeInterval)currentTime
{
- auto& controller_1 = self->nes_->ref_controller_1();
+ _nes->ref_controller_1().set_pressed(_primaryController ? [_primaryController readPressedButtons] : nes::button_mask{});
+ _nes->ref_controller_2().set_pressed(_secondaryController ? [_secondaryController readPressedButtons] : nes::button_mask{});
- switch ([event keyCode])
- {
- case 0x7e: // up arrow
- controller_1.ref_pressed().remove(nes::buttons::up);
- break;
- case 0x7d: // down arrow
- controller_1.ref_pressed().remove(nes::buttons::down);
- break;
- case 0x7b: // left arrow
- controller_1.ref_pressed().remove(nes::buttons::left);
- break;
- case 0x7c: // right arrow
- controller_1.ref_pressed().remove(nes::buttons::right);
- break;
- case 0x6: // "Z"
- controller_1.ref_pressed().remove(nes::buttons::a);
- break;
- case 0x7: // "X"
- controller_1.ref_pressed().remove(nes::buttons::b);
- break;
- case 0x24: // return
- controller_1.ref_pressed().remove(nes::buttons::start);
- break;
- case 0x31: // space
- controller_1.ref_pressed().remove(nes::buttons::select);
- break;
- default:
- [super keyUp:event];
- break;
- }
-}
+ auto const lastTimestamp = _lastTimestamp.value_or(currentTime);
+ _lastTimestamp = currentTime;
+ auto deltaMicroseconds = static_cast<std::uint64_t>((currentTime - lastTimestamp) * 1000. * 1000.);
+ deltaMicroseconds = std::min(deltaMicroseconds, std::uint64_t{ 50000 });
+ _nes->step(std::chrono::microseconds{ deltaMicroseconds });
-- (void)update:(NSTimeInterval)currentTime
-{
- auto const last_time = self->last_time_.value_or(currentTime);
- last_time_ = currentTime;
- auto delta_us = static_cast<std::uint64_t>((currentTime - last_time) * 1000. * 1000.);
- delta_us = std::min(delta_us, std::uint64_t{ 50000 });
- self->nes_->step(std::chrono::microseconds{ delta_us });
- if (self->nes_->get_status() != nes::status::success)
+ if (_nes->get_status() != nes::status::success)
{
- std::cerr << "Invalid state: " << to_string(self->nes_->get_status()) << std::endl;
+ std::cerr << "Invalid state: " << to_string(_nes->get_status()) << std::endl;
std::abort();
}
- self->display_->actually_switch_buffers();
}
@end \ No newline at end of file
diff --git a/app/mac/view-controller.hh b/app/mac/view-controller.hh
new file mode 100644
index 0000000..4947b99
--- /dev/null
+++ b/app/mac/view-controller.hh
@@ -0,0 +1,17 @@
+#pragma once
+
+#import <Cocoa/Cocoa.h>
+#import <GameController/GameController.h>
+
+@interface ViewController : GCEventViewController
+
+- (instancetype)initWithFilePath:(NSString*)filePath NS_DESIGNATED_INITIALIZER;
+
+- (instancetype)initWithNibName:(NSString*)nibNameOrNil bundle:(NSBundle*)nibBundleOrNil NS_UNAVAILABLE;
+- (instancetype)initWithCoder:(NSCoder*)coder NS_UNAVAILABLE;
+- (instancetype)init NS_UNAVAILABLE;
+
+@property (readonly) NSSize minimumDisplaySize;
+@property (readonly) NSSize preferredDisplaySize;
+
+@end \ No newline at end of file
diff --git a/app/mac/view-controller.mm b/app/mac/view-controller.mm
new file mode 100644
index 0000000..8a1fcbd
--- /dev/null
+++ b/app/mac/view-controller.mm
@@ -0,0 +1,46 @@
+#import "view-controller.hh"
+#import "controller-adapter-keyboard.hh"
+#import "scene.hh"
+
+@implementation ViewController
+{
+ Scene* _scene;
+ NSSize _sceneSize;
+ ControllerAdapterKeyboard* _keyboardController;
+}
+
+- (instancetype)initWithFilePath:(NSString*)filePath
+{
+ self = [super initWithNibName:nil bundle:nil];
+ if (self)
+ {
+ _scene = [[Scene alloc] initWithFilePath:filePath];
+ _sceneSize = [_scene size];
+
+ _keyboardController = [[ControllerAdapterKeyboard alloc] init];
+ [_scene setPrimaryController:_keyboardController];
+ }
+ return self;
+}
+
+- (void)loadView
+{
+ auto view = [[SKView alloc] init];
+ [view setIgnoresSiblingOrder:YES];
+ [view setShowsFPS:YES];
+ [view presentScene:_scene];
+
+ [self setView:view];
+}
+
+- (NSSize)minimumDisplaySize
+{
+ return _sceneSize;
+}
+
+- (NSSize)preferredDisplaySize
+{
+ return NSMakeSize(_sceneSize.width * 3., _sceneSize.height * 3.);
+}
+
+@end \ No newline at end of file