diff --git a/CMakeLists.txt b/CMakeLists.txt index 924d470fa..3ff886aec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -186,6 +186,12 @@ if(PLATFORM STREQUAL "desktop") # Matches the GitHub Actions container flags (FORTIFY requires building with optimizations enabled!) target_compile_options(butterscotch PRIVATE "$<$:-U_FORTIFY_SOURCE;-D_FORTIFY_SOURCE=2>") + if(APPLE AND NOT IOS) + target_sources(butterscotch PRIVATE src/desktop/backends/platform/macos.m) + find_library(COCOA_LIBRARY Cocoa REQUIRED) + target_link_libraries(butterscotch PRIVATE ${COCOA_LIBRARY}) + endif() + if(NOT ENABLE_LEGACY_GL AND NOT ENABLE_MODERN_GL) message(FATAL_ERROR "You must enable at least one renderer!") endif() diff --git a/Makefile b/Makefile index 088238a50..71ac80780 100644 --- a/Makefile +++ b/Makefile @@ -76,21 +76,35 @@ PKG_CONFIG_FLAGS := --static endif INCLUDES += $(INCLUDE)src/desktop ifeq ($(DESKTOP_BACKEND),glfw3) -GLFW3_LIBS += $(shell $(PKG_CONFIG) $(PKG_CONFIG_FLAGS) --libs glfw3) +GLFW3_CFLAGS := $(shell $(PKG_CONFIG) $(PKG_CONFIG_FLAGS) --cflags glfw3) +GLFW3_LIBS := $(shell $(PKG_CONFIG) $(PKG_CONFIG_FLAGS) --libs glfw3) +CFLAGS += $(GLFW3_CFLAGS) LIBS += $(GLFW3_LIBS) DEFINES += $(DEFINE)USE_GLFW3 ENABLE_GLAD := 1 +ifeq ($(OS),Darwin) +SRCS += src/desktop/backends/platform/macos.m +LIBS += -framework Cocoa +endif endif ifeq ($(DESKTOP_BACKEND),glfw2) GLFW2_LIBS += $(shell $(PKG_CONFIG) $(PKG_CONFIG_FLAGS) --libs libglfw) LIBS += $(GLFW2_LIBS) DEFINES += $(DEFINE)USE_GLFW2 ENABLE_GLAD := 1 +ifeq ($(OS),Darwin) +SRCS += src/desktop/backends/platform/macos.m +LIBS += -framework Cocoa +endif endif ifeq ($(DESKTOP_BACKEND),sdl1) SDL1_LIBS += $(shell $(PKG_CONFIG) $(PKG_CONFIG_FLAGS) --libs sdl) LIBS += $(SDL1_LIBS) DEFINES += $(DEFINE)USE_SDL1 +ifeq ($(OS),Darwin) +SRCS += src/desktop/backends/platform/macos.m +LIBS += -framework Cocoa +endif endif ifeq ($(DESKTOP_BACKEND),sdl2) SDL2_LIBS += $(shell $(PKG_CONFIG) $(PKG_CONFIG_FLAGS) --libs sdl2) @@ -193,7 +207,8 @@ ifndef VERBOSE V := @ endif -OBJS := $(addprefix build/,$(SRCS:.c=.c.$(OBJ_EXT))) +OBJS := $(addprefix build/,$(SRCS)) +OBJS := $(OBJS:%=%.$(OBJ_EXT)) all: build/butterscotch @@ -211,7 +226,7 @@ build/butterscotch: $(OBJS) $(V)MSYS2_ARG_CONV_EXCL='*' $(_CC) $(LDFLAGS) $(OBJS) $(LIBS) $(EXTRALIBS) $(OUTPUT_EXE)$@ @[ -f $@.exe ] && chmod +x $@.exe || true -build/%.c.$(OBJ_EXT): %.c compat/config.mk $(if $(DISABLE_MMD),$(HEADERS)) +build/%.$(OBJ_EXT): % compat/config.mk $(if $(DISABLE_MMD),$(HEADERS)) @mkdir -p $(dir $@) @{ [ -z "$(NO_COLOR)" ] && [ -t 1 ]; } && printf " \033[1;32mCC\033[0m $<\n" || printf " CC $<\n" $(V)MSYS2_ARG_CONV_EXCL='*' $(_CC) $(DEFINES) $(INCLUDES) $(CFLAGS) $(DEPFLAGS) $(COMPILE_OBJ) $(SRCFLAG)$< $(OUTPUT_OBJ)$@ diff --git a/src/desktop/backends/glfw2.c b/src/desktop/backends/glfw2.c index e167674f3..e8ac77e4e 100644 --- a/src/desktop/backends/glfw2.c +++ b/src/desktop/backends/glfw2.c @@ -56,6 +56,20 @@ static bool tryOpenWindow(int reqW, int reqH) { #endif } +#ifdef TARGET_OS_MAC +void show_error_box(const char *message); +#endif + +void platformShowErrorDialogue(const char* message) { +#ifdef _WIN32 + MessageBoxA(NULL, message, "Error", MB_OK | MB_ICONERROR); +#elif defined(TARGET_OS_MAC) + show_error_box(message); +#else + (void)message; // Suppress unused parameter warning +#endif +} + void platformSetWindowTitle(const char* title) { char windowTitle[256]; snprintf(windowTitle, sizeof(windowTitle), "Butterscotch - %s", title); diff --git a/src/desktop/backends/glfw3.c b/src/desktop/backends/glfw3.c index 6d85924d8..3c78ac595 100644 --- a/src/desktop/backends/glfw3.c +++ b/src/desktop/backends/glfw3.c @@ -79,6 +79,20 @@ static GLFWwindow *tryOpenWindow(int reqW, int reqH, const char* title) { return NULL; } +#ifdef TARGET_OS_MAC +void show_error_box(const char *message); +#endif + +void platformShowErrorDialogue(const char* message) { +#ifdef _WIN32 + MessageBoxA(NULL, message, "Error", MB_OK | MB_ICONERROR); +#elif defined(TARGET_OS_MAC) + show_error_box(message); +#else + (void)message; // Suppress unused parameter warning +#endif +} + void platformSetWindowTitle(const char* title) { char windowTitle[256]; snprintf(windowTitle, sizeof(windowTitle), "Butterscotch - %s", title); diff --git a/src/desktop/backends/platform/macos.m b/src/desktop/backends/platform/macos.m new file mode 100644 index 000000000..19501fdaa --- /dev/null +++ b/src/desktop/backends/platform/macos.m @@ -0,0 +1,37 @@ +#include +#include + +static void show_error_box(const char *message) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1030 // NSAlert is available in 10.3 and later + + NSAlert *alert = [[NSAlert alloc] init]; + + [alert setMessageText:@"Error"]; + [alert setInformativeText:[NSString stringWithUTF8String:message]]; + [alert addButtonWithTitle:@"OK"]; +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101200 // NSAlertStyleCritical is available in 10.12 and later + [alert setAlertStyle:NSAlertStyleCritical]; +#else + [alert setAlertStyle:NSCriticalAlertStyle]; +#endif + + [alert runModal]; + [alert release]; + +#else + + NSRunAlertPanel( + @"Error", + [NSString stringWithUTF8String:message], + @"OK", + nil, + nil + ); + +#endif + + [pool drain]; +} \ No newline at end of file diff --git a/src/desktop/backends/sdl1.c b/src/desktop/backends/sdl1.c index 766cc0c8d..e72f51872 100644 --- a/src/desktop/backends/sdl1.c +++ b/src/desktop/backends/sdl1.c @@ -4,6 +4,10 @@ #include #include +#ifdef _WIN32 +#include +#endif + #include #include "common.h" @@ -201,6 +205,21 @@ static void loadGamepadMappings(void) { } fclose(f); } + +#ifdef TARGET_OS_MAC +void show_error_box(const char *message); +#endif + +void platformShowErrorDialogue(const char* message) { +#ifdef _WIN32 + MessageBoxA(NULL, message, "Error", MB_OK | MB_ICONERROR); +#elif defined(TARGET_OS_MAC) + show_error_box(message); +#else + (void)message; // Suppress unused parameter warning +#endif +} + void platformSetWindowTitle(const char* title) { char windowTitle[256]; snprintf(windowTitle, sizeof(windowTitle), "Butterscotch - %s", title); diff --git a/src/desktop/backends/sdl2.c b/src/desktop/backends/sdl2.c index ca4ca9828..708f63f2c 100644 --- a/src/desktop/backends/sdl2.c +++ b/src/desktop/backends/sdl2.c @@ -91,6 +91,15 @@ static SDL_Window *tryOpenWindow(int reqW, int reqH, const char* title, Uint32 f return NULL; } +void platformShowErrorDialogue(const char* message) { + SDL_ShowSimpleMessageBox( + SDL_MESSAGEBOX_ERROR, + "Error", + message, + NULL + ); +} + void platformSetWindowTitle(const char* title) { char windowTitle[256]; snprintf(windowTitle, sizeof(windowTitle), "Butterscotch - %s", title); diff --git a/src/desktop/backends/sdl3.c b/src/desktop/backends/sdl3.c index e1895790f..a00046356 100644 --- a/src/desktop/backends/sdl3.c +++ b/src/desktop/backends/sdl3.c @@ -108,6 +108,15 @@ static SDL_Window *tryOpenWindow(int reqW, int reqH, const char* title, Uint32 f return NULL; } +void platformShowErrorDialogue(const char* message) { + SDL_ShowSimpleMessageBox( + SDL_MESSAGEBOX_ERROR, + "Error", + message, + NULL + ); +} + void platformSetWindowTitle(const char* title) { char windowTitle[256]; snprintf(windowTitle, sizeof(windowTitle), "Butterscotch - %s", title); diff --git a/src/desktop/main.c b/src/desktop/main.c index 90635ae8d..c0b3ad6c8 100644 --- a/src/desktop/main.c +++ b/src/desktop/main.c @@ -1420,6 +1420,7 @@ int main(int argc, char* argv[]) { runner->setWindowSize = platformSetWindowSize; runner->getWindowSize = platformGetWindowSize; runner->setWindowTitle = platformSetWindowTitle; + runner->showErrorDialogue = platformShowErrorDialogue; Runner_setGameArgs(runner, currentGameArgs, (int32_t) arrlen(currentGameArgs)); platformInitFunctions(runner); diff --git a/src/desktop/platformdefs.h b/src/desktop/platformdefs.h index ad93f4b81..3a1cb71f3 100644 --- a/src/desktop/platformdefs.h +++ b/src/desktop/platformdefs.h @@ -19,6 +19,7 @@ bool platformGetScaledWindowSize(int32_t* outW, int32_t* outH); void platformSetWindowSize(int32_t width, int32_t height); void platformSetWindowTitle(const char* title); void platformSleepUntil(uint64_t time); +void platformShowErrorDialogue(const char* message); enum GraphicsAPI { SOFTWARE, diff --git a/src/runner.h b/src/runner.h index 066a7cadb..74ed97922 100644 --- a/src/runner.h +++ b/src/runner.h @@ -505,6 +505,7 @@ struct Runner { void (*setWindowTitle)(const char* title); bool (*getWindowSize)(int32_t* outW, int32_t* outH); void (*setWindowSize)(int32_t width, int32_t height); + void (*showErrorDialogue)(const char* message); bool (*windowHasFocus)(void); void (*setCursor)(int32_t cursorType); int32_t currentCursor; // last value passed to window_set_cursor diff --git a/src/vm_builtins.c b/src/vm_builtins.c index e0ce4918e..7683e2e67 100644 --- a/src/vm_builtins.c +++ b/src/vm_builtins.c @@ -1821,6 +1821,32 @@ static RValue builtin_show_debug_message(MAYBE_UNUSED VMContext* ctx, RValue* ar return RValue_makeUndefined(); } +// show_error(str, abort) - Displays an error message and optionally aborts the game +static RValue builtin_show_error(MAYBE_UNUSED VMContext* ctx, RValue* args, int32_t argCount) { + if (1 > argCount) { + fprintf(stderr, "[show_error] Expected at least 1 argument\n"); + return RValue_makeUndefined(); + } + + char* val = RValue_toString(args[0]); + bool abort = false; + if (2 <= argCount) abort = RValue_toBool(args[1]); + + fprintf(stderr, "[show_error] %s\n", val); + Runner* runner = ctx->runner; + if (runner->showErrorDialogue) { + runner->showErrorDialogue(val); + } + free(val); + + if (abort) { + fprintf(stderr, "Game aborted due to show_error() call.\n"); + exit(EXIT_FAILURE); + } + + return RValue_makeUndefined(); +} + static RValue builtin_string_length(MAYBE_UNUSED VMContext* ctx, RValue* args, int32_t argCount) { if (1 > argCount) return RValue_makeInt32(0); // GML converts non-string arguments to string before measuring length @@ -16368,6 +16394,7 @@ void VMBuiltins_registerAll(VMContext* ctx) { // Core output VM_registerBuiltin(ctx, "show_debug_message", builtin_show_debug_message); + VM_registerBuiltin(ctx, "show_error", builtin_show_error); // String functions VM_registerBuiltin(ctx, "string_length", builtin_string_length);