tuxanci2-engine
Game engine for tuxanci2
Log | Files | Refs | README | LICENSE
commit 30fa75b0df7b3a0c4efdb684ea4227b4c7ffa9f3
parent 4990e297f0e3d8a2cd709f88148d08e1c56a9e3b
Author: Connor Thomson <connor@gnufault.org>
Date: Sat, 24 Oct 2026 20:42:46 -0700
Add cocoa port
Diffstat:
4 files changed, 506 insertions(+), 0 deletions(-)
diff --git a/README.md b/README.md
@@ -5,6 +5,7 @@ Spirtiual successor to Tuxánci
Tuxánci currently supports:
- AAlib
+- Cocoa
- DOS
- Dreamcast
- N64
diff --git a/platforms/cocoa/.gitignore b/platforms/cocoa/.gitignore
@@ -0,0 +1 @@
+tuxanci2
diff --git a/platforms/cocoa/Makefile b/platforms/cocoa/Makefile
@@ -0,0 +1,42 @@
+MAKEFILE_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
+ROOT_DIR := $(abspath $(MAKEFILE_DIR)/../..)
+
+COCOA_CC ?= cc
+COCOA_BUILD ?= build/cocoa
+
+COCOA_TRIPLE := $(shell $(COCOA_CC) -dumpmachine 2>/dev/null)
+ifneq ($(findstring darwin,$(COCOA_TRIPLE))$(findstring macos,$(COCOA_TRIPLE)),)
+ COCOA_CFLAGS ?= -O2 -Wno-deprecated-declarations
+ COCOA_LDFLAGS ?=
+ COCOA_LDLIBS ?= -framework Cocoa
+else
+ COCOA_CFLAGS ?= -O2 $(filter-out -MMD -MP,$(shell gnustep-config --objc-flags))
+ COCOA_LDFLAGS ?=
+ COCOA_LDLIBS ?= $(shell gnustep-config --gui-libs)
+endif
+
+COCOA_SOURCE := $(MAKEFILE_DIR)cocoa.m
+COCOA_BUILD_DIR := $(ROOT_DIR)/$(COCOA_BUILD)
+COCOA_LIBRARY := $(COCOA_BUILD_DIR)/tuxanci2.a
+TARGET := $(MAKEFILE_DIR)tuxanci2
+ROOT_INPUTS := $(shell find $(ROOT_DIR)/src -type f -name '*.c') \
+ $(wildcard $(ROOT_DIR)/include/*.h) \
+ $(ROOT_DIR)/include/ta_config.h.in \
+ $(shell find $(ROOT_DIR)/files -type f) \
+ $(ROOT_DIR)/Makefile
+
+.DEFAULT_GOAL := all
+.PHONY: all clean
+
+all: $(TARGET)
+
+$(COCOA_LIBRARY): $(ROOT_INPUTS)
+ $(MAKE) -C "$(ROOT_DIR)" CC="$(COCOA_CC)" BUILD_DIR="$(COCOA_BUILD)" all
+
+$(TARGET): $(COCOA_SOURCE) $(COCOA_LIBRARY)
+ $(COCOA_CC) $(COCOA_CFLAGS) -I"$(ROOT_DIR)/include" -I"$(COCOA_BUILD_DIR)" "$(COCOA_SOURCE)" "$(COCOA_LIBRARY)" $(COCOA_LDFLAGS) $(COCOA_LDLIBS) -o "$@"
+
+clean:
+ $(RM) "$(TARGET)"
+ $(MAKE) -C "$(ROOT_DIR)" BUILD_DIR="$(COCOA_BUILD)" clean
+
+\ No newline at end of file
diff --git a/platforms/cocoa/cocoa.m b/platforms/cocoa/cocoa.m
@@ -0,0 +1,461 @@
+#include "ta_api.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#import <Foundation/Foundation.h>
+#import <AppKit/AppKit.h>
+
+#define WINDOW_WIDTH 800
+#define WINDOW_HEIGHT 600
+#define WINDOW_BPP 32
+#define TARGET_FPS 60
+
+#if defined(__APPLE__) && defined(MAC_OS_X_VERSION_10_12) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_12
+#define TA_STYLE_MASK (NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable)
+#define TA_NUMPAD_FLAG NSEventModifierFlagNumericPad
+#define TA_SHIFT_FLAG NSEventModifierFlagShift
+#define TA_CTRL_FLAG NSEventModifierFlagControl
+#define TA_ALT_FLAG NSEventModifierFlagOption
+#define TA_CMD_FLAG NSEventModifierFlagCommand
+#define TA_CAPS_FLAG NSEventModifierFlagCapsLock
+#else
+#define TA_STYLE_MASK (NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask)
+#define TA_NUMPAD_FLAG NSNumericPadKeyMask
+#define TA_SHIFT_FLAG NSShiftKeyMask
+#define TA_CTRL_FLAG NSControlKeyMask
+#define TA_ALT_FLAG NSAlternateKeyMask
+#define TA_CMD_FLAG NSCommandKeyMask
+#define TA_CAPS_FLAG NSAlphaShiftKeyMask
+#endif
+
+#define KEYCODE_TABLE_SIZE 256
+
+static ta_keyboard_keys pressed_keys[KEYCODE_TABLE_SIZE];
+
+static ta_uint8 modifier_down[KEYCODE_TABLE_SIZE];
+
+static ta_keyboard_keys key_from_function_key(unichar c) {
+ if (c >= NSF1FunctionKey && c <= NSF1FunctionKey + 11) {
+ return (ta_keyboard_keys)(KEY_F1 + (c - NSF1FunctionKey));
+ }
+
+ switch (c) {
+ case NSUpArrowFunctionKey: return KEY_UP;
+ case NSDownArrowFunctionKey: return KEY_DOWN;
+ case NSLeftArrowFunctionKey: return KEY_LEFT;
+ case NSRightArrowFunctionKey: return KEY_RIGHT;
+ case NSDeleteFunctionKey: return KEY_DELETE;
+ case NSHomeFunctionKey: return KEY_HOME;
+ case NSEndFunctionKey: return KEY_END;
+ case NSPageUpFunctionKey: return KEY_PAGE_UP;
+ case NSPageDownFunctionKey: return KEY_PAGE_DOWN;
+ case NSInsertFunctionKey: return KEY_INSERT;
+ case NSPrintScreenFunctionKey: return KEY_PRINT_SCREEN;
+ case NSScrollLockFunctionKey: return KEY_SCROLL_LOCK;
+ case NSPauseFunctionKey: return KEY_PAUSE;
+ case NSMenuFunctionKey: return KEY_MENU;
+ default: return KEY_NONE;
+ }
+}
+
+static ta_keyboard_keys key_from_keypad(unichar c) {
+ if (c >= '0' && c <= '9') {
+ return (ta_keyboard_keys)(KEY_KP_0 + (c - '0'));
+ }
+
+ switch (c) {
+ case '.': return KEY_KP_DECIMAL;
+ case '/': return KEY_KP_DIVIDE;
+ case '*': return KEY_KP_MULTIPLY;
+ case '-': return KEY_KP_SUBTRACT;
+ case '+': return KEY_KP_ADD;
+ case '=': return KEY_KP_EQUALS;
+ case '\r':
+ case '\n':
+ case 3: return KEY_KP_ENTER;
+ default: return KEY_NONE;
+ }
+}
+
+static ta_keyboard_keys key_from_event(NSEvent *event) {
+ NSString *characters = [event charactersIgnoringModifiers];
+ if ([characters length] == 0) {
+ return KEY_NONE;
+ }
+
+ unichar c = [characters characterAtIndex: 0];
+
+ if (c >= 0xF700 && c <= 0xF8FF) {
+ return key_from_function_key(c);
+ }
+
+ if ([event modifierFlags] & TA_NUMPAD_FLAG) {
+ ta_keyboard_keys keypad = key_from_keypad(c);
+ if (keypad != KEY_NONE) {
+ return keypad;
+ }
+ }
+
+ if (c >= 'A' && c <= 'Z') {
+ c = (unichar)(c - 'A' + 'a');
+ }
+
+ if (c >= 32 && c <= 126) {
+ return (ta_keyboard_keys)c;
+ }
+
+ switch (c) {
+ case 8:
+ case 127: return KEY_BACKSPACE;
+ case 9:
+ case 0x19: return KEY_TAB;
+ case 13:
+ case 3: return KEY_ENTER;
+ case 27: return KEY_ESCAPE;
+ default: return KEY_NONE;
+ }
+}
+
+static void clear_input(void) {
+ memset(pressed_keys, 0, sizeof(pressed_keys));
+ memset(modifier_down, 0, sizeof(modifier_down));
+ ta_keyboard_clear();
+}
+
+@interface TAView : NSView {
+ NSBitmapImageRep *image;
+}
+- (void)present;
+@end
+
+@implementation TAView
+
+- (id)initWithFrame:(NSRect)frame {
+ self = [super initWithFrame: frame];
+ if (!self) {
+ return nil;
+ }
+
+ image = [[NSBitmapImageRep alloc]
+ initWithBitmapDataPlanes: NULL
+ pixelsWide: WINDOW_WIDTH
+ pixelsHigh: WINDOW_HEIGHT
+ bitsPerSample: 8
+ samplesPerPixel: 4
+ hasAlpha: YES
+ isPlanar: NO
+ colorSpaceName: NSDeviceRGBColorSpace
+ bytesPerRow: WINDOW_WIDTH * 4
+ bitsPerPixel: 32];
+
+ return self;
+}
+
+- (void)dealloc {
+ [image release];
+ [super dealloc];
+}
+
+- (BOOL)isOpaque {
+ return YES;
+}
+
+- (BOOL)acceptsFirstResponder {
+ return YES;
+}
+
+- (BOOL)acceptsFirstMouse:(NSEvent *)event {
+ return YES;
+}
+
+- (void)present {
+ if (!image || !ta_fb.buffers[0]) {
+ return;
+ }
+
+ const ta_uint32 *source = (const ta_uint32 *)ta_fb.buffers[0];
+ unsigned char *destination = [image bitmapData];
+ NSInteger row_bytes = [image bytesPerRow];
+ int x, y;
+
+ for (y = 0; y < WINDOW_HEIGHT; y++) {
+ unsigned char *row = destination + (NSInteger)y * row_bytes;
+
+ for (x = 0; x < WINDOW_WIDTH; x++) {
+ ta_uint32 pixel = source[y * WINDOW_WIDTH + x];
+
+ row[x * 4 + 0] = (unsigned char)((pixel >> 16) & 0xff);
+ row[x * 4 + 1] = (unsigned char)((pixel >> 8) & 0xff);
+ row[x * 4 + 2] = (unsigned char)(pixel & 0xff);
+ row[x * 4 + 3] = 0xff;
+ }
+ }
+
+ [self setNeedsDisplay: YES];
+}
+
+- (void)drawRect:(NSRect)rect {
+ if (!image) {
+ return;
+ }
+
+ [[NSGraphicsContext currentContext] setImageInterpolation: NSImageInterpolationNone];
+ [image drawInRect: [self bounds]];
+}
+
+- (void)keyDown:(NSEvent *)event {
+ if ([event isARepeat]) {
+ return;
+ }
+
+ unsigned short code = [event keyCode] % KEYCODE_TABLE_SIZE;
+ ta_keyboard_keys key = key_from_event(event);
+
+ if (pressed_keys[code] != KEY_NONE) {
+ ta_keyboard_key_up(pressed_keys[code]);
+ }
+
+ pressed_keys[code] = key;
+ ta_keyboard_key_down(key);
+}
+
+- (void)keyUp:(NSEvent *)event {
+ unsigned short code = [event keyCode] % KEYCODE_TABLE_SIZE;
+
+ if (pressed_keys[code] != KEY_NONE) {
+ ta_keyboard_key_up(pressed_keys[code]);
+ pressed_keys[code] = KEY_NONE;
+ }
+}
+
+- (void)flagsChanged:(NSEvent *)event {
+ unsigned short code = [event keyCode];
+ NSUInteger flags = [event modifierFlags];
+ ta_keyboard_keys key = KEY_NONE;
+ NSUInteger mask = 0;
+
+ switch (code) {
+ case 56: key = KEY_LEFT_SHIFT; mask = TA_SHIFT_FLAG; break;
+ case 60: key = KEY_RIGHT_SHIFT; mask = TA_SHIFT_FLAG; break;
+ case 59: key = KEY_LEFT_CTRL; mask = TA_CTRL_FLAG; break;
+ case 62: key = KEY_RIGHT_CTRL; mask = TA_CTRL_FLAG; break;
+ case 58: key = KEY_LEFT_ALT; mask = TA_ALT_FLAG; break;
+ case 61: key = KEY_RIGHT_ALT; mask = TA_ALT_FLAG; break;
+ case 55: key = KEY_LEFT_SUPER; mask = TA_CMD_FLAG; break;
+ case 54: key = KEY_RIGHT_SUPER; mask = TA_CMD_FLAG; break;
+ case 57: key = KEY_CAPS_LOCK; mask = TA_CAPS_FLAG; break;
+ default: return;
+ }
+
+ if (code >= KEYCODE_TABLE_SIZE) {
+ return;
+ }
+
+ if (key == KEY_CAPS_LOCK) {
+ ta_keyboard_key_down(key);
+ ta_keyboard_key_up(key);
+ return;
+ }
+
+ if (flags & mask) {
+ modifier_down[code] = 1;
+ ta_keyboard_key_down(key);
+ } else if (modifier_down[code]) {
+ modifier_down[code] = 0;
+ ta_keyboard_key_up(key);
+ }
+}
+
+@end
+
+@interface TAController : NSObject {
+ NSWindow *window;
+ TAView *view;
+ char title[256];
+}
+- (void)start;
+- (void)tick:(NSTimer *)timer;
+@end
+
+@implementation TAController
+
+- (void)start {
+ NSRect content = NSMakeRect(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
+
+ window = [[NSWindow alloc] initWithContentRect: content
+ styleMask: TA_STYLE_MASK
+ backing: NSBackingStoreBuffered
+ defer: NO];
+ [window setReleasedWhenClosed: NO];
+ [window setDelegate: self];
+
+ view = [[TAView alloc] initWithFrame: content];
+ [window setContentView: view];
+ [window makeFirstResponder: view];
+ [window center];
+
+ title[0] = '\0';
+ [self tick: nil];
+ [window makeKeyAndOrderFront: nil];
+
+ NSTimer *timer = [NSTimer timerWithTimeInterval: 1.0 / TARGET_FPS
+ target: self
+ selector: @selector(tick:)
+ userInfo: nil
+ repeats: YES];
+#ifdef __APPLE__
+ [[NSRunLoop currentRunLoop] addTimer: timer forMode: NSRunLoopCommonModes];
+#else
+ [[NSRunLoop currentRunLoop] addTimer: timer forMode: NSDefaultRunLoopMode];
+#endif
+}
+
+- (void)tick:(NSTimer *)timer {
+ ta_main_update();
+
+ if (ta_title && strncmp(title, ta_title, sizeof(title)) != 0) {
+ strncpy(title, ta_title, sizeof(title) - 1);
+ title[sizeof(title) - 1] = '\0';
+ [window setTitle: [NSString stringWithUTF8String: title]];
+ }
+
+ [view present];
+}
+
+- (void)applicationDidFinishLaunching:(NSNotification *)notification {
+#ifdef __APPLE__
+ if ([NSApp respondsToSelector: @selector(setActivationPolicy:)]) {
+ [NSApp setActivationPolicy: NSApplicationActivationPolicyRegular];
+ }
+#endif
+ [NSApp activateIgnoringOtherApps: YES];
+}
+
+- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)application {
+ return YES;
+}
+
+- (void)windowDidResignKey:(NSNotification *)notification {
+ clear_input();
+}
+
+@end
+
+static void build_menu(void) {
+ NSMenu *menubar = [[[NSMenu alloc] init] autorelease];
+ NSMenuItem *app_item = [[[NSMenuItem alloc] initWithTitle: @"" action: NULL keyEquivalent: @""] autorelease];
+ NSMenu *app_menu = [[[NSMenu alloc] initWithTitle: @""] autorelease];
+ NSMenuItem *quit_item = [[[NSMenuItem alloc] initWithTitle: @"Quit"
+ action: @selector(terminate:)
+ keyEquivalent: @"q"] autorelease];
+
+ [app_menu addItem: quit_item];
+ [app_item setSubmenu: app_menu];
+ [menubar addItem: app_item];
+ [NSApp setMainMenu: menubar];
+}
+
+int main(void) {
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+
+ ta_fb.width = WINDOW_WIDTH;
+ ta_fb.height = WINDOW_HEIGHT;
+ ta_fb.bpp = WINDOW_BPP;
+
+ ta_main_init();
+
+ clear_input();
+
+ [NSApplication sharedApplication];
+
+ TAController *controller = [[TAController alloc] init];
+ [NSApp setDelegate: controller];
+ build_menu();
+ [controller start];
+
+ [NSApp run];
+
+ [controller release];
+ [pool release];
+
+ return 0;
+}
+
+void *ta_malloc(ta_size size) {
+ return malloc(size);
+}
+
+void *ta_realloc(void *ptr, ta_size new_size) {
+ return realloc(ptr, new_size);
+}
+
+void ta_free(void *ptr) {
+ free(ptr);
+}
+
+ta_fs_file ta_read_file(const char *path) {
+ ta_fs_file file = {
+ .data = NULL,
+ .size = 0
+ };
+
+ FILE *file_pointer = fopen(path, "rb");
+ if (!file_pointer) {
+ return file;
+ }
+
+ if (fseek(file_pointer, 0, SEEK_END) != 0) {
+ fclose(file_pointer);
+
+ return file;
+ }
+
+ long file_size = ftell(file_pointer);
+ if (file_size < 0) {
+ fclose(file_pointer);
+
+ return file;
+ }
+
+ rewind(file_pointer);
+
+ file.data = ta_malloc((ta_size)file_size);
+ if (!file.data) {
+ fclose(file_pointer);
+
+ return file;
+ }
+
+ ta_size bytes_read = fread(file.data, 1, (size_t)file_size, file_pointer);
+
+ if (bytes_read != (ta_size)file_size) {
+ ta_free(file.data);
+ file.data = NULL;
+ fclose(file_pointer);
+
+ return file;
+ }
+
+ file.size = (ta_size)file_size;
+
+ fclose(file_pointer);
+
+ return file;
+}
+
+void ta_close_file(ta_fs_file *file) {
+ if (!file) {
+ return;
+ }
+
+ if (file->data) {
+ ta_free(file->data);
+ file->data = NULL;
+ }
+
+ file->size = 0;
+}
+
+void ta_exit(int status) {
+ exit(status);
+}