From e650c4a7a7a9187efcbfe6770f35b264b90fe199 Mon Sep 17 00:00:00 2001 From: "Joshua T. Fisher" Date: Fri, 15 Aug 2025 02:58:15 -0700 Subject: [PATCH 01/35] Initial prototype of Windows menubar --- examples/CMakeLists.txt | 1 + examples/video/01-menubar/menubar.c | 115 ++++++++++++++++++++++++ include/SDL3/SDL_events.h | 12 +++ include/SDL3/SDL_video.h | 18 ++++ src/dynapi/SDL_dynapi.sym | 6 ++ src/dynapi/SDL_dynapi_overrides.h | 6 ++ src/dynapi/SDL_dynapi_procs.h | 6 ++ src/video/SDL_sysvideo.h | 51 +++++++++++ src/video/SDL_video.c | 82 +++++++++++++++++ src/video/windows/SDL_windowsevents.c | 16 ++++ src/video/windows/SDL_windowsvideo.c | 123 ++++++++++++++++++++++++++ src/video/windows/SDL_windowsvideo.h | 46 ++++++++++ 12 files changed, 482 insertions(+) create mode 100644 examples/video/01-menubar/menubar.c diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 195545536db94..e5cb287728dde 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -126,6 +126,7 @@ macro(add_sdl_example_executable TARGET) endif() endmacro() +add_sdl_example_executable(video-menubar SOURCES video/01-menubar/menubar.c) add_sdl_example_executable(renderer-clear SOURCES renderer/01-clear/clear.c) add_sdl_example_executable(renderer-primitives SOURCES renderer/02-primitives/primitives.c) add_sdl_example_executable(renderer-lines SOURCES renderer/03-lines/lines.c) diff --git a/examples/video/01-menubar/menubar.c b/examples/video/01-menubar/menubar.c new file mode 100644 index 0000000000000..1cb9ddce4b217 --- /dev/null +++ b/examples/video/01-menubar/menubar.c @@ -0,0 +1,115 @@ +/* + * This example code $WHAT_IT_DOES. + * + * This code is public domain. Feel free to use it for any purpose! + */ + +#define SDL_MAIN_USE_CALLBACKS 1 /* use the callbacks instead of main() */ +#include +#include + +SDL_Window* window = NULL; +SDL_Renderer* renderer = NULL; + + +typedef enum SDL_EventType_MenuExt +{ + MENU_BAR_FILE, + MENU_BAR_FILE_NEW_WINDOW, + MENU_BAR_FILE_AUTOSAVE_TABS_ON_CLOSE, + MENU_BAR_BOOKMARKS, + MENU_BAR_BOOKMARKS_TOOLBAR, + MENU_BAR_BOOKMARKS_TOOLBAR_GITHUB, + MENU_BAR_BOOKMARKS_TOOLBAR_WIKI, + MENU_BAR_BOOKMARKS_TOOLBAR_DISCORD, + MENU_BAR_BOOKMARKS_OTHER_BOOKMARKS, + MENU_BAR_BOOKMARKS_OTHER_BOOKMARKS_TWITTER, + MENU_BAR_BOOKMARKS_OTHER_BOOKMARKS_REDDIT, + MENU_BAR_INCOGNITO, + MENU_BAR_TOP_LEVEL_BUTTON, + MENU_BAR_EXIT, + + MENU_BAR_LAST +} SDL_EventType_MenuExt; + +static SDL_EventType_MenuExt EVENT_START = (SDL_EventType_MenuExt)0; + +void CreateMenuBar() +{ + SDL_MenuBar* bar = SDL_CreateMenuBar(window); + + { + SDL_MenuItem* menu = SDL_CreateMenuBarItem(bar, "File", SDL_MENU, MENU_BAR_LAST); + SDL_CreateMenuItem((SDL_Menu*)menu, "New Window", SDL_MENU_BUTTON, MENU_BAR_FILE_NEW_WINDOW); + SDL_MenuItem* checkable = SDL_CreateMenuItem((SDL_Menu*)menu, "Autosave Tabs on Close", SDL_MENU_CHECKABLE, MENU_BAR_FILE_AUTOSAVE_TABS_ON_CLOSE); + + SDL_CheckMenuItem(checkable, true); + } + + { + SDL_MenuItem* menu = SDL_CreateMenuBarItem(bar, "Bookmarks", SDL_MENU, MENU_BAR_LAST); + SDL_MenuItem* main_bookmarks = SDL_CreateMenuItem((SDL_Menu*)menu, "Bookmarks Toolbar", SDL_MENU, MENU_BAR_LAST); + SDL_CreateMenuItem((SDL_Menu*)main_bookmarks, "SDL GitHub", SDL_MENU_BUTTON, MENU_BAR_BOOKMARKS_TOOLBAR_GITHUB); + SDL_CreateMenuItem((SDL_Menu*)main_bookmarks, "SDL Wiki", SDL_MENU_BUTTON, MENU_BAR_BOOKMARKS_TOOLBAR_WIKI); + SDL_CreateMenuItem((SDL_Menu*)main_bookmarks, "SDL Discord", SDL_MENU_BUTTON, MENU_BAR_BOOKMARKS_TOOLBAR_DISCORD); + + SDL_MenuItem* other_bookmarks = SDL_CreateMenuItem((SDL_Menu*)menu, "Other Bookmarks", SDL_MENU, MENU_BAR_LAST); + SDL_CreateMenuItem((SDL_Menu*)other_bookmarks, "Stack Overflow", SDL_MENU_BUTTON, MENU_BAR_BOOKMARKS_OTHER_BOOKMARKS); + + SDL_EnableMenuItem(other_bookmarks, false); + } + + { + // We can't create a top level checkable . + SDL_MenuItem* checkable = SDL_CreateMenuBarItem(bar, "Incognito", SDL_MENU_CHECKABLE, MENU_BAR_INCOGNITO); + + SDL_MenuItem* disabled = SDL_CreateMenuBarItem(bar, "Disabled Top-Level Button", SDL_MENU_BUTTON, MENU_BAR_TOP_LEVEL_BUTTON); + SDL_EnableMenuItem(disabled, false); + + SDL_CreateMenuBarItem(bar, "Exit", SDL_MENU_BUTTON, MENU_BAR_EXIT); + } + + EVENT_START = (SDL_EventType_MenuExt)SDL_RegisterEvents(MENU_BAR_LAST); +} + +SDL_AppResult SDL_AppInit(void** appstate, int argc, char** argv) { + SDL_CreateWindowAndRenderer("menu bar test", 640, 480, 0, &window, &renderer); + + CreateMenuBar(); + + //return SDL_APP_SUCCESS; + return SDL_APP_CONTINUE; +} + +SDL_AppResult SDL_AppIterate(void* appstate) { + + SDL_SetRenderDrawColor(renderer, 128, 128, 128, 255); + SDL_RenderClear(renderer); + SDL_RenderPresent(renderer); + + return SDL_APP_CONTINUE; +} + +SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) { + + switch (event->common.type) + { + case SDL_EVENT_QUIT: + { + return SDL_APP_SUCCESS; + } + case SDL_EVENT_MENU_BUTTON_CLICKED: + case SDL_EVENT_MENU_CHECKABLE_CLICKED: + { + SDL_Log("%d\n", event->menu.user_event_type); + } + } + + return SDL_APP_CONTINUE; +} + +void SDL_AppQuit(void* appstate, SDL_AppResult result) { + +} + + diff --git a/include/SDL3/SDL_events.h b/include/SDL3/SDL_events.h index 54e8292343e0d..eb6aa05dbbbe2 100644 --- a/include/SDL3/SDL_events.h +++ b/include/SDL3/SDL_events.h @@ -256,6 +256,10 @@ typedef enum SDL_EventType SDL_EVENT_RENDER_DEVICE_RESET, /**< The device has been reset and all textures need to be recreated */ SDL_EVENT_RENDER_DEVICE_LOST, /**< The device has been lost and can't be recovered. */ + /* Menu events */ + SDL_EVENT_MENU_BUTTON_CLICKED = 0x2100, + SDL_EVENT_MENU_CHECKABLE_CLICKED, + /* Reserved events for private platforms */ SDL_EVENT_PRIVATE0 = 0x4000, SDL_EVENT_PRIVATE1, @@ -981,6 +985,13 @@ typedef struct SDL_UserEvent void *data2; /**< User defined data pointer */ } SDL_UserEvent; +typedef struct SDL_MenuEvent +{ + Uint32 type; /**< SDL_EVENT_KEY_DOWN or SDL_EVENT_KEY_UP */ + Uint32 reserved; + Uint64 timestamp; /**< In nanoseconds, populated using SDL_GetTicksNS() */ + Uint16 user_event_type; +} SDL_MenuEvent; /** * The structure for all events in SDL. @@ -1030,6 +1041,7 @@ typedef union SDL_Event SDL_RenderEvent render; /**< Render event data */ SDL_DropEvent drop; /**< Drag and drop event data */ SDL_ClipboardEvent clipboard; /**< Clipboard event data */ + SDL_MenuEvent menu; /**< Menu event data */ /* This is necessary for ABI compatibility between Visual C++ and GCC. Visual C++ will respect the push pack pragma and use 52 bytes (size of diff --git a/include/SDL3/SDL_video.h b/include/SDL3/SDL_video.h index 3adebc29a1ca4..537a060ab7d55 100644 --- a/include/SDL3/SDL_video.h +++ b/include/SDL3/SDL_video.h @@ -3034,6 +3034,24 @@ extern SDL_DECLSPEC bool SDLCALL SDL_EnableScreenSaver(void); */ extern SDL_DECLSPEC bool SDLCALL SDL_DisableScreenSaver(void); +typedef enum SDL_MenuItemType +{ + SDL_MENU, + SDL_MENU_BUTTON, // Start Enabled + SDL_MENU_CHECKABLE, // Start Unchecked and Enabled +} SDL_MenuItemType; + +typedef struct SDL_MenuBar SDL_MenuBar; +typedef struct SDL_Menu SDL_Menu; +typedef union SDL_MenuItem SDL_MenuItem; + +extern SDL_DECLSPEC SDL_MenuBar* SDL_CreateMenuBar(SDL_Window*window); +extern SDL_DECLSPEC SDL_MenuItem* SDL_CreateMenuBarItem(SDL_MenuBar*menu_bar, const char*name, SDL_MenuItemType type, Uint16 event_type); +extern SDL_DECLSPEC SDL_MenuItem* SDL_CreateMenuItem(SDL_Menu*menu_bar, const char*name, SDL_MenuItemType type, Uint16 event_type); +// SDL_SetMenu(SDL_MenuBar* menu_bar); +extern SDL_DECLSPEC bool SDL_CheckMenuItem(SDL_MenuItem*menu_item, bool checked); +extern SDL_DECLSPEC bool SDL_EnableMenuItem(SDL_MenuItem*menu_item, bool enabled); // Start Enabled +extern SDL_DECLSPEC bool SDL_DestroyMenuBar(SDL_MenuBar*menu_bar); /** * \name OpenGL support functions diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index 3aa7a822e61cc..596c9d5af2d6e 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -1264,6 +1264,12 @@ SDL3_0.0.0 { SDL_LoadPNG; SDL_SavePNG_IO; SDL_SavePNG; + SDL_CreateMenuBar; + SDL_CreateMenuBarItem; + SDL_CreateMenuItem; + SDL_CheckMenuItem; + SDL_EnableMenuItem; + SDL_DestroyMenuBar; # extra symbols go here (don't modify this line) local: *; }; diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index f0693ad980589..24228203a581c 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -1290,3 +1290,9 @@ #define SDL_LoadPNG SDL_LoadPNG_REAL #define SDL_SavePNG_IO SDL_SavePNG_IO_REAL #define SDL_SavePNG SDL_SavePNG_REAL +#define SDL_CreateMenuBar SDL_CreateMenuBar_REAL +#define SDL_CreateMenuBarItem SDL_CreateMenuBarItem_REAL +#define SDL_CreateMenuItem SDL_CreateMenuItem_REAL +#define SDL_CheckMenuItem SDL_CheckMenuItem_REAL +#define SDL_EnableMenuItem SDL_EnableMenuItem_REAL +#define SDL_DestroyMenuBar SDL_DestroyMenuBar_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index 4e3ca27e3ed2f..0e72e685bc831 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -1298,3 +1298,9 @@ SDL_DYNAPI_PROC(SDL_Surface*,SDL_LoadPNG_IO,(SDL_IOStream *a,bool b),(a,b),retur SDL_DYNAPI_PROC(SDL_Surface*,SDL_LoadPNG,(const char *a),(a),return) SDL_DYNAPI_PROC(bool,SDL_SavePNG_IO,(SDL_Surface *a,SDL_IOStream *b,bool c),(a,b,c),return) SDL_DYNAPI_PROC(bool,SDL_SavePNG,(SDL_Surface *a,const char *b),(a,b),return) +SDL_DYNAPI_PROC(SDL_MenuBar*, SDL_CreateMenuBar,(SDL_Window*a),(a),return) +SDL_DYNAPI_PROC(SDL_MenuItem*,SDL_CreateMenuBarItem,(SDL_MenuBar*a,const char*b,SDL_MenuItemType c,Uint16 d),(a,b,c,d),return) +SDL_DYNAPI_PROC(SDL_MenuItem*,SDL_CreateMenuItem,(SDL_Menu*a,const char*b,SDL_MenuItemType c,Uint16 d),(a,b,c,d),return) +SDL_DYNAPI_PROC(bool, SDL_CheckMenuItem,(SDL_MenuItem*a, bool b),(a,b),return) +SDL_DYNAPI_PROC(bool, SDL_EnableMenuItem,(SDL_MenuItem*a, bool b),(a,b),return) +SDL_DYNAPI_PROC(bool, SDL_DestroyMenuBar,(SDL_MenuBar*a),(a),return) diff --git a/src/video/SDL_sysvideo.h b/src/video/SDL_sysvideo.h index 433eb72f153c7..add79b2e5b7ea 100644 --- a/src/video/SDL_sysvideo.h +++ b/src/video/SDL_sysvideo.h @@ -209,6 +209,49 @@ typedef enum SDL_FULLSCREEN_PENDING } SDL_FullscreenResult; + +typedef struct PlatformMenuData PlatformMenuData; + +typedef struct SDL_MenuBar +{ + SDL_Window *window; + SDL_MenuItem *item_list; + PlatformMenuData *platform; +} SDL_MenuBar; + +typedef struct SDL_MenuItem_CommonData +{ + size_t type; + PlatformMenuData *platform; + SDL_MenuBar *menu_bar; + SDL_MenuItem *prev; + SDL_MenuItem *next; +} SDL_MenuItem_CommonData; + +typedef struct SDL_Menu +{ + SDL_MenuItem_CommonData common; + SDL_MenuItem *menuitem_list; +} SDL_Menu; + +typedef struct SDL_MenuItem_Button +{ + SDL_MenuItem_CommonData common; +} SDL_MenuItem_Button; + +typedef struct SDL_MenuItem_Checkable +{ + SDL_MenuItem_CommonData common; + bool is_checked; +} SDL_MenuItem_Checkable; + +typedef union SDL_MenuItem +{ + SDL_MenuItem_CommonData common; + SDL_MenuItem_Button button; + SDL_MenuItem_Checkable checkable; +} SDL_MenuItem; + struct SDL_VideoDevice { /* * * */ @@ -315,6 +358,14 @@ struct SDL_VideoDevice bool (*SyncWindow)(SDL_VideoDevice *_this, SDL_Window *window); bool (*ReconfigureWindow)(SDL_VideoDevice *_this, SDL_Window *window, SDL_WindowFlags flags); + + SDL_MenuBar *(*CreateMenuBar)(SDL_Window *window); + SDL_MenuItem *(*CreateMenuBarItem)(SDL_MenuBar *menu_bar, const char *name, SDL_MenuItemType type, Uint16 event_type); + SDL_MenuItem *(*CreateMenuItem)(SDL_Menu *menu_bar, const char *name, SDL_MenuItemType type, Uint16 event_type); + bool (*CheckMenuItem)(SDL_MenuItem *menu_item, bool checked); + bool (*EnableMenuItem)(SDL_MenuItem *menu_item, bool enabled); + void (*DestroyMenuItem)(SDL_MenuItem *menu_item); + /* * * */ /* * OpenGL support diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c index d961db578945e..5bc6772161c8f 100644 --- a/src/video/SDL_video.c +++ b/src/video/SDL_video.c @@ -6111,6 +6111,88 @@ void SDL_OnApplicationDidEnterForeground(void) } } +SDL_MenuBar* SDL_CreateMenuBar(SDL_Window *window) +{ + return _this->CreateMenuBar(window); +} + +SDL_MenuItem* SDL_CreateMenuBarItem(SDL_MenuBar* menu_bar, const char *name, SDL_MenuItemType type, Uint16 event_type) +{ + if (type == SDL_MENU_CHECKABLE) { + SDL_SetError("Can't created a checkable item on the menubar, they must be in a menu."); + return false; + } + + SDL_MenuItem* menu_item = _this->CreateMenuBarItem(menu_bar, name, type, event_type); + + if (menu_item == NULL) { + return NULL; + } + + // Get the last item in the list and insert our new item. + if (menu_bar->item_list) { + SDL_MenuItem* last = menu_bar->item_list; + + for (; last->common.next; last = last->common.next) + ; + + last->common.next = menu_item; + menu_item->common.prev = last; + } else { + menu_bar->item_list = menu_item; + } + + return menu_item; +} + +SDL_MenuItem* SDL_CreateMenuItem(SDL_Menu *menu, const char *name, SDL_MenuItemType type, Uint16 event_type) +{ + SDL_MenuItem* menu_item = _this->CreateMenuItem(menu, name, type, event_type); + + if (menu_item == NULL) { + return NULL; + } + + // Get the last item in the list and insert our new item. + if (menu->menuitem_list) { + SDL_MenuItem* last = menu->menuitem_list; + + while (last->common.next) last = last->common.next; + + last->common.next = menu_item; + menu_item->common.prev = last; + } else { + menu->menuitem_list = menu_item; + } + + return menu_item; +} + +bool SDL_CheckMenuItem(SDL_MenuItem* menu_item, bool checked) +{ + if (menu_item->common.type != SDL_MENU_CHECKABLE) { + SDL_SetError("menu_item isn't a checkable."); + return false; + } + + return _this->CheckMenuItem(menu_item, checked); +} + +bool SDL_EnableMenuItem(SDL_MenuItem* menu_item, bool enabled) +{ + if (menu_item->common.type == SDL_MENU) { + SDL_SetError("menu_item can't be a menu."); + return false; + } + + return _this->EnableMenuItem(menu_item, enabled); +} + +bool SDL_DestroyMenuBar(SDL_MenuBar* menu_bar) +{ + return true; +} + #define NOT_A_VULKAN_WINDOW "The specified window isn't a Vulkan window" bool SDL_Vulkan_LoadLibrary(const char *path) diff --git a/src/video/windows/SDL_windowsevents.c b/src/video/windows/SDL_windowsevents.c index 9c8bf6d103bc2..425270de22e3f 100644 --- a/src/video/windows/SDL_windowsevents.c +++ b/src/video/windows/SDL_windowsevents.c @@ -2427,6 +2427,22 @@ LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara } break; + case WM_COMMAND: + { + if (HIWORD(wParam) != 0) { + break; + } + + WORD command_id = LOWORD(wParam); + + SDL_Event event; + event.type = SDL_EVENT_MENU_BUTTON_CLICKED; + event.menu.timestamp = SDL_GetTicksNS(); + event.menu.user_event_type = (Uint16)command_id; + + SDL_PushEvent(&event); + } + #endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) default: diff --git a/src/video/windows/SDL_windowsvideo.c b/src/video/windows/SDL_windowsvideo.c index 93d3bf60ecf46..c6a3eaa886289 100644 --- a/src/video/windows/SDL_windowsvideo.c +++ b/src/video/windows/SDL_windowsvideo.c @@ -359,8 +359,15 @@ static SDL_VideoDevice *WIN_CreateDevice(void) device->ShowWindowSystemMenu = WIN_ShowWindowSystemMenu; device->SetWindowFocusable = WIN_SetWindowFocusable; device->UpdateWindowShape = WIN_UpdateWindowShape; + device->CreateMenuBar = Win32_CreateMenuBar; + device->CreateMenuBarItem = Win32_CreateMenuBarItem; + device->CreateMenuItem = Win32_CreateMenuItem; + device->CheckMenuItem = Win32_CheckMenuItem; + device->EnableMenuItem = Win32_EnableMenuItem; + device->DestroyMenuItem = Win32_DestroyMenuItem; #endif + #ifdef SDL_VIDEO_OPENGL_WGL device->GL_LoadLibrary = WIN_GL_LoadLibrary; device->GL_GetProcAddress = WIN_GL_GetProcAddress; @@ -878,4 +885,120 @@ bool WIN_IsPerMonitorV2DPIAware(SDL_VideoDevice *_this) return false; } +#define SDL_WIN32_INVALID_MENU_ID 65535 + +static PlatformMenuData *CreatePlatformMenuData(HMENU owner, SDL_MenuItemType type) +{ + PlatformMenuData *platform = SDL_calloc(1, sizeof(PlatformMenuData)); + + switch (type) { + case SDL_MENU: + platform->menu_owner = INVALID_HANDLE_VALUE; + platform->handle_or_id = (UINT_PTR)INVALID_HANDLE_VALUE; + break; + default: + platform->menu_owner = owner; + platform->handle_or_id = (UINT_PTR)SDL_WIN32_INVALID_MENU_ID; + break; + } + + return platform; +} + +static HWND GetHwndFromWindow(SDL_Window *window) +{ + return (HWND)SDL_GetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL); +} + +SDL_MenuBar *SDLCALL Win32_CreateMenuBar(SDL_Window *window) +{ + SDL_MenuBar *menu_bar = SDL_calloc(1, sizeof(SDL_MenuBar)); + menu_bar->platform = CreatePlatformMenuData(INVALID_HANDLE_VALUE, SDL_MENU); + menu_bar->window = window; + menu_bar->platform->handle_or_id = (UINT_PTR)CreateMenu(); + + SetMenu(GetHwndFromWindow(window), (HMENU)menu_bar->platform->handle_or_id); + + return menu_bar; +} + +static SDL_MenuItem *Win32_CreateMenuItemImpl(HMENU menu, const char *name, SDL_MenuItemType type, Uint16 event_type, bool toplevel_menu) +{ + SDL_MenuItem *menu_item = SDL_calloc(1, sizeof(SDL_MenuItem)); + menu_item->common.type = type; + menu_item->common.platform = CreatePlatformMenuData(menu, type); + UINT flags = 0; + + if (!toplevel_menu) { + flags = MF_STRING; + } + + if (type == SDL_MENU) { + if (toplevel_menu) { + menu_item->common.platform->handle_or_id = (UINT_PTR)CreateMenu(); + } else { + menu_item->common.platform->handle_or_id = (UINT_PTR)CreatePopupMenu(); + } + + flags |= MF_POPUP; + } else { + if (toplevel_menu) { + flags |= MF_STRING; + } + + menu_item->common.platform->handle_or_id = (UINT_PTR)event_type; + } + + AppendMenuA(menu, flags, menu_item->common.platform->handle_or_id, name); + + return menu_item; +} + +static SDL_MenuItem *Win32_CreateMenuBarItem(SDL_MenuBar *menu_bar, const char *name, SDL_MenuItemType type, Uint16 event_type) +{ + SDL_MenuItem *item = Win32_CreateMenuItemImpl((HMENU)menu_bar->platform->handle_or_id, name, type, event_type, true); + item->common.menu_bar = menu_bar; + + DrawMenuBar(GetHwndFromWindow(menu_bar->window)); + return item; +} + +static SDL_MenuItem *Win32_CreateMenuItem(SDL_Menu *menu, const char *name, SDL_MenuItemType type, Uint16 event_type) +{ + SDL_MenuItem *item = Win32_CreateMenuItemImpl((HMENU)menu->common.platform->handle_or_id, name, type, event_type, false); + item->common.menu_bar = menu->common.menu_bar; + + DrawMenuBar(GetHwndFromWindow(item->common.menu_bar->window)); + return item; +} + +static bool Win32_CheckMenuItem(SDL_MenuItem *menu_item, bool checked) +{ + return CheckMenuItem(menu_item->common.platform->menu_owner, (UINT)menu_item->common.platform->handle_or_id, MF_BYCOMMAND | (checked ? MF_CHECKED : MF_UNCHECKED)); + ; +} + +static bool Win32_EnableMenuItem(SDL_MenuItem *menu_item, bool enabled) +{ + return EnableMenuItem(menu_item->common.platform->menu_owner, (UINT)menu_item->common.platform->handle_or_id, MF_BYCOMMAND | (enabled ? MF_ENABLED : MF_GRAYED)); + ; +} + +static void Win32_DestroyMenuItem(SDL_MenuItem *menu_item) +{ + RemoveMenu(menu_item->common.platform->menu_owner, (UINT)menu_item->common.platform->handle_or_id, MF_BYCOMMAND); + SDL_free(menu_item->common.platform); +} + +static bool SDLCALL Win32_WindowsMessageHook(void *userdata, MSG *msg) +{ + if (msg->message != WM_COMMAND) { + return true; + } + + + return true; +} + + #endif // SDL_VIDEO_DRIVER_WINDOWS diff --git a/src/video/windows/SDL_windowsvideo.h b/src/video/windows/SDL_windowsvideo.h index b192fb664c2b0..e2cb2c834d5b7 100644 --- a/src/video/windows/SDL_windowsvideo.h +++ b/src/video/windows/SDL_windowsvideo.h @@ -660,5 +660,51 @@ extern bool D3D_LoadDLL(void **pD3DDLL, IDirect3D9 **pDirect3D9Interface); extern SDL_SystemTheme WIN_GetSystemTheme(void); extern bool WIN_IsPerMonitorV2DPIAware(SDL_VideoDevice *_this); + + + +#define SDL_WIN32_INVALID_MENU_ID 65535 + +typedef struct PlatformMenuData +{ + HMENU menu_owner; + UINT_PTR handle_or_id; +} PlatformMenuData; + +extern PlatformMenuData *CreatePlatformMenuData(HMENU owner, SDL_MenuItemType type); +extern HWND GetHwndFromWindow(SDL_Window *window); +extern SDL_MenuBar *SDLCALL Win32_CreateMenuBar(SDL_Window *window); +extern SDL_MenuItem *Win32_CreateMenuItemImpl(HMENU menu, const char *name, SDL_MenuItemType type, Uint16 event_type, bool toplevel_menu); +extern SDL_MenuItem *Win32_CreateMenuBarItem(SDL_MenuBar *menu_bar, const char *name, SDL_MenuItemType type, Uint16 event_type); +extern SDL_MenuItem *Win32_CreateMenuItem(SDL_Menu *menu, const char *name, SDL_MenuItemType type, Uint16 event_type); +extern bool Win32_CheckMenuItem(SDL_MenuItem *menu_item, bool checked); +extern bool Win32_EnableMenuItem(SDL_MenuItem *menu_item, bool enabled); +extern void Win32_DestroyMenuItem(SDL_MenuItem *menu_item); +extern bool SDLCALL Win32_WindowsMessageHook(void *userdata, MSG *msg); + + + + + + + + + + + + + + + + + + + + + + + + + #endif // SDL_windowsvideo_h_ From 24005e434933f2f3206ef7183129af7195b6c825 Mon Sep 17 00:00:00 2001 From: "Joshua T. Fisher" Date: Tue, 5 Aug 2025 21:37:43 -0700 Subject: [PATCH 02/35] Try to fix CI issues. --- include/SDL3/SDL_video.h | 7 +++---- src/video/windows/SDL_windowsvideo.c | 11 ----------- src/video/windows/SDL_windowsvideo.h | 27 --------------------------- 3 files changed, 3 insertions(+), 42 deletions(-) diff --git a/include/SDL3/SDL_video.h b/include/SDL3/SDL_video.h index 537a060ab7d55..93b3ebe8447f8 100644 --- a/include/SDL3/SDL_video.h +++ b/include/SDL3/SDL_video.h @@ -3037,8 +3037,8 @@ extern SDL_DECLSPEC bool SDLCALL SDL_DisableScreenSaver(void); typedef enum SDL_MenuItemType { SDL_MENU, - SDL_MENU_BUTTON, // Start Enabled - SDL_MENU_CHECKABLE, // Start Unchecked and Enabled + SDL_MENU_BUTTON, + SDL_MENU_CHECKABLE, } SDL_MenuItemType; typedef struct SDL_MenuBar SDL_MenuBar; @@ -3048,9 +3048,8 @@ typedef union SDL_MenuItem SDL_MenuItem; extern SDL_DECLSPEC SDL_MenuBar* SDL_CreateMenuBar(SDL_Window*window); extern SDL_DECLSPEC SDL_MenuItem* SDL_CreateMenuBarItem(SDL_MenuBar*menu_bar, const char*name, SDL_MenuItemType type, Uint16 event_type); extern SDL_DECLSPEC SDL_MenuItem* SDL_CreateMenuItem(SDL_Menu*menu_bar, const char*name, SDL_MenuItemType type, Uint16 event_type); -// SDL_SetMenu(SDL_MenuBar* menu_bar); extern SDL_DECLSPEC bool SDL_CheckMenuItem(SDL_MenuItem*menu_item, bool checked); -extern SDL_DECLSPEC bool SDL_EnableMenuItem(SDL_MenuItem*menu_item, bool enabled); // Start Enabled +extern SDL_DECLSPEC bool SDL_EnableMenuItem(SDL_MenuItem*menu_item, bool enabled); extern SDL_DECLSPEC bool SDL_DestroyMenuBar(SDL_MenuBar*menu_bar); /** diff --git a/src/video/windows/SDL_windowsvideo.c b/src/video/windows/SDL_windowsvideo.c index c6a3eaa886289..c713fc143e13f 100644 --- a/src/video/windows/SDL_windowsvideo.c +++ b/src/video/windows/SDL_windowsvideo.c @@ -990,15 +990,4 @@ static void Win32_DestroyMenuItem(SDL_MenuItem *menu_item) SDL_free(menu_item->common.platform); } -static bool SDLCALL Win32_WindowsMessageHook(void *userdata, MSG *msg) -{ - if (msg->message != WM_COMMAND) { - return true; - } - - - return true; -} - - #endif // SDL_VIDEO_DRIVER_WINDOWS diff --git a/src/video/windows/SDL_windowsvideo.h b/src/video/windows/SDL_windowsvideo.h index e2cb2c834d5b7..59bafe7d97b8c 100644 --- a/src/video/windows/SDL_windowsvideo.h +++ b/src/video/windows/SDL_windowsvideo.h @@ -672,7 +672,6 @@ typedef struct PlatformMenuData } PlatformMenuData; extern PlatformMenuData *CreatePlatformMenuData(HMENU owner, SDL_MenuItemType type); -extern HWND GetHwndFromWindow(SDL_Window *window); extern SDL_MenuBar *SDLCALL Win32_CreateMenuBar(SDL_Window *window); extern SDL_MenuItem *Win32_CreateMenuItemImpl(HMENU menu, const char *name, SDL_MenuItemType type, Uint16 event_type, bool toplevel_menu); extern SDL_MenuItem *Win32_CreateMenuBarItem(SDL_MenuBar *menu_bar, const char *name, SDL_MenuItemType type, Uint16 event_type); @@ -680,31 +679,5 @@ extern SDL_MenuItem *Win32_CreateMenuItem(SDL_Menu *menu, const char *name, SDL_ extern bool Win32_CheckMenuItem(SDL_MenuItem *menu_item, bool checked); extern bool Win32_EnableMenuItem(SDL_MenuItem *menu_item, bool enabled); extern void Win32_DestroyMenuItem(SDL_MenuItem *menu_item); -extern bool SDLCALL Win32_WindowsMessageHook(void *userdata, MSG *msg); - - - - - - - - - - - - - - - - - - - - - - - - - #endif // SDL_windowsvideo_h_ From 779d22fa4ee1ad25cb910c56b787d7326e942abf Mon Sep 17 00:00:00 2001 From: "Joshua T. Fisher" Date: Wed, 6 Aug 2025 02:12:24 -0700 Subject: [PATCH 03/35] Started working on Mac implementation. --- examples/video/01-menubar/menubar.c | 3 +- include/SDL3/SDL_video.h | 4 +- src/video/SDL_sysvideo.h | 15 ++--- src/video/SDL_video.c | 66 +++++++++++++++++--- src/video/cocoa/SDL_cocoavideo.m | 92 ++++++++++++++++++++++++++++ src/video/cocoa/SDL_cocoawindow.h | 8 +++ src/video/windows/SDL_windowsvideo.c | 18 ++---- 7 files changed, 172 insertions(+), 34 deletions(-) diff --git a/examples/video/01-menubar/menubar.c b/examples/video/01-menubar/menubar.c index 1cb9ddce4b217..c511e0adf0254 100644 --- a/examples/video/01-menubar/menubar.c +++ b/examples/video/01-menubar/menubar.c @@ -62,6 +62,7 @@ void CreateMenuBar() { // We can't create a top level checkable . SDL_MenuItem* checkable = SDL_CreateMenuBarItem(bar, "Incognito", SDL_MENU_CHECKABLE, MENU_BAR_INCOGNITO); + SDL_assert(!checkable); SDL_MenuItem* disabled = SDL_CreateMenuBarItem(bar, "Disabled Top-Level Button", SDL_MENU_BUTTON, MENU_BAR_TOP_LEVEL_BUTTON); SDL_EnableMenuItem(disabled, false); @@ -75,7 +76,7 @@ void CreateMenuBar() SDL_AppResult SDL_AppInit(void** appstate, int argc, char** argv) { SDL_CreateWindowAndRenderer("menu bar test", 640, 480, 0, &window, &renderer); - CreateMenuBar(); + //CreateMenuBar(); //return SDL_APP_SUCCESS; return SDL_APP_CONTINUE; diff --git a/include/SDL3/SDL_video.h b/include/SDL3/SDL_video.h index 93b3ebe8447f8..ee47458307cef 100644 --- a/include/SDL3/SDL_video.h +++ b/include/SDL3/SDL_video.h @@ -3046,8 +3046,8 @@ typedef struct SDL_Menu SDL_Menu; typedef union SDL_MenuItem SDL_MenuItem; extern SDL_DECLSPEC SDL_MenuBar* SDL_CreateMenuBar(SDL_Window*window); -extern SDL_DECLSPEC SDL_MenuItem* SDL_CreateMenuBarItem(SDL_MenuBar*menu_bar, const char*name, SDL_MenuItemType type, Uint16 event_type); -extern SDL_DECLSPEC SDL_MenuItem* SDL_CreateMenuItem(SDL_Menu*menu_bar, const char*name, SDL_MenuItemType type, Uint16 event_type); +extern SDL_DECLSPEC SDL_MenuItem* SDL_CreateMenuBarItem(SDL_MenuBar*menu_bar, const char *name, SDL_MenuItemType type, Uint16 event_type); +extern SDL_DECLSPEC SDL_MenuItem* SDL_CreateMenuItem(SDL_Menu*menu_bar, const char *name, SDL_MenuItemType type, Uint16 event_type); extern SDL_DECLSPEC bool SDL_CheckMenuItem(SDL_MenuItem*menu_item, bool checked); extern SDL_DECLSPEC bool SDL_EnableMenuItem(SDL_MenuItem*menu_item, bool enabled); extern SDL_DECLSPEC bool SDL_DestroyMenuBar(SDL_MenuBar*menu_bar); diff --git a/src/video/SDL_sysvideo.h b/src/video/SDL_sysvideo.h index add79b2e5b7ea..ba8849910fb15 100644 --- a/src/video/SDL_sysvideo.h +++ b/src/video/SDL_sysvideo.h @@ -209,23 +209,20 @@ typedef enum SDL_FULLSCREEN_PENDING } SDL_FullscreenResult; - -typedef struct PlatformMenuData PlatformMenuData; - typedef struct SDL_MenuBar { SDL_Window *window; SDL_MenuItem *item_list; - PlatformMenuData *platform; + void *platform_data; } SDL_MenuBar; typedef struct SDL_MenuItem_CommonData { - size_t type; - PlatformMenuData *platform; + void *platform_data; SDL_MenuBar *menu_bar; SDL_MenuItem *prev; SDL_MenuItem *next; + SDL_MenuItemType type; } SDL_MenuItem_CommonData; typedef struct SDL_Menu @@ -359,9 +356,9 @@ struct SDL_VideoDevice bool (*ReconfigureWindow)(SDL_VideoDevice *_this, SDL_Window *window, SDL_WindowFlags flags); - SDL_MenuBar *(*CreateMenuBar)(SDL_Window *window); - SDL_MenuItem *(*CreateMenuBarItem)(SDL_MenuBar *menu_bar, const char *name, SDL_MenuItemType type, Uint16 event_type); - SDL_MenuItem *(*CreateMenuItem)(SDL_Menu *menu_bar, const char *name, SDL_MenuItemType type, Uint16 event_type); + bool (*CreateMenuBar)(SDL_MenuBar *menu_bar); + bool (*CreateMenuBarItem)(SDL_MenuItem *menu_item, const char *name, Uint16 event_type); + bool (*CreateMenuItem)(SDL_MenuItem *menu_item, const char *name, Uint16 event_type); bool (*CheckMenuItem)(SDL_MenuItem *menu_item, bool checked); bool (*EnableMenuItem)(SDL_MenuItem *menu_item, bool enabled); void (*DestroyMenuItem)(SDL_MenuItem *menu_item); diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c index 5bc6772161c8f..a346221b5feb3 100644 --- a/src/video/SDL_video.c +++ b/src/video/SDL_video.c @@ -177,6 +177,24 @@ static VideoBootStrap *bootstrap[] = { return result; \ } +#define CHECK_MENUBAR_MAGIC(menubar, result) \ + if (!menubar) { \ + SDL_SetError("Invalid menubar"); \ + return result; \ + } + +#define CHECK_MENU_MAGIC(menu, result) \ + if (!menu) { \ + SDL_SetError("Invalid menu"); \ + return result; \ + } + +#define CHECK_MENUITEM_MAGIC(menuitem, result) \ + if (!menuitem) { \ + SDL_SetError("Invalid menuitem"); \ + return result; \ + } + #if defined(SDL_PLATFORM_MACOS) && defined(SDL_VIDEO_DRIVER_COCOA) // Support for macOS fullscreen spaces, etc. extern bool Cocoa_IsWindowInFullscreenSpace(SDL_Window *window); @@ -6113,19 +6131,41 @@ void SDL_OnApplicationDidEnterForeground(void) SDL_MenuBar* SDL_CreateMenuBar(SDL_Window *window) { - return _this->CreateMenuBar(window); + CHECK_WINDOW_MAGIC(window, NULL); + + if (!_this) { + return NULL; + } + + if (!_this->CreateMenuBar) { + return NULL; + } + + SDL_MenuBar *menu_bar = SDL_calloc(1, sizeof(SDL_MenuBar)); + menu_bar->window = window; + + if (!_this->CreateMenuBar(menu_bar)) { + SDL_free(menu_bar); + return NULL; + } + + return menu_bar; } SDL_MenuItem* SDL_CreateMenuBarItem(SDL_MenuBar* menu_bar, const char *name, SDL_MenuItemType type, Uint16 event_type) { + CHECK_MENUBAR_MAGIC(menu_bar, NULL); if (type == SDL_MENU_CHECKABLE) { SDL_SetError("Can't created a checkable item on the menubar, they must be in a menu."); return false; } + + SDL_MenuItem *menu_item = SDL_calloc(1, sizeof(SDL_MenuItem)); + menu_item->common.menu_bar = menu_bar; + menu_item->common.type = type; - SDL_MenuItem* menu_item = _this->CreateMenuBarItem(menu_bar, name, type, event_type); - - if (menu_item == NULL) { + if (_this->CreateMenuBarItem(menu_item, name, event_type)) { + SDL_free(menu_item); return NULL; } @@ -6133,8 +6173,7 @@ SDL_MenuItem* SDL_CreateMenuBarItem(SDL_MenuBar* menu_bar, const char *name, SDL if (menu_bar->item_list) { SDL_MenuItem* last = menu_bar->item_list; - for (; last->common.next; last = last->common.next) - ; + while (last->common.next) last = last->common.next; last->common.next = menu_item; menu_item->common.prev = last; @@ -6145,11 +6184,16 @@ SDL_MenuItem* SDL_CreateMenuBarItem(SDL_MenuBar* menu_bar, const char *name, SDL return menu_item; } -SDL_MenuItem* SDL_CreateMenuItem(SDL_Menu *menu, const char *name, SDL_MenuItemType type, Uint16 event_type) +SDL_MenuItem* SDL_CreateMenuItem(SDL_Menu* menu, const char *name, SDL_MenuItemType type, Uint16 event_type) { - SDL_MenuItem* menu_item = _this->CreateMenuItem(menu, name, type, event_type); - - if (menu_item == NULL) { + CHECK_MENU_MAGIC(menu, NULL); + + SDL_MenuItem *menu_item = SDL_calloc(1, sizeof(SDL_MenuItem)); + menu_item->common.menu_bar = menu->common.menu_bar; + menu_item->common.type = type; + + if (!_this->CreateMenuItem(menu_item, name, event_type)) { + SDL_free(menu_item); return NULL; } @@ -6170,6 +6214,7 @@ SDL_MenuItem* SDL_CreateMenuItem(SDL_Menu *menu, const char *name, SDL_MenuItemT bool SDL_CheckMenuItem(SDL_MenuItem* menu_item, bool checked) { + CHECK_MENUITEM_MAGIC(menu_item, NULL); if (menu_item->common.type != SDL_MENU_CHECKABLE) { SDL_SetError("menu_item isn't a checkable."); return false; @@ -6180,6 +6225,7 @@ bool SDL_CheckMenuItem(SDL_MenuItem* menu_item, bool checked) bool SDL_EnableMenuItem(SDL_MenuItem* menu_item, bool enabled) { + CHECK_MENUITEM_MAGIC(menu_item, NULL); if (menu_item->common.type == SDL_MENU) { SDL_SetError("menu_item can't be a menu."); return false; diff --git a/src/video/cocoa/SDL_cocoavideo.m b/src/video/cocoa/SDL_cocoavideo.m index d77080779d848..78eb8d058de97 100644 --- a/src/video/cocoa/SDL_cocoavideo.m +++ b/src/video/cocoa/SDL_cocoavideo.m @@ -126,6 +126,12 @@ static void Cocoa_DeleteDevice(SDL_VideoDevice *device) device->SetWindowParent = Cocoa_SetWindowParent; device->SetWindowModal = Cocoa_SetWindowModal; device->SyncWindow = Cocoa_SyncWindow; + device->CreateMenuBar = Cocoa_CreateMenuBar; + device->CreateMenuBarItem = Cocoa_CreateMenuBarItem; + device->CreateMenuItem = Cocoa_CreateMenuItem; + device->CheckMenuItem = Cocoa_CheckMenuItem; + device->EnableMenuItem = Cocoa_EnableMenuItem; + device->DestroyMenuItem = Cocoa_DestroyMenuItem; #ifdef SDL_VIDEO_OPENGL_CGL device->GL_LoadLibrary = Cocoa_GL_LoadLibrary; @@ -330,4 +336,90 @@ void SDL_NSLog(const char *prefix, const char *text) } } + + +@interface PlatformMenuData2 : NSObject { +@public + Uint16 user_event_type; + NSMenu *menubar; +} + +- (void)Cocoa_PlatformMenuData_MenuButtonClicked; + +@end + + +@implementation PlatformMenuData2 ++ (PlatformMenuData2*)Cocoa_CreatePlatformMenuData{ + +} +- (void)Cocoa_PlatformMenuData_MenuButtonClicked{ + SDL_Event event; + event.type = SDL_EVENT_MENU_BUTTON_CLICKED; + event.menu.timestamp = SDL_GetTicksNS(); + event.menu.user_event_type = user_event_type; + + SDL_PushEvent(&event); +} + +@end + + +bool SDLCALL Cocoa_CreateMenuBar(SDL_MenuBar *menu_bar) +{ + PlatformMenuData2* platform_menu =[PlatformMenuData2 new]; + platform_menu->menubar = [NSMenu new]; + [NSApp setMainMenu:platform_menu->menubar]; + menu_bar->platform_data = CFBridgingRetain(platform_menu); + + return true; + // **** App Menu **** // +// NSMenuItem *appMenuItem = [NSMenuItem new]; +// NSMenu *appMenu = [NSMenu new]; +// [appMenu addItemWithTitle: @"Quit" action:@selector(terminate:) keyEquivalent:@"q"]; +// [appMenuItem setSubmenu:appMenu]; +// [menubar addItem:appMenuItem]; +// // **** File Menu **** // +// NSMenuItem *fileMenuItem = [NSMenuItem new]; +// NSMenu *fileMenu = [[NSMenu alloc] initWithTitle:@"File"]; +// //[fileMenu addItemWithTitle:@"New" action:@selector(menuAction:) keyEquivalent:@""]; +// //[fileMenu addItemWithTitle:@"Open" action:@selector(menuAction:) keyEquivalent:@""]; +// //[fileMenu addItemWithTitle:@"Save" action:@selector(menuAction:) keyEquivalent:@""]; +// NSMenuItem *newItem = [fileMenu addItemWithTitle:@"New" action:@selector(Cocoa_PlatformMenuData_MenuButtonClicked:) keyEquivalent:@""]; +// newItem.target = platform_menu; +// [fileMenuItem setSubmenu: fileMenu]; +// [menubar addItem: fileMenuItem]; + + + + + return NULL; +} + +bool Cocoa_CreateMenuBarItem(SDL_MenuItem *menu_item, const char *name, Uint16 event_type) +{ + menu_item->common.platform = CreatePlatformMenuData(menu, type); + return NULL; +} + +bool Cocoa_CreateMenuItem(SDL_MenuItem *menu_item, const char *name, Uint16 event_type) +{ + return NULL; +} + +bool Cocoa_CheckMenuItem(SDL_MenuItem *menu_item, bool checked) +{ + return false; +} + +bool Cocoa_EnableMenuItem(SDL_MenuItem *menu_item, bool enabled) +{ + return false; +} + +void Cocoa_DestroyMenuItem(SDL_MenuItem *menu_item) +{ + return false; +} + #endif // SDL_VIDEO_DRIVER_COCOA diff --git a/src/video/cocoa/SDL_cocoawindow.h b/src/video/cocoa/SDL_cocoawindow.h index e4ab6efed4e19..c8bbc650547d9 100644 --- a/src/video/cocoa/SDL_cocoawindow.h +++ b/src/video/cocoa/SDL_cocoawindow.h @@ -196,4 +196,12 @@ extern bool Cocoa_SyncWindow(SDL_VideoDevice *_this, SDL_Window *window); extern void Cocoa_MenuVisibilityCallback(void *userdata, const char *name, const char *oldValue, const char *newValue); + +extern bool SDLCALL Cocoa_CreateMenuBar(SDL_MenuBar *menu_bar); +extern bool Cocoa_CreateMenuBarItem(SDL_MenuItem *menu_item, const char *name, Uint16 event_type); +extern bool Cocoa_CreateMenuItem(SDL_MenuItem *menu_item, const char *name, Uint16 event_type); +extern bool Cocoa_CheckMenuItem(SDL_MenuItem *menu_item, bool checked); +extern bool Cocoa_EnableMenuItem(SDL_MenuItem *menu_item, bool enabled); +extern void Cocoa_DestroyMenuItem(SDL_MenuItem *menu_item); + #endif // SDL_cocoawindow_h_ diff --git a/src/video/windows/SDL_windowsvideo.c b/src/video/windows/SDL_windowsvideo.c index c713fc143e13f..55e3d7b679f6a 100644 --- a/src/video/windows/SDL_windowsvideo.c +++ b/src/video/windows/SDL_windowsvideo.c @@ -910,11 +910,9 @@ static HWND GetHwndFromWindow(SDL_Window *window) return (HWND)SDL_GetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL); } -SDL_MenuBar *SDLCALL Win32_CreateMenuBar(SDL_Window *window) +bool SDLCALL Win32_CreateMenuBar(SDL_MenuBar *menu_bar) { - SDL_MenuBar *menu_bar = SDL_calloc(1, sizeof(SDL_MenuBar)); menu_bar->platform = CreatePlatformMenuData(INVALID_HANDLE_VALUE, SDL_MENU); - menu_bar->window = window; menu_bar->platform->handle_or_id = (UINT_PTR)CreateMenu(); SetMenu(GetHwndFromWindow(window), (HMENU)menu_bar->platform->handle_or_id); @@ -922,10 +920,8 @@ SDL_MenuBar *SDLCALL Win32_CreateMenuBar(SDL_Window *window) return menu_bar; } -static SDL_MenuItem *Win32_CreateMenuItemImpl(HMENU menu, const char *name, SDL_MenuItemType type, Uint16 event_type, bool toplevel_menu) +static bool Win32_CreateMenuItemImpl(SDL_MenuItem* menu_item, HMENU menu, const char *name, Uint16 event_type, bool toplevel_menu) { - SDL_MenuItem *menu_item = SDL_calloc(1, sizeof(SDL_MenuItem)); - menu_item->common.type = type; menu_item->common.platform = CreatePlatformMenuData(menu, type); UINT flags = 0; @@ -954,19 +950,17 @@ static SDL_MenuItem *Win32_CreateMenuItemImpl(HMENU menu, const char *name, SDL_ return menu_item; } -static SDL_MenuItem *Win32_CreateMenuBarItem(SDL_MenuBar *menu_bar, const char *name, SDL_MenuItemType type, Uint16 event_type) +static bool Win32_CreateMenuBarItem(SDL_MenuItem* menu_item, const char *name, SDL_MenuItemType type, Uint16 event_type) { - SDL_MenuItem *item = Win32_CreateMenuItemImpl((HMENU)menu_bar->platform->handle_or_id, name, type, event_type, true); - item->common.menu_bar = menu_bar; + bool ret = Win32_CreateMenuItemImpl((HMENU)menu_bar->platform->handle_or_id, name, type, event_type, true); DrawMenuBar(GetHwndFromWindow(menu_bar->window)); return item; } -static SDL_MenuItem *Win32_CreateMenuItem(SDL_Menu *menu, const char *name, SDL_MenuItemType type, Uint16 event_type) +static bool Win32_CreateMenuItem(SDL_MenuItem* menu_item, const char *name, SDL_MenuItemType type, Uint16 event_type) { - SDL_MenuItem *item = Win32_CreateMenuItemImpl((HMENU)menu->common.platform->handle_or_id, name, type, event_type, false); - item->common.menu_bar = menu->common.menu_bar; + bool ret = Win32_CreateMenuItemImpl((HMENU)menu->common.platform->handle_or_id, name, type, event_type, false); DrawMenuBar(GetHwndFromWindow(item->common.menu_bar->window)); return item; From 0886be1607e14f4194891c76b815c8f1352390bc Mon Sep 17 00:00:00 2001 From: "Joshua T. Fisher" Date: Wed, 6 Aug 2025 03:00:44 -0700 Subject: [PATCH 04/35] more work --- examples/video/01-menubar/menubar.c | 2 +- src/video/SDL_video.c | 2 +- src/video/cocoa/SDL_cocoavideo.m | 54 ++++++++++++++++++++++++----- 3 files changed, 48 insertions(+), 10 deletions(-) diff --git a/examples/video/01-menubar/menubar.c b/examples/video/01-menubar/menubar.c index c511e0adf0254..2fed8977c1c35 100644 --- a/examples/video/01-menubar/menubar.c +++ b/examples/video/01-menubar/menubar.c @@ -76,7 +76,7 @@ void CreateMenuBar() SDL_AppResult SDL_AppInit(void** appstate, int argc, char** argv) { SDL_CreateWindowAndRenderer("menu bar test", 640, 480, 0, &window, &renderer); - //CreateMenuBar(); + CreateMenuBar(); //return SDL_APP_SUCCESS; return SDL_APP_CONTINUE; diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c index a346221b5feb3..a570320cbf820 100644 --- a/src/video/SDL_video.c +++ b/src/video/SDL_video.c @@ -6164,7 +6164,7 @@ SDL_MenuItem* SDL_CreateMenuBarItem(SDL_MenuBar* menu_bar, const char *name, SDL menu_item->common.menu_bar = menu_bar; menu_item->common.type = type; - if (_this->CreateMenuBarItem(menu_item, name, event_type)) { + if (!_this->CreateMenuBarItem(menu_item, name, event_type)) { SDL_free(menu_item); return NULL; } diff --git a/src/video/cocoa/SDL_cocoavideo.m b/src/video/cocoa/SDL_cocoavideo.m index 78eb8d058de97..802cb60f0f7b1 100644 --- a/src/video/cocoa/SDL_cocoavideo.m +++ b/src/video/cocoa/SDL_cocoavideo.m @@ -341,18 +341,24 @@ void SDL_NSLog(const char *prefix, const char *text) @interface PlatformMenuData2 : NSObject { @public Uint16 user_event_type; - NSMenu *menubar; + NSMenu *menu; + NSMenuItem *menu_item; + SDL_Menu* parent; } ++ (PlatformMenuData2*)Cocoa_CreatePlatformMenuData:(SDL_Menu*)menu :(SDL_MenuItemType)type; - (void)Cocoa_PlatformMenuData_MenuButtonClicked; @end @implementation PlatformMenuData2 -+ (PlatformMenuData2*)Cocoa_CreatePlatformMenuData{ - ++ (PlatformMenuData2*)Cocoa_CreatePlatformMenuDataWithMenu:(SDL_Menu*)menu { + PlatformMenuData2* platform_menu =[PlatformMenuData2 new]; + platform_menu->parent = menu; + return platform_menu; } + - (void)Cocoa_PlatformMenuData_MenuButtonClicked{ SDL_Event event; event.type = SDL_EVENT_MENU_BUTTON_CLICKED; @@ -368,8 +374,8 @@ - (void)Cocoa_PlatformMenuData_MenuButtonClicked{ bool SDLCALL Cocoa_CreateMenuBar(SDL_MenuBar *menu_bar) { PlatformMenuData2* platform_menu =[PlatformMenuData2 new]; - platform_menu->menubar = [NSMenu new]; - [NSApp setMainMenu:platform_menu->menubar]; + platform_menu->menu = [NSMenu new]; + [NSApp setMainMenu:platform_menu->menu]; menu_bar->platform_data = CFBridgingRetain(platform_menu); return true; @@ -398,13 +404,45 @@ bool SDLCALL Cocoa_CreateMenuBar(SDL_MenuBar *menu_bar) bool Cocoa_CreateMenuBarItem(SDL_MenuItem *menu_item, const char *name, Uint16 event_type) { - menu_item->common.platform = CreatePlatformMenuData(menu, type); - return NULL; + PlatformMenuData2* platform_data = [PlatformMenuData2 new]; + menu_item->common.platform_data = CFBridgingRetain(platform_data); + PlatformMenuData2* parent_platform_data = (PlatformMenuData2*)CFBridgingRelease(menu_item->common.platform_data); + + if (menu_item->common.type == SDL_MENU) { + platform_data->menu = [NSMenu new]; + platform_data->menu_item = [NSMenuItem new]; + [platform_data->menu_item setSubmenu: platform_data->menu]; + [parent_platform_data->menu addItem: platform_data->menu_item]; + + } else { + NSMenuItem *newItem = [platform_data->menu addItemWithTitle:[NSString stringWithUTF8String:name] action:@selector(Cocoa_PlatformMenuData_MenuButtonClicked:) keyEquivalent:@""]; + newItem.target = platform_data; + + [platform_data->menu addItem: newItem]; + } + return true; } bool Cocoa_CreateMenuItem(SDL_MenuItem *menu_item, const char *name, Uint16 event_type) { - return NULL; + PlatformMenuData2* platform_data = [PlatformMenuData2 new]; + menu_item->common.platform_data = CFBridgingRetain(platform_data); + PlatformMenuData2* parent_platform_data = (PlatformMenuData2*)CFBridgingRelease(menu_item->common.platform_data); + + if (menu_item->common.type == SDL_MENU) { + platform_data->menu = [NSMenu new]; + platform_data->menu_item = [NSMenuItem new]; + [platform_data->menu_item setSubmenu: platform_data->menu]; + [parent_platform_data->menu addItem: platform_data->menu_item]; + + } else { + NSMenuItem *newItem = [platform_data->menu addItemWithTitle:[NSString stringWithUTF8String:name] action:@selector(Cocoa_PlatformMenuData_MenuButtonClicked:) keyEquivalent:@""]; + newItem.target = platform_data; + + platform_data->menu = parent_platform_data->menu; + [platform_data->menu addItem: newItem]; + } + return true; } bool Cocoa_CheckMenuItem(SDL_MenuItem *menu_item, bool checked) From e1088b44a18e389b2b117536dee265155d129396 Mon Sep 17 00:00:00 2001 From: "Joshua T. Fisher" Date: Wed, 6 Aug 2025 23:49:32 -0700 Subject: [PATCH 05/35] Fix some of the compilation errors I caused on the windows side. --- src/video/windows/SDL_windowsvideo.c | 51 +++++++++++++++------------- src/video/windows/SDL_windowsvideo.h | 21 +++++++++--- 2 files changed, 44 insertions(+), 28 deletions(-) diff --git a/src/video/windows/SDL_windowsvideo.c b/src/video/windows/SDL_windowsvideo.c index 55e3d7b679f6a..b7202b24b0dc9 100644 --- a/src/video/windows/SDL_windowsvideo.c +++ b/src/video/windows/SDL_windowsvideo.c @@ -893,11 +893,11 @@ static PlatformMenuData *CreatePlatformMenuData(HMENU owner, SDL_MenuItemType ty switch (type) { case SDL_MENU: - platform->menu_owner = INVALID_HANDLE_VALUE; + platform->menu_bar = INVALID_HANDLE_VALUE; platform->handle_or_id = (UINT_PTR)INVALID_HANDLE_VALUE; break; default: - platform->menu_owner = owner; + platform->menu_bar = owner; platform->handle_or_id = (UINT_PTR)SDL_WIN32_INVALID_MENU_ID; break; } @@ -910,30 +910,35 @@ static HWND GetHwndFromWindow(SDL_Window *window) return (HWND)SDL_GetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL); } +// PlatformMenuData platform_data = + bool SDLCALL Win32_CreateMenuBar(SDL_MenuBar *menu_bar) { - menu_bar->platform = CreatePlatformMenuData(INVALID_HANDLE_VALUE, SDL_MENU); - menu_bar->platform->handle_or_id = (UINT_PTR)CreateMenu(); + PlatformMenuData* platform_data = CreatePlatformMenuData(INVALID_HANDLE_VALUE, SDL_MENU); + menu_bar->platform_data = (void*)platform_data; + platform_data->handle_or_id = (UINT_PTR)CreateMenu(); - SetMenu(GetHwndFromWindow(window), (HMENU)menu_bar->platform->handle_or_id); + SetMenu(GetHwndFromWindow(menu_bar->window), (HMENU)platform_data->handle_or_id); return menu_bar; } -static bool Win32_CreateMenuItemImpl(SDL_MenuItem* menu_item, HMENU menu, const char *name, Uint16 event_type, bool toplevel_menu) +static bool Win32_CreateMenuItemImpl(SDL_MenuItem *menu_item, HMENU menu, const char *name, Uint16 event_type, bool toplevel_menu) { - menu_item->common.platform = CreatePlatformMenuData(menu, type); + PlatformMenuData *platform_data = CreatePlatformMenuData(menu, menu_item->common.type); + menu_item->common.platform_data = (void *)platform_data; + UINT flags = 0; if (!toplevel_menu) { flags = MF_STRING; } - if (type == SDL_MENU) { + if (menu_item->common.type == SDL_MENU) { if (toplevel_menu) { - menu_item->common.platform->handle_or_id = (UINT_PTR)CreateMenu(); + platform_data->handle_or_id = (UINT_PTR)CreateMenu(); } else { - menu_item->common.platform->handle_or_id = (UINT_PTR)CreatePopupMenu(); + platform_data->handle_or_id = (UINT_PTR)CreatePopupMenu(); } flags |= MF_POPUP; @@ -942,46 +947,44 @@ static bool Win32_CreateMenuItemImpl(SDL_MenuItem* menu_item, HMENU menu, const flags |= MF_STRING; } - menu_item->common.platform->handle_or_id = (UINT_PTR)event_type; + platform_data->handle_or_id = (UINT_PTR)event_type; } - AppendMenuA(menu, flags, menu_item->common.platform->handle_or_id, name); + AppendMenuA(menu, flags, platform_data->handle_or_id, name); return menu_item; } -static bool Win32_CreateMenuBarItem(SDL_MenuItem* menu_item, const char *name, SDL_MenuItemType type, Uint16 event_type) +static bool Win32_CreateMenuBarItem(SDL_MenuItem *menu_item, const char *name, SDL_MenuItemType type, Uint16 event_type) { - bool ret = Win32_CreateMenuItemImpl((HMENU)menu_bar->platform->handle_or_id, name, type, event_type, true); + bool ret = Win32_CreateMenuItemImpl((HMENU)menu_bar->platform_data->handle_or_id, name, type, event_type, true); DrawMenuBar(GetHwndFromWindow(menu_bar->window)); return item; } -static bool Win32_CreateMenuItem(SDL_MenuItem* menu_item, const char *name, SDL_MenuItemType type, Uint16 event_type) +static bool Win32_CreateMenuItem(SDL_MenuItem *menu_item, const char *name, SDL_MenuItemType type, Uint16 event_type) { - bool ret = Win32_CreateMenuItemImpl((HMENU)menu->common.platform->handle_or_id, name, type, event_type, false); + bool ret = Win32_CreateMenuItemImpl((HMENU)menu_item->common.platform_data->handle_or_id, name, type, event_type, false); - DrawMenuBar(GetHwndFromWindow(item->common.menu_bar->window)); - return item; + DrawMenuBar(GetHwndFromWindow(menu_item->common.menu_bar->window)); + return true; } static bool Win32_CheckMenuItem(SDL_MenuItem *menu_item, bool checked) { - return CheckMenuItem(menu_item->common.platform->menu_owner, (UINT)menu_item->common.platform->handle_or_id, MF_BYCOMMAND | (checked ? MF_CHECKED : MF_UNCHECKED)); - ; + return CheckMenuItem(menu_item->common.platform_data->menu_bar, (UINT)menu_item->common.platform_data->handle_or_id, MF_BYCOMMAND | (checked ? MF_CHECKED : MF_UNCHECKED)); } static bool Win32_EnableMenuItem(SDL_MenuItem *menu_item, bool enabled) { - return EnableMenuItem(menu_item->common.platform->menu_owner, (UINT)menu_item->common.platform->handle_or_id, MF_BYCOMMAND | (enabled ? MF_ENABLED : MF_GRAYED)); - ; + return EnableMenuItem(menu_item->common.platform_data->menu_bar, (UINT)menu_item->common.platform_data->handle_or_id, MF_BYCOMMAND | (enabled ? MF_ENABLED : MF_GRAYED)); } static void Win32_DestroyMenuItem(SDL_MenuItem *menu_item) { - RemoveMenu(menu_item->common.platform->menu_owner, (UINT)menu_item->common.platform->handle_or_id, MF_BYCOMMAND); - SDL_free(menu_item->common.platform); + RemoveMenu(menu_item->common.platform_data->menu_bar, (UINT)menu_item->common.platform_data->handle_or_id, MF_BYCOMMAND); + SDL_free(menu_item->common.platform_data); } #endif // SDL_VIDEO_DRIVER_WINDOWS diff --git a/src/video/windows/SDL_windowsvideo.h b/src/video/windows/SDL_windowsvideo.h index 59bafe7d97b8c..6b716e0866332 100644 --- a/src/video/windows/SDL_windowsvideo.h +++ b/src/video/windows/SDL_windowsvideo.h @@ -672,10 +672,23 @@ typedef struct PlatformMenuData } PlatformMenuData; extern PlatformMenuData *CreatePlatformMenuData(HMENU owner, SDL_MenuItemType type); -extern SDL_MenuBar *SDLCALL Win32_CreateMenuBar(SDL_Window *window); -extern SDL_MenuItem *Win32_CreateMenuItemImpl(HMENU menu, const char *name, SDL_MenuItemType type, Uint16 event_type, bool toplevel_menu); -extern SDL_MenuItem *Win32_CreateMenuBarItem(SDL_MenuBar *menu_bar, const char *name, SDL_MenuItemType type, Uint16 event_type); -extern SDL_MenuItem *Win32_CreateMenuItem(SDL_Menu *menu, const char *name, SDL_MenuItemType type, Uint16 event_type); +extern bool SDLCALL Win32_CreateMenuBar(SDL_MenuBar *menu_bar); + + + + +//bool (*CreateMenuBar)(SDL_MenuBar *menu_bar); +//bool (*CreateMenuBarItem)(SDL_MenuItem *menu_item, const char *name, Uint16 event_type); +//bool (*CreateMenuItem)(SDL_MenuItem *menu_item, const char *name, Uint16 event_type); +//bool (*CheckMenuItem)(SDL_MenuItem *menu_item, bool checked); +//bool (*EnableMenuItem)(SDL_MenuItem *menu_item, bool enabled); +//void (*DestroyMenuItem)(SDL_MenuItem *menu_item); + +//extern SDL_MenuItem *Win32_CreateMenuItemImpl(HMENU menu, const char *name, SDL_MenuItemType type, Uint16 event_type, bool toplevel_menu); +extern bool Win32_CreateMenuBarItem(SDL_MenuItem *menu_item, const char *name, Uint16 event_type); + +//(*CreateMenuItem)(SDL_MenuItem *menu_item, const char *name, Uint16 event_type) +extern bool Win32_CreateMenuItem(SDL_MenuItem *menu_item, const char *name, Uint16 event_type); extern bool Win32_CheckMenuItem(SDL_MenuItem *menu_item, bool checked); extern bool Win32_EnableMenuItem(SDL_MenuItem *menu_item, bool enabled); extern void Win32_DestroyMenuItem(SDL_MenuItem *menu_item); From 8f993d2c247832665875d9d76cc66676d24a1ec2 Mon Sep 17 00:00:00 2001 From: "Joshua T. Fisher" Date: Sun, 10 Aug 2025 19:06:07 -0700 Subject: [PATCH 06/35] Fix windows. --- include/SDL3/SDL_video.h | 1 + src/video/SDL_sysvideo.h | 17 +++-- src/video/SDL_video.c | 13 ++-- src/video/windows/SDL_windowsvideo.c | 105 +++++++++++++++++++-------- src/video/windows/SDL_windowsvideo.h | 4 +- 5 files changed, 94 insertions(+), 46 deletions(-) diff --git a/include/SDL3/SDL_video.h b/include/SDL3/SDL_video.h index ee47458307cef..0a21c62c06357 100644 --- a/include/SDL3/SDL_video.h +++ b/include/SDL3/SDL_video.h @@ -3036,6 +3036,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_DisableScreenSaver(void); typedef enum SDL_MenuItemType { + SDL_MENUBAR, SDL_MENU, SDL_MENU_BUTTON, SDL_MENU_CHECKABLE, diff --git a/src/video/SDL_sysvideo.h b/src/video/SDL_sysvideo.h index ba8849910fb15..4739b25f91faa 100644 --- a/src/video/SDL_sysvideo.h +++ b/src/video/SDL_sysvideo.h @@ -209,22 +209,23 @@ typedef enum SDL_FULLSCREEN_PENDING } SDL_FullscreenResult; -typedef struct SDL_MenuBar -{ - SDL_Window *window; - SDL_MenuItem *item_list; - void *platform_data; -} SDL_MenuBar; typedef struct SDL_MenuItem_CommonData { void *platform_data; - SDL_MenuBar *menu_bar; + SDL_MenuItem *parent; + SDL_Window *window; SDL_MenuItem *prev; SDL_MenuItem *next; SDL_MenuItemType type; } SDL_MenuItem_CommonData; +typedef struct SDL_MenuBar +{ + SDL_MenuItem_CommonData common; + SDL_MenuItem *item_list; +} SDL_MenuBar; + typedef struct SDL_Menu { SDL_MenuItem_CommonData common; @@ -245,6 +246,8 @@ typedef struct SDL_MenuItem_Checkable typedef union SDL_MenuItem { SDL_MenuItem_CommonData common; + SDL_MenuBar menu_bar; + SDL_Menu menu; SDL_MenuItem_Button button; SDL_MenuItem_Checkable checkable; } SDL_MenuItem; diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c index a570320cbf820..7eee2593b27af 100644 --- a/src/video/SDL_video.c +++ b/src/video/SDL_video.c @@ -6142,7 +6142,8 @@ SDL_MenuBar* SDL_CreateMenuBar(SDL_Window *window) } SDL_MenuBar *menu_bar = SDL_calloc(1, sizeof(SDL_MenuBar)); - menu_bar->window = window; + menu_bar->common.window = window; + menu_bar->common.type = SDL_MENUBAR; if (!_this->CreateMenuBar(menu_bar)) { SDL_free(menu_bar); @@ -6161,7 +6162,8 @@ SDL_MenuItem* SDL_CreateMenuBarItem(SDL_MenuBar* menu_bar, const char *name, SDL } SDL_MenuItem *menu_item = SDL_calloc(1, sizeof(SDL_MenuItem)); - menu_item->common.menu_bar = menu_bar; + menu_item->common.parent = (SDL_MenuItem *)menu_bar; + menu_item->common.window = menu_bar->common.window; menu_item->common.type = type; if (!_this->CreateMenuBarItem(menu_item, name, event_type)) { @@ -6189,7 +6191,8 @@ SDL_MenuItem* SDL_CreateMenuItem(SDL_Menu* menu, const char *name, SDL_MenuItemT CHECK_MENU_MAGIC(menu, NULL); SDL_MenuItem *menu_item = SDL_calloc(1, sizeof(SDL_MenuItem)); - menu_item->common.menu_bar = menu->common.menu_bar; + menu_item->common.window = menu->common.window; + menu_item->common.parent = (SDL_MenuItem *)menu; menu_item->common.type = type; if (!_this->CreateMenuItem(menu_item, name, event_type)) { @@ -6214,7 +6217,7 @@ SDL_MenuItem* SDL_CreateMenuItem(SDL_Menu* menu, const char *name, SDL_MenuItemT bool SDL_CheckMenuItem(SDL_MenuItem* menu_item, bool checked) { - CHECK_MENUITEM_MAGIC(menu_item, NULL); + CHECK_MENUITEM_MAGIC(menu_item, false); if (menu_item->common.type != SDL_MENU_CHECKABLE) { SDL_SetError("menu_item isn't a checkable."); return false; @@ -6225,7 +6228,7 @@ bool SDL_CheckMenuItem(SDL_MenuItem* menu_item, bool checked) bool SDL_EnableMenuItem(SDL_MenuItem* menu_item, bool enabled) { - CHECK_MENUITEM_MAGIC(menu_item, NULL); + CHECK_MENUITEM_MAGIC(menu_item, false); if (menu_item->common.type == SDL_MENU) { SDL_SetError("menu_item can't be a menu."); return false; diff --git a/src/video/windows/SDL_windowsvideo.c b/src/video/windows/SDL_windowsvideo.c index b7202b24b0dc9..cb5ac00afcb47 100644 --- a/src/video/windows/SDL_windowsvideo.c +++ b/src/video/windows/SDL_windowsvideo.c @@ -887,20 +887,12 @@ bool WIN_IsPerMonitorV2DPIAware(SDL_VideoDevice *_this) #define SDL_WIN32_INVALID_MENU_ID 65535 -static PlatformMenuData *CreatePlatformMenuData(HMENU owner, SDL_MenuItemType type) +static PlatformMenuData *CreatePlatformMenuData(HMENU owner_handle, UINT_PTR self_handle) { PlatformMenuData *platform = SDL_calloc(1, sizeof(PlatformMenuData)); - switch (type) { - case SDL_MENU: - platform->menu_bar = INVALID_HANDLE_VALUE; - platform->handle_or_id = (UINT_PTR)INVALID_HANDLE_VALUE; - break; - default: - platform->menu_bar = owner; - platform->handle_or_id = (UINT_PTR)SDL_WIN32_INVALID_MENU_ID; - break; - } + platform->owner_handle = owner_handle; + platform->self_handle = self_handle; return platform; } @@ -914,18 +906,29 @@ static HWND GetHwndFromWindow(SDL_Window *window) bool SDLCALL Win32_CreateMenuBar(SDL_MenuBar *menu_bar) { - PlatformMenuData* platform_data = CreatePlatformMenuData(INVALID_HANDLE_VALUE, SDL_MENU); - menu_bar->platform_data = (void*)platform_data; - platform_data->handle_or_id = (UINT_PTR)CreateMenu(); + HMENU menu_handle = CreateMenu(); + + if (!menu_handle) { + WIN_SetError("Unable to create MenuBar"); + return false; + } - SetMenu(GetHwndFromWindow(menu_bar->window), (HMENU)platform_data->handle_or_id); + menu_bar->common.platform_data = (void*)CreatePlatformMenuData(NULL, (UINT_PTR)menu_handle); - return menu_bar; + if (!SetMenu(GetHwndFromWindow(menu_bar->common.window), menu_handle)) { + WIN_SetError("Unable to set MenuBar"); + SDL_free(menu_bar->common.platform_data); + DestroyMenu(menu_handle); + return false; + } + + return true; } -static bool Win32_CreateMenuItemImpl(SDL_MenuItem *menu_item, HMENU menu, const char *name, Uint16 event_type, bool toplevel_menu) +static bool Win32_CreateMenuItemImpl(SDL_MenuItem *menu_item, const char *name, Uint16 event_type, bool toplevel_menu) { - PlatformMenuData *platform_data = CreatePlatformMenuData(menu, menu_item->common.type); + PlatformMenuData *menu_bar_platform_data = (PlatformMenuData *)menu_item->common.parent->common.platform_data; + PlatformMenuData *platform_data = CreatePlatformMenuData((HMENU)menu_bar_platform_data->self_handle, menu_item->common.type); menu_item->common.platform_data = (void *)platform_data; UINT flags = 0; @@ -936,9 +939,14 @@ static bool Win32_CreateMenuItemImpl(SDL_MenuItem *menu_item, HMENU menu, const if (menu_item->common.type == SDL_MENU) { if (toplevel_menu) { - platform_data->handle_or_id = (UINT_PTR)CreateMenu(); + platform_data->self_handle = (UINT_PTR)CreateMenu(); } else { - platform_data->handle_or_id = (UINT_PTR)CreatePopupMenu(); + platform_data->self_handle = (UINT_PTR)CreatePopupMenu(); + } + + if (!platform_data->self_handle) { + SDL_free(platform_data); + return WIN_SetError("Unable to create Menu."); } flags |= MF_POPUP; @@ -947,43 +955,76 @@ static bool Win32_CreateMenuItemImpl(SDL_MenuItem *menu_item, HMENU menu, const flags |= MF_STRING; } - platform_data->handle_or_id = (UINT_PTR)event_type; + platform_data->self_handle = (UINT_PTR)event_type; } - AppendMenuA(menu, flags, platform_data->handle_or_id, name); + if (!AppendMenuA((HMENU)menu_bar_platform_data->self_handle, flags, platform_data->self_handle, name)) { + return WIN_SetError("Unable to append item to Menu."); + } return menu_item; } -static bool Win32_CreateMenuBarItem(SDL_MenuItem *menu_item, const char *name, SDL_MenuItemType type, Uint16 event_type) +static bool Win32_CreateMenuBarItem(SDL_MenuItem *menu_item, const char *name, Uint16 event_type) { - bool ret = Win32_CreateMenuItemImpl((HMENU)menu_bar->platform_data->handle_or_id, name, type, event_type, true); + bool ret = Win32_CreateMenuItemImpl(menu_item, name, event_type, true); + + if (!ret) { + return WIN_SetError("Unable to create menu item"); + } + + if (!DrawMenuBar(GetHwndFromWindow(menu_item->common.window))) { + return WIN_SetError("Unable to draw menu bar"); + } - DrawMenuBar(GetHwndFromWindow(menu_bar->window)); - return item; + return true; } -static bool Win32_CreateMenuItem(SDL_MenuItem *menu_item, const char *name, SDL_MenuItemType type, Uint16 event_type) +static bool Win32_CreateMenuItem(SDL_MenuItem *menu_item, const char *name, Uint16 event_type) { - bool ret = Win32_CreateMenuItemImpl((HMENU)menu_item->common.platform_data->handle_or_id, name, type, event_type, false); + bool ret = Win32_CreateMenuItemImpl(menu_item, name, event_type, false); + + if (!ret) { + return WIN_SetError("Unable to create menu item"); + } + + if (!DrawMenuBar(GetHwndFromWindow(menu_item->common.window))) { + return WIN_SetError("Unable to draw menu bar"); + } - DrawMenuBar(GetHwndFromWindow(menu_item->common.menu_bar->window)); return true; } static bool Win32_CheckMenuItem(SDL_MenuItem *menu_item, bool checked) { - return CheckMenuItem(menu_item->common.platform_data->menu_bar, (UINT)menu_item->common.platform_data->handle_or_id, MF_BYCOMMAND | (checked ? MF_CHECKED : MF_UNCHECKED)); + PlatformMenuData *platform_data = (PlatformMenuData *)menu_item->common.platform_data; + + if (!CheckMenuItem(platform_data->owner_handle, (UINT)platform_data->self_handle, MF_BYCOMMAND | (checked ? MF_CHECKED : MF_UNCHECKED))) { + return WIN_SetError("Unable to check menu item."); + } + + return true; } static bool Win32_EnableMenuItem(SDL_MenuItem *menu_item, bool enabled) { - return EnableMenuItem(menu_item->common.platform_data->menu_bar, (UINT)menu_item->common.platform_data->handle_or_id, MF_BYCOMMAND | (enabled ? MF_ENABLED : MF_GRAYED)); + PlatformMenuData *platform_data = (PlatformMenuData *)menu_item->common.platform_data; + + if (!EnableMenuItem(platform_data->owner_handle, (UINT)platform_data->self_handle, MF_BYCOMMAND | (enabled ? MF_ENABLED : MF_GRAYED))) { + return WIN_SetError("Unable to enable menu item."); + } + + return true; } static void Win32_DestroyMenuItem(SDL_MenuItem *menu_item) { - RemoveMenu(menu_item->common.platform_data->menu_bar, (UINT)menu_item->common.platform_data->handle_or_id, MF_BYCOMMAND); + PlatformMenuData *platform_data = (PlatformMenuData *)menu_item->common.platform_data; + + if (!RemoveMenu(platform_data->owner_handle, (UINT)platform_data->self_handle, MF_BYCOMMAND)) { + return WIN_SetError("Unable to remove menu item."); + } + SDL_free(menu_item->common.platform_data); } diff --git a/src/video/windows/SDL_windowsvideo.h b/src/video/windows/SDL_windowsvideo.h index 6b716e0866332..7419b91ce8461 100644 --- a/src/video/windows/SDL_windowsvideo.h +++ b/src/video/windows/SDL_windowsvideo.h @@ -667,8 +667,8 @@ extern bool WIN_IsPerMonitorV2DPIAware(SDL_VideoDevice *_this); typedef struct PlatformMenuData { - HMENU menu_owner; - UINT_PTR handle_or_id; + HMENU owner_handle; + UINT_PTR self_handle; } PlatformMenuData; extern PlatformMenuData *CreatePlatformMenuData(HMENU owner, SDL_MenuItemType type); From f0e478cf9bd081a77ad64cc035d6d7ce40e57735 Mon Sep 17 00:00:00 2001 From: "Joshua T. Fisher" Date: Mon, 11 Aug 2025 01:34:24 -0700 Subject: [PATCH 07/35] Okay, we've got one of these showing up. --- src/video/cocoa/SDL_cocoavideo.m | 31 ++++++++++--------------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/src/video/cocoa/SDL_cocoavideo.m b/src/video/cocoa/SDL_cocoavideo.m index 802cb60f0f7b1..28c03f3027b5d 100644 --- a/src/video/cocoa/SDL_cocoavideo.m +++ b/src/video/cocoa/SDL_cocoavideo.m @@ -338,26 +338,20 @@ void SDL_NSLog(const char *prefix, const char *text) -@interface PlatformMenuData2 : NSObject { +@interface PlatformMenuData : NSObject { @public Uint16 user_event_type; NSMenu *menu; NSMenuItem *menu_item; - SDL_Menu* parent; } -+ (PlatformMenuData2*)Cocoa_CreatePlatformMenuData:(SDL_Menu*)menu :(SDL_MenuItemType)type; ++ (PlatformMenuData*)Cocoa_CreatePlatformMenuData:(SDL_Menu*)menu :(SDL_MenuItemType)type; - (void)Cocoa_PlatformMenuData_MenuButtonClicked; @end -@implementation PlatformMenuData2 -+ (PlatformMenuData2*)Cocoa_CreatePlatformMenuDataWithMenu:(SDL_Menu*)menu { - PlatformMenuData2* platform_menu =[PlatformMenuData2 new]; - platform_menu->parent = menu; - return platform_menu; -} +@implementation PlatformMenuData - (void)Cocoa_PlatformMenuData_MenuButtonClicked{ SDL_Event event; @@ -373,10 +367,10 @@ - (void)Cocoa_PlatformMenuData_MenuButtonClicked{ bool SDLCALL Cocoa_CreateMenuBar(SDL_MenuBar *menu_bar) { - PlatformMenuData2* platform_menu =[PlatformMenuData2 new]; + PlatformMenuData* platform_menu =[PlatformMenuData new]; platform_menu->menu = [NSMenu new]; [NSApp setMainMenu:platform_menu->menu]; - menu_bar->platform_data = CFBridgingRetain(platform_menu); + menu_bar->common.platform_data = CFBridgingRetain(platform_menu); return true; // **** App Menu **** // @@ -395,21 +389,16 @@ bool SDLCALL Cocoa_CreateMenuBar(SDL_MenuBar *menu_bar) // newItem.target = platform_menu; // [fileMenuItem setSubmenu: fileMenu]; // [menubar addItem: fileMenuItem]; - - - - - return NULL; } bool Cocoa_CreateMenuBarItem(SDL_MenuItem *menu_item, const char *name, Uint16 event_type) { - PlatformMenuData2* platform_data = [PlatformMenuData2 new]; + PlatformMenuData* platform_data = [PlatformMenuData new]; menu_item->common.platform_data = CFBridgingRetain(platform_data); - PlatformMenuData2* parent_platform_data = (PlatformMenuData2*)CFBridgingRelease(menu_item->common.platform_data); + PlatformMenuData* parent_platform_data = (__bridge id _Nullable)(menu_item->common.parent->common.platform_data); if (menu_item->common.type == SDL_MENU) { - platform_data->menu = [NSMenu new]; + platform_data->menu = [[NSMenu alloc] initWithTitle:[NSString stringWithUTF8String:name]]; platform_data->menu_item = [NSMenuItem new]; [platform_data->menu_item setSubmenu: platform_data->menu]; [parent_platform_data->menu addItem: platform_data->menu_item]; @@ -425,9 +414,9 @@ bool Cocoa_CreateMenuBarItem(SDL_MenuItem *menu_item, const char *name, Uint16 e bool Cocoa_CreateMenuItem(SDL_MenuItem *menu_item, const char *name, Uint16 event_type) { - PlatformMenuData2* platform_data = [PlatformMenuData2 new]; + PlatformMenuData* platform_data = [PlatformMenuData new]; menu_item->common.platform_data = CFBridgingRetain(platform_data); - PlatformMenuData2* parent_platform_data = (PlatformMenuData2*)CFBridgingRelease(menu_item->common.platform_data); + PlatformMenuData* parent_platform_data = (__bridge id _Nullable)(menu_item->common.parent->common.platform_data); if (menu_item->common.type == SDL_MENU) { platform_data->menu = [NSMenu new]; From 043009ff4575940dea81f55fc729da9764453622 Mon Sep 17 00:00:00 2001 From: "Joshua T. Fisher" Date: Mon, 11 Aug 2025 01:56:19 -0700 Subject: [PATCH 08/35] Getting closer... --- src/video/cocoa/SDL_cocoavideo.m | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/video/cocoa/SDL_cocoavideo.m b/src/video/cocoa/SDL_cocoavideo.m index 28c03f3027b5d..3802f2e0b0937 100644 --- a/src/video/cocoa/SDL_cocoavideo.m +++ b/src/video/cocoa/SDL_cocoavideo.m @@ -370,6 +370,14 @@ bool SDLCALL Cocoa_CreateMenuBar(SDL_MenuBar *menu_bar) PlatformMenuData* platform_menu =[PlatformMenuData new]; platform_menu->menu = [NSMenu new]; [NSApp setMainMenu:platform_menu->menu]; + + NSMenuItem *appMenuItem = [NSMenuItem new]; + NSMenu *appMenu = [NSMenu new]; + [appMenu addItemWithTitle: @"Quit" action:@selector(terminate:) keyEquivalent:@"q"]; + [appMenuItem setSubmenu:appMenu]; + + [platform_menu->menu addItem:appMenuItem]; + menu_bar->common.platform_data = CFBridgingRetain(platform_menu); return true; @@ -400,14 +408,14 @@ bool Cocoa_CreateMenuBarItem(SDL_MenuItem *menu_item, const char *name, Uint16 e if (menu_item->common.type == SDL_MENU) { platform_data->menu = [[NSMenu alloc] initWithTitle:[NSString stringWithUTF8String:name]]; platform_data->menu_item = [NSMenuItem new]; + [platform_data->menu_item setTitle:[NSString stringWithUTF8String:name]]; [platform_data->menu_item setSubmenu: platform_data->menu]; [parent_platform_data->menu addItem: platform_data->menu_item]; } else { - NSMenuItem *newItem = [platform_data->menu addItemWithTitle:[NSString stringWithUTF8String:name] action:@selector(Cocoa_PlatformMenuData_MenuButtonClicked:) keyEquivalent:@""]; - newItem.target = platform_data; - - [platform_data->menu addItem: newItem]; + platform_data->menu_item = [parent_platform_data->menu addItemWithTitle:[NSString stringWithUTF8String:name] action:@selector(Cocoa_PlatformMenuData_MenuButtonClicked:) keyEquivalent:@""]; + + [platform_data->menu_item setTarget:platform_data]; } return true; } @@ -419,17 +427,16 @@ bool Cocoa_CreateMenuItem(SDL_MenuItem *menu_item, const char *name, Uint16 even PlatformMenuData* parent_platform_data = (__bridge id _Nullable)(menu_item->common.parent->common.platform_data); if (menu_item->common.type == SDL_MENU) { - platform_data->menu = [NSMenu new]; + platform_data->menu = [[NSMenu alloc] initWithTitle:[NSString stringWithUTF8String:name]]; platform_data->menu_item = [NSMenuItem new]; + [platform_data->menu_item setTitle:[NSString stringWithUTF8String:name]]; [platform_data->menu_item setSubmenu: platform_data->menu]; [parent_platform_data->menu addItem: platform_data->menu_item]; } else { - NSMenuItem *newItem = [platform_data->menu addItemWithTitle:[NSString stringWithUTF8String:name] action:@selector(Cocoa_PlatformMenuData_MenuButtonClicked:) keyEquivalent:@""]; - newItem.target = platform_data; - - platform_data->menu = parent_platform_data->menu; - [platform_data->menu addItem: newItem]; + platform_data->menu_item = [parent_platform_data->menu addItemWithTitle:[NSString stringWithUTF8String:name] action:@selector(Cocoa_PlatformMenuData_MenuButtonClicked:) keyEquivalent:@""]; + + [platform_data->menu_item setTarget:platform_data]; } return true; } From d7120ea1bc098779173a90c0144480a1174e401b Mon Sep 17 00:00:00 2001 From: "Joshua T. Fisher" Date: Mon, 11 Aug 2025 02:28:27 -0700 Subject: [PATCH 09/35] I think I sorta got it? Need to handle destruction. --- src/video/cocoa/SDL_cocoavideo.m | 33 ++++++++++---------------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/src/video/cocoa/SDL_cocoavideo.m b/src/video/cocoa/SDL_cocoavideo.m index 3802f2e0b0937..7c223182a4442 100644 --- a/src/video/cocoa/SDL_cocoavideo.m +++ b/src/video/cocoa/SDL_cocoavideo.m @@ -345,15 +345,14 @@ @interface PlatformMenuData : NSObject { NSMenuItem *menu_item; } -+ (PlatformMenuData*)Cocoa_CreatePlatformMenuData:(SDL_Menu*)menu :(SDL_MenuItemType)type; -- (void)Cocoa_PlatformMenuData_MenuButtonClicked; +- (void) Cocoa_PlatformMenuData_MenuButtonClicked: (id)sender; @end @implementation PlatformMenuData -- (void)Cocoa_PlatformMenuData_MenuButtonClicked{ +- (void) Cocoa_PlatformMenuData_MenuButtonClicked: (id)sender;{ SDL_Event event; event.type = SDL_EVENT_MENU_BUTTON_CLICKED; event.menu.timestamp = SDL_GetTicksNS(); @@ -401,42 +400,30 @@ bool SDLCALL Cocoa_CreateMenuBar(SDL_MenuBar *menu_bar) bool Cocoa_CreateMenuBarItem(SDL_MenuItem *menu_item, const char *name, Uint16 event_type) { - PlatformMenuData* platform_data = [PlatformMenuData new]; - menu_item->common.platform_data = CFBridgingRetain(platform_data); - PlatformMenuData* parent_platform_data = (__bridge id _Nullable)(menu_item->common.parent->common.platform_data); - - if (menu_item->common.type == SDL_MENU) { - platform_data->menu = [[NSMenu alloc] initWithTitle:[NSString stringWithUTF8String:name]]; - platform_data->menu_item = [NSMenuItem new]; - [platform_data->menu_item setTitle:[NSString stringWithUTF8String:name]]; - [platform_data->menu_item setSubmenu: platform_data->menu]; - [parent_platform_data->menu addItem: platform_data->menu_item]; - - } else { - platform_data->menu_item = [parent_platform_data->menu addItemWithTitle:[NSString stringWithUTF8String:name] action:@selector(Cocoa_PlatformMenuData_MenuButtonClicked:) keyEquivalent:@""]; - - [platform_data->menu_item setTarget:platform_data]; - } - return true; + return Cocoa_CreateMenuItem((SDL_MenuItem*)menu_item, name, event_type); } bool Cocoa_CreateMenuItem(SDL_MenuItem *menu_item, const char *name, Uint16 event_type) { PlatformMenuData* platform_data = [PlatformMenuData new]; + platform_data->user_event_type = event_type; menu_item->common.platform_data = CFBridgingRetain(platform_data); PlatformMenuData* parent_platform_data = (__bridge id _Nullable)(menu_item->common.parent->common.platform_data); + NSString* name_ns = [NSString stringWithUTF8String:name]; if (menu_item->common.type == SDL_MENU) { - platform_data->menu = [[NSMenu alloc] initWithTitle:[NSString stringWithUTF8String:name]]; + platform_data->menu = [[NSMenu alloc] initWithTitle:name_ns]; + [platform_data->menu setAutoenablesItems:true]; platform_data->menu_item = [NSMenuItem new]; - [platform_data->menu_item setTitle:[NSString stringWithUTF8String:name]]; + [platform_data->menu_item setTitle:name_ns]; [platform_data->menu_item setSubmenu: platform_data->menu]; [parent_platform_data->menu addItem: platform_data->menu_item]; } else { - platform_data->menu_item = [parent_platform_data->menu addItemWithTitle:[NSString stringWithUTF8String:name] action:@selector(Cocoa_PlatformMenuData_MenuButtonClicked:) keyEquivalent:@""]; + platform_data->menu_item = [parent_platform_data->menu addItemWithTitle:name_ns action:@selector(Cocoa_PlatformMenuData_MenuButtonClicked:) keyEquivalent:@""]; [platform_data->menu_item setTarget:platform_data]; + [platform_data->menu_item setEnabled:true]; } return true; } From 11a1c8e47b5348645b6323ec7a2eb9dd2d0c552c Mon Sep 17 00:00:00 2001 From: "Joshua T. Fisher" Date: Mon, 11 Aug 2025 02:30:46 -0700 Subject: [PATCH 10/35] Improve example --- examples/video/01-menubar/menubar.c | 31 +++++++++++++++++++++--- src/video/SDL_sysvideo.h | 5 ++-- src/video/SDL_video.c | 36 ++++++++++++++++++---------- src/video/windows/SDL_windowsvideo.c | 20 ++++++++++++---- src/video/windows/SDL_windowsvideo.h | 18 ++------------ 5 files changed, 72 insertions(+), 38 deletions(-) diff --git a/examples/video/01-menubar/menubar.c b/examples/video/01-menubar/menubar.c index 2fed8977c1c35..7b9bf37d89973 100644 --- a/examples/video/01-menubar/menubar.c +++ b/examples/video/01-menubar/menubar.c @@ -23,8 +23,7 @@ typedef enum SDL_EventType_MenuExt MENU_BAR_BOOKMARKS_TOOLBAR_WIKI, MENU_BAR_BOOKMARKS_TOOLBAR_DISCORD, MENU_BAR_BOOKMARKS_OTHER_BOOKMARKS, - MENU_BAR_BOOKMARKS_OTHER_BOOKMARKS_TWITTER, - MENU_BAR_BOOKMARKS_OTHER_BOOKMARKS_REDDIT, + MENU_BAR_BOOKMARKS_OTHER_BOOKMARKS_STACKOVERFLOW, MENU_BAR_INCOGNITO, MENU_BAR_TOP_LEVEL_BUTTON, MENU_BAR_EXIT, @@ -54,7 +53,7 @@ void CreateMenuBar() SDL_CreateMenuItem((SDL_Menu*)main_bookmarks, "SDL Discord", SDL_MENU_BUTTON, MENU_BAR_BOOKMARKS_TOOLBAR_DISCORD); SDL_MenuItem* other_bookmarks = SDL_CreateMenuItem((SDL_Menu*)menu, "Other Bookmarks", SDL_MENU, MENU_BAR_LAST); - SDL_CreateMenuItem((SDL_Menu*)other_bookmarks, "Stack Overflow", SDL_MENU_BUTTON, MENU_BAR_BOOKMARKS_OTHER_BOOKMARKS); + SDL_CreateMenuItem((SDL_Menu *)other_bookmarks, "Stack Overflow", SDL_MENU_BUTTON, MENU_BAR_BOOKMARKS_OTHER_BOOKMARKS_STACKOVERFLOW); SDL_EnableMenuItem(other_bookmarks, false); } @@ -102,6 +101,32 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) { case SDL_EVENT_MENU_BUTTON_CLICKED: case SDL_EVENT_MENU_CHECKABLE_CLICKED: { + switch (event->menu.user_event_type) { + case MENU_BAR_BOOKMARKS_TOOLBAR_GITHUB: + { + SDL_OpenURL("https://github.com/libsdl-org/SDL"); + break; + } + case MENU_BAR_BOOKMARKS_TOOLBAR_WIKI: + { + SDL_OpenURL("https://wiki.libsdl.org/SDL3/FrontPage"); + break; + } + case MENU_BAR_BOOKMARKS_TOOLBAR_DISCORD: + { + SDL_OpenURL("https://discord.gg/BwpFGBWsv8"); + break; + } + case MENU_BAR_BOOKMARKS_OTHER_BOOKMARKS_STACKOVERFLOW: + { + SDL_OpenURL("https://stackoverflow.com/questions"); + break; + } + case MENU_BAR_EXIT: + { + return SDL_APP_SUCCESS; + } + } SDL_Log("%d\n", event->menu.user_event_type); } } diff --git a/src/video/SDL_sysvideo.h b/src/video/SDL_sysvideo.h index 4739b25f91faa..d0ef4c3d00b0c 100644 --- a/src/video/SDL_sysvideo.h +++ b/src/video/SDL_sysvideo.h @@ -223,13 +223,11 @@ typedef struct SDL_MenuItem_CommonData typedef struct SDL_MenuBar { SDL_MenuItem_CommonData common; - SDL_MenuItem *item_list; } SDL_MenuBar; typedef struct SDL_Menu { SDL_MenuItem_CommonData common; - SDL_MenuItem *menuitem_list; } SDL_Menu; typedef struct SDL_MenuItem_Button @@ -364,7 +362,8 @@ struct SDL_VideoDevice bool (*CreateMenuItem)(SDL_MenuItem *menu_item, const char *name, Uint16 event_type); bool (*CheckMenuItem)(SDL_MenuItem *menu_item, bool checked); bool (*EnableMenuItem)(SDL_MenuItem *menu_item, bool enabled); - void (*DestroyMenuItem)(SDL_MenuItem *menu_item); + bool (*DestroyMenuItem)(SDL_MenuItem *menu_item); + bool (*DestroyMenuBar)(SDL_MenuBar *menu_bar); /* * * */ /* diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c index 7eee2593b27af..77711748cce74 100644 --- a/src/video/SDL_video.c +++ b/src/video/SDL_video.c @@ -6172,15 +6172,15 @@ SDL_MenuItem* SDL_CreateMenuBarItem(SDL_MenuBar* menu_bar, const char *name, SDL } // Get the last item in the list and insert our new item. - if (menu_bar->item_list) { - SDL_MenuItem* last = menu_bar->item_list; + if (menu_bar->common.next) { + SDL_MenuItem* common = menu_bar->common.next; - while (last->common.next) last = last->common.next; + while (common->common.next) common = common->common.next; - last->common.next = menu_item; - menu_item->common.prev = last; + common->common.next = menu_item; + menu_item->common.prev = common; } else { - menu_bar->item_list = menu_item; + menu_bar->common.next = menu_item; } return menu_item; @@ -6201,15 +6201,15 @@ SDL_MenuItem* SDL_CreateMenuItem(SDL_Menu* menu, const char *name, SDL_MenuItemT } // Get the last item in the list and insert our new item. - if (menu->menuitem_list) { - SDL_MenuItem* last = menu->menuitem_list; + if (menu->common.next) { + SDL_MenuItem* current = menu->common.next; - while (last->common.next) last = last->common.next; + while (current->common.next) current = current->common.next; - last->common.next = menu_item; - menu_item->common.prev = last; + current->common.next = menu_item; + menu_item->common.prev = current; } else { - menu->menuitem_list = menu_item; + menu->common.next = menu_item; } return menu_item; @@ -6237,8 +6237,20 @@ bool SDL_EnableMenuItem(SDL_MenuItem* menu_item, bool enabled) return _this->EnableMenuItem(menu_item, enabled); } +static void SDL_DestroyMenuItem(SDL_MenuItem* menu_item) { + for (SDL_MenuItem *current = menu_item->common.next; current != NULL; current = current->common.next) { + SDL_DestroyMenuItem(current); + } + + _this->DestroyMenuItem(menu_item); + SDL_free(menu_item); +} + bool SDL_DestroyMenuBar(SDL_MenuBar* menu_bar) { + bool ret = _this->DestroyMenuBar(menu_bar); + SDL_DestroyMenuItem((SDL_MenuItem *)menu_bar); + return true; } diff --git a/src/video/windows/SDL_windowsvideo.c b/src/video/windows/SDL_windowsvideo.c index cb5ac00afcb47..e82e6ab22d6ab 100644 --- a/src/video/windows/SDL_windowsvideo.c +++ b/src/video/windows/SDL_windowsvideo.c @@ -365,6 +365,7 @@ static SDL_VideoDevice *WIN_CreateDevice(void) device->CheckMenuItem = Win32_CheckMenuItem; device->EnableMenuItem = Win32_EnableMenuItem; device->DestroyMenuItem = Win32_DestroyMenuItem; + device->DestroyMenuBar = Win32_DestroyMenuBar; #endif @@ -1017,15 +1018,26 @@ static bool Win32_EnableMenuItem(SDL_MenuItem *menu_item, bool enabled) return true; } -static void Win32_DestroyMenuItem(SDL_MenuItem *menu_item) +// We rely on the top level Win32_DestroyMenuBar to recursively deal with the native handles, +// but we need to recurse through and delete the platform datas. +static bool Win32_DestroyMenuItem(SDL_MenuItem *menu_item) { - PlatformMenuData *platform_data = (PlatformMenuData *)menu_item->common.platform_data; + SDL_free(menu_item->common.platform_data); + return true; +} - if (!RemoveMenu(platform_data->owner_handle, (UINT)platform_data->self_handle, MF_BYCOMMAND)) { +static bool Win32_DestroyMenuBar(SDL_MenuBar *menu_bar) +{ + PlatformMenuData *platform_data = (PlatformMenuData *)menu_bar->common.platform_data; + + // This will take care of all of the child handles underneath the menubar. + if (!DestroyMenu((HMENU)platform_data->self_handle)) { return WIN_SetError("Unable to remove menu item."); } - SDL_free(menu_item->common.platform_data); + Win32_DestroyMenuItem((SDL_MenuItem *)menu_bar); + SDL_free(menu_bar->common.platform_data); + return true; } #endif // SDL_VIDEO_DRIVER_WINDOWS diff --git a/src/video/windows/SDL_windowsvideo.h b/src/video/windows/SDL_windowsvideo.h index 7419b91ce8461..1c91820cd5373 100644 --- a/src/video/windows/SDL_windowsvideo.h +++ b/src/video/windows/SDL_windowsvideo.h @@ -671,26 +671,12 @@ typedef struct PlatformMenuData UINT_PTR self_handle; } PlatformMenuData; -extern PlatformMenuData *CreatePlatformMenuData(HMENU owner, SDL_MenuItemType type); extern bool SDLCALL Win32_CreateMenuBar(SDL_MenuBar *menu_bar); - - - - -//bool (*CreateMenuBar)(SDL_MenuBar *menu_bar); -//bool (*CreateMenuBarItem)(SDL_MenuItem *menu_item, const char *name, Uint16 event_type); -//bool (*CreateMenuItem)(SDL_MenuItem *menu_item, const char *name, Uint16 event_type); -//bool (*CheckMenuItem)(SDL_MenuItem *menu_item, bool checked); -//bool (*EnableMenuItem)(SDL_MenuItem *menu_item, bool enabled); -//void (*DestroyMenuItem)(SDL_MenuItem *menu_item); - -//extern SDL_MenuItem *Win32_CreateMenuItemImpl(HMENU menu, const char *name, SDL_MenuItemType type, Uint16 event_type, bool toplevel_menu); extern bool Win32_CreateMenuBarItem(SDL_MenuItem *menu_item, const char *name, Uint16 event_type); - -//(*CreateMenuItem)(SDL_MenuItem *menu_item, const char *name, Uint16 event_type) extern bool Win32_CreateMenuItem(SDL_MenuItem *menu_item, const char *name, Uint16 event_type); extern bool Win32_CheckMenuItem(SDL_MenuItem *menu_item, bool checked); extern bool Win32_EnableMenuItem(SDL_MenuItem *menu_item, bool enabled); -extern void Win32_DestroyMenuItem(SDL_MenuItem *menu_item); +extern bool Win32_DestroyMenuItem(SDL_MenuItem *menu_item); +extern bool Win32_DestroyMenuBar(SDL_MenuBar *menu_bar); #endif // SDL_windowsvideo_h_ From 2ee1b8209412fb3d4a4488009cd7a55a3344d587 Mon Sep 17 00:00:00 2001 From: "Joshua T. Fisher" Date: Tue, 12 Aug 2025 23:21:42 -0700 Subject: [PATCH 11/35] Added way more error checking, switched to insertAt API(still appending rn tho) --- examples/video/01-menubar/menubar.c | 34 ++--- include/SDL3/SDL_video.h | 35 +++-- src/dynapi/SDL_dynapi.sym | 12 +- src/dynapi/SDL_dynapi_overrides.h | 9 +- src/dynapi/SDL_dynapi_procs.h | 12 +- src/video/SDL_sysvideo.h | 28 ++-- src/video/SDL_video.c | 185 ++++++++++++++++++--------- src/video/windows/SDL_windowsvideo.c | 111 ++++++++++------ src/video/windows/SDL_windowsvideo.h | 15 ++- 9 files changed, 294 insertions(+), 147 deletions(-) diff --git a/examples/video/01-menubar/menubar.c b/examples/video/01-menubar/menubar.c index 7b9bf37d89973..e6442dfbba3a4 100644 --- a/examples/video/01-menubar/menubar.c +++ b/examples/video/01-menubar/menubar.c @@ -35,38 +35,38 @@ static SDL_EventType_MenuExt EVENT_START = (SDL_EventType_MenuExt)0; void CreateMenuBar() { - SDL_MenuBar* bar = SDL_CreateMenuBar(window); + SDL_MenuItem* bar = SDL_CreateMenuBar(window); { - SDL_MenuItem* menu = SDL_CreateMenuBarItem(bar, "File", SDL_MENU, MENU_BAR_LAST); - SDL_CreateMenuItem((SDL_Menu*)menu, "New Window", SDL_MENU_BUTTON, MENU_BAR_FILE_NEW_WINDOW); - SDL_MenuItem* checkable = SDL_CreateMenuItem((SDL_Menu*)menu, "Autosave Tabs on Close", SDL_MENU_CHECKABLE, MENU_BAR_FILE_AUTOSAVE_TABS_ON_CLOSE); + SDL_MenuItem* menu = SDL_CreateMenuItem(bar, "File", SDL_MENU, MENU_BAR_LAST); + SDL_CreateMenuItem(menu, "New Window", SDL_MENU_BUTTON, MENU_BAR_FILE_NEW_WINDOW); + SDL_MenuItem* checkable = SDL_CreateMenuItem(menu, "Autosave Tabs on Close", SDL_MENU_CHECKABLE, MENU_BAR_FILE_AUTOSAVE_TABS_ON_CLOSE); - SDL_CheckMenuItem(checkable, true); + SDL_CheckMenuItem(checkable); } { - SDL_MenuItem* menu = SDL_CreateMenuBarItem(bar, "Bookmarks", SDL_MENU, MENU_BAR_LAST); - SDL_MenuItem* main_bookmarks = SDL_CreateMenuItem((SDL_Menu*)menu, "Bookmarks Toolbar", SDL_MENU, MENU_BAR_LAST); - SDL_CreateMenuItem((SDL_Menu*)main_bookmarks, "SDL GitHub", SDL_MENU_BUTTON, MENU_BAR_BOOKMARKS_TOOLBAR_GITHUB); - SDL_CreateMenuItem((SDL_Menu*)main_bookmarks, "SDL Wiki", SDL_MENU_BUTTON, MENU_BAR_BOOKMARKS_TOOLBAR_WIKI); - SDL_CreateMenuItem((SDL_Menu*)main_bookmarks, "SDL Discord", SDL_MENU_BUTTON, MENU_BAR_BOOKMARKS_TOOLBAR_DISCORD); + SDL_MenuItem* menu = SDL_CreateMenuItem(bar, "Bookmarks", SDL_MENU, MENU_BAR_LAST); + SDL_MenuItem* main_bookmarks = SDL_CreateMenuItem(menu, "Bookmarks Toolbar", SDL_MENU, MENU_BAR_LAST); + SDL_CreateMenuItem(main_bookmarks, "SDL GitHub", SDL_MENU_BUTTON, MENU_BAR_BOOKMARKS_TOOLBAR_GITHUB); + SDL_CreateMenuItem(main_bookmarks, "SDL Wiki", SDL_MENU_BUTTON, MENU_BAR_BOOKMARKS_TOOLBAR_WIKI); + SDL_CreateMenuItem(main_bookmarks, "SDL Discord", SDL_MENU_BUTTON, MENU_BAR_BOOKMARKS_TOOLBAR_DISCORD); - SDL_MenuItem* other_bookmarks = SDL_CreateMenuItem((SDL_Menu*)menu, "Other Bookmarks", SDL_MENU, MENU_BAR_LAST); - SDL_CreateMenuItem((SDL_Menu *)other_bookmarks, "Stack Overflow", SDL_MENU_BUTTON, MENU_BAR_BOOKMARKS_OTHER_BOOKMARKS_STACKOVERFLOW); + SDL_MenuItem* other_bookmarks = SDL_CreateMenuItem(menu, "Other Bookmarks", SDL_MENU, MENU_BAR_LAST); + SDL_CreateMenuItem(other_bookmarks, "Stack Overflow", SDL_MENU_BUTTON, MENU_BAR_BOOKMARKS_OTHER_BOOKMARKS_STACKOVERFLOW); - SDL_EnableMenuItem(other_bookmarks, false); + SDL_DisableMenuItem(other_bookmarks); } { // We can't create a top level checkable . - SDL_MenuItem* checkable = SDL_CreateMenuBarItem(bar, "Incognito", SDL_MENU_CHECKABLE, MENU_BAR_INCOGNITO); + SDL_MenuItem* checkable = SDL_CreateMenuItem(bar, "Incognito", SDL_MENU_CHECKABLE, MENU_BAR_INCOGNITO); SDL_assert(!checkable); - SDL_MenuItem* disabled = SDL_CreateMenuBarItem(bar, "Disabled Top-Level Button", SDL_MENU_BUTTON, MENU_BAR_TOP_LEVEL_BUTTON); - SDL_EnableMenuItem(disabled, false); + SDL_MenuItem* disabled = SDL_CreateMenuItem(bar, "Disabled Top-Level Button", SDL_MENU_BUTTON, MENU_BAR_TOP_LEVEL_BUTTON); + SDL_DisableMenuItem(disabled); - SDL_CreateMenuBarItem(bar, "Exit", SDL_MENU_BUTTON, MENU_BAR_EXIT); + SDL_CreateMenuItem(bar, "Exit", SDL_MENU_BUTTON, MENU_BAR_EXIT); } EVENT_START = (SDL_EventType_MenuExt)SDL_RegisterEvents(MENU_BAR_LAST); diff --git a/include/SDL3/SDL_video.h b/include/SDL3/SDL_video.h index 0a21c62c06357..4ab9216acb3df 100644 --- a/include/SDL3/SDL_video.h +++ b/include/SDL3/SDL_video.h @@ -3042,16 +3042,35 @@ typedef enum SDL_MenuItemType SDL_MENU_CHECKABLE, } SDL_MenuItemType; -typedef struct SDL_MenuBar SDL_MenuBar; -typedef struct SDL_Menu SDL_Menu; typedef union SDL_MenuItem SDL_MenuItem; -extern SDL_DECLSPEC SDL_MenuBar* SDL_CreateMenuBar(SDL_Window*window); -extern SDL_DECLSPEC SDL_MenuItem* SDL_CreateMenuBarItem(SDL_MenuBar*menu_bar, const char *name, SDL_MenuItemType type, Uint16 event_type); -extern SDL_DECLSPEC SDL_MenuItem* SDL_CreateMenuItem(SDL_Menu*menu_bar, const char *name, SDL_MenuItemType type, Uint16 event_type); -extern SDL_DECLSPEC bool SDL_CheckMenuItem(SDL_MenuItem*menu_item, bool checked); -extern SDL_DECLSPEC bool SDL_EnableMenuItem(SDL_MenuItem*menu_item, bool enabled); -extern SDL_DECLSPEC bool SDL_DestroyMenuBar(SDL_MenuBar*menu_bar); +extern SDL_DECLSPEC SDL_MenuItem *SDL_CreateMenuBar(SDL_Window *window); + +// Must be a SDL_MENUBAR or SDL_MENU +// On MacOS, buttoms created under a menubar will go into the "App" submenu +extern SDL_DECLSPEC SDL_MenuItem *SDL_CreateMenuItemAt(SDL_MenuItem *menu_bar_as_item, size_t index, const char *name, SDL_MenuItemType type, Uint16 event_type); + +// Must be a SDL_MENUBAR or SDL_MENU +// On MacOS, buttoms created under a menubar will go into the "App" submenu +extern SDL_DECLSPEC SDL_MenuItem *SDL_CreateMenuItem(SDL_MenuItem *menu_bar_as_item, const char *name, SDL_MenuItemType type, Uint16 event_type); + +// -1 on error +extern SDL_DECLSPEC Sint64 SDL_ChildItems(SDL_MenuItem *menu_bar_as_item); + + +// Must be a SDL_MENU_CHECKABLE +extern SDL_DECLSPEC bool SDL_CheckMenuItem(SDL_MenuItem *menu_item); + +// Must be a SDL_MENU_CHECKABLE +extern SDL_DECLSPEC bool SDL_UncheckMenuItem(SDL_MenuItem *menu_item); + +// Must be a SDL_MENU_CHECKABLE +extern SDL_DECLSPEC bool SDL_MenuItemChecked(SDL_MenuItem *menu_item, bool *checked); + +extern SDL_DECLSPEC bool SDL_MenuItemEnabled(SDL_MenuItem *menu_item, bool *enabled); +extern SDL_DECLSPEC bool SDL_EnableMenuItem(SDL_MenuItem *menu_item); +extern SDL_DECLSPEC bool SDL_DisableMenuItem(SDL_MenuItem *menu_item); +extern SDL_DECLSPEC bool SDL_DestroyMenuItem(SDL_MenuItem *menu_item); /** * \name OpenGL support functions diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index 596c9d5af2d6e..c50b1ce7daf31 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -1269,7 +1269,17 @@ SDL3_0.0.0 { SDL_CreateMenuItem; SDL_CheckMenuItem; SDL_EnableMenuItem; - SDL_DestroyMenuBar; + SDL_CreateMenuBar + SDL_CreateMenuItemAt + SDL_CreateMenuItem + SDL_ChildItems + SDL_CheckMenuItem + SDL_UncheckMenuItem + SDL_MenuItemChecked + SDL_MenuItemEnabled + SDL_EnableMenuItem + SDL_DisableMenuItem + SDL_DestroyMenuItem # extra symbols go here (don't modify this line) local: *; }; diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index 24228203a581c..0bb3e1d21bd08 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -1291,8 +1291,13 @@ #define SDL_SavePNG_IO SDL_SavePNG_IO_REAL #define SDL_SavePNG SDL_SavePNG_REAL #define SDL_CreateMenuBar SDL_CreateMenuBar_REAL -#define SDL_CreateMenuBarItem SDL_CreateMenuBarItem_REAL +#define SDL_CreateMenuItemAt SDL_CreateMenuItemAt_REAL #define SDL_CreateMenuItem SDL_CreateMenuItem_REAL +#define SDL_ChildItems SDL_ChildItems_REAL #define SDL_CheckMenuItem SDL_CheckMenuItem_REAL +#define SDL_UncheckMenuItem SDL_UncheckMenuItem_REAL +#define SDL_MenuItemChecked SDL_MenuItemChecked_REAL +#define SDL_MenuItemEnabled SDL_MenuItemEnabled_REAL #define SDL_EnableMenuItem SDL_EnableMenuItem_REAL -#define SDL_DestroyMenuBar SDL_DestroyMenuBar_REAL +#define SDL_DisableMenuItem SDL_DisableMenuItem_REAL +#define SDL_DestroyMenuItem SDL_DestroyMenuItem_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index 0e72e685bc831..cca369a3874b7 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -1303,4 +1303,14 @@ SDL_DYNAPI_PROC(SDL_MenuItem*,SDL_CreateMenuBarItem,(SDL_MenuBar*a,const char*b, SDL_DYNAPI_PROC(SDL_MenuItem*,SDL_CreateMenuItem,(SDL_Menu*a,const char*b,SDL_MenuItemType c,Uint16 d),(a,b,c,d),return) SDL_DYNAPI_PROC(bool, SDL_CheckMenuItem,(SDL_MenuItem*a, bool b),(a,b),return) SDL_DYNAPI_PROC(bool, SDL_EnableMenuItem,(SDL_MenuItem*a, bool b),(a,b),return) -SDL_DYNAPI_PROC(bool, SDL_DestroyMenuBar,(SDL_MenuBar*a),(a),return) +SDL_DYNAPI_PROC(SDL_MenuItem*,SDL_CreateMenuBar,(SDL_Window *a),(a),return) +SDL_DYNAPI_PROC(SDL_MenuItem*,SDL_CreateMenuItemAt,(SDL_MenuItem *a,size_t b,const char *c,SDL_MenuItemType d, Uint16 e), (a,b,c,d,e), return) +SDL_DYNAPI_PROC(SDL_MenuItem*,SDL_CreateMenuItem,(SDL_MenuItem *a, const char *b, SDL_MenuItemType c, Uint16 d),(a,b,c,d), return) +SDL_DYNAPI_PROC(Sint64,SDL_ChildItems, (SDL_MenuItem *a),(a),return) +SDL_DYNAPI_PROC(bool,SDL_CheckMenuItem,(SDL_MenuItem *a),(a),return) +SDL_DYNAPI_PROC(bool,SDL_UncheckMenuItem,(SDL_MenuItem *a),(a),return) +SDL_DYNAPI_PROC(bool,SDL_MenuItemChecked,(SDL_MenuItem *a,bool *b),(a, b),return) +SDL_DYNAPI_PROC(bool,SDL_MenuItemEnabled,(SDL_MenuItem *a,bool *b),(a, b),return) +SDL_DYNAPI_PROC(bool,SDL_EnableMenuItem,(SDL_MenuItem *a),(a),return) +SDL_DYNAPI_PROC(bool,SDL_DisableMenuItem,(SDL_MenuItem *a),(a),return) +SDL_DYNAPI_PROC(bool,SDL_DestroyMenuItem,(SDL_MenuItem *a),(a),return) diff --git a/src/video/SDL_sysvideo.h b/src/video/SDL_sysvideo.h index d0ef4c3d00b0c..07be3c8eff38b 100644 --- a/src/video/SDL_sysvideo.h +++ b/src/video/SDL_sysvideo.h @@ -210,6 +210,9 @@ typedef enum } SDL_FullscreenResult; +typedef struct SDL_MenuBar SDL_MenuBar; +typedef struct SDL_Menu SDL_Menu; + typedef struct SDL_MenuItem_CommonData { void *platform_data; @@ -220,14 +223,21 @@ typedef struct SDL_MenuItem_CommonData SDL_MenuItemType type; } SDL_MenuItem_CommonData; +typedef struct SDL_Menu_CommonData +{ + SDL_MenuItem_CommonData item_common; + SDL_MenuItem *child_list; + Sint64 children; +} SDL_Menu_CommonData; + typedef struct SDL_MenuBar { - SDL_MenuItem_CommonData common; + SDL_Menu_CommonData common; } SDL_MenuBar; typedef struct SDL_Menu { - SDL_MenuItem_CommonData common; + SDL_Menu_CommonData common; } SDL_Menu; typedef struct SDL_MenuItem_Button @@ -244,6 +254,7 @@ typedef struct SDL_MenuItem_Checkable typedef union SDL_MenuItem { SDL_MenuItem_CommonData common; + SDL_Menu_CommonData menu_common; SDL_MenuBar menu_bar; SDL_Menu menu; SDL_MenuItem_Button button; @@ -356,14 +367,15 @@ struct SDL_VideoDevice bool (*SyncWindow)(SDL_VideoDevice *_this, SDL_Window *window); bool (*ReconfigureWindow)(SDL_VideoDevice *_this, SDL_Window *window, SDL_WindowFlags flags); - bool (*CreateMenuBar)(SDL_MenuBar *menu_bar); - bool (*CreateMenuBarItem)(SDL_MenuItem *menu_item, const char *name, Uint16 event_type); - bool (*CreateMenuItem)(SDL_MenuItem *menu_item, const char *name, Uint16 event_type); - bool (*CheckMenuItem)(SDL_MenuItem *menu_item, bool checked); - bool (*EnableMenuItem)(SDL_MenuItem *menu_item, bool enabled); + bool (*CreateMenuItemAt)(SDL_MenuItem *menu_item, size_t index, const char *name, Uint16 event_type); + bool (*CheckMenuItem)(SDL_MenuItem *menu_item); + bool (*UncheckMenuItem)(SDL_MenuItem *menu_item); + bool (*MenuItemChecked)(SDL_MenuItem *menu_item, bool *checked); + bool (*MenuItemEnabled)(SDL_MenuItem *menu_item, bool *enabled); + bool (*EnableMenuItem)(SDL_MenuItem *menu_item); + bool (*DisableMenuItem)(SDL_MenuItem *menu_item); bool (*DestroyMenuItem)(SDL_MenuItem *menu_item); - bool (*DestroyMenuBar)(SDL_MenuBar *menu_bar); /* * * */ /* diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c index 77711748cce74..4513b8bbcccf9 100644 --- a/src/video/SDL_video.c +++ b/src/video/SDL_video.c @@ -177,21 +177,9 @@ static VideoBootStrap *bootstrap[] = { return result; \ } -#define CHECK_MENUBAR_MAGIC(menubar, result) \ - if (!menubar) { \ - SDL_SetError("Invalid menubar"); \ - return result; \ - } - -#define CHECK_MENU_MAGIC(menu, result) \ - if (!menu) { \ - SDL_SetError("Invalid menu"); \ - return result; \ - } - #define CHECK_MENUITEM_MAGIC(menuitem, result) \ if (!menuitem) { \ - SDL_SetError("Invalid menuitem"); \ + SDL_SetError("Invalid menu_item"); \ return result; \ } @@ -6129,7 +6117,7 @@ void SDL_OnApplicationDidEnterForeground(void) } } -SDL_MenuBar* SDL_CreateMenuBar(SDL_Window *window) +SDL_MenuItem* SDL_CreateMenuBar(SDL_Window *window) { CHECK_WINDOW_MAGIC(window, NULL); @@ -6142,80 +6130,92 @@ SDL_MenuBar* SDL_CreateMenuBar(SDL_Window *window) } SDL_MenuBar *menu_bar = SDL_calloc(1, sizeof(SDL_MenuBar)); - menu_bar->common.window = window; - menu_bar->common.type = SDL_MENUBAR; + menu_bar->common.item_common.window = window; + menu_bar->common.item_common.type = SDL_MENUBAR; if (!_this->CreateMenuBar(menu_bar)) { SDL_free(menu_bar); return NULL; } - return menu_bar; + return (SDL_MenuItem *)menu_bar; } -SDL_MenuItem* SDL_CreateMenuBarItem(SDL_MenuBar* menu_bar, const char *name, SDL_MenuItemType type, Uint16 event_type) +SDL_MenuItem *SDL_CreateMenuItemAt(SDL_MenuItem *menu_bar_as_item, size_t index, const char *name, SDL_MenuItemType type, Uint16 event_type) { - CHECK_MENUBAR_MAGIC(menu_bar, NULL); - if (type == SDL_MENU_CHECKABLE) { - SDL_SetError("Can't created a checkable item on the menubar, they must be in a menu."); + CHECK_MENUITEM_MAGIC(menu_bar_as_item, NULL); + + if (menu_bar_as_item->common.type != SDL_MENU && menu_bar_as_item->common.type != SDL_MENUBAR) { + SDL_SetError("Can't create an item on a Menu Item that isn't a Menu or MenuBar."); return false; } - + + if ((menu_bar_as_item->common.type == SDL_MENUBAR) && (type == SDL_MENU_CHECKABLE)) { + SDL_SetError("Can't create a checkable item on the Menu Bar, they must be in a menu."); + return false; + } + + SDL_Menu_CommonData *menu = (SDL_Menu_CommonData *)menu_bar_as_item; + + if (menu->children < (Sint64)index) { + SDL_SetError("Can't create a menu item beyond the number of children."); + return false; + } + SDL_MenuItem *menu_item = SDL_calloc(1, sizeof(SDL_MenuItem)); - menu_item->common.parent = (SDL_MenuItem *)menu_bar; - menu_item->common.window = menu_bar->common.window; + menu_item->common.parent = (SDL_MenuItem *)menu; + menu_item->common.window = menu->item_common.window; menu_item->common.type = type; - if (!_this->CreateMenuBarItem(menu_item, name, event_type)) { + if (!_this->CreateMenuItemAt(menu_item, index, name, event_type)) { SDL_free(menu_item); return NULL; } // Get the last item in the list and insert our new item. - if (menu_bar->common.next) { - SDL_MenuItem* common = menu_bar->common.next; + if (menu->child_list) { + SDL_MenuItem *common = menu->child_list; - while (common->common.next) common = common->common.next; + while (common->common.next) + common = common->common.next; common->common.next = menu_item; menu_item->common.prev = common; } else { - menu_bar->common.next = menu_item; + menu->child_list = menu_item; } + ++menu->children; + return menu_item; } -SDL_MenuItem* SDL_CreateMenuItem(SDL_Menu* menu, const char *name, SDL_MenuItemType type, Uint16 event_type) +SDL_MenuItem *SDL_CreateMenuItem(SDL_MenuItem *menu_bar_as_item, const char *name, SDL_MenuItemType type, Uint16 event_type) { - CHECK_MENU_MAGIC(menu, NULL); - - SDL_MenuItem *menu_item = SDL_calloc(1, sizeof(SDL_MenuItem)); - menu_item->common.window = menu->common.window; - menu_item->common.parent = (SDL_MenuItem *)menu; - menu_item->common.type = type; - - if (!_this->CreateMenuItem(menu_item, name, event_type)) { - SDL_free(menu_item); - return NULL; + CHECK_MENUITEM_MAGIC(menu_bar_as_item, NULL); + if (menu_bar_as_item->common.type != SDL_MENU && menu_bar_as_item->common.type != SDL_MENUBAR) { + SDL_SetError("Can't create an item on a Menu Item that isn't a Menu or MenuBar."); + return false; } - // Get the last item in the list and insert our new item. - if (menu->common.next) { - SDL_MenuItem* current = menu->common.next; + SDL_Menu_CommonData *menu = (SDL_Menu_CommonData *)menu_bar_as_item; + return SDL_CreateMenuItemAt(menu_bar_as_item, menu->children, name, type, event_type); +} - while (current->common.next) current = current->common.next; - current->common.next = menu_item; - menu_item->common.prev = current; - } else { - menu->common.next = menu_item; +Sint64 SDL_ChildItems(SDL_MenuItem *menu_bar_as_item) +{ + CHECK_MENUITEM_MAGIC(menu_bar_as_item, -1); + if (menu_bar_as_item->common.type != SDL_MENU && menu_bar_as_item->common.type != SDL_MENUBAR) { + SDL_SetError("Can't create an item on a Menu Item that isn't a Menu or MenuBar."); + return false; } - return menu_item; + SDL_Menu_CommonData *menu = (SDL_Menu_CommonData *)menu_bar_as_item; + return menu->children; } -bool SDL_CheckMenuItem(SDL_MenuItem* menu_item, bool checked) +bool SDL_CheckMenuItem(SDL_MenuItem* menu_item) { CHECK_MENUITEM_MAGIC(menu_item, false); if (menu_item->common.type != SDL_MENU_CHECKABLE) { @@ -6223,34 +6223,95 @@ bool SDL_CheckMenuItem(SDL_MenuItem* menu_item, bool checked) return false; } - return _this->CheckMenuItem(menu_item, checked); + return _this->CheckMenuItem(menu_item); } -bool SDL_EnableMenuItem(SDL_MenuItem* menu_item, bool enabled) +bool SDL_UncheckMenuItem(SDL_MenuItem* menu_item) { CHECK_MENUITEM_MAGIC(menu_item, false); - if (menu_item->common.type == SDL_MENU) { + if (menu_item->common.type != SDL_MENU_CHECKABLE) { + SDL_SetError("menu_item isn't a checkable."); + return false; + } + + return _this->UncheckMenuItem(menu_item); +} + +bool SDL_MenuItemChecked(SDL_MenuItem* menu_item, bool* checked) +{ + CHECK_MENUITEM_MAGIC(menu_item, false); + if (menu_item->common.type != SDL_MENU_CHECKABLE) { + SDL_SetError("menu_item isn't a checkable."); + return false; + } + + return _this->MenuItemChecked(menu_item, checked); +} + +bool SDL_MenuItemEnabled(SDL_MenuItem *menu_item, bool *enabled) +{ + CHECK_MENUITEM_MAGIC(menu_item, false); + if (menu_item->common.type != SDL_MENU_CHECKABLE) { + SDL_SetError("menu_item isn't a checkable."); + return false; + } + + return _this->MenuItemEnabled(menu_item, enabled); +} + + +bool SDL_EnableMenuItem(SDL_MenuItem* menu_item) +{ + CHECK_MENUITEM_MAGIC(menu_item, false); + if (menu_item->common.type == SDL_MENU || menu_item->common.type == SDL_MENUBAR) { SDL_SetError("menu_item can't be a menu."); return false; } - return _this->EnableMenuItem(menu_item, enabled); + return _this->EnableMenuItem(menu_item); } -static void SDL_DestroyMenuItem(SDL_MenuItem* menu_item) { - for (SDL_MenuItem *current = menu_item->common.next; current != NULL; current = current->common.next) { - SDL_DestroyMenuItem(current); +bool SDL_DisableMenuItem(SDL_MenuItem *menu_item) +{ + CHECK_MENUITEM_MAGIC(menu_item, false); + if (menu_item->common.type == SDL_MENU || menu_item->common.type == SDL_MENUBAR) { + SDL_SetError("menu_item can't be a menu."); + return false; } - _this->DestroyMenuItem(menu_item); - SDL_free(menu_item); + return _this->DisableMenuItem(menu_item); } -bool SDL_DestroyMenuBar(SDL_MenuBar* menu_bar) +bool SDL_DestroyMenuItem(SDL_MenuItem *menu_item) { - bool ret = _this->DestroyMenuBar(menu_bar); - SDL_DestroyMenuItem((SDL_MenuItem *)menu_bar); + if (menu_item->common.type == SDL_MENU || menu_item->common.type == SDL_MENUBAR) { + for (SDL_MenuItem *current = ((SDL_Menu_CommonData *)menu_item)->child_list; current != NULL; current = current->common.next) { + if (!SDL_DestroyMenuItem(current)) { + SDL_SetError("Failed to destroy Child Menu Item"); + return false; + } + } + } + if (!_this->DestroyMenuItem(menu_item)) + { + SDL_SetError("Failed to destroy Menu Item"); + return false; + } + + if (menu_item->common.prev) { + menu_item->common.prev->common.next = menu_item->common.next; + menu_item->common.next->common.next = menu_item->common.prev; + + if (menu_item == menu_item->common.parent->menu_common.child_list) { + menu_item->common.parent->menu_common.child_list = menu_item->common.next; + } + + menu_item->common.prev = NULL; + menu_item->common.next = NULL; + } + + SDL_free(menu_item); return true; } diff --git a/src/video/windows/SDL_windowsvideo.c b/src/video/windows/SDL_windowsvideo.c index e82e6ab22d6ab..88619fb02d556 100644 --- a/src/video/windows/SDL_windowsvideo.c +++ b/src/video/windows/SDL_windowsvideo.c @@ -359,13 +359,16 @@ static SDL_VideoDevice *WIN_CreateDevice(void) device->ShowWindowSystemMenu = WIN_ShowWindowSystemMenu; device->SetWindowFocusable = WIN_SetWindowFocusable; device->UpdateWindowShape = WIN_UpdateWindowShape; + device->CreateMenuBar = Win32_CreateMenuBar; - device->CreateMenuBarItem = Win32_CreateMenuBarItem; - device->CreateMenuItem = Win32_CreateMenuItem; + device->CreateMenuItemAt = Win32_CreateMenuItemAt; device->CheckMenuItem = Win32_CheckMenuItem; + device->UncheckMenuItem = Win32_UncheckMenuItem; + device->MenuItemChecked = Win32_MenuItemChecked; + device->MenuItemEnabled = Win32_MenuItemEnabled; device->EnableMenuItem = Win32_EnableMenuItem; + device->DisableMenuItem = Win32_DisableMenuItem; device->DestroyMenuItem = Win32_DestroyMenuItem; - device->DestroyMenuBar = Win32_DestroyMenuBar; #endif @@ -914,11 +917,11 @@ bool SDLCALL Win32_CreateMenuBar(SDL_MenuBar *menu_bar) return false; } - menu_bar->common.platform_data = (void*)CreatePlatformMenuData(NULL, (UINT_PTR)menu_handle); + menu_bar->common.item_common.platform_data = (void*)CreatePlatformMenuData(NULL, (UINT_PTR)menu_handle); - if (!SetMenu(GetHwndFromWindow(menu_bar->common.window), menu_handle)) { + if (!SetMenu(GetHwndFromWindow(menu_bar->common.item_common.window), menu_handle)) { WIN_SetError("Unable to set MenuBar"); - SDL_free(menu_bar->common.platform_data); + SDL_free(menu_bar->common.item_common.platform_data); DestroyMenu(menu_handle); return false; } @@ -926,20 +929,23 @@ bool SDLCALL Win32_CreateMenuBar(SDL_MenuBar *menu_bar) return true; } -static bool Win32_CreateMenuItemImpl(SDL_MenuItem *menu_item, const char *name, Uint16 event_type, bool toplevel_menu) +static bool Win32_CreateMenuItemAt(SDL_MenuItem *menu_item, size_t index, const char *name, Uint16 event_type) { - PlatformMenuData *menu_bar_platform_data = (PlatformMenuData *)menu_item->common.parent->common.platform_data; - PlatformMenuData *platform_data = CreatePlatformMenuData((HMENU)menu_bar_platform_data->self_handle, menu_item->common.type); + PlatformMenuData *menu_platform_data = (PlatformMenuData *)menu_item->common.parent->common.platform_data; + PlatformMenuData *platform_data = CreatePlatformMenuData((HMENU)menu_platform_data->self_handle, menu_item->common.type); menu_item->common.platform_data = (void *)platform_data; + platform_data->user_event_type = event_type; UINT flags = 0; - if (!toplevel_menu) { + bool top_level_menu = menu_item->common.parent->common.type == SDL_MENUBAR; + + if (!top_level_menu) { flags = MF_STRING; } if (menu_item->common.type == SDL_MENU) { - if (toplevel_menu) { + if (top_level_menu) { platform_data->self_handle = (UINT_PTR)CreateMenu(); } else { platform_data->self_handle = (UINT_PTR)CreatePopupMenu(); @@ -952,91 +958,112 @@ static bool Win32_CreateMenuItemImpl(SDL_MenuItem *menu_item, const char *name, flags |= MF_POPUP; } else { - if (toplevel_menu) { + if (top_level_menu) { flags |= MF_STRING; } platform_data->self_handle = (UINT_PTR)event_type; } - if (!AppendMenuA((HMENU)menu_bar_platform_data->self_handle, flags, platform_data->self_handle, name)) { + if (!AppendMenuA((HMENU)menu_platform_data->self_handle, flags, platform_data->self_handle, name)) { return WIN_SetError("Unable to append item to Menu."); } + + if (!DrawMenuBar(GetHwndFromWindow(menu_item->common.window))) { + return WIN_SetError("Unable to draw menu bar"); + } + return menu_item; } -static bool Win32_CreateMenuBarItem(SDL_MenuItem *menu_item, const char *name, Uint16 event_type) +static bool Win32_CheckMenuItem(SDL_MenuItem *menu_item) { - bool ret = Win32_CreateMenuItemImpl(menu_item, name, event_type, true); + PlatformMenuData *platform_data = (PlatformMenuData *)menu_item->common.platform_data; - if (!ret) { - return WIN_SetError("Unable to create menu item"); + if (!CheckMenuItem(platform_data->owner_handle, (UINT)platform_data->self_handle, MF_BYCOMMAND | MF_CHECKED)) { + return WIN_SetError("Unable to check menu item."); } - if (!DrawMenuBar(GetHwndFromWindow(menu_item->common.window))) { - return WIN_SetError("Unable to draw menu bar"); + return true; +} + +static bool Win32_UncheckMenuItem(SDL_MenuItem *menu_item) +{ + PlatformMenuData *platform_data = (PlatformMenuData *)menu_item->common.platform_data; + + if (!CheckMenuItem(platform_data->owner_handle, (UINT)platform_data->self_handle, MF_BYCOMMAND | MF_UNCHECKED)) { + return WIN_SetError("Unable to check menu item."); } return true; } -static bool Win32_CreateMenuItem(SDL_MenuItem *menu_item, const char *name, Uint16 event_type) +static bool Win32_MenuItemChecked(SDL_MenuItem *menu_item, bool* checked) { - bool ret = Win32_CreateMenuItemImpl(menu_item, name, event_type, false); + PlatformMenuData *platform_data = (PlatformMenuData *)menu_item->common.platform_data; - if (!ret) { - return WIN_SetError("Unable to create menu item"); - } + UINT flags = GetMenuState(platform_data->owner_handle, (UINT)platform_data->self_handle, MF_BYCOMMAND); - if (!DrawMenuBar(GetHwndFromWindow(menu_item->common.window))) { - return WIN_SetError("Unable to draw menu bar"); + if (flags == -1) { + return WIN_SetError("Unable to get menu_item check state."); } + *checked = flags &MF_CHECKED; return true; } -static bool Win32_CheckMenuItem(SDL_MenuItem *menu_item, bool checked) +static bool Win32_MenuItemEnabled(SDL_MenuItem *menu_item, bool *enabled) { PlatformMenuData *platform_data = (PlatformMenuData *)menu_item->common.platform_data; - if (!CheckMenuItem(platform_data->owner_handle, (UINT)platform_data->self_handle, MF_BYCOMMAND | (checked ? MF_CHECKED : MF_UNCHECKED))) { - return WIN_SetError("Unable to check menu item."); + UINT flags = GetMenuState(platform_data->owner_handle, (UINT)platform_data->self_handle, MF_BYCOMMAND); + + if (flags == -1) { + return WIN_SetError("Unable to get menu_item check state."); } + *enabled = !(flags & MF_DISABLED); + return true; } -static bool Win32_EnableMenuItem(SDL_MenuItem *menu_item, bool enabled) +static bool Win32_EnableMenuItem(SDL_MenuItem *menu_item) { PlatformMenuData *platform_data = (PlatformMenuData *)menu_item->common.platform_data; - if (!EnableMenuItem(platform_data->owner_handle, (UINT)platform_data->self_handle, MF_BYCOMMAND | (enabled ? MF_ENABLED : MF_GRAYED))) { + if (!EnableMenuItem(platform_data->owner_handle, (UINT)platform_data->self_handle, MF_BYCOMMAND | MF_ENABLED)) { return WIN_SetError("Unable to enable menu item."); } return true; } -// We rely on the top level Win32_DestroyMenuBar to recursively deal with the native handles, -// but we need to recurse through and delete the platform datas. -static bool Win32_DestroyMenuItem(SDL_MenuItem *menu_item) +static bool Win32_DisableMenuItem(SDL_MenuItem *menu_item) { - SDL_free(menu_item->common.platform_data); + PlatformMenuData *platform_data = (PlatformMenuData *)menu_item->common.platform_data; + + if (!EnableMenuItem(platform_data->owner_handle, (UINT)platform_data->self_handle, MF_BYCOMMAND | MF_GRAYED)) { + return WIN_SetError("Unable to enable menu item."); + } + return true; } -static bool Win32_DestroyMenuBar(SDL_MenuBar *menu_bar) +static bool Win32_DestroyMenuItem(SDL_MenuItem *menu_item) { - PlatformMenuData *platform_data = (PlatformMenuData *)menu_bar->common.platform_data; + PlatformMenuData *platform_data = (PlatformMenuData *)menu_item->common.platform_data; - // This will take care of all of the child handles underneath the menubar. - if (!DestroyMenu((HMENU)platform_data->self_handle)) { - return WIN_SetError("Unable to remove menu item."); + if (menu_item->common.type == SDL_MENUBAR) { + if (!DestroyMenu((HMENU)platform_data->self_handle)) { + return WIN_SetError("Unable to remove menu item."); + } + } else { + DeleteMenu((HMENU)platform_data->self_handle, platform_data->user_event_type, MF_BYCOMMAND); } - Win32_DestroyMenuItem((SDL_MenuItem *)menu_bar); - SDL_free(menu_bar->common.platform_data); + SDL_free(menu_item->common.platform_data); + menu_item->common.platform_data = NULL; return true; } diff --git a/src/video/windows/SDL_windowsvideo.h b/src/video/windows/SDL_windowsvideo.h index 1c91820cd5373..ba84487634014 100644 --- a/src/video/windows/SDL_windowsvideo.h +++ b/src/video/windows/SDL_windowsvideo.h @@ -669,14 +669,17 @@ typedef struct PlatformMenuData { HMENU owner_handle; UINT_PTR self_handle; + Uint16 user_event_type; } PlatformMenuData; -extern bool SDLCALL Win32_CreateMenuBar(SDL_MenuBar *menu_bar); -extern bool Win32_CreateMenuBarItem(SDL_MenuItem *menu_item, const char *name, Uint16 event_type); -extern bool Win32_CreateMenuItem(SDL_MenuItem *menu_item, const char *name, Uint16 event_type); -extern bool Win32_CheckMenuItem(SDL_MenuItem *menu_item, bool checked); -extern bool Win32_EnableMenuItem(SDL_MenuItem *menu_item, bool enabled); +extern bool Win32_CreateMenuBar(SDL_MenuBar *menu_bar); +extern bool Win32_CreateMenuItemAt(SDL_MenuItem *menu_item, size_t index, const char *name, Uint16 event_type); +extern bool Win32_CheckMenuItem(SDL_MenuItem *menu_item); +extern bool Win32_UncheckMenuItem(SDL_MenuItem *menu_item); +extern bool Win32_MenuItemChecked(SDL_MenuItem *menu_item, bool *checked); +extern bool Win32_MenuItemEnabled(SDL_MenuItem *menu_item, bool *enabled); +extern bool Win32_EnableMenuItem(SDL_MenuItem *menu_item); +extern bool Win32_DisableMenuItem(SDL_MenuItem *menu_item); extern bool Win32_DestroyMenuItem(SDL_MenuItem *menu_item); -extern bool Win32_DestroyMenuBar(SDL_MenuBar *menu_bar); #endif // SDL_windowsvideo_h_ From f6d85d15ef25579f186fec80f687fc3bd0b37de3 Mon Sep 17 00:00:00 2001 From: "Joshua T. Fisher" Date: Wed, 13 Aug 2025 18:13:51 -0700 Subject: [PATCH 12/35] Not working, gotta switch --- examples/video/01-menubar/menubar.c | 2 +- include/SDL3/SDL_video.h | 6 ++++-- src/video/SDL_video.c | 20 ++++++++++++++------ src/video/windows/SDL_windowsvideo.c | 15 +++++++-------- 4 files changed, 26 insertions(+), 17 deletions(-) diff --git a/examples/video/01-menubar/menubar.c b/examples/video/01-menubar/menubar.c index e6442dfbba3a4..4f6cd3c0505fd 100644 --- a/examples/video/01-menubar/menubar.c +++ b/examples/video/01-menubar/menubar.c @@ -52,7 +52,7 @@ void CreateMenuBar() SDL_CreateMenuItem(main_bookmarks, "SDL Wiki", SDL_MENU_BUTTON, MENU_BAR_BOOKMARKS_TOOLBAR_WIKI); SDL_CreateMenuItem(main_bookmarks, "SDL Discord", SDL_MENU_BUTTON, MENU_BAR_BOOKMARKS_TOOLBAR_DISCORD); - SDL_MenuItem* other_bookmarks = SDL_CreateMenuItem(menu, "Other Bookmarks", SDL_MENU, MENU_BAR_LAST); + SDL_MenuItem *other_bookmarks = SDL_CreateMenuItem(main_bookmarks, "Other Bookmarks", SDL_MENU, MENU_BAR_LAST); SDL_CreateMenuItem(other_bookmarks, "Stack Overflow", SDL_MENU_BUTTON, MENU_BAR_BOOKMARKS_OTHER_BOOKMARKS_STACKOVERFLOW); SDL_DisableMenuItem(other_bookmarks); diff --git a/include/SDL3/SDL_video.h b/include/SDL3/SDL_video.h index 4ab9216acb3df..357ca4498ad6f 100644 --- a/include/SDL3/SDL_video.h +++ b/include/SDL3/SDL_video.h @@ -3046,11 +3046,13 @@ typedef union SDL_MenuItem SDL_MenuItem; extern SDL_DECLSPEC SDL_MenuItem *SDL_CreateMenuBar(SDL_Window *window); -// Must be a SDL_MENUBAR or SDL_MENU +// menu_bar_as_item must be a SDL_MENUBAR or SDL_MENU +// event_type will be ignored if type == SDL_MENU // On MacOS, buttoms created under a menubar will go into the "App" submenu extern SDL_DECLSPEC SDL_MenuItem *SDL_CreateMenuItemAt(SDL_MenuItem *menu_bar_as_item, size_t index, const char *name, SDL_MenuItemType type, Uint16 event_type); -// Must be a SDL_MENUBAR or SDL_MENU +// menu_bar_as_item must be a SDL_MENUBAR or SDL_MENU +// event_type will be ignored if type == SDL_MENU // On MacOS, buttoms created under a menubar will go into the "App" submenu extern SDL_DECLSPEC SDL_MenuItem *SDL_CreateMenuItem(SDL_MenuItem *menu_bar_as_item, const char *name, SDL_MenuItemType type, Uint16 event_type); diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c index 4513b8bbcccf9..56cb38c4af52a 100644 --- a/src/video/SDL_video.c +++ b/src/video/SDL_video.c @@ -6174,13 +6174,21 @@ SDL_MenuItem *SDL_CreateMenuItemAt(SDL_MenuItem *menu_bar_as_item, size_t index, // Get the last item in the list and insert our new item. if (menu->child_list) { - SDL_MenuItem *common = menu->child_list; + SDL_MenuItem *current = menu->child_list; - while (common->common.next) - common = common->common.next; + for (size_t i = 1; i < index; ++i) { + current = current->common.next; + } + + SDL_assert(current); + + if (current->common.next) { + current->common.next->common.prev = menu_item; + menu_item->common.next = current->common.next; + } - common->common.next = menu_item; - menu_item->common.prev = common; + current->common.next = menu_item; + menu_item->common.prev = current; } else { menu->child_list = menu_item; } @@ -6199,7 +6207,7 @@ SDL_MenuItem *SDL_CreateMenuItem(SDL_MenuItem *menu_bar_as_item, const char *nam } SDL_Menu_CommonData *menu = (SDL_Menu_CommonData *)menu_bar_as_item; - return SDL_CreateMenuItemAt(menu_bar_as_item, menu->children, name, type, event_type); + return SDL_CreateMenuItemAt(menu_bar_as_item, -1, name, type, event_type); } diff --git a/src/video/windows/SDL_windowsvideo.c b/src/video/windows/SDL_windowsvideo.c index 88619fb02d556..2434b44705138 100644 --- a/src/video/windows/SDL_windowsvideo.c +++ b/src/video/windows/SDL_windowsvideo.c @@ -901,11 +901,6 @@ static PlatformMenuData *CreatePlatformMenuData(HMENU owner_handle, UINT_PTR sel return platform; } -static HWND GetHwndFromWindow(SDL_Window *window) -{ - return (HWND)SDL_GetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL); -} - // PlatformMenuData platform_data = bool SDLCALL Win32_CreateMenuBar(SDL_MenuBar *menu_bar) @@ -918,8 +913,9 @@ bool SDLCALL Win32_CreateMenuBar(SDL_MenuBar *menu_bar) } menu_bar->common.item_common.platform_data = (void*)CreatePlatformMenuData(NULL, (UINT_PTR)menu_handle); + const SDL_WindowData *data = menu_bar->common.item_common.window->internal; - if (!SetMenu(GetHwndFromWindow(menu_bar->common.item_common.window), menu_handle)) { + if (!SetMenu(data->hwnd, menu_handle)) { WIN_SetError("Unable to set MenuBar"); SDL_free(menu_bar->common.item_common.platform_data); DestroyMenu(menu_handle); @@ -962,15 +958,18 @@ static bool Win32_CreateMenuItemAt(SDL_MenuItem *menu_item, size_t index, const flags |= MF_STRING; } + flags |= MF_BYPOSITION; + platform_data->self_handle = (UINT_PTR)event_type; } - if (!AppendMenuA((HMENU)menu_platform_data->self_handle, flags, platform_data->self_handle, name)) { + if (!InsertMenuA((HMENU)menu_platform_data->self_handle, (UINT)index, flags, platform_data->self_handle, name)) { return WIN_SetError("Unable to append item to Menu."); } + const SDL_WindowData *data = menu_item->common.window->internal; - if (!DrawMenuBar(GetHwndFromWindow(menu_item->common.window))) { + if (!DrawMenuBar(data->hwnd)) { return WIN_SetError("Unable to draw menu bar"); } From fc9b414b348d6b542eb2c8f3637f0eb5f14d1093 Mon Sep 17 00:00:00 2001 From: "Joshua T. Fisher" Date: Thu, 14 Aug 2025 22:41:28 -0700 Subject: [PATCH 13/35] I think it's been fixed. --- include/SDL3/SDL_video.h | 1 + src/video/SDL_sysvideo.h | 2 ++ src/video/SDL_video.c | 22 ++++++++++++++++++-- src/video/windows/SDL_windowsvideo.c | 31 +++++++++++++++++++--------- 4 files changed, 44 insertions(+), 12 deletions(-) diff --git a/include/SDL3/SDL_video.h b/include/SDL3/SDL_video.h index 357ca4498ad6f..ca83e17190660 100644 --- a/include/SDL3/SDL_video.h +++ b/include/SDL3/SDL_video.h @@ -3055,6 +3055,7 @@ extern SDL_DECLSPEC SDL_MenuItem *SDL_CreateMenuItemAt(SDL_MenuItem *menu_bar_as // event_type will be ignored if type == SDL_MENU // On MacOS, buttoms created under a menubar will go into the "App" submenu extern SDL_DECLSPEC SDL_MenuItem *SDL_CreateMenuItem(SDL_MenuItem *menu_bar_as_item, const char *name, SDL_MenuItemType type, Uint16 event_type); +extern SDL_DECLSPEC SDL_MenuItem *SDL_CreateMenuItemWithProperties(SDL_MenuItem *menu_bar_as_item, SDL_PropertiesID props); // -1 on error extern SDL_DECLSPEC Sint64 SDL_ChildItems(SDL_MenuItem *menu_bar_as_item); diff --git a/src/video/SDL_sysvideo.h b/src/video/SDL_sysvideo.h index 07be3c8eff38b..58fb767e7990e 100644 --- a/src/video/SDL_sysvideo.h +++ b/src/video/SDL_sysvideo.h @@ -675,4 +675,6 @@ extern SDL_Capitalization SDL_GetTextInputCapitalization(SDL_PropertiesID props) extern bool SDL_GetTextInputAutocorrect(SDL_PropertiesID props); extern bool SDL_GetTextInputMultiline(SDL_PropertiesID props); +extern Uint32 SDL_GetIndexInMenu(SDL_MenuItem *menu_item); + #endif // SDL_sysvideo_h_ diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c index 56cb38c4af52a..2b2fed42e4e06 100644 --- a/src/video/SDL_video.c +++ b/src/video/SDL_video.c @@ -6176,7 +6176,7 @@ SDL_MenuItem *SDL_CreateMenuItemAt(SDL_MenuItem *menu_bar_as_item, size_t index, if (menu->child_list) { SDL_MenuItem *current = menu->child_list; - for (size_t i = 1; i < index; ++i) { + for (size_t i = 1; (i < index) && current; ++i) { current = current->common.next; } @@ -6198,6 +6198,24 @@ SDL_MenuItem *SDL_CreateMenuItemAt(SDL_MenuItem *menu_bar_as_item, size_t index, return menu_item; } + +Uint32 SDL_GetIndexInMenu(SDL_MenuItem *menu_item) +{ + Uint32 i = 0; + SDL_MenuItem *current = menu_item->common.prev; + while (current) { + current = current->common.prev; + ++i; + } + return i; +} + + +SDL_MenuItem* SDL_CreateMenuItemWithProperties(SDL_MenuItem* menu_bar_as_item, SDL_PropertiesID props) +{ + return NULL; +} + SDL_MenuItem *SDL_CreateMenuItem(SDL_MenuItem *menu_bar_as_item, const char *name, SDL_MenuItemType type, Uint16 event_type) { CHECK_MENUITEM_MAGIC(menu_bar_as_item, NULL); @@ -6207,7 +6225,7 @@ SDL_MenuItem *SDL_CreateMenuItem(SDL_MenuItem *menu_bar_as_item, const char *nam } SDL_Menu_CommonData *menu = (SDL_Menu_CommonData *)menu_bar_as_item; - return SDL_CreateMenuItemAt(menu_bar_as_item, -1, name, type, event_type); + return SDL_CreateMenuItemAt(menu_bar_as_item, menu->children, name, type, event_type); } diff --git a/src/video/windows/SDL_windowsvideo.c b/src/video/windows/SDL_windowsvideo.c index 2434b44705138..25fc71a2bb8b7 100644 --- a/src/video/windows/SDL_windowsvideo.c +++ b/src/video/windows/SDL_windowsvideo.c @@ -927,13 +927,12 @@ bool SDLCALL Win32_CreateMenuBar(SDL_MenuBar *menu_bar) static bool Win32_CreateMenuItemAt(SDL_MenuItem *menu_item, size_t index, const char *name, Uint16 event_type) { + SDL_Menu_CommonData *parent = menu_item->common.parent; PlatformMenuData *menu_platform_data = (PlatformMenuData *)menu_item->common.parent->common.platform_data; PlatformMenuData *platform_data = CreatePlatformMenuData((HMENU)menu_platform_data->self_handle, menu_item->common.type); menu_item->common.platform_data = (void *)platform_data; platform_data->user_event_type = event_type; - UINT flags = 0; - bool top_level_menu = menu_item->common.parent->common.type == SDL_MENUBAR; if (!top_level_menu) { @@ -963,6 +962,11 @@ static bool Win32_CreateMenuItemAt(SDL_MenuItem *menu_item, size_t index, const platform_data->self_handle = (UINT_PTR)event_type; } + // To add items at the back, we need to set the index to -1. + if (index == parent->children) { + index = -1; + } + if (!InsertMenuA((HMENU)menu_platform_data->self_handle, (UINT)index, flags, platform_data->self_handle, name)) { return WIN_SetError("Unable to append item to Menu."); } @@ -979,8 +983,9 @@ static bool Win32_CreateMenuItemAt(SDL_MenuItem *menu_item, size_t index, const static bool Win32_CheckMenuItem(SDL_MenuItem *menu_item) { PlatformMenuData *platform_data = (PlatformMenuData *)menu_item->common.platform_data; + Uint32 i = SDL_GetIndexInMenu(menu_item); - if (!CheckMenuItem(platform_data->owner_handle, (UINT)platform_data->self_handle, MF_BYCOMMAND | MF_CHECKED)) { + if (!CheckMenuItem(platform_data->owner_handle, i, MF_BYPOSITION | MF_CHECKED)) { return WIN_SetError("Unable to check menu item."); } @@ -990,19 +995,21 @@ static bool Win32_CheckMenuItem(SDL_MenuItem *menu_item) static bool Win32_UncheckMenuItem(SDL_MenuItem *menu_item) { PlatformMenuData *platform_data = (PlatformMenuData *)menu_item->common.platform_data; + Uint32 i = SDL_GetIndexInMenu(menu_item); - if (!CheckMenuItem(platform_data->owner_handle, (UINT)platform_data->self_handle, MF_BYCOMMAND | MF_UNCHECKED)) { + if (!CheckMenuItem(platform_data->owner_handle, i, MF_BYPOSITION | MF_UNCHECKED)) { return WIN_SetError("Unable to check menu item."); } return true; } -static bool Win32_MenuItemChecked(SDL_MenuItem *menu_item, bool* checked) +static bool Win32_MenuItemChecked(SDL_MenuItem *menu_item, bool *checked) { PlatformMenuData *platform_data = (PlatformMenuData *)menu_item->common.platform_data; + Uint32 i = SDL_GetIndexInMenu(menu_item); - UINT flags = GetMenuState(platform_data->owner_handle, (UINT)platform_data->self_handle, MF_BYCOMMAND); + UINT flags = GetMenuState(platform_data->owner_handle, i, MF_BYPOSITION); if (flags == -1) { return WIN_SetError("Unable to get menu_item check state."); @@ -1015,8 +1022,9 @@ static bool Win32_MenuItemChecked(SDL_MenuItem *menu_item, bool* checked) static bool Win32_MenuItemEnabled(SDL_MenuItem *menu_item, bool *enabled) { PlatformMenuData *platform_data = (PlatformMenuData *)menu_item->common.platform_data; + Uint32 i = SDL_GetIndexInMenu(menu_item); - UINT flags = GetMenuState(platform_data->owner_handle, (UINT)platform_data->self_handle, MF_BYCOMMAND); + UINT flags = GetMenuState(platform_data->owner_handle, i, MF_BYPOSITION); if (flags == -1) { return WIN_SetError("Unable to get menu_item check state."); @@ -1030,8 +1038,9 @@ static bool Win32_MenuItemEnabled(SDL_MenuItem *menu_item, bool *enabled) static bool Win32_EnableMenuItem(SDL_MenuItem *menu_item) { PlatformMenuData *platform_data = (PlatformMenuData *)menu_item->common.platform_data; + Uint32 i = SDL_GetIndexInMenu(menu_item); - if (!EnableMenuItem(platform_data->owner_handle, (UINT)platform_data->self_handle, MF_BYCOMMAND | MF_ENABLED)) { + if (!EnableMenuItem(platform_data->owner_handle, i, MF_BYPOSITION | MF_ENABLED)) { return WIN_SetError("Unable to enable menu item."); } @@ -1041,8 +1050,9 @@ static bool Win32_EnableMenuItem(SDL_MenuItem *menu_item) static bool Win32_DisableMenuItem(SDL_MenuItem *menu_item) { PlatformMenuData *platform_data = (PlatformMenuData *)menu_item->common.platform_data; + Uint32 i = SDL_GetIndexInMenu(menu_item); - if (!EnableMenuItem(platform_data->owner_handle, (UINT)platform_data->self_handle, MF_BYCOMMAND | MF_GRAYED)) { + if (!EnableMenuItem(platform_data->owner_handle, i, MF_BYPOSITION | MF_GRAYED)) { return WIN_SetError("Unable to enable menu item."); } @@ -1052,13 +1062,14 @@ static bool Win32_DisableMenuItem(SDL_MenuItem *menu_item) static bool Win32_DestroyMenuItem(SDL_MenuItem *menu_item) { PlatformMenuData *platform_data = (PlatformMenuData *)menu_item->common.platform_data; + Uint32 i = SDL_GetIndexInMenu(menu_item); if (menu_item->common.type == SDL_MENUBAR) { if (!DestroyMenu((HMENU)platform_data->self_handle)) { return WIN_SetError("Unable to remove menu item."); } } else { - DeleteMenu((HMENU)platform_data->self_handle, platform_data->user_event_type, MF_BYCOMMAND); + DeleteMenu((HMENU)platform_data->self_handle, i, MF_BYPOSITION); } SDL_free(menu_item->common.platform_data); From c46ae0819a5c5f20858f17749a3d79c326aaf876 Mon Sep 17 00:00:00 2001 From: "Joshua T. Fisher" Date: Thu, 14 Aug 2025 23:04:48 -0700 Subject: [PATCH 14/35] Fix the temp comments --- include/SDL3/SDL_video.h | 42 ++++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/include/SDL3/SDL_video.h b/include/SDL3/SDL_video.h index ca83e17190660..3f343181a4b97 100644 --- a/include/SDL3/SDL_video.h +++ b/include/SDL3/SDL_video.h @@ -3046,29 +3046,41 @@ typedef union SDL_MenuItem SDL_MenuItem; extern SDL_DECLSPEC SDL_MenuItem *SDL_CreateMenuBar(SDL_Window *window); -// menu_bar_as_item must be a SDL_MENUBAR or SDL_MENU -// event_type will be ignored if type == SDL_MENU -// On MacOS, buttoms created under a menubar will go into the "App" submenu +/** + * menu_bar_as_item must be a SDL_MENUBAR or SDL_MENU + * event_type will be ignored if type == SDL_MENU + * On MacOS, buttoms created under a menubar will go into the "App" submenu + */ extern SDL_DECLSPEC SDL_MenuItem *SDL_CreateMenuItemAt(SDL_MenuItem *menu_bar_as_item, size_t index, const char *name, SDL_MenuItemType type, Uint16 event_type); -// menu_bar_as_item must be a SDL_MENUBAR or SDL_MENU -// event_type will be ignored if type == SDL_MENU -// On MacOS, buttoms created under a menubar will go into the "App" submenu -extern SDL_DECLSPEC SDL_MenuItem *SDL_CreateMenuItem(SDL_MenuItem *menu_bar_as_item, const char *name, SDL_MenuItemType type, Uint16 event_type); +/** + * menu_bar_as_item must be a SDL_MENUBAR or SDL_MENU + * event_type will be ignored if type == SDL_MENU + * On MacOS, buttoms created under a menubar will go into the "App" submenu + */ + extern SDL_DECLSPEC SDL_MenuItem *SDL_CreateMenuItem(SDL_MenuItem *menu_bar_as_item, const char *name, SDL_MenuItemType type, Uint16 event_type); extern SDL_DECLSPEC SDL_MenuItem *SDL_CreateMenuItemWithProperties(SDL_MenuItem *menu_bar_as_item, SDL_PropertiesID props); -// -1 on error -extern SDL_DECLSPEC Sint64 SDL_ChildItems(SDL_MenuItem *menu_bar_as_item); +/** + * -1 on error + */ + extern SDL_DECLSPEC Sint64 SDL_ChildItems(SDL_MenuItem *menu_bar_as_item); -// Must be a SDL_MENU_CHECKABLE -extern SDL_DECLSPEC bool SDL_CheckMenuItem(SDL_MenuItem *menu_item); +/** + * Must be a SDL_MENU_CHECKABLE + */ + extern SDL_DECLSPEC bool SDL_CheckMenuItem(SDL_MenuItem *menu_item); -// Must be a SDL_MENU_CHECKABLE -extern SDL_DECLSPEC bool SDL_UncheckMenuItem(SDL_MenuItem *menu_item); +/** + * Must be a SDL_MENU_CHECKABLE + */ + extern SDL_DECLSPEC bool SDL_UncheckMenuItem(SDL_MenuItem *menu_item); -// Must be a SDL_MENU_CHECKABLE -extern SDL_DECLSPEC bool SDL_MenuItemChecked(SDL_MenuItem *menu_item, bool *checked); +/** + * Must be a SDL_MENU_CHECKABLE + */ + extern SDL_DECLSPEC bool SDL_MenuItemChecked(SDL_MenuItem *menu_item, bool *checked); extern SDL_DECLSPEC bool SDL_MenuItemEnabled(SDL_MenuItem *menu_item, bool *enabled); extern SDL_DECLSPEC bool SDL_EnableMenuItem(SDL_MenuItem *menu_item); From fd3c62163bb07ba5cb31acd4bab2b0587cca9f80 Mon Sep 17 00:00:00 2001 From: "Joshua T. Fisher" Date: Fri, 15 Aug 2025 02:02:50 -0700 Subject: [PATCH 15/35] Mac is working again, enabling and disabling don't seem to work. --- examples/video/01-menubar/menubar.c | 23 ++++++- src/video/cocoa/SDL_cocoavideo.m | 102 ++++++++++++++++++---------- src/video/cocoa/SDL_cocoawindow.h | 16 +++-- 3 files changed, 96 insertions(+), 45 deletions(-) diff --git a/examples/video/01-menubar/menubar.c b/examples/video/01-menubar/menubar.c index 4f6cd3c0505fd..eef8913861477 100644 --- a/examples/video/01-menubar/menubar.c +++ b/examples/video/01-menubar/menubar.c @@ -11,12 +11,14 @@ SDL_Window* window = NULL; SDL_Renderer* renderer = NULL; +SDL_MenuItem* checkable = NULL; +SDL_MenuItem* disabled = NULL; typedef enum SDL_EventType_MenuExt { MENU_BAR_FILE, MENU_BAR_FILE_NEW_WINDOW, - MENU_BAR_FILE_AUTOSAVE_TABS_ON_CLOSE, + MENU_BAR_FILE_DISABLE_NEW_WINDOW, MENU_BAR_BOOKMARKS, MENU_BAR_BOOKMARKS_TOOLBAR, MENU_BAR_BOOKMARKS_TOOLBAR_GITHUB, @@ -33,6 +35,7 @@ typedef enum SDL_EventType_MenuExt static SDL_EventType_MenuExt EVENT_START = (SDL_EventType_MenuExt)0; + void CreateMenuBar() { SDL_MenuItem* bar = SDL_CreateMenuBar(window); @@ -40,7 +43,7 @@ void CreateMenuBar() { SDL_MenuItem* menu = SDL_CreateMenuItem(bar, "File", SDL_MENU, MENU_BAR_LAST); SDL_CreateMenuItem(menu, "New Window", SDL_MENU_BUTTON, MENU_BAR_FILE_NEW_WINDOW); - SDL_MenuItem* checkable = SDL_CreateMenuItem(menu, "Autosave Tabs on Close", SDL_MENU_CHECKABLE, MENU_BAR_FILE_AUTOSAVE_TABS_ON_CLOSE); + checkable = SDL_CreateMenuItem(menu, "Autosave Tabs on Close", SDL_MENU_CHECKABLE, MENU_BAR_FILE_DISABLE_NEW_WINDOW); SDL_CheckMenuItem(checkable); } @@ -63,7 +66,7 @@ void CreateMenuBar() SDL_MenuItem* checkable = SDL_CreateMenuItem(bar, "Incognito", SDL_MENU_CHECKABLE, MENU_BAR_INCOGNITO); SDL_assert(!checkable); - SDL_MenuItem* disabled = SDL_CreateMenuItem(bar, "Disabled Top-Level Button", SDL_MENU_BUTTON, MENU_BAR_TOP_LEVEL_BUTTON); + disabled = SDL_CreateMenuItem(bar, "Disabled Top-Level Button", SDL_MENU_BUTTON, MENU_BAR_TOP_LEVEL_BUTTON); SDL_DisableMenuItem(disabled); SDL_CreateMenuItem(bar, "Exit", SDL_MENU_BUTTON, MENU_BAR_EXIT); @@ -122,6 +125,20 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) { SDL_OpenURL("https://stackoverflow.com/questions"); break; } + case MENU_BAR_FILE_DISABLE_NEW_WINDOW: + { + bool is_checked = false; + SDL_MenuItemChecked(checkable, &is_checked); + if (is_checked) { + SDL_UncheckMenuItem(checkable); + SDL_EnableMenuItem(disabled); + } + else { + SDL_CheckMenuItem(checkable); + SDL_DisableMenuItem(disabled); + } + break; + } case MENU_BAR_EXIT: { return SDL_APP_SUCCESS; diff --git a/src/video/cocoa/SDL_cocoavideo.m b/src/video/cocoa/SDL_cocoavideo.m index 7c223182a4442..dc5630cf2830e 100644 --- a/src/video/cocoa/SDL_cocoavideo.m +++ b/src/video/cocoa/SDL_cocoavideo.m @@ -127,10 +127,13 @@ static void Cocoa_DeleteDevice(SDL_VideoDevice *device) device->SetWindowModal = Cocoa_SetWindowModal; device->SyncWindow = Cocoa_SyncWindow; device->CreateMenuBar = Cocoa_CreateMenuBar; - device->CreateMenuBarItem = Cocoa_CreateMenuBarItem; - device->CreateMenuItem = Cocoa_CreateMenuItem; + device->CreateMenuItemAt = Cocoa_CreateMenuItemAt; device->CheckMenuItem = Cocoa_CheckMenuItem; + device->UncheckMenuItem = Cocoa_UncheckMenuItem; + device->MenuItemChecked = Cocoa_MenuItemChecked; + device->MenuItemEnabled = Cocoa_MenuItemEnabled; device->EnableMenuItem = Cocoa_EnableMenuItem; + device->DisableMenuItem = Cocoa_DisableMenuItem; device->DestroyMenuItem = Cocoa_DestroyMenuItem; #ifdef SDL_VIDEO_OPENGL_CGL @@ -364,7 +367,12 @@ - (void) Cocoa_PlatformMenuData_MenuButtonClicked: (id)sender;{ @end -bool SDLCALL Cocoa_CreateMenuBar(SDL_MenuBar *menu_bar) + + + + + +bool Cocoa_CreateMenuBar(SDL_MenuBar *menu_bar) { PlatformMenuData* platform_menu =[PlatformMenuData new]; platform_menu->menu = [NSMenu new]; @@ -372,42 +380,22 @@ bool SDLCALL Cocoa_CreateMenuBar(SDL_MenuBar *menu_bar) NSMenuItem *appMenuItem = [NSMenuItem new]; NSMenu *appMenu = [NSMenu new]; - [appMenu addItemWithTitle: @"Quit" action:@selector(terminate:) keyEquivalent:@"q"]; + //[appMenu addItemWithTitle: @"Quit" action:@selector(terminate:) keyEquivalent:@"q"]; [appMenuItem setSubmenu:appMenu]; [platform_menu->menu addItem:appMenuItem]; - menu_bar->common.platform_data = CFBridgingRetain(platform_menu); + menu_bar->common.item_common.platform_data = CFBridgingRetain(platform_menu); return true; - // **** App Menu **** // -// NSMenuItem *appMenuItem = [NSMenuItem new]; -// NSMenu *appMenu = [NSMenu new]; -// [appMenu addItemWithTitle: @"Quit" action:@selector(terminate:) keyEquivalent:@"q"]; -// [appMenuItem setSubmenu:appMenu]; -// [menubar addItem:appMenuItem]; -// // **** File Menu **** // -// NSMenuItem *fileMenuItem = [NSMenuItem new]; -// NSMenu *fileMenu = [[NSMenu alloc] initWithTitle:@"File"]; -// //[fileMenu addItemWithTitle:@"New" action:@selector(menuAction:) keyEquivalent:@""]; -// //[fileMenu addItemWithTitle:@"Open" action:@selector(menuAction:) keyEquivalent:@""]; -// //[fileMenu addItemWithTitle:@"Save" action:@selector(menuAction:) keyEquivalent:@""]; -// NSMenuItem *newItem = [fileMenu addItemWithTitle:@"New" action:@selector(Cocoa_PlatformMenuData_MenuButtonClicked:) keyEquivalent:@""]; -// newItem.target = platform_menu; -// [fileMenuItem setSubmenu: fileMenu]; -// [menubar addItem: fileMenuItem]; } -bool Cocoa_CreateMenuBarItem(SDL_MenuItem *menu_item, const char *name, Uint16 event_type) -{ - return Cocoa_CreateMenuItem((SDL_MenuItem*)menu_item, name, event_type); -} - -bool Cocoa_CreateMenuItem(SDL_MenuItem *menu_item, const char *name, Uint16 event_type) +bool Cocoa_CreateMenuItemAt(SDL_MenuItem *menu_item, size_t index, const char *name, Uint16 event_type) { PlatformMenuData* platform_data = [PlatformMenuData new]; platform_data->user_event_type = event_type; menu_item->common.platform_data = CFBridgingRetain(platform_data); + PlatformMenuData* parent_platform_data = (__bridge id _Nullable)(menu_item->common.parent->common.platform_data); NSString* name_ns = [NSString stringWithUTF8String:name]; @@ -420,27 +408,71 @@ bool Cocoa_CreateMenuItem(SDL_MenuItem *menu_item, const char *name, Uint16 even [parent_platform_data->menu addItem: platform_data->menu_item]; } else { - platform_data->menu_item = [parent_platform_data->menu addItemWithTitle:name_ns action:@selector(Cocoa_PlatformMenuData_MenuButtonClicked:) keyEquivalent:@""]; + platform_data->menu_item = [NSMenuItem alloc]; + [platform_data->menu_item setTitle:name_ns]; + [platform_data->menu_item setAction:@selector(Cocoa_PlatformMenuData_MenuButtonClicked:)]; + [platform_data->menu_item setTarget:platform_data]; + [platform_data->menu_item setEnabled:true]; - [platform_data->menu_item setTarget:platform_data]; - [platform_data->menu_item setEnabled:true]; + if ((menu_item->common.parent->common.type == SDL_MENUBAR) && (menu_item->common.type != SDL_MENU)) { + NSMenu* app_menu = [[parent_platform_data->menu itemAtIndex:(NSInteger)0] submenu]; + [app_menu addItem:platform_data->menu_item]; + + } else { + [parent_platform_data->menu insertItem:platform_data->menu_item atIndex:(NSInteger)index]; + } } return true; } -bool Cocoa_CheckMenuItem(SDL_MenuItem *menu_item, bool checked) + +bool Cocoa_CheckMenuItem(SDL_MenuItem *menu_item) { - return false; + PlatformMenuData* platform_data = (__bridge PlatformMenuData*)menu_item->common.platform_data; + [platform_data->menu_item setState:NSControlStateValueOn]; + return true; } -bool Cocoa_EnableMenuItem(SDL_MenuItem *menu_item, bool enabled) +bool Cocoa_UncheckMenuItem(SDL_MenuItem *menu_item) { - return false; + PlatformMenuData* platform_data = (__bridge PlatformMenuData*)menu_item->common.platform_data; + [platform_data->menu_item setState:NSControlStateValueOff]; + return true; } -void Cocoa_DestroyMenuItem(SDL_MenuItem *menu_item) +bool Cocoa_MenuItemChecked(SDL_MenuItem *menu_item, bool *checked) { + PlatformMenuData* platform_data = (__bridge PlatformMenuData*)menu_item->common.platform_data; + return [platform_data->menu_item state] == NSControlStateValueOn; +} + +bool Cocoa_MenuItemEnabled(SDL_MenuItem *menu_item, bool *enabled) +{ + PlatformMenuData* platform_data = (__bridge PlatformMenuData*)menu_item->common.platform_data; + return [platform_data->menu_item isEnabled]; +} + +bool Cocoa_EnableMenuItem(SDL_MenuItem *menu_item) +{ + PlatformMenuData* platform_data = (__bridge PlatformMenuData*)menu_item->common.platform_data; + [platform_data->menu_item setEnabled:true]; + return true; +} + +bool Cocoa_DisableMenuItem(SDL_MenuItem *menu_item) +{ + PlatformMenuData* platform_data = (__bridge PlatformMenuData*)menu_item->common.platform_data; + [platform_data->menu_item setEnabled:false]; + return true; +} + +bool Cocoa_DestroyMenuItem(SDL_MenuItem *menu_item) +{ + PlatformMenuData* platform_data = (__bridge PlatformMenuData*)menu_item->common.platform_data; + PlatformMenuData* parent_platform_data = (__bridge id _Nullable)(menu_item->common.parent->common.platform_data); + [parent_platform_data->menu removeItem:platform_data->menu_item]; return false; } + #endif // SDL_VIDEO_DRIVER_COCOA diff --git a/src/video/cocoa/SDL_cocoawindow.h b/src/video/cocoa/SDL_cocoawindow.h index c8bbc650547d9..27eb660ac1ae5 100644 --- a/src/video/cocoa/SDL_cocoawindow.h +++ b/src/video/cocoa/SDL_cocoawindow.h @@ -196,12 +196,14 @@ extern bool Cocoa_SyncWindow(SDL_VideoDevice *_this, SDL_Window *window); extern void Cocoa_MenuVisibilityCallback(void *userdata, const char *name, const char *oldValue, const char *newValue); - -extern bool SDLCALL Cocoa_CreateMenuBar(SDL_MenuBar *menu_bar); -extern bool Cocoa_CreateMenuBarItem(SDL_MenuItem *menu_item, const char *name, Uint16 event_type); -extern bool Cocoa_CreateMenuItem(SDL_MenuItem *menu_item, const char *name, Uint16 event_type); -extern bool Cocoa_CheckMenuItem(SDL_MenuItem *menu_item, bool checked); -extern bool Cocoa_EnableMenuItem(SDL_MenuItem *menu_item, bool enabled); -extern void Cocoa_DestroyMenuItem(SDL_MenuItem *menu_item); +extern bool Cocoa_CreateMenuBar(SDL_MenuBar *menu_bar); +extern bool Cocoa_CreateMenuItemAt(SDL_MenuItem *menu_item, size_t index, const char *name, Uint16 event_type); +extern bool Cocoa_CheckMenuItem(SDL_MenuItem *menu_item); +extern bool Cocoa_UncheckMenuItem(SDL_MenuItem *menu_item); +extern bool Cocoa_MenuItemChecked(SDL_MenuItem *menu_item, bool *checked); +extern bool Cocoa_MenuItemEnabled(SDL_MenuItem *menu_item, bool *enabled); +extern bool Cocoa_EnableMenuItem(SDL_MenuItem *menu_item); +extern bool Cocoa_DisableMenuItem(SDL_MenuItem *menu_item); +extern bool Cocoa_DestroyMenuItem(SDL_MenuItem *menu_item); #endif // SDL_cocoawindow_h_ From bf88c085b4d43af780f5061b58b6084633859e0b Mon Sep 17 00:00:00 2001 From: "Joshua T. Fisher" Date: Fri, 15 Aug 2025 02:39:40 -0700 Subject: [PATCH 16/35] Mac looks alright. --- examples/video/01-menubar/menubar.c | 31 ++++++++++++++++++----------- src/video/SDL_video.c | 13 ++++-------- src/video/cocoa/SDL_cocoavideo.m | 18 ++++++++++++----- 3 files changed, 36 insertions(+), 26 deletions(-) diff --git a/examples/video/01-menubar/menubar.c b/examples/video/01-menubar/menubar.c index eef8913861477..e394ee1eac806 100644 --- a/examples/video/01-menubar/menubar.c +++ b/examples/video/01-menubar/menubar.c @@ -12,7 +12,7 @@ SDL_Window* window = NULL; SDL_Renderer* renderer = NULL; SDL_MenuItem* checkable = NULL; -SDL_MenuItem* disabled = NULL; +SDL_MenuItem* new_window = NULL; typedef enum SDL_EventType_MenuExt { @@ -42,7 +42,7 @@ void CreateMenuBar() { SDL_MenuItem* menu = SDL_CreateMenuItem(bar, "File", SDL_MENU, MENU_BAR_LAST); - SDL_CreateMenuItem(menu, "New Window", SDL_MENU_BUTTON, MENU_BAR_FILE_NEW_WINDOW); + new_window = SDL_CreateMenuItem(menu, "New Window", SDL_MENU_BUTTON, MENU_BAR_FILE_NEW_WINDOW); checkable = SDL_CreateMenuItem(menu, "Autosave Tabs on Close", SDL_MENU_CHECKABLE, MENU_BAR_FILE_DISABLE_NEW_WINDOW); SDL_CheckMenuItem(checkable); @@ -51,24 +51,23 @@ void CreateMenuBar() { SDL_MenuItem* menu = SDL_CreateMenuItem(bar, "Bookmarks", SDL_MENU, MENU_BAR_LAST); SDL_MenuItem* main_bookmarks = SDL_CreateMenuItem(menu, "Bookmarks Toolbar", SDL_MENU, MENU_BAR_LAST); + SDL_MenuItem* discord = SDL_CreateMenuItem(main_bookmarks, "SDL Discord", SDL_MENU_BUTTON, MENU_BAR_BOOKMARKS_TOOLBAR_DISCORD); SDL_CreateMenuItem(main_bookmarks, "SDL GitHub", SDL_MENU_BUTTON, MENU_BAR_BOOKMARKS_TOOLBAR_GITHUB); - SDL_CreateMenuItem(main_bookmarks, "SDL Wiki", SDL_MENU_BUTTON, MENU_BAR_BOOKMARKS_TOOLBAR_WIKI); - SDL_CreateMenuItem(main_bookmarks, "SDL Discord", SDL_MENU_BUTTON, MENU_BAR_BOOKMARKS_TOOLBAR_DISCORD); + SDL_CreateMenuItemAt(main_bookmarks, 0, "SDL Wiki", SDL_MENU_BUTTON, MENU_BAR_BOOKMARKS_TOOLBAR_WIKI); SDL_MenuItem *other_bookmarks = SDL_CreateMenuItem(main_bookmarks, "Other Bookmarks", SDL_MENU, MENU_BAR_LAST); SDL_CreateMenuItem(other_bookmarks, "Stack Overflow", SDL_MENU_BUTTON, MENU_BAR_BOOKMARKS_OTHER_BOOKMARKS_STACKOVERFLOW); + + SDL_DestroyMenuItem(discord); + SDL_DisableMenuItem(other_bookmarks); } { // We can't create a top level checkable . - SDL_MenuItem* checkable = SDL_CreateMenuItem(bar, "Incognito", SDL_MENU_CHECKABLE, MENU_BAR_INCOGNITO); - SDL_assert(!checkable); - - disabled = SDL_CreateMenuItem(bar, "Disabled Top-Level Button", SDL_MENU_BUTTON, MENU_BAR_TOP_LEVEL_BUTTON); - SDL_DisableMenuItem(disabled); - + SDL_assert(!SDL_CreateMenuItem(bar, "Incognito", SDL_MENU_CHECKABLE, MENU_BAR_INCOGNITO)); + SDL_CreateMenuItem(bar, "Exit", SDL_MENU_BUTTON, MENU_BAR_EXIT); } @@ -131,11 +130,19 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) { SDL_MenuItemChecked(checkable, &is_checked); if (is_checked) { SDL_UncheckMenuItem(checkable); - SDL_EnableMenuItem(disabled); } else { SDL_CheckMenuItem(checkable); - SDL_DisableMenuItem(disabled); + } + + bool is_enabled = false; + SDL_MenuItemEnabled(new_window, &is_enabled); + + if (is_enabled) { + SDL_DisableMenuItem(new_window); + } + else { + SDL_EnableMenuItem(new_window); } break; } diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c index 2b2fed42e4e06..051660bf5d119 100644 --- a/src/video/SDL_video.c +++ b/src/video/SDL_video.c @@ -6277,11 +6277,6 @@ bool SDL_MenuItemChecked(SDL_MenuItem* menu_item, bool* checked) bool SDL_MenuItemEnabled(SDL_MenuItem *menu_item, bool *enabled) { CHECK_MENUITEM_MAGIC(menu_item, false); - if (menu_item->common.type != SDL_MENU_CHECKABLE) { - SDL_SetError("menu_item isn't a checkable."); - return false; - } - return _this->MenuItemEnabled(menu_item, enabled); } @@ -6300,10 +6295,10 @@ bool SDL_EnableMenuItem(SDL_MenuItem* menu_item) bool SDL_DisableMenuItem(SDL_MenuItem *menu_item) { CHECK_MENUITEM_MAGIC(menu_item, false); - if (menu_item->common.type == SDL_MENU || menu_item->common.type == SDL_MENUBAR) { - SDL_SetError("menu_item can't be a menu."); - return false; - } +// if (menu_item->common.type == SDL_MENU || menu_item->common.type == SDL_MENUBAR) { +// SDL_SetError("menu_item can't be a menu."); +// return false; +// } return _this->DisableMenuItem(menu_item); } diff --git a/src/video/cocoa/SDL_cocoavideo.m b/src/video/cocoa/SDL_cocoavideo.m index dc5630cf2830e..55d9886038e1c 100644 --- a/src/video/cocoa/SDL_cocoavideo.m +++ b/src/video/cocoa/SDL_cocoavideo.m @@ -380,7 +380,8 @@ bool Cocoa_CreateMenuBar(SDL_MenuBar *menu_bar) NSMenuItem *appMenuItem = [NSMenuItem new]; NSMenu *appMenu = [NSMenu new]; - //[appMenu addItemWithTitle: @"Quit" action:@selector(terminate:) keyEquivalent:@"q"]; + [appMenu setAutoenablesItems:false]; + [appMenuItem setSubmenu:appMenu]; [platform_menu->menu addItem:appMenuItem]; @@ -401,7 +402,7 @@ bool Cocoa_CreateMenuItemAt(SDL_MenuItem *menu_item, size_t index, const char *n if (menu_item->common.type == SDL_MENU) { platform_data->menu = [[NSMenu alloc] initWithTitle:name_ns]; - [platform_data->menu setAutoenablesItems:true]; + [platform_data->menu setAutoenablesItems:false]; platform_data->menu_item = [NSMenuItem new]; [platform_data->menu_item setTitle:name_ns]; [platform_data->menu_item setSubmenu: platform_data->menu]; @@ -430,6 +431,7 @@ bool Cocoa_CheckMenuItem(SDL_MenuItem *menu_item) { PlatformMenuData* platform_data = (__bridge PlatformMenuData*)menu_item->common.platform_data; [platform_data->menu_item setState:NSControlStateValueOn]; + [platform_data->menu update]; return true; } @@ -437,25 +439,30 @@ bool Cocoa_UncheckMenuItem(SDL_MenuItem *menu_item) { PlatformMenuData* platform_data = (__bridge PlatformMenuData*)menu_item->common.platform_data; [platform_data->menu_item setState:NSControlStateValueOff]; + [platform_data->menu update]; return true; } bool Cocoa_MenuItemChecked(SDL_MenuItem *menu_item, bool *checked) { PlatformMenuData* platform_data = (__bridge PlatformMenuData*)menu_item->common.platform_data; - return [platform_data->menu_item state] == NSControlStateValueOn; + NSControlStateValue state = [platform_data->menu_item state]; + *checked = state == NSControlStateValueOn; + return true; } bool Cocoa_MenuItemEnabled(SDL_MenuItem *menu_item, bool *enabled) { PlatformMenuData* platform_data = (__bridge PlatformMenuData*)menu_item->common.platform_data; - return [platform_data->menu_item isEnabled]; + *enabled = [platform_data->menu_item isEnabled]; + return true; } bool Cocoa_EnableMenuItem(SDL_MenuItem *menu_item) { PlatformMenuData* platform_data = (__bridge PlatformMenuData*)menu_item->common.platform_data; [platform_data->menu_item setEnabled:true]; + [platform_data->menu update]; return true; } @@ -463,12 +470,13 @@ bool Cocoa_DisableMenuItem(SDL_MenuItem *menu_item) { PlatformMenuData* platform_data = (__bridge PlatformMenuData*)menu_item->common.platform_data; [platform_data->menu_item setEnabled:false]; + [platform_data->menu update]; return true; } bool Cocoa_DestroyMenuItem(SDL_MenuItem *menu_item) { - PlatformMenuData* platform_data = (__bridge PlatformMenuData*)menu_item->common.platform_data; + PlatformMenuData* platform_data = CFBridgingRelease(menu_item->common.platform_data); PlatformMenuData* parent_platform_data = (__bridge id _Nullable)(menu_item->common.parent->common.platform_data); [parent_platform_data->menu removeItem:platform_data->menu_item]; return false; From 392c8ac4082b858aa4464b8e324345c0d68abd5b Mon Sep 17 00:00:00 2001 From: "Joshua T. Fisher" Date: Fri, 15 Aug 2025 02:56:16 -0700 Subject: [PATCH 17/35] Fix the windows backend --- examples/video/01-menubar/menubar.c | 2 +- src/video/SDL_video.c | 29 ++++++++++++++++------------ src/video/windows/SDL_windowsvideo.c | 4 ++-- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/examples/video/01-menubar/menubar.c b/examples/video/01-menubar/menubar.c index e394ee1eac806..c3e7d8c60277e 100644 --- a/examples/video/01-menubar/menubar.c +++ b/examples/video/01-menubar/menubar.c @@ -43,7 +43,7 @@ void CreateMenuBar() { SDL_MenuItem* menu = SDL_CreateMenuItem(bar, "File", SDL_MENU, MENU_BAR_LAST); new_window = SDL_CreateMenuItem(menu, "New Window", SDL_MENU_BUTTON, MENU_BAR_FILE_NEW_WINDOW); - checkable = SDL_CreateMenuItem(menu, "Autosave Tabs on Close", SDL_MENU_CHECKABLE, MENU_BAR_FILE_DISABLE_NEW_WINDOW); + checkable = SDL_CreateMenuItem(menu, "Enable New Window", SDL_MENU_CHECKABLE, MENU_BAR_FILE_DISABLE_NEW_WINDOW); SDL_CheckMenuItem(checkable); } diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c index 051660bf5d119..fd7904ce2b6ef 100644 --- a/src/video/SDL_video.c +++ b/src/video/SDL_video.c @@ -6174,21 +6174,26 @@ SDL_MenuItem *SDL_CreateMenuItemAt(SDL_MenuItem *menu_bar_as_item, size_t index, // Get the last item in the list and insert our new item. if (menu->child_list) { - SDL_MenuItem *current = menu->child_list; + if (index == 0) { + menu_item->common.next = menu->child_list; + menu->child_list->common.prev = menu_item; + menu->child_list = menu_item; + } else { + SDL_MenuItem *current = menu->child_list; + for (size_t i = 1; (i < index) && current; ++i) { + current = current->common.next; + } - for (size_t i = 1; (i < index) && current; ++i) { - current = current->common.next; - } + SDL_assert(current); - SDL_assert(current); + if (current->common.next) { + current->common.next->common.prev = menu_item; + menu_item->common.next = current->common.next; + } - if (current->common.next) { - current->common.next->common.prev = menu_item; - menu_item->common.next = current->common.next; + current->common.next = menu_item; + menu_item->common.prev = current; } - - current->common.next = menu_item; - menu_item->common.prev = current; } else { menu->child_list = menu_item; } @@ -6322,7 +6327,7 @@ bool SDL_DestroyMenuItem(SDL_MenuItem *menu_item) if (menu_item->common.prev) { menu_item->common.prev->common.next = menu_item->common.next; - menu_item->common.next->common.next = menu_item->common.prev; + menu_item->common.next->common.prev = menu_item->common.prev; if (menu_item == menu_item->common.parent->menu_common.child_list) { menu_item->common.parent->menu_common.child_list = menu_item->common.next; diff --git a/src/video/windows/SDL_windowsvideo.c b/src/video/windows/SDL_windowsvideo.c index 25fc71a2bb8b7..39268580b052a 100644 --- a/src/video/windows/SDL_windowsvideo.c +++ b/src/video/windows/SDL_windowsvideo.c @@ -1030,7 +1030,7 @@ static bool Win32_MenuItemEnabled(SDL_MenuItem *menu_item, bool *enabled) return WIN_SetError("Unable to get menu_item check state."); } - *enabled = !(flags & MF_DISABLED); + *enabled = !(flags & MF_GRAYED); return true; } @@ -1069,7 +1069,7 @@ static bool Win32_DestroyMenuItem(SDL_MenuItem *menu_item) return WIN_SetError("Unable to remove menu item."); } } else { - DeleteMenu((HMENU)platform_data->self_handle, i, MF_BYPOSITION); + DeleteMenu((HMENU)platform_data->owner_handle, i, MF_BYPOSITION); } SDL_free(menu_item->common.platform_data); From f10a24e873d643e5afa2c3d4babf6e8810cc18d8 Mon Sep 17 00:00:00 2001 From: "Joshua T. Fisher" Date: Fri, 15 Aug 2025 03:03:46 -0700 Subject: [PATCH 18/35] Fix symbol issues --- src/dynapi/SDL_dynapi.sym | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index c50b1ce7daf31..b284144aa327d 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -1280,6 +1280,17 @@ SDL3_0.0.0 { SDL_EnableMenuItem SDL_DisableMenuItem SDL_DestroyMenuItem + SDL_CreateMenuBar; + SDL_CreateMenuItemAt; + SDL_CreateMenuItem; + SDL_ChildItems; + SDL_CheckMenuItem; + SDL_UncheckMenuItem; + SDL_MenuItemChecked; + SDL_MenuItemEnabled; + SDL_EnableMenuItem; + SDL_DisableMenuItem; + SDL_DestroyMenuItem; # extra symbols go here (don't modify this line) local: *; }; From a8b91746127b663fc1f64a07bf8ad1a7644d419d Mon Sep 17 00:00:00 2001 From: "Joshua T. Fisher" Date: Fri, 15 Aug 2025 03:04:31 -0700 Subject: [PATCH 19/35] Delete the WithProperties stub, can be added later. --- include/SDL3/SDL_video.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/include/SDL3/SDL_video.h b/include/SDL3/SDL_video.h index 3f343181a4b97..8ef7cc5e4cb7d 100644 --- a/include/SDL3/SDL_video.h +++ b/include/SDL3/SDL_video.h @@ -3058,8 +3058,7 @@ extern SDL_DECLSPEC SDL_MenuItem *SDL_CreateMenuItemAt(SDL_MenuItem *menu_bar_as * event_type will be ignored if type == SDL_MENU * On MacOS, buttoms created under a menubar will go into the "App" submenu */ - extern SDL_DECLSPEC SDL_MenuItem *SDL_CreateMenuItem(SDL_MenuItem *menu_bar_as_item, const char *name, SDL_MenuItemType type, Uint16 event_type); -extern SDL_DECLSPEC SDL_MenuItem *SDL_CreateMenuItemWithProperties(SDL_MenuItem *menu_bar_as_item, SDL_PropertiesID props); +extern SDL_DECLSPEC SDL_MenuItem *SDL_CreateMenuItem(SDL_MenuItem *menu_bar_as_item, const char *name, SDL_MenuItemType type, Uint16 event_type); /** * -1 on error From 9682a7a35b3e066c058976e471f5cbdbf6ec668d Mon Sep 17 00:00:00 2001 From: "Joshua T. Fisher" Date: Fri, 15 Aug 2025 04:02:25 -0700 Subject: [PATCH 20/35] Fix one of the docs, although, I think I'm only sending button clicked.. --- include/SDL3/SDL_events.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/SDL3/SDL_events.h b/include/SDL3/SDL_events.h index eb6aa05dbbbe2..45aaeeaac5a8a 100644 --- a/include/SDL3/SDL_events.h +++ b/include/SDL3/SDL_events.h @@ -987,7 +987,7 @@ typedef struct SDL_UserEvent typedef struct SDL_MenuEvent { - Uint32 type; /**< SDL_EVENT_KEY_DOWN or SDL_EVENT_KEY_UP */ + Uint32 type; /**< SDL_EVENT_MENU_BUTTON_CLICKED or SDL_EVENT_MENU_CHECKABLE_CLICKED */ Uint32 reserved; Uint64 timestamp; /**< In nanoseconds, populated using SDL_GetTicksNS() */ Uint16 user_event_type; From 49b0823bd306f1aba8e1e0d00136ba0412de947f Mon Sep 17 00:00:00 2001 From: "Joshua T. Fisher" Date: Fri, 15 Aug 2025 04:10:58 -0700 Subject: [PATCH 21/35] Try to fix Windows CI --- src/video/windows/SDL_windowsvideo.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/video/windows/SDL_windowsvideo.c b/src/video/windows/SDL_windowsvideo.c index 39268580b052a..682959cfef852 100644 --- a/src/video/windows/SDL_windowsvideo.c +++ b/src/video/windows/SDL_windowsvideo.c @@ -925,9 +925,9 @@ bool SDLCALL Win32_CreateMenuBar(SDL_MenuBar *menu_bar) return true; } -static bool Win32_CreateMenuItemAt(SDL_MenuItem *menu_item, size_t index, const char *name, Uint16 event_type) +bool Win32_CreateMenuItemAt(SDL_MenuItem *menu_item, size_t index, const char *name, Uint16 event_type) { - SDL_Menu_CommonData *parent = menu_item->common.parent; + SDL_Menu_CommonData *parent = (SDL_Menu_CommonData *)menu_item->common.parent; PlatformMenuData *menu_platform_data = (PlatformMenuData *)menu_item->common.parent->common.platform_data; PlatformMenuData *platform_data = CreatePlatformMenuData((HMENU)menu_platform_data->self_handle, menu_item->common.type); menu_item->common.platform_data = (void *)platform_data; @@ -980,7 +980,7 @@ static bool Win32_CreateMenuItemAt(SDL_MenuItem *menu_item, size_t index, const return menu_item; } -static bool Win32_CheckMenuItem(SDL_MenuItem *menu_item) +bool Win32_CheckMenuItem(SDL_MenuItem *menu_item) { PlatformMenuData *platform_data = (PlatformMenuData *)menu_item->common.platform_data; Uint32 i = SDL_GetIndexInMenu(menu_item); @@ -992,7 +992,7 @@ static bool Win32_CheckMenuItem(SDL_MenuItem *menu_item) return true; } -static bool Win32_UncheckMenuItem(SDL_MenuItem *menu_item) +bool Win32_UncheckMenuItem(SDL_MenuItem *menu_item) { PlatformMenuData *platform_data = (PlatformMenuData *)menu_item->common.platform_data; Uint32 i = SDL_GetIndexInMenu(menu_item); @@ -1004,7 +1004,7 @@ static bool Win32_UncheckMenuItem(SDL_MenuItem *menu_item) return true; } -static bool Win32_MenuItemChecked(SDL_MenuItem *menu_item, bool *checked) +bool Win32_MenuItemChecked(SDL_MenuItem *menu_item, bool *checked) { PlatformMenuData *platform_data = (PlatformMenuData *)menu_item->common.platform_data; Uint32 i = SDL_GetIndexInMenu(menu_item); @@ -1019,7 +1019,7 @@ static bool Win32_MenuItemChecked(SDL_MenuItem *menu_item, bool *checked) return true; } -static bool Win32_MenuItemEnabled(SDL_MenuItem *menu_item, bool *enabled) +bool Win32_MenuItemEnabled(SDL_MenuItem *menu_item, bool *enabled) { PlatformMenuData *platform_data = (PlatformMenuData *)menu_item->common.platform_data; Uint32 i = SDL_GetIndexInMenu(menu_item); @@ -1035,7 +1035,7 @@ static bool Win32_MenuItemEnabled(SDL_MenuItem *menu_item, bool *enabled) return true; } -static bool Win32_EnableMenuItem(SDL_MenuItem *menu_item) +bool Win32_EnableMenuItem(SDL_MenuItem *menu_item) { PlatformMenuData *platform_data = (PlatformMenuData *)menu_item->common.platform_data; Uint32 i = SDL_GetIndexInMenu(menu_item); @@ -1047,7 +1047,7 @@ static bool Win32_EnableMenuItem(SDL_MenuItem *menu_item) return true; } -static bool Win32_DisableMenuItem(SDL_MenuItem *menu_item) +bool Win32_DisableMenuItem(SDL_MenuItem *menu_item) { PlatformMenuData *platform_data = (PlatformMenuData *)menu_item->common.platform_data; Uint32 i = SDL_GetIndexInMenu(menu_item); @@ -1059,7 +1059,7 @@ static bool Win32_DisableMenuItem(SDL_MenuItem *menu_item) return true; } -static bool Win32_DestroyMenuItem(SDL_MenuItem *menu_item) +bool Win32_DestroyMenuItem(SDL_MenuItem *menu_item) { PlatformMenuData *platform_data = (PlatformMenuData *)menu_item->common.platform_data; Uint32 i = SDL_GetIndexInMenu(menu_item); From 4447d647c686f2998202e30314b0e0f380393212 Mon Sep 17 00:00:00 2001 From: "Joshua T. Fisher" Date: Fri, 15 Aug 2025 04:24:57 -0700 Subject: [PATCH 22/35] Try to fix a couple warnings I missed, I feel like there's a cmake flag I need to pass... --- src/video/windows/SDL_windowsvideo.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/video/windows/SDL_windowsvideo.c b/src/video/windows/SDL_windowsvideo.c index 682959cfef852..8f8bcd4a63ae2 100644 --- a/src/video/windows/SDL_windowsvideo.c +++ b/src/video/windows/SDL_windowsvideo.c @@ -962,12 +962,14 @@ bool Win32_CreateMenuItemAt(SDL_MenuItem *menu_item, size_t index, const char *n platform_data->self_handle = (UINT_PTR)event_type; } - // To add items at the back, we need to set the index to -1. - if (index == parent->children) { - index = -1; + UINT win_index = (UINT)index; + + // To add items at the back, we need to set the index to -1, despite it being unsigned. + if ((Sint64)index == parent->children) { + win_index = (UINT)-1; } - if (!InsertMenuA((HMENU)menu_platform_data->self_handle, (UINT)index, flags, platform_data->self_handle, name)) { + if (!InsertMenuA((HMENU)menu_platform_data->self_handle, win_index, flags, platform_data->self_handle, name)) { return WIN_SetError("Unable to append item to Menu."); } From 0d074cea3514194ad98f17465ca6a7ce1c10db28 Mon Sep 17 00:00:00 2001 From: "Joshua T. Fisher" Date: Thu, 2 Oct 2025 17:44:28 -0700 Subject: [PATCH 23/35] Fix the macos bridging errors I think --- src/video/cocoa/SDL_cocoavideo.m | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/video/cocoa/SDL_cocoavideo.m b/src/video/cocoa/SDL_cocoavideo.m index 55d9886038e1c..b06dd56c176aa 100644 --- a/src/video/cocoa/SDL_cocoavideo.m +++ b/src/video/cocoa/SDL_cocoavideo.m @@ -386,16 +386,16 @@ bool Cocoa_CreateMenuBar(SDL_MenuBar *menu_bar) [platform_menu->menu addItem:appMenuItem]; - menu_bar->common.item_common.platform_data = CFBridgingRetain(platform_menu); - + menu_bar->common.item_common.platform_data = (void*)CFBridgingRetain(platform_menu); + return true; } bool Cocoa_CreateMenuItemAt(SDL_MenuItem *menu_item, size_t index, const char *name, Uint16 event_type) { PlatformMenuData* platform_data = [PlatformMenuData new]; + menu_item->common.platform_data = (void*)CFBridgingRetain(platform_data); platform_data->user_event_type = event_type; - menu_item->common.platform_data = CFBridgingRetain(platform_data); PlatformMenuData* parent_platform_data = (__bridge id _Nullable)(menu_item->common.parent->common.platform_data); NSString* name_ns = [NSString stringWithUTF8String:name]; @@ -477,7 +477,8 @@ bool Cocoa_DisableMenuItem(SDL_MenuItem *menu_item) bool Cocoa_DestroyMenuItem(SDL_MenuItem *menu_item) { PlatformMenuData* platform_data = CFBridgingRelease(menu_item->common.platform_data); - PlatformMenuData* parent_platform_data = (__bridge id _Nullable)(menu_item->common.parent->common.platform_data); + menu_item->common.platform_data = NULL; + PlatformMenuData* parent_platform_data = (__bridge PlatformMenuData*)(menu_item->common.parent->common.platform_data); [parent_platform_data->menu removeItem:platform_data->menu_item]; return false; } From 6e53105e2d744aa80a4f97ebe2145b48bbbe57bb Mon Sep 17 00:00:00 2001 From: "Joshua T. Fisher" Date: Sun, 5 Oct 2025 01:37:13 -0700 Subject: [PATCH 24/35] A bunch of refactoring to address the open questions. --- examples/video/01-menubar/menubar.c | 88 ++++++---- include/SDL3/SDL_video.h | 74 +++++--- src/SDL_utils_c.h | 1 + src/dynapi/SDL_dynapi.sym | 34 ++-- src/dynapi/SDL_dynapi_overrides.h | 18 +- src/dynapi/SDL_dynapi_procs.h | 23 ++- src/video/SDL_sysvideo.h | 38 ++-- src/video/SDL_video.c | 248 +++++++++++++++++++-------- src/video/cocoa/SDL_cocoavideo.m | 16 +- src/video/cocoa/SDL_cocoawindow.h | 8 +- src/video/windows/SDL_windowsvideo.c | 116 +++++++------ src/video/windows/SDL_windowsvideo.h | 13 +- 12 files changed, 421 insertions(+), 256 deletions(-) diff --git a/examples/video/01-menubar/menubar.c b/examples/video/01-menubar/menubar.c index c3e7d8c60277e..67fce763e09e6 100644 --- a/examples/video/01-menubar/menubar.c +++ b/examples/video/01-menubar/menubar.c @@ -14,6 +14,8 @@ SDL_Renderer* renderer = NULL; SDL_MenuItem* checkable = NULL; SDL_MenuItem* new_window = NULL; +SDL_MenuItem *menu_bar; + typedef enum SDL_EventType_MenuExt { MENU_BAR_FILE, @@ -35,42 +37,68 @@ typedef enum SDL_EventType_MenuExt static SDL_EventType_MenuExt EVENT_START = (SDL_EventType_MenuExt)0; +void PrintMenuItems(SDL_MenuItem *menu_item, int indent, int *total_index) +{ + if (!menu_item) { + return; + } + + const char* label = SDL_GetMenuItemLabel(menu_item); + + if (!label) { + label = "no label given"; + } + + SDL_RenderDebugText(renderer, (float)(8 * indent * 2), (float)(*total_index * 8), label); + + ++(*total_index); + + size_t item_count = SDL_GetMenuChildItems(menu_item); + + for (size_t i = 0; i < item_count; ++i) { + PrintMenuItems(SDL_GetMenuChildItem(menu_item, i), indent + 1, total_index); + } +} + void CreateMenuBar() { - SDL_MenuItem* bar = SDL_CreateMenuBar(window); + menu_bar = SDL_CreateMenuBar(window); { - SDL_MenuItem* menu = SDL_CreateMenuItem(bar, "File", SDL_MENU, MENU_BAR_LAST); - new_window = SDL_CreateMenuItem(menu, "New Window", SDL_MENU_BUTTON, MENU_BAR_FILE_NEW_WINDOW); - checkable = SDL_CreateMenuItem(menu, "Enable New Window", SDL_MENU_CHECKABLE, MENU_BAR_FILE_DISABLE_NEW_WINDOW); + SDL_MenuItem* menu = SDL_CreateMenuItem(menu_bar, "File", SDL_MENUITEM_SUBMENU, MENU_BAR_LAST); + new_window = SDL_CreateMenuItem(menu, "New Window", SDL_MENUITEM_BUTTON, MENU_BAR_FILE_NEW_WINDOW); + checkable = SDL_CreateMenuItem(menu, "Enable New Window", SDL_MENUITEM_CHECKABLE, MENU_BAR_FILE_DISABLE_NEW_WINDOW); - SDL_CheckMenuItem(checkable); + SDL_SetMenuItemChecked(checkable, true); } { - SDL_MenuItem* menu = SDL_CreateMenuItem(bar, "Bookmarks", SDL_MENU, MENU_BAR_LAST); - SDL_MenuItem* main_bookmarks = SDL_CreateMenuItem(menu, "Bookmarks Toolbar", SDL_MENU, MENU_BAR_LAST); - SDL_MenuItem* discord = SDL_CreateMenuItem(main_bookmarks, "SDL Discord", SDL_MENU_BUTTON, MENU_BAR_BOOKMARKS_TOOLBAR_DISCORD); - SDL_CreateMenuItem(main_bookmarks, "SDL GitHub", SDL_MENU_BUTTON, MENU_BAR_BOOKMARKS_TOOLBAR_GITHUB); - SDL_CreateMenuItemAt(main_bookmarks, 0, "SDL Wiki", SDL_MENU_BUTTON, MENU_BAR_BOOKMARKS_TOOLBAR_WIKI); - - SDL_MenuItem *other_bookmarks = SDL_CreateMenuItem(main_bookmarks, "Other Bookmarks", SDL_MENU, MENU_BAR_LAST); - SDL_CreateMenuItem(other_bookmarks, "Stack Overflow", SDL_MENU_BUTTON, MENU_BAR_BOOKMARKS_OTHER_BOOKMARKS_STACKOVERFLOW); + SDL_MenuItem* menu = SDL_CreateMenuItem(menu_bar, "Bookmarks", SDL_MENUITEM_SUBMENU, MENU_BAR_LAST); + SDL_MenuItem* main_bookmarks = SDL_CreateMenuItem(menu, "Bookmarks Toolbar", SDL_MENUITEM_SUBMENU, MENU_BAR_LAST); + SDL_MenuItem* discord = SDL_CreateMenuItem(main_bookmarks, "SDL Discord", SDL_MENUITEM_BUTTON, MENU_BAR_BOOKMARKS_TOOLBAR_DISCORD); + SDL_CreateMenuItem(main_bookmarks, "SDL GitHub", SDL_MENUITEM_BUTTON, MENU_BAR_BOOKMARKS_TOOLBAR_GITHUB); + SDL_CreateMenuItemAt(main_bookmarks, 0, "SDL Wiki", SDL_MENUITEM_BUTTON, MENU_BAR_BOOKMARKS_TOOLBAR_WIKI); + + SDL_MenuItem *other_bookmarks = SDL_CreateMenuItem(main_bookmarks, "Other Bookmarks", SDL_MENUITEM_SUBMENU, MENU_BAR_LAST); + SDL_MenuItem *stack_overflow = SDL_CreateMenuItem(other_bookmarks, "Stack Overflow-test", SDL_MENUITEM_BUTTON, MENU_BAR_BOOKMARKS_OTHER_BOOKMARKS_STACKOVERFLOW); + SDL_SetMenuItemLabel(stack_overflow, "Stack Overflow"); SDL_DestroyMenuItem(discord); - SDL_DisableMenuItem(other_bookmarks); + SDL_SetMenuItemChecked(other_bookmarks, false); } { // We can't create a top level checkable . - SDL_assert(!SDL_CreateMenuItem(bar, "Incognito", SDL_MENU_CHECKABLE, MENU_BAR_INCOGNITO)); + SDL_assert(!SDL_CreateMenuItem(menu_bar, "Incognito", SDL_MENUITEM_CHECKABLE, MENU_BAR_INCOGNITO)); - SDL_CreateMenuItem(bar, "Exit", SDL_MENU_BUTTON, MENU_BAR_EXIT); + SDL_CreateMenuItem(menu_bar, "Exit", SDL_MENUITEM_BUTTON, MENU_BAR_EXIT); } + SDL_SetWindowMenuBar(window, menu_bar); + EVENT_START = (SDL_EventType_MenuExt)SDL_RegisterEvents(MENU_BAR_LAST); } @@ -85,8 +113,13 @@ SDL_AppResult SDL_AppInit(void** appstate, int argc, char** argv) { SDL_AppResult SDL_AppIterate(void* appstate) { - SDL_SetRenderDrawColor(renderer, 128, 128, 128, 255); + SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); SDL_RenderClear(renderer); + + + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); + int total_index = 0; + PrintMenuItems(menu_bar, 0, &total_index); SDL_RenderPresent(renderer); return SDL_APP_CONTINUE; @@ -127,23 +160,13 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) { case MENU_BAR_FILE_DISABLE_NEW_WINDOW: { bool is_checked = false; - SDL_MenuItemChecked(checkable, &is_checked); - if (is_checked) { - SDL_UncheckMenuItem(checkable); - } - else { - SDL_CheckMenuItem(checkable); - } + SDL_GetMenuItemChecked(checkable, &is_checked); + SDL_SetMenuItemChecked(checkable, !is_checked); bool is_enabled = false; - SDL_MenuItemEnabled(new_window, &is_enabled); - - if (is_enabled) { - SDL_DisableMenuItem(new_window); - } - else { - SDL_EnableMenuItem(new_window); - } + SDL_GetMenuItemEnabled(new_window, &is_enabled); + SDL_SetMenuItemEnabled(new_window, !is_enabled); + break; } case MENU_BAR_EXIT: @@ -155,6 +178,7 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) { } } + return SDL_APP_CONTINUE; } diff --git a/include/SDL3/SDL_video.h b/include/SDL3/SDL_video.h index 8ef7cc5e4cb7d..c2e50fa0eabd5 100644 --- a/include/SDL3/SDL_video.h +++ b/include/SDL3/SDL_video.h @@ -541,6 +541,19 @@ typedef Uint32 SDL_GLContextResetNotification; #define SDL_GL_CONTEXT_RESET_LOSE_CONTEXT 0x0001 + +typedef enum SDL_MenuItemType +{ + SDL_MENUITEM_INVALID, + SDL_MENUITEM_MENUBAR, + SDL_MENUITEM_SUBMENU, + SDL_MENUITEM_BUTTON, + SDL_MENUITEM_CHECKABLE, +} SDL_MenuItemType; + +typedef union SDL_MenuItem SDL_MenuItem; + + /* Function prototypes */ /** @@ -2944,6 +2957,7 @@ extern SDL_DECLSPEC SDL_ProgressState SDLCALL SDL_GetWindowProgressState(SDL_Win */ extern SDL_DECLSPEC bool SDLCALL SDL_SetWindowProgressValue(SDL_Window *window, float value); + /** * Get the value of the progress bar for the given window’s taskbar icon. * @@ -2957,6 +2971,18 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetWindowProgressValue(SDL_Window *window, */ extern SDL_DECLSPEC float SDLCALL SDL_GetWindowProgressValue(SDL_Window *window); +/** + * Assigns a menu_bar to the given window, which will take ownership of it's destruction. NULL + * releases the menu_bar without destroying it. + */ +extern SDL_DECLSPEC SDL_MenuItem *SDL_GetWindowMenuBar(SDL_Window *window); + +/** + * Assigns a menu_bar to the given window, which will take ownership of it's destruction. NULL + * releases the menu_bar without destroying it. + */ +extern SDL_DECLSPEC bool SDL_SetWindowMenuBar(SDL_Window *window, SDL_MenuItem *menu_bar); + /** * Destroy a window. * @@ -3034,56 +3060,48 @@ extern SDL_DECLSPEC bool SDLCALL SDL_EnableScreenSaver(void); */ extern SDL_DECLSPEC bool SDLCALL SDL_DisableScreenSaver(void); -typedef enum SDL_MenuItemType -{ - SDL_MENUBAR, - SDL_MENU, - SDL_MENU_BUTTON, - SDL_MENU_CHECKABLE, -} SDL_MenuItemType; - -typedef union SDL_MenuItem SDL_MenuItem; - extern SDL_DECLSPEC SDL_MenuItem *SDL_CreateMenuBar(SDL_Window *window); +#define SDL_PROP_MENUITEM_CREATE_LABEL "SDL.menuitem.create.label" + /** - * menu_bar_as_item must be a SDL_MENUBAR or SDL_MENU - * event_type will be ignored if type == SDL_MENU + * menu_as_item must be a SDL_MENUITEM_MENUBAR or SDL_MENUITEM_SUBMENU + * event_type will be ignored if type == SDL_MENUITEM_SUBMENU * On MacOS, buttoms created under a menubar will go into the "App" submenu */ -extern SDL_DECLSPEC SDL_MenuItem *SDL_CreateMenuItemAt(SDL_MenuItem *menu_bar_as_item, size_t index, const char *name, SDL_MenuItemType type, Uint16 event_type); +extern SDL_DECLSPEC SDL_MenuItem *SDL_CreateMenuItemAt(SDL_MenuItem *menu_as_item, size_t index, const char *Label, SDL_MenuItemType type, Uint16 event_type); /** - * menu_bar_as_item must be a SDL_MENUBAR or SDL_MENU - * event_type will be ignored if type == SDL_MENU + * menu_as_item must be a SDL_MENUITEM_MENUBAR or SDL_MENUITEM_SUBMENU + * event_type will be ignored if type == SDL_MENUITEM_SUBMENU * On MacOS, buttoms created under a menubar will go into the "App" submenu */ -extern SDL_DECLSPEC SDL_MenuItem *SDL_CreateMenuItem(SDL_MenuItem *menu_bar_as_item, const char *name, SDL_MenuItemType type, Uint16 event_type); +extern SDL_DECLSPEC SDL_MenuItem *SDL_CreateMenuItem(SDL_MenuItem *menu_as_item, const char *Label, SDL_MenuItemType type, Uint16 event_type); /** * -1 on error */ - extern SDL_DECLSPEC Sint64 SDL_ChildItems(SDL_MenuItem *menu_bar_as_item); +extern SDL_DECLSPEC Sint64 SDL_GetMenuChildItems(SDL_MenuItem *menu_as_item); +extern SDL_DECLSPEC SDL_MenuItem *SDL_GetMenuChildItem(SDL_MenuItem *menu_as_item, size_t index); +extern SDL_DECLSPEC const char *SDL_GetMenuItemLabel(SDL_MenuItem *menu_item); +extern SDL_DECLSPEC bool SDL_SetMenuItemLabel(SDL_MenuItem *menu_item, const char *label); +extern SDL_DECLSPEC SDL_MenuItemType SDL_GetMenuItemType(SDL_MenuItem *menu_item); /** - * Must be a SDL_MENU_CHECKABLE + * Must be a SDL_MENUITEM_CHECKABLE */ - extern SDL_DECLSPEC bool SDL_CheckMenuItem(SDL_MenuItem *menu_item); +extern SDL_DECLSPEC bool SDL_GetMenuItemChecked(SDL_MenuItem *menu_item, bool *checked); /** - * Must be a SDL_MENU_CHECKABLE + * Must be a SDL_MENUITEM_CHECKABLE */ - extern SDL_DECLSPEC bool SDL_UncheckMenuItem(SDL_MenuItem *menu_item); +extern SDL_DECLSPEC bool SDL_SetMenuItemChecked(SDL_MenuItem *menu_item, bool checked); -/** - * Must be a SDL_MENU_CHECKABLE - */ - extern SDL_DECLSPEC bool SDL_MenuItemChecked(SDL_MenuItem *menu_item, bool *checked); -extern SDL_DECLSPEC bool SDL_MenuItemEnabled(SDL_MenuItem *menu_item, bool *enabled); -extern SDL_DECLSPEC bool SDL_EnableMenuItem(SDL_MenuItem *menu_item); -extern SDL_DECLSPEC bool SDL_DisableMenuItem(SDL_MenuItem *menu_item); +extern SDL_DECLSPEC bool SDL_GetMenuItemEnabled(SDL_MenuItem *menu_item, bool *enabled); +extern SDL_DECLSPEC bool SDL_SetMenuItemEnabled(SDL_MenuItem *menu_item, bool enabled); + extern SDL_DECLSPEC bool SDL_DestroyMenuItem(SDL_MenuItem *menu_item); /** diff --git a/src/SDL_utils_c.h b/src/SDL_utils_c.h index 93d1abe47c8cf..f23b0710645da 100644 --- a/src/SDL_utils_c.h +++ b/src/SDL_utils_c.h @@ -62,6 +62,7 @@ typedef enum SDL_OBJECT_TYPE_HIDAPI_JOYSTICK, SDL_OBJECT_TYPE_THREAD, SDL_OBJECT_TYPE_TRAY, + SDL_OBJECT_TYPE_MENUBAR, } SDL_ObjectType; diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index b284144aa327d..77b2dec63779c 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -1265,31 +1265,19 @@ SDL3_0.0.0 { SDL_SavePNG_IO; SDL_SavePNG; SDL_CreateMenuBar; - SDL_CreateMenuBarItem; - SDL_CreateMenuItem; - SDL_CheckMenuItem; - SDL_EnableMenuItem; - SDL_CreateMenuBar - SDL_CreateMenuItemAt - SDL_CreateMenuItem - SDL_ChildItems - SDL_CheckMenuItem - SDL_UncheckMenuItem - SDL_MenuItemChecked - SDL_MenuItemEnabled - SDL_EnableMenuItem - SDL_DisableMenuItem - SDL_DestroyMenuItem - SDL_CreateMenuBar; + SDL_GetWindowMenuBar; + SDL_SetWindowMenuBar; SDL_CreateMenuItemAt; SDL_CreateMenuItem; - SDL_ChildItems; - SDL_CheckMenuItem; - SDL_UncheckMenuItem; - SDL_MenuItemChecked; - SDL_MenuItemEnabled; - SDL_EnableMenuItem; - SDL_DisableMenuItem; + SDL_GetMenuChildItems; + SDL_GetMenuChildItem; + SDL_GetMenuItemLabel; + SDL_GetMenuItemType; + SDL_SetMenuItemLabel; + SDL_SetMenuItemChecked; + SDL_GetMenuItemChecked; + SDL_GetMenuItemEnabled; + SDL_SetMenuItemEnabled; SDL_DestroyMenuItem; # extra symbols go here (don't modify this line) local: *; diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index 0bb3e1d21bd08..9775139c139a1 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -1291,13 +1291,17 @@ #define SDL_SavePNG_IO SDL_SavePNG_IO_REAL #define SDL_SavePNG SDL_SavePNG_REAL #define SDL_CreateMenuBar SDL_CreateMenuBar_REAL +#define SDL_GetWindowMenuBar SDL_GetWindowMenuBar_REAL +#define SDL_SetWindowMenuBar SDL_SetWindowMenuBar_REAL #define SDL_CreateMenuItemAt SDL_CreateMenuItemAt_REAL #define SDL_CreateMenuItem SDL_CreateMenuItem_REAL -#define SDL_ChildItems SDL_ChildItems_REAL -#define SDL_CheckMenuItem SDL_CheckMenuItem_REAL -#define SDL_UncheckMenuItem SDL_UncheckMenuItem_REAL -#define SDL_MenuItemChecked SDL_MenuItemChecked_REAL -#define SDL_MenuItemEnabled SDL_MenuItemEnabled_REAL -#define SDL_EnableMenuItem SDL_EnableMenuItem_REAL -#define SDL_DisableMenuItem SDL_DisableMenuItem_REAL +#define SDL_GetMenuChildItems SDL_GetMenuChildItems_REAL +#define SDL_GetMenuChildItem SDL_GetMenuChildItem_REAL +#define SDL_GetMenuItemLabel SDL_GetMenuItemLabel_REAL +#define SDL_GetMenuItemType SDL_GetMenuItemType_REAL +#define SDL_SetMenuItemLabel SDL_SetMenuItemLabel_REAL +#define SDL_SetMenuItemChecked SDL_SetMenuItemChecked_REAL +#define SDL_GetMenuItemChecked SDL_GetMenuItemChecked_REAL +#define SDL_GetMenuItemEnabled SDL_GetMenuItemEnabled_REAL +#define SDL_SetMenuItemEnabled SDL_SetMenuItemEnabled_REAL #define SDL_DestroyMenuItem SDL_DestroyMenuItem_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index cca369a3874b7..2992181e59862 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -1298,19 +1298,18 @@ SDL_DYNAPI_PROC(SDL_Surface*,SDL_LoadPNG_IO,(SDL_IOStream *a,bool b),(a,b),retur SDL_DYNAPI_PROC(SDL_Surface*,SDL_LoadPNG,(const char *a),(a),return) SDL_DYNAPI_PROC(bool,SDL_SavePNG_IO,(SDL_Surface *a,SDL_IOStream *b,bool c),(a,b,c),return) SDL_DYNAPI_PROC(bool,SDL_SavePNG,(SDL_Surface *a,const char *b),(a,b),return) -SDL_DYNAPI_PROC(SDL_MenuBar*, SDL_CreateMenuBar,(SDL_Window*a),(a),return) -SDL_DYNAPI_PROC(SDL_MenuItem*,SDL_CreateMenuBarItem,(SDL_MenuBar*a,const char*b,SDL_MenuItemType c,Uint16 d),(a,b,c,d),return) -SDL_DYNAPI_PROC(SDL_MenuItem*,SDL_CreateMenuItem,(SDL_Menu*a,const char*b,SDL_MenuItemType c,Uint16 d),(a,b,c,d),return) -SDL_DYNAPI_PROC(bool, SDL_CheckMenuItem,(SDL_MenuItem*a, bool b),(a,b),return) -SDL_DYNAPI_PROC(bool, SDL_EnableMenuItem,(SDL_MenuItem*a, bool b),(a,b),return) SDL_DYNAPI_PROC(SDL_MenuItem*,SDL_CreateMenuBar,(SDL_Window *a),(a),return) +SDL_DYNAPI_PROC(SDL_MenuItem*,SDL_GetWindowMenuBar, (SDL_Window * a), (a),return) +SDL_DYNAPI_PROC(bool,SDL_SetWindowMenuBar,(SDL_Window *a, SDL_MenuItem *b),(a, b),return) SDL_DYNAPI_PROC(SDL_MenuItem*,SDL_CreateMenuItemAt,(SDL_MenuItem *a,size_t b,const char *c,SDL_MenuItemType d, Uint16 e), (a,b,c,d,e), return) SDL_DYNAPI_PROC(SDL_MenuItem*,SDL_CreateMenuItem,(SDL_MenuItem *a, const char *b, SDL_MenuItemType c, Uint16 d),(a,b,c,d), return) -SDL_DYNAPI_PROC(Sint64,SDL_ChildItems, (SDL_MenuItem *a),(a),return) -SDL_DYNAPI_PROC(bool,SDL_CheckMenuItem,(SDL_MenuItem *a),(a),return) -SDL_DYNAPI_PROC(bool,SDL_UncheckMenuItem,(SDL_MenuItem *a),(a),return) -SDL_DYNAPI_PROC(bool,SDL_MenuItemChecked,(SDL_MenuItem *a,bool *b),(a, b),return) -SDL_DYNAPI_PROC(bool,SDL_MenuItemEnabled,(SDL_MenuItem *a,bool *b),(a, b),return) -SDL_DYNAPI_PROC(bool,SDL_EnableMenuItem,(SDL_MenuItem *a),(a),return) -SDL_DYNAPI_PROC(bool,SDL_DisableMenuItem,(SDL_MenuItem *a),(a),return) +SDL_DYNAPI_PROC(Sint64,SDL_GetMenuChildItems, (SDL_MenuItem *a),(a),return) +SDL_DYNAPI_PROC(SDL_MenuItem*, SDL_GetMenuChildItem, (SDL_MenuItem *a, size_t b), (a,b), return) +SDL_DYNAPI_PROC(const char *, SDL_GetMenuItemLabel, (SDL_MenuItem *a), (a), return) +SDL_DYNAPI_PROC(bool, SDL_SetMenuItemLabel, (SDL_MenuItem *a,const char *b), (a,b), return) +SDL_DYNAPI_PROC(SDL_MenuItemType, SDL_GetMenuItemType, (SDL_MenuItem *a), (a), return) +SDL_DYNAPI_PROC(bool,SDL_GetMenuItemChecked,(SDL_MenuItem *a,bool *b),(a,b),return) +SDL_DYNAPI_PROC(bool,SDL_SetMenuItemChecked,(SDL_MenuItem *a,bool b),(a,b),return) +SDL_DYNAPI_PROC(bool,SDL_GetMenuItemEnabled,(SDL_MenuItem *a,bool *b),(a,b),return) +SDL_DYNAPI_PROC(bool,SDL_SetMenuItemEnabled,(SDL_MenuItem *a,bool b),(a,b),return) SDL_DYNAPI_PROC(bool,SDL_DestroyMenuItem,(SDL_MenuItem *a),(a),return) diff --git a/src/video/SDL_sysvideo.h b/src/video/SDL_sysvideo.h index 58fb767e7990e..25029776c3ef3 100644 --- a/src/video/SDL_sysvideo.h +++ b/src/video/SDL_sysvideo.h @@ -34,6 +34,9 @@ typedef struct SDL_VideoDevice SDL_VideoDevice; typedef struct SDL_VideoData SDL_VideoData; typedef struct SDL_DisplayData SDL_DisplayData; typedef struct SDL_WindowData SDL_WindowData; +typedef struct SDL_MenuBar SDL_MenuBar; +typedef struct SDL_Menu SDL_Menu; + typedef struct { @@ -138,6 +141,8 @@ struct SDL_Window // If a toplevel window, holds the current keyboard focus for grabbing popups. SDL_Window *keyboard_focus; + SDL_MenuBar *menu_bar; + SDL_Window *prev; SDL_Window *next; @@ -209,36 +214,34 @@ typedef enum SDL_FULLSCREEN_PENDING } SDL_FullscreenResult; - -typedef struct SDL_MenuBar SDL_MenuBar; -typedef struct SDL_Menu SDL_Menu; - typedef struct SDL_MenuItem_CommonData { void *platform_data; + SDL_MenuBar *menu_bar; SDL_MenuItem *parent; - SDL_Window *window; SDL_MenuItem *prev; SDL_MenuItem *next; + const char *label; SDL_MenuItemType type; } SDL_MenuItem_CommonData; typedef struct SDL_Menu_CommonData { SDL_MenuItem_CommonData item_common; - SDL_MenuItem *child_list; - Sint64 children; + SDL_MenuItem *children; + Sint64 num_children; } SDL_Menu_CommonData; typedef struct SDL_MenuBar { SDL_Menu_CommonData common; + SDL_Window *window; } SDL_MenuBar; -typedef struct SDL_Menu +typedef struct SDL_SubMenu { SDL_Menu_CommonData common; -} SDL_Menu; +} SDL_SubMenu; typedef struct SDL_MenuItem_Button { @@ -256,7 +259,7 @@ typedef union SDL_MenuItem SDL_MenuItem_CommonData common; SDL_Menu_CommonData menu_common; SDL_MenuBar menu_bar; - SDL_Menu menu; + SDL_SubMenu sub_menu; SDL_MenuItem_Button button; SDL_MenuItem_Checkable checkable; } SDL_MenuItem; @@ -368,13 +371,13 @@ struct SDL_VideoDevice bool (*ReconfigureWindow)(SDL_VideoDevice *_this, SDL_Window *window, SDL_WindowFlags flags); bool (*CreateMenuBar)(SDL_MenuBar *menu_bar); + bool (*SetWindowMenuBar)(SDL_MenuBar *menu_bar); bool (*CreateMenuItemAt)(SDL_MenuItem *menu_item, size_t index, const char *name, Uint16 event_type); - bool (*CheckMenuItem)(SDL_MenuItem *menu_item); - bool (*UncheckMenuItem)(SDL_MenuItem *menu_item); - bool (*MenuItemChecked)(SDL_MenuItem *menu_item, bool *checked); - bool (*MenuItemEnabled)(SDL_MenuItem *menu_item, bool *enabled); - bool (*EnableMenuItem)(SDL_MenuItem *menu_item); - bool (*DisableMenuItem)(SDL_MenuItem *menu_item); + bool (*SetMenuItemLabel)(SDL_MenuItem *menu_item, const char *label); + bool (*GetMenuItemChecked)(SDL_MenuItem *menu_item, bool *checked); + bool (*SetMenuItemChecked)(SDL_MenuItem *menu_item, bool checked); + bool (*GetMenuItemEnabled)(SDL_MenuItem *menu_item, bool *enabled); + bool (*SetMenuItemEnabled)(SDL_MenuItem *menu_item, bool enabled); bool (*DestroyMenuItem)(SDL_MenuItem *menu_item); /* * * */ @@ -484,6 +487,7 @@ struct SDL_VideoDevice bool setting_display_mode; Uint32 device_caps; SDL_SystemTheme system_theme; + int num_menu_bars; /* * * */ // Data used by the GL drivers @@ -677,4 +681,6 @@ extern bool SDL_GetTextInputMultiline(SDL_PropertiesID props); extern Uint32 SDL_GetIndexInMenu(SDL_MenuItem *menu_item); +void SDL_CleanupMenubars(); + #endif // SDL_sysvideo_h_ diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c index fd7904ce2b6ef..30c58ab3efdf3 100644 --- a/src/video/SDL_video.c +++ b/src/video/SDL_video.c @@ -4471,6 +4471,8 @@ void SDL_DestroyWindow(SDL_Window *window) SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_DESTROYED, 0, 0); + SDL_DestroyMenuItem((SDL_MenuItem*)window->menu_bar); + SDL_DestroyWindowSurface(window); SDL_Renderer *renderer = SDL_GetRenderer(window); @@ -4633,6 +4635,9 @@ void SDL_VideoQuit(void) while (_this->windows) { SDL_DestroyWindow(_this->windows); } + + SDL_CleanupMenubars(); + _this->VideoQuit(_this); for (i = _this->num_displays; i--; ) { @@ -6117,7 +6122,7 @@ void SDL_OnApplicationDidEnterForeground(void) } } -SDL_MenuItem* SDL_CreateMenuBar(SDL_Window *window) +SDL_MenuItem *SDL_CreateMenuBar(SDL_Window *window) { CHECK_WINDOW_MAGIC(window, NULL); @@ -6130,56 +6135,121 @@ SDL_MenuItem* SDL_CreateMenuBar(SDL_Window *window) } SDL_MenuBar *menu_bar = SDL_calloc(1, sizeof(SDL_MenuBar)); - menu_bar->common.item_common.window = window; - menu_bar->common.item_common.type = SDL_MENUBAR; + menu_bar->common.item_common.menu_bar = menu_bar; + menu_bar->common.item_common.type = SDL_MENUITEM_MENUBAR; if (!_this->CreateMenuBar(menu_bar)) { SDL_free(menu_bar); return NULL; } + + ++_this->num_menu_bars; + + SDL_SetObjectValid(menu_bar, SDL_OBJECT_TYPE_MENUBAR, true); return (SDL_MenuItem *)menu_bar; } -SDL_MenuItem *SDL_CreateMenuItemAt(SDL_MenuItem *menu_bar_as_item, size_t index, const char *name, SDL_MenuItemType type, Uint16 event_type) + +SDL_MenuItem *SDL_GetWindowMenuBar(SDL_Window *window) { - CHECK_MENUITEM_MAGIC(menu_bar_as_item, NULL); + CHECK_WINDOW_MAGIC(window, false); + return (SDL_MenuItem*)window->menu_bar; +} + +bool SDL_SetWindowMenuBar(SDL_Window* window, SDL_MenuItem* menu_bar) +{ + CHECK_WINDOW_MAGIC(window, false); + + if (!_this) { + return false; + } + + if (!menu_bar) { + window->menu_bar = NULL; + return true; + } + + if (menu_bar->common.type != SDL_MENUITEM_MENUBAR) { + SDL_SetError("Can't set menu Item that isn't a Menu onto a Window."); + return false; + } - if (menu_bar_as_item->common.type != SDL_MENU && menu_bar_as_item->common.type != SDL_MENUBAR) { + SDL_MenuBar *menu_bar_real = (SDL_MenuBar*)menu_bar; + menu_bar_real->window = window; + + if (window->menu_bar) { + SDL_SetObjectValid(window->menu_bar, SDL_OBJECT_TYPE_MENUBAR, true); + } + + window->menu_bar = menu_bar_real; + + SDL_SetObjectValid(menu_bar_real, SDL_OBJECT_TYPE_MENUBAR, false); + + return _this->SetWindowMenuBar(menu_bar_real); +} + +void SDL_CleanupMenubars() +{ + if (_this->num_menu_bars == 0) { + return; + } + + void **menu_bars = (void **)SDL_malloc(_this->num_menu_bars * sizeof(*menu_bars)); + if (!menu_bars) { + return; + } + + int count = SDL_GetObjects(SDL_OBJECT_TYPE_MENUBAR, menu_bars, _this->num_menu_bars); + SDL_assert(count == _this->num_menu_bars); + for (int i = 0; i < count; ++i) { + SDL_DestroyMenuItem((SDL_MenuItem *)menu_bars[i]); + } + SDL_free(menu_bars); +} + +SDL_MenuItem *SDL_CreateMenuItemAt(SDL_MenuItem *menu_as_item, size_t index, const char *label, SDL_MenuItemType type, Uint16 event_type) +{ + CHECK_MENUITEM_MAGIC(menu_as_item, NULL); + + if (menu_as_item->common.type != SDL_MENUITEM_SUBMENU && menu_as_item->common.type != SDL_MENUITEM_MENUBAR) { SDL_SetError("Can't create an item on a Menu Item that isn't a Menu or MenuBar."); return false; } - if ((menu_bar_as_item->common.type == SDL_MENUBAR) && (type == SDL_MENU_CHECKABLE)) { + if ((menu_as_item->common.type == SDL_MENUITEM_MENUBAR) && (type == SDL_MENUITEM_CHECKABLE)) { SDL_SetError("Can't create a checkable item on the Menu Bar, they must be in a menu."); return false; } - SDL_Menu_CommonData *menu = (SDL_Menu_CommonData *)menu_bar_as_item; + SDL_Menu_CommonData *menu = (SDL_Menu_CommonData *)menu_as_item; - if (menu->children < (Sint64)index) { + if (menu->num_children < (Sint64)index) { SDL_SetError("Can't create a menu item beyond the number of children."); return false; } SDL_MenuItem *menu_item = SDL_calloc(1, sizeof(SDL_MenuItem)); menu_item->common.parent = (SDL_MenuItem *)menu; - menu_item->common.window = menu->item_common.window; + menu_item->common.menu_bar = menu->item_common.menu_bar; menu_item->common.type = type; - if (!_this->CreateMenuItemAt(menu_item, index, name, event_type)) { + if (!_this->CreateMenuItemAt(menu_item, index, label, event_type)) { SDL_free(menu_item); return NULL; } + // Succeeded in creating the menu_item, we can dupe the name now. + menu_item->common.label = SDL_strdup(label); + // Get the last item in the list and insert our new item. - if (menu->child_list) { + if (menu->children) { if (index == 0) { - menu_item->common.next = menu->child_list; - menu->child_list->common.prev = menu_item; - menu->child_list = menu_item; + menu_item->common.next = menu->children; + menu->children->common.prev = menu_item; + menu->children = menu_item; } else { - SDL_MenuItem *current = menu->child_list; + SDL_MenuItem *current = menu->children; for (size_t i = 1; (i < index) && current; ++i) { current = current->common.next; } @@ -6195,15 +6265,14 @@ SDL_MenuItem *SDL_CreateMenuItemAt(SDL_MenuItem *menu_bar_as_item, size_t index, menu_item->common.prev = current; } } else { - menu->child_list = menu_item; + menu->children = menu_item; } - ++menu->children; + ++menu->num_children; return menu_item; } - Uint32 SDL_GetIndexInMenu(SDL_MenuItem *menu_item) { Uint32 i = 0; @@ -6215,128 +6284,169 @@ Uint32 SDL_GetIndexInMenu(SDL_MenuItem *menu_item) return i; } - -SDL_MenuItem* SDL_CreateMenuItemWithProperties(SDL_MenuItem* menu_bar_as_item, SDL_PropertiesID props) +SDL_MenuItem *SDL_CreateMenuItemWithProperties(SDL_MenuItem *menu_bar_as_item, SDL_PropertiesID props) { return NULL; } -SDL_MenuItem *SDL_CreateMenuItem(SDL_MenuItem *menu_bar_as_item, const char *name, SDL_MenuItemType type, Uint16 event_type) +SDL_MenuItem *SDL_CreateMenuItem(SDL_MenuItem *menu_bar_as_item, const char *label, SDL_MenuItemType type, Uint16 event_type) { CHECK_MENUITEM_MAGIC(menu_bar_as_item, NULL); - if (menu_bar_as_item->common.type != SDL_MENU && menu_bar_as_item->common.type != SDL_MENUBAR) { + if (menu_bar_as_item->common.type != SDL_MENUITEM_SUBMENU && menu_bar_as_item->common.type != SDL_MENUITEM_MENUBAR) { SDL_SetError("Can't create an item on a Menu Item that isn't a Menu or MenuBar."); return false; } SDL_Menu_CommonData *menu = (SDL_Menu_CommonData *)menu_bar_as_item; - return SDL_CreateMenuItemAt(menu_bar_as_item, menu->children, name, type, event_type); + return SDL_CreateMenuItemAt(menu_bar_as_item, menu->num_children, label, type, event_type); } +Sint64 SDL_GetMenuChildItems(SDL_MenuItem *menu_as_item) +{ + CHECK_MENUITEM_MAGIC(menu_as_item, -1); + if (menu_as_item->common.type != SDL_MENUITEM_SUBMENU && menu_as_item->common.type != SDL_MENUITEM_MENUBAR) { + SDL_SetError("Can't get child items on Menu Item that isn't a Menu or MenuBar."); + return false; + } + + SDL_Menu_CommonData *menu = (SDL_Menu_CommonData *)menu_as_item; + return menu->num_children; +} -Sint64 SDL_ChildItems(SDL_MenuItem *menu_bar_as_item) +SDL_MenuItem *SDL_GetMenuChildItem(SDL_MenuItem *menu_as_item, size_t index) { - CHECK_MENUITEM_MAGIC(menu_bar_as_item, -1); - if (menu_bar_as_item->common.type != SDL_MENU && menu_bar_as_item->common.type != SDL_MENUBAR) { - SDL_SetError("Can't create an item on a Menu Item that isn't a Menu or MenuBar."); + CHECK_MENUITEM_MAGIC(menu_as_item, NULL); + if (menu_as_item->common.type != SDL_MENUITEM_SUBMENU && menu_as_item->common.type != SDL_MENUITEM_MENUBAR) { + SDL_SetError("Can't get child items on Menu Item that isn't a Menu or MenuBar."); return false; } - SDL_Menu_CommonData *menu = (SDL_Menu_CommonData *)menu_bar_as_item; - return menu->children; + SDL_MenuItem *it = menu_as_item->menu_common.children; + size_t i = 0; + while (it != NULL && i < index) { + it = it->common.next; + ++i; + } + + if (it == NULL) { + SDL_SetError("SDL_MenuItem index out of range."); + return NULL; + } + + return (SDL_MenuItem *)it; +} + +const char *SDL_GetMenuItemLabel(SDL_MenuItem *menu_item) +{ + CHECK_MENUITEM_MAGIC(menu_item, NULL); + return menu_item->common.label; } -bool SDL_CheckMenuItem(SDL_MenuItem* menu_item) +bool SDL_SetMenuItemLabel(SDL_MenuItem *menu_item, const char *label) { CHECK_MENUITEM_MAGIC(menu_item, false); - if (menu_item->common.type != SDL_MENU_CHECKABLE) { - SDL_SetError("menu_item isn't a checkable."); - return false; + if (_this->SetMenuItemLabel(menu_item, label)) { + if (menu_item->common.label) { + SDL_free((void*)menu_item->common.label); + } + + menu_item->common.label = SDL_strdup(label); + return true; } - return _this->CheckMenuItem(menu_item); + return false; +} + +SDL_MenuItemType SDL_GetMenuItemType(SDL_MenuItem *menu_item) +{ + CHECK_MENUITEM_MAGIC(menu_item, SDL_MENUITEM_INVALID); + return menu_item->common.type; } -bool SDL_UncheckMenuItem(SDL_MenuItem* menu_item) +bool SDL_GetMenuItemChecked(SDL_MenuItem *menu_item, bool *checked) { CHECK_MENUITEM_MAGIC(menu_item, false); - if (menu_item->common.type != SDL_MENU_CHECKABLE) { + if (menu_item->common.type != SDL_MENUITEM_CHECKABLE) { SDL_SetError("menu_item isn't a checkable."); return false; } - return _this->UncheckMenuItem(menu_item); + return _this->GetMenuItemChecked(menu_item, checked); } -bool SDL_MenuItemChecked(SDL_MenuItem* menu_item, bool* checked) +bool SDL_SetMenuItemChecked(SDL_MenuItem *menu_item, bool enabled) { CHECK_MENUITEM_MAGIC(menu_item, false); - if (menu_item->common.type != SDL_MENU_CHECKABLE) { + if (menu_item->common.type != SDL_MENUITEM_CHECKABLE) { SDL_SetError("menu_item isn't a checkable."); return false; } - return _this->MenuItemChecked(menu_item, checked); + return _this->SetMenuItemChecked(menu_item, enabled); } -bool SDL_MenuItemEnabled(SDL_MenuItem *menu_item, bool *enabled) +bool SDL_GetMenuItemEnabled(SDL_MenuItem *menu_item, bool *enabled) { CHECK_MENUITEM_MAGIC(menu_item, false); - return _this->MenuItemEnabled(menu_item, enabled); + return _this->GetMenuItemEnabled(menu_item, enabled); } - -bool SDL_EnableMenuItem(SDL_MenuItem* menu_item) +bool SDL_SetMenuItemEnabled(SDL_MenuItem *menu_item, bool enabled) { CHECK_MENUITEM_MAGIC(menu_item, false); - if (menu_item->common.type == SDL_MENU || menu_item->common.type == SDL_MENUBAR) { - SDL_SetError("menu_item can't be a menu."); + if (menu_item->common.type == SDL_MENUITEM_SUBMENU || menu_item->common.type == SDL_MENUITEM_MENUBAR) { + SDL_SetError("menu_item can't be a Menu."); return false; } - return _this->EnableMenuItem(menu_item); -} - -bool SDL_DisableMenuItem(SDL_MenuItem *menu_item) -{ - CHECK_MENUITEM_MAGIC(menu_item, false); -// if (menu_item->common.type == SDL_MENU || menu_item->common.type == SDL_MENUBAR) { -// SDL_SetError("menu_item can't be a menu."); -// return false; -// } - - return _this->DisableMenuItem(menu_item); + return _this->SetMenuItemEnabled(menu_item, enabled); } bool SDL_DestroyMenuItem(SDL_MenuItem *menu_item) { - if (menu_item->common.type == SDL_MENU || menu_item->common.type == SDL_MENUBAR) { - for (SDL_MenuItem *current = ((SDL_Menu_CommonData *)menu_item)->child_list; current != NULL; current = current->common.next) { + if (menu_item->common.type == SDL_MENUITEM_SUBMENU || menu_item->common.type == SDL_MENUITEM_MENUBAR) { + if (menu_item->common.type == SDL_MENUITEM_MENUBAR) { + SDL_SetObjectValid(menu_item, SDL_OBJECT_TYPE_MENUBAR, false); + --_this->num_menu_bars; + } + + SDL_MenuItem *current = menu_item->menu_common.children; + SDL_MenuItem *next = current->common.next; + while (current) { if (!SDL_DestroyMenuItem(current)) { SDL_SetError("Failed to destroy Child Menu Item"); return false; } + + current = next; + + if (current) { + next = current->common.next; + } } } - if (!_this->DestroyMenuItem(menu_item)) - { + if (!_this->DestroyMenuItem(menu_item)) { SDL_SetError("Failed to destroy Menu Item"); return false; } if (menu_item->common.prev) { menu_item->common.prev->common.next = menu_item->common.next; - menu_item->common.next->common.prev = menu_item->common.prev; + } - if (menu_item == menu_item->common.parent->menu_common.child_list) { - menu_item->common.parent->menu_common.child_list = menu_item->common.next; - } + if (menu_item->common.next) { + menu_item->common.next->common.prev = menu_item->common.prev; + } - menu_item->common.prev = NULL; - menu_item->common.next = NULL; + if (menu_item->common.parent && (menu_item->common.parent->menu_common.children == menu_item)) { + menu_item->common.parent->menu_common.children = menu_item->common.next; + --menu_item->common.parent->menu_common.num_children; } + menu_item->common.prev = NULL; + menu_item->common.next = NULL; + SDL_free(menu_item); return true; } diff --git a/src/video/cocoa/SDL_cocoavideo.m b/src/video/cocoa/SDL_cocoavideo.m index b06dd56c176aa..e4ccee2b39718 100644 --- a/src/video/cocoa/SDL_cocoavideo.m +++ b/src/video/cocoa/SDL_cocoavideo.m @@ -128,10 +128,10 @@ static void Cocoa_DeleteDevice(SDL_VideoDevice *device) device->SyncWindow = Cocoa_SyncWindow; device->CreateMenuBar = Cocoa_CreateMenuBar; device->CreateMenuItemAt = Cocoa_CreateMenuItemAt; - device->CheckMenuItem = Cocoa_CheckMenuItem; - device->UncheckMenuItem = Cocoa_UncheckMenuItem; - device->MenuItemChecked = Cocoa_MenuItemChecked; - device->MenuItemEnabled = Cocoa_MenuItemEnabled; + device->SetMenuItemChecked = Cocoa_SetMenuItemChecked; + device->UnSetMenuItemChecked = Cocoa_UnSetMenuItemChecked; + device->GetMenuItemChecked = Cocoa_GetMenuItemChecked; + device->GetMenuItemEnabled = Cocoa_GetMenuItemEnabled; device->EnableMenuItem = Cocoa_EnableMenuItem; device->DisableMenuItem = Cocoa_DisableMenuItem; device->DestroyMenuItem = Cocoa_DestroyMenuItem; @@ -427,7 +427,7 @@ bool Cocoa_CreateMenuItemAt(SDL_MenuItem *menu_item, size_t index, const char *n } -bool Cocoa_CheckMenuItem(SDL_MenuItem *menu_item) +bool Cocoa_SetMenuItemChecked(SDL_MenuItem *menu_item) { PlatformMenuData* platform_data = (__bridge PlatformMenuData*)menu_item->common.platform_data; [platform_data->menu_item setState:NSControlStateValueOn]; @@ -435,7 +435,7 @@ bool Cocoa_CheckMenuItem(SDL_MenuItem *menu_item) return true; } -bool Cocoa_UncheckMenuItem(SDL_MenuItem *menu_item) +bool Cocoa_UnSetMenuItemChecked(SDL_MenuItem *menu_item) { PlatformMenuData* platform_data = (__bridge PlatformMenuData*)menu_item->common.platform_data; [platform_data->menu_item setState:NSControlStateValueOff]; @@ -443,7 +443,7 @@ bool Cocoa_UncheckMenuItem(SDL_MenuItem *menu_item) return true; } -bool Cocoa_MenuItemChecked(SDL_MenuItem *menu_item, bool *checked) +bool Cocoa_GetMenuItemChecked(SDL_MenuItem *menu_item, bool *checked) { PlatformMenuData* platform_data = (__bridge PlatformMenuData*)menu_item->common.platform_data; NSControlStateValue state = [platform_data->menu_item state]; @@ -451,7 +451,7 @@ bool Cocoa_MenuItemChecked(SDL_MenuItem *menu_item, bool *checked) return true; } -bool Cocoa_MenuItemEnabled(SDL_MenuItem *menu_item, bool *enabled) +bool Cocoa_GetMenuItemEnabled(SDL_MenuItem *menu_item, bool *enabled) { PlatformMenuData* platform_data = (__bridge PlatformMenuData*)menu_item->common.platform_data; *enabled = [platform_data->menu_item isEnabled]; diff --git a/src/video/cocoa/SDL_cocoawindow.h b/src/video/cocoa/SDL_cocoawindow.h index 27eb660ac1ae5..069bd4f119a19 100644 --- a/src/video/cocoa/SDL_cocoawindow.h +++ b/src/video/cocoa/SDL_cocoawindow.h @@ -198,10 +198,10 @@ extern void Cocoa_MenuVisibilityCallback(void *userdata, const char *name, const extern bool Cocoa_CreateMenuBar(SDL_MenuBar *menu_bar); extern bool Cocoa_CreateMenuItemAt(SDL_MenuItem *menu_item, size_t index, const char *name, Uint16 event_type); -extern bool Cocoa_CheckMenuItem(SDL_MenuItem *menu_item); -extern bool Cocoa_UncheckMenuItem(SDL_MenuItem *menu_item); -extern bool Cocoa_MenuItemChecked(SDL_MenuItem *menu_item, bool *checked); -extern bool Cocoa_MenuItemEnabled(SDL_MenuItem *menu_item, bool *enabled); +extern bool Cocoa_SetMenuItemChecked(SDL_MenuItem *menu_item); +extern bool Cocoa_UnSetMenuItemChecked(SDL_MenuItem *menu_item); +extern bool Cocoa_GetMenuItemChecked(SDL_MenuItem *menu_item, bool *checked); +extern bool Cocoa_GetMenuItemEnabled(SDL_MenuItem *menu_item, bool *enabled); extern bool Cocoa_EnableMenuItem(SDL_MenuItem *menu_item); extern bool Cocoa_DisableMenuItem(SDL_MenuItem *menu_item); extern bool Cocoa_DestroyMenuItem(SDL_MenuItem *menu_item); diff --git a/src/video/windows/SDL_windowsvideo.c b/src/video/windows/SDL_windowsvideo.c index 8f8bcd4a63ae2..d3c2d7546c73f 100644 --- a/src/video/windows/SDL_windowsvideo.c +++ b/src/video/windows/SDL_windowsvideo.c @@ -361,13 +361,13 @@ static SDL_VideoDevice *WIN_CreateDevice(void) device->UpdateWindowShape = WIN_UpdateWindowShape; device->CreateMenuBar = Win32_CreateMenuBar; + device->SetWindowMenuBar = Win32_SetWindowMenuBar; device->CreateMenuItemAt = Win32_CreateMenuItemAt; - device->CheckMenuItem = Win32_CheckMenuItem; - device->UncheckMenuItem = Win32_UncheckMenuItem; - device->MenuItemChecked = Win32_MenuItemChecked; - device->MenuItemEnabled = Win32_MenuItemEnabled; - device->EnableMenuItem = Win32_EnableMenuItem; - device->DisableMenuItem = Win32_DisableMenuItem; + device->SetMenuItemLabel = Win32_SetMenuItemLabel; + device->GetMenuItemChecked = Win32_GetMenuItemChecked; + device->SetMenuItemChecked = Win32_SetMenuItemChecked; + device->GetMenuItemEnabled = Win32_GetMenuItemEnabled; + device->SetMenuItemEnabled = Win32_SetMenuItemEnabled; device->DestroyMenuItem = Win32_DestroyMenuItem; #endif @@ -901,9 +901,7 @@ static PlatformMenuData *CreatePlatformMenuData(HMENU owner_handle, UINT_PTR sel return platform; } -// PlatformMenuData platform_data = - -bool SDLCALL Win32_CreateMenuBar(SDL_MenuBar *menu_bar) +bool Win32_CreateMenuBar(SDL_MenuBar *menu_bar) { HMENU menu_handle = CreateMenu(); @@ -913,11 +911,8 @@ bool SDLCALL Win32_CreateMenuBar(SDL_MenuBar *menu_bar) } menu_bar->common.item_common.platform_data = (void*)CreatePlatformMenuData(NULL, (UINT_PTR)menu_handle); - const SDL_WindowData *data = menu_bar->common.item_common.window->internal; - if (!SetMenu(data->hwnd, menu_handle)) { - WIN_SetError("Unable to set MenuBar"); - SDL_free(menu_bar->common.item_common.platform_data); + if (!menu_bar->common.item_common.platform_data) { DestroyMenu(menu_handle); return false; } @@ -925,7 +920,20 @@ bool SDLCALL Win32_CreateMenuBar(SDL_MenuBar *menu_bar) return true; } -bool Win32_CreateMenuItemAt(SDL_MenuItem *menu_item, size_t index, const char *name, Uint16 event_type) +bool Win32_SetWindowMenuBar(SDL_MenuBar *menu_bar) +{ + const PlatformMenuData *menu_platform_data = (PlatformMenuData *)menu_bar->common.item_common.platform_data; + const SDL_WindowData *data = menu_bar->common.item_common.menu_bar->window->internal; + + if (!SetMenu(data->hwnd, (HMENU)menu_platform_data->self_handle)) { + WIN_SetError("Unable to set MenuBar"); + return false; + } + + return true; +} + +bool Win32_CreateMenuItemAt(SDL_MenuItem *menu_item, size_t index, const char *label, Uint16 event_type) { SDL_Menu_CommonData *parent = (SDL_Menu_CommonData *)menu_item->common.parent; PlatformMenuData *menu_platform_data = (PlatformMenuData *)menu_item->common.parent->common.platform_data; @@ -933,13 +941,13 @@ bool Win32_CreateMenuItemAt(SDL_MenuItem *menu_item, size_t index, const char *n menu_item->common.platform_data = (void *)platform_data; platform_data->user_event_type = event_type; UINT flags = 0; - bool top_level_menu = menu_item->common.parent->common.type == SDL_MENUBAR; + bool top_level_menu = menu_item->common.parent->common.type == SDL_MENUITEM_MENUBAR; if (!top_level_menu) { flags = MF_STRING; } - if (menu_item->common.type == SDL_MENU) { + if (menu_item->common.type == SDL_MENUITEM_SUBMENU) { if (top_level_menu) { platform_data->self_handle = (UINT_PTR)CreateMenu(); } else { @@ -965,63 +973,79 @@ bool Win32_CreateMenuItemAt(SDL_MenuItem *menu_item, size_t index, const char *n UINT win_index = (UINT)index; // To add items at the back, we need to set the index to -1, despite it being unsigned. - if ((Sint64)index == parent->children) { + if ((Sint64)index == parent->num_children) { win_index = (UINT)-1; } - if (!InsertMenuA((HMENU)menu_platform_data->self_handle, win_index, flags, platform_data->self_handle, name)) { + wchar_t* label_w = WIN_UTF8ToStringW(label); + + if (!InsertMenuW((HMENU)menu_platform_data->self_handle, win_index, flags, platform_data->self_handle, label_w)) { + SDL_free(label_w); return WIN_SetError("Unable to append item to Menu."); } - const SDL_WindowData *data = menu_item->common.window->internal; - - if (!DrawMenuBar(data->hwnd)) { - return WIN_SetError("Unable to draw menu bar"); + SDL_free(label_w); + + if (menu_item->common.menu_bar->window) { + const SDL_WindowData *data = menu_item->common.menu_bar->window->internal; + + if (!DrawMenuBar(data->hwnd)) { + return WIN_SetError("Unable to draw menu bar"); + } } return menu_item; } -bool Win32_CheckMenuItem(SDL_MenuItem *menu_item) +bool Win32_SetMenuItemLabel(SDL_MenuItem* menu_item, const char* label) { PlatformMenuData *platform_data = (PlatformMenuData *)menu_item->common.platform_data; Uint32 i = SDL_GetIndexInMenu(menu_item); - if (!CheckMenuItem(platform_data->owner_handle, i, MF_BYPOSITION | MF_CHECKED)) { - return WIN_SetError("Unable to check menu item."); - } - - return true; + + wchar_t *label_w = WIN_UTF8ToStringW(label); + + MENUITEMINFOW info; + SDL_zero(info); + info.cbSize = sizeof(MENUITEMINFOW); + info.fMask = MIIM_STRING; + info.dwTypeData = label_w; + bool success = SetMenuItemInfoW(platform_data->owner_handle, i, true, &info); + + SDL_free(label_w); + return success; } -bool Win32_UncheckMenuItem(SDL_MenuItem *menu_item) +bool Win32_GetMenuItemChecked(SDL_MenuItem *menu_item, bool *checked) { PlatformMenuData *platform_data = (PlatformMenuData *)menu_item->common.platform_data; Uint32 i = SDL_GetIndexInMenu(menu_item); - if (!CheckMenuItem(platform_data->owner_handle, i, MF_BYPOSITION | MF_UNCHECKED)) { - return WIN_SetError("Unable to check menu item."); + UINT flags = GetMenuState(platform_data->owner_handle, i, MF_BYPOSITION); + + if (flags == -1) { + return WIN_SetError("Unable to get menu_item check state."); } + *checked = flags &MF_CHECKED; return true; } -bool Win32_MenuItemChecked(SDL_MenuItem *menu_item, bool *checked) +bool Win32_SetMenuItemChecked(SDL_MenuItem *menu_item, bool checked) { PlatformMenuData *platform_data = (PlatformMenuData *)menu_item->common.platform_data; Uint32 i = SDL_GetIndexInMenu(menu_item); - UINT flags = GetMenuState(platform_data->owner_handle, i, MF_BYPOSITION); + UINT32 flag = checked ? MF_CHECKED : MF_UNCHECKED; - if (flags == -1) { - return WIN_SetError("Unable to get menu_item check state."); + if (!CheckMenuItem(platform_data->owner_handle, i, MF_BYPOSITION | flag)) { + return WIN_SetError("Unable to check menu item."); } - *checked = flags &MF_CHECKED; return true; } -bool Win32_MenuItemEnabled(SDL_MenuItem *menu_item, bool *enabled) +bool Win32_GetMenuItemEnabled(SDL_MenuItem *menu_item, bool *enabled) { PlatformMenuData *platform_data = (PlatformMenuData *)menu_item->common.platform_data; Uint32 i = SDL_GetIndexInMenu(menu_item); @@ -1037,24 +1061,14 @@ bool Win32_MenuItemEnabled(SDL_MenuItem *menu_item, bool *enabled) return true; } -bool Win32_EnableMenuItem(SDL_MenuItem *menu_item) +bool Win32_SetMenuItemEnabled(SDL_MenuItem *menu_item, bool enabled) { PlatformMenuData *platform_data = (PlatformMenuData *)menu_item->common.platform_data; Uint32 i = SDL_GetIndexInMenu(menu_item); - if (!EnableMenuItem(platform_data->owner_handle, i, MF_BYPOSITION | MF_ENABLED)) { - return WIN_SetError("Unable to enable menu item."); - } - - return true; -} - -bool Win32_DisableMenuItem(SDL_MenuItem *menu_item) -{ - PlatformMenuData *platform_data = (PlatformMenuData *)menu_item->common.platform_data; - Uint32 i = SDL_GetIndexInMenu(menu_item); + UINT32 flag = enabled ? MF_ENABLED : MF_GRAYED; - if (!EnableMenuItem(platform_data->owner_handle, i, MF_BYPOSITION | MF_GRAYED)) { + if (!EnableMenuItem(platform_data->owner_handle, i, MF_BYPOSITION | flag)) { return WIN_SetError("Unable to enable menu item."); } @@ -1066,7 +1080,7 @@ bool Win32_DestroyMenuItem(SDL_MenuItem *menu_item) PlatformMenuData *platform_data = (PlatformMenuData *)menu_item->common.platform_data; Uint32 i = SDL_GetIndexInMenu(menu_item); - if (menu_item->common.type == SDL_MENUBAR) { + if (menu_item->common.type == SDL_MENUITEM_MENUBAR) { if (!DestroyMenu((HMENU)platform_data->self_handle)) { return WIN_SetError("Unable to remove menu item."); } diff --git a/src/video/windows/SDL_windowsvideo.h b/src/video/windows/SDL_windowsvideo.h index ba84487634014..294f890371421 100644 --- a/src/video/windows/SDL_windowsvideo.h +++ b/src/video/windows/SDL_windowsvideo.h @@ -673,13 +673,14 @@ typedef struct PlatformMenuData } PlatformMenuData; extern bool Win32_CreateMenuBar(SDL_MenuBar *menu_bar); +extern bool Win32_SetWindowMenuBar(SDL_MenuBar *menu_bar); extern bool Win32_CreateMenuItemAt(SDL_MenuItem *menu_item, size_t index, const char *name, Uint16 event_type); -extern bool Win32_CheckMenuItem(SDL_MenuItem *menu_item); -extern bool Win32_UncheckMenuItem(SDL_MenuItem *menu_item); -extern bool Win32_MenuItemChecked(SDL_MenuItem *menu_item, bool *checked); -extern bool Win32_MenuItemEnabled(SDL_MenuItem *menu_item, bool *enabled); -extern bool Win32_EnableMenuItem(SDL_MenuItem *menu_item); -extern bool Win32_DisableMenuItem(SDL_MenuItem *menu_item); + +extern bool Win32_SetMenuItemLabel(SDL_MenuItem *menu_item, const char *label); +extern bool Win32_GetMenuItemChecked(SDL_MenuItem *menu_item, bool *checked); +extern bool Win32_SetMenuItemChecked(SDL_MenuItem *menu_item, bool checked); +extern bool Win32_GetMenuItemEnabled(SDL_MenuItem *menu_item, bool *enabled); +extern bool Win32_SetMenuItemEnabled(SDL_MenuItem *menu_item, bool enabled); extern bool Win32_DestroyMenuItem(SDL_MenuItem *menu_item); #endif // SDL_windowsvideo_h_ From bb07a98e3f7367106f9b9247c41192a0923c6df2 Mon Sep 17 00:00:00 2001 From: "Joshua T. Fisher" Date: Sun, 5 Oct 2025 21:11:07 -0700 Subject: [PATCH 25/35] Mac compiling and seeming to work. App menu work next. --- src/video/cocoa/SDL_cocoavideo.h | 12 +++++ src/video/cocoa/SDL_cocoavideo.m | 76 +++++++++++----------------- src/video/cocoa/SDL_cocoawindow.h | 8 +-- src/video/cocoa/SDL_cocoawindow.m | 7 +++ src/video/windows/SDL_windowsvideo.h | 20 ++++---- 5 files changed, 63 insertions(+), 60 deletions(-) diff --git a/src/video/cocoa/SDL_cocoavideo.h b/src/video/cocoa/SDL_cocoavideo.h index 353fb43509d2c..c50783c2eff1b 100644 --- a/src/video/cocoa/SDL_cocoavideo.h +++ b/src/video/cocoa/SDL_cocoavideo.h @@ -64,6 +64,18 @@ typedef enum @property(nonatomic) OptionAsAlt option_as_alt; @end +@interface PlatformMenuData : NSObject { +@public + Uint16 user_event_type; + NSMenu *menu; + NSMenuItem *menu_item; +} + +- (void) Cocoa_PlatformMenuData_MenuButtonClicked: (id)sender; + +@end + + // Utility functions extern SDL_SystemTheme Cocoa_GetSystemTheme(void); extern NSImage *Cocoa_CreateImage(SDL_Surface *surface); diff --git a/src/video/cocoa/SDL_cocoavideo.m b/src/video/cocoa/SDL_cocoavideo.m index e4ccee2b39718..6fa873be8de38 100644 --- a/src/video/cocoa/SDL_cocoavideo.m +++ b/src/video/cocoa/SDL_cocoavideo.m @@ -127,13 +127,13 @@ static void Cocoa_DeleteDevice(SDL_VideoDevice *device) device->SetWindowModal = Cocoa_SetWindowModal; device->SyncWindow = Cocoa_SyncWindow; device->CreateMenuBar = Cocoa_CreateMenuBar; + device->SetWindowMenuBar = Cocoa_SetWindowMenuBar; device->CreateMenuItemAt = Cocoa_CreateMenuItemAt; - device->SetMenuItemChecked = Cocoa_SetMenuItemChecked; - device->UnSetMenuItemChecked = Cocoa_UnSetMenuItemChecked; + device->SetMenuItemLabel = Cocoa_SetMenuItemLabel; device->GetMenuItemChecked = Cocoa_GetMenuItemChecked; + device->SetMenuItemChecked = Cocoa_SetMenuItemChecked; device->GetMenuItemEnabled = Cocoa_GetMenuItemEnabled; - device->EnableMenuItem = Cocoa_EnableMenuItem; - device->DisableMenuItem = Cocoa_DisableMenuItem; + device->SetMenuItemEnabled = Cocoa_SetMenuItemEnabled; device->DestroyMenuItem = Cocoa_DestroyMenuItem; #ifdef SDL_VIDEO_OPENGL_CGL @@ -339,20 +339,6 @@ void SDL_NSLog(const char *prefix, const char *text) } } - - -@interface PlatformMenuData : NSObject { -@public - Uint16 user_event_type; - NSMenu *menu; - NSMenuItem *menu_item; -} - -- (void) Cocoa_PlatformMenuData_MenuButtonClicked: (id)sender; - -@end - - @implementation PlatformMenuData - (void) Cocoa_PlatformMenuData_MenuButtonClicked: (id)sender;{ @@ -366,17 +352,10 @@ - (void) Cocoa_PlatformMenuData_MenuButtonClicked: (id)sender;{ @end - - - - - - bool Cocoa_CreateMenuBar(SDL_MenuBar *menu_bar) { PlatformMenuData* platform_menu =[PlatformMenuData new]; platform_menu->menu = [NSMenu new]; - [NSApp setMainMenu:platform_menu->menu]; NSMenuItem *appMenuItem = [NSMenuItem new]; NSMenu *appMenu = [NSMenu new]; @@ -391,6 +370,19 @@ bool Cocoa_CreateMenuBar(SDL_MenuBar *menu_bar) return true; } +bool Cocoa_SetWindowMenuBar(SDL_MenuBar *menu_bar) +{ + // We don't actually set the menubar until the window is in focus + if (!menu_bar->window->keyboard_focus) { + return true; + } + + PlatformMenuData* platform_data = (__bridge PlatformMenuData*)menu_bar->common.item_common.platform_data; + [NSApp setMainMenu:platform_data->menu]; + + return true; +} + bool Cocoa_CreateMenuItemAt(SDL_MenuItem *menu_item, size_t index, const char *name, Uint16 event_type) { PlatformMenuData* platform_data = [PlatformMenuData new]; @@ -400,7 +392,7 @@ bool Cocoa_CreateMenuItemAt(SDL_MenuItem *menu_item, size_t index, const char *n PlatformMenuData* parent_platform_data = (__bridge id _Nullable)(menu_item->common.parent->common.platform_data); NSString* name_ns = [NSString stringWithUTF8String:name]; - if (menu_item->common.type == SDL_MENU) { + if (menu_item->common.type == SDL_MENUITEM_SUBMENU) { platform_data->menu = [[NSMenu alloc] initWithTitle:name_ns]; [platform_data->menu setAutoenablesItems:false]; platform_data->menu_item = [NSMenuItem new]; @@ -415,7 +407,7 @@ bool Cocoa_CreateMenuItemAt(SDL_MenuItem *menu_item, size_t index, const char *n [platform_data->menu_item setTarget:platform_data]; [platform_data->menu_item setEnabled:true]; - if ((menu_item->common.parent->common.type == SDL_MENUBAR) && (menu_item->common.type != SDL_MENU)) { + if ((menu_item->common.parent->common.type == SDL_MENUITEM_MENUBAR) && (menu_item->common.type != SDL_MENUITEM_SUBMENU)) { NSMenu* app_menu = [[parent_platform_data->menu itemAtIndex:(NSInteger)0] submenu]; [app_menu addItem:platform_data->menu_item]; @@ -426,20 +418,11 @@ bool Cocoa_CreateMenuItemAt(SDL_MenuItem *menu_item, size_t index, const char *n return true; } - -bool Cocoa_SetMenuItemChecked(SDL_MenuItem *menu_item) +bool Cocoa_SetMenuItemLabel(SDL_MenuItem *menu_item, const char *label) { + NSString* label_ns = [NSString stringWithUTF8String:label]; PlatformMenuData* platform_data = (__bridge PlatformMenuData*)menu_item->common.platform_data; - [platform_data->menu_item setState:NSControlStateValueOn]; - [platform_data->menu update]; - return true; -} - -bool Cocoa_UnSetMenuItemChecked(SDL_MenuItem *menu_item) -{ - PlatformMenuData* platform_data = (__bridge PlatformMenuData*)menu_item->common.platform_data; - [platform_data->menu_item setState:NSControlStateValueOff]; - [platform_data->menu update]; + [platform_data->menu_item setTitle:label_ns]; return true; } @@ -451,25 +434,26 @@ bool Cocoa_GetMenuItemChecked(SDL_MenuItem *menu_item, bool *checked) return true; } -bool Cocoa_GetMenuItemEnabled(SDL_MenuItem *menu_item, bool *enabled) +bool Cocoa_SetMenuItemChecked(SDL_MenuItem *menu_item, bool checked) { + NSControlStateValue flag = checked ? NSControlStateValueOn : NSControlStateValueOff; PlatformMenuData* platform_data = (__bridge PlatformMenuData*)menu_item->common.platform_data; - *enabled = [platform_data->menu_item isEnabled]; + [platform_data->menu_item setState:flag]; + [platform_data->menu update]; return true; } -bool Cocoa_EnableMenuItem(SDL_MenuItem *menu_item) +bool Cocoa_GetMenuItemEnabled(SDL_MenuItem *menu_item, bool *enabled) { PlatformMenuData* platform_data = (__bridge PlatformMenuData*)menu_item->common.platform_data; - [platform_data->menu_item setEnabled:true]; - [platform_data->menu update]; + *enabled = [platform_data->menu_item isEnabled]; return true; } -bool Cocoa_DisableMenuItem(SDL_MenuItem *menu_item) +bool Cocoa_SetMenuItemEnabled(SDL_MenuItem *menu_item, bool enabled) { PlatformMenuData* platform_data = (__bridge PlatformMenuData*)menu_item->common.platform_data; - [platform_data->menu_item setEnabled:false]; + [platform_data->menu_item setEnabled:enabled]; [platform_data->menu update]; return true; } diff --git a/src/video/cocoa/SDL_cocoawindow.h b/src/video/cocoa/SDL_cocoawindow.h index 069bd4f119a19..0eb0fa760d2df 100644 --- a/src/video/cocoa/SDL_cocoawindow.h +++ b/src/video/cocoa/SDL_cocoawindow.h @@ -197,13 +197,13 @@ extern bool Cocoa_SyncWindow(SDL_VideoDevice *_this, SDL_Window *window); extern void Cocoa_MenuVisibilityCallback(void *userdata, const char *name, const char *oldValue, const char *newValue); extern bool Cocoa_CreateMenuBar(SDL_MenuBar *menu_bar); +extern bool Cocoa_SetWindowMenuBar(SDL_MenuBar *menu_bar); extern bool Cocoa_CreateMenuItemAt(SDL_MenuItem *menu_item, size_t index, const char *name, Uint16 event_type); -extern bool Cocoa_SetMenuItemChecked(SDL_MenuItem *menu_item); -extern bool Cocoa_UnSetMenuItemChecked(SDL_MenuItem *menu_item); +extern bool Cocoa_SetMenuItemLabel(SDL_MenuItem *menu_item, const char *label); extern bool Cocoa_GetMenuItemChecked(SDL_MenuItem *menu_item, bool *checked); +extern bool Cocoa_SetMenuItemChecked(SDL_MenuItem *menu_item, bool checked); extern bool Cocoa_GetMenuItemEnabled(SDL_MenuItem *menu_item, bool *enabled); -extern bool Cocoa_EnableMenuItem(SDL_MenuItem *menu_item); -extern bool Cocoa_DisableMenuItem(SDL_MenuItem *menu_item); +extern bool Cocoa_SetMenuItemEnabled(SDL_MenuItem *menu_item, bool enabled); extern bool Cocoa_DestroyMenuItem(SDL_MenuItem *menu_item); #endif // SDL_cocoawindow_h_ diff --git a/src/video/cocoa/SDL_cocoawindow.m b/src/video/cocoa/SDL_cocoawindow.m index 72741fc6ff243..c773b6d9e8421 100644 --- a/src/video/cocoa/SDL_cocoawindow.m +++ b/src/video/cocoa/SDL_cocoawindow.m @@ -1277,6 +1277,13 @@ - (void)windowDidDeminiaturize:(NSNotification *)aNotification - (void)windowDidBecomeKey:(NSNotification *)aNotification { SDL_Window *window = _data.window; + + if (window->menu_bar) { + PlatformMenuData* platform_data = (__bridge PlatformMenuData*)window->menu_bar->common.item_common.platform_data; + [NSApp setMainMenu:platform_data->menu]; + } else { + [NSApp setMainMenu:nil]; + } // We're going to get keyboard events, since we're key. // This needs to be done before restoring the relative mouse mode. diff --git a/src/video/windows/SDL_windowsvideo.h b/src/video/windows/SDL_windowsvideo.h index 294f890371421..d673d6c6d35ce 100644 --- a/src/video/windows/SDL_windowsvideo.h +++ b/src/video/windows/SDL_windowsvideo.h @@ -672,15 +672,15 @@ typedef struct PlatformMenuData Uint16 user_event_type; } PlatformMenuData; -extern bool Win32_CreateMenuBar(SDL_MenuBar *menu_bar); -extern bool Win32_SetWindowMenuBar(SDL_MenuBar *menu_bar); -extern bool Win32_CreateMenuItemAt(SDL_MenuItem *menu_item, size_t index, const char *name, Uint16 event_type); - -extern bool Win32_SetMenuItemLabel(SDL_MenuItem *menu_item, const char *label); -extern bool Win32_GetMenuItemChecked(SDL_MenuItem *menu_item, bool *checked); -extern bool Win32_SetMenuItemChecked(SDL_MenuItem *menu_item, bool checked); -extern bool Win32_GetMenuItemEnabled(SDL_MenuItem *menu_item, bool *enabled); -extern bool Win32_SetMenuItemEnabled(SDL_MenuItem *menu_item, bool enabled); -extern bool Win32_DestroyMenuItem(SDL_MenuItem *menu_item); +extern bool Cocoa_CreateMenuBar(SDL_MenuBar *menu_bar); +extern bool Cocoa_SetWindowMenuBar(SDL_MenuBar *menu_bar); +extern bool Cocoa_CreateMenuItemAt(SDL_MenuItem *menu_item, size_t index, const char *name, Uint16 event_type); + +extern bool Cocoa_SetMenuItemLabel(SDL_MenuItem *menu_item, const char *label); +extern bool Cocoa_GetMenuItemChecked(SDL_MenuItem *menu_item, bool *checked); +extern bool Cocoa_SetMenuItemChecked(SDL_MenuItem *menu_item, bool checked); +extern bool Cocoa_GetMenuItemEnabled(SDL_MenuItem *menu_item, bool *enabled); +extern bool Cocoa_SetMenuItemEnabled(SDL_MenuItem *menu_item, bool enabled); +extern bool Cocoa_DestroyMenuItem(SDL_MenuItem *menu_item); #endif // SDL_windowsvideo_h_ From 3d39b399a28a0cfc52cc9b40124cba26d8b198ea Mon Sep 17 00:00:00 2001 From: "Joshua T. Fisher" Date: Sun, 5 Oct 2025 21:50:23 -0700 Subject: [PATCH 26/35] Started appmenu work, not remotely ready. --- src/dynapi/SDL_dynapi.sym | 1 + src/dynapi/SDL_dynapi_overrides.h | 1 + src/dynapi/SDL_dynapi_procs.h | 1 + src/video/SDL_sysvideo.h | 1 + src/video/SDL_video.c | 42 +++++++++++++++++++++++++------ 5 files changed, 38 insertions(+), 8 deletions(-) diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index 77b2dec63779c..64fe4daa8cf33 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -1265,6 +1265,7 @@ SDL3_0.0.0 { SDL_SavePNG_IO; SDL_SavePNG; SDL_CreateMenuBar; + SDL_GetMenuBarAppMenu; SDL_GetWindowMenuBar; SDL_SetWindowMenuBar; SDL_CreateMenuItemAt; diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index 9775139c139a1..0e395c51c9566 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -1291,6 +1291,7 @@ #define SDL_SavePNG_IO SDL_SavePNG_IO_REAL #define SDL_SavePNG SDL_SavePNG_REAL #define SDL_CreateMenuBar SDL_CreateMenuBar_REAL +#define SDL_GetMenuBarAppMenu SDL_GetMenuBarAppMenu_REAL #define SDL_GetWindowMenuBar SDL_GetWindowMenuBar_REAL #define SDL_SetWindowMenuBar SDL_SetWindowMenuBar_REAL #define SDL_CreateMenuItemAt SDL_CreateMenuItemAt_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index 2992181e59862..ccb9039153732 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -1301,6 +1301,7 @@ SDL_DYNAPI_PROC(bool,SDL_SavePNG,(SDL_Surface *a,const char *b),(a,b),return) SDL_DYNAPI_PROC(SDL_MenuItem*,SDL_CreateMenuBar,(SDL_Window *a),(a),return) SDL_DYNAPI_PROC(SDL_MenuItem*,SDL_GetWindowMenuBar, (SDL_Window * a), (a),return) SDL_DYNAPI_PROC(bool,SDL_SetWindowMenuBar,(SDL_Window *a, SDL_MenuItem *b),(a, b),return) +SDL_DYNAPI_PROC(SDL_MenuItem*,SDL_GetMenuBarAppMenu,(SDL_MenuItem *a),(a),return) SDL_DYNAPI_PROC(SDL_MenuItem*,SDL_CreateMenuItemAt,(SDL_MenuItem *a,size_t b,const char *c,SDL_MenuItemType d, Uint16 e), (a,b,c,d,e), return) SDL_DYNAPI_PROC(SDL_MenuItem*,SDL_CreateMenuItem,(SDL_MenuItem *a, const char *b, SDL_MenuItemType c, Uint16 d),(a,b,c,d), return) SDL_DYNAPI_PROC(Sint64,SDL_GetMenuChildItems, (SDL_MenuItem *a),(a),return) diff --git a/src/video/SDL_sysvideo.h b/src/video/SDL_sysvideo.h index 25029776c3ef3..a29505a4dfc9a 100644 --- a/src/video/SDL_sysvideo.h +++ b/src/video/SDL_sysvideo.h @@ -236,6 +236,7 @@ typedef struct SDL_MenuBar { SDL_Menu_CommonData common; SDL_Window *window; + SDL_MenuItem *app_menu; } SDL_MenuBar; typedef struct SDL_SubMenu diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c index 30c58ab3efdf3..4ea5ab3a70c08 100644 --- a/src/video/SDL_video.c +++ b/src/video/SDL_video.c @@ -6162,18 +6162,37 @@ bool SDL_SetWindowMenuBar(SDL_Window* window, SDL_MenuItem* menu_bar) CHECK_WINDOW_MAGIC(window, false); if (!_this) { - return false; + return SDL_UninitializedVideo(); + } + + // Same Window/MenuBar combination, no need to do anything. + if (menu_bar->menu_bar.window == window) { + return true; } - if (!menu_bar) { + // Passed NULL to the Window, user wants to retake ownership of the menubar. + if (!menu_bar && window->menu_bar) { + bool success = _this->SetWindowMenuBar(NULL); + + SDL_SetObjectValid(window->menu_bar, SDL_OBJECT_TYPE_MENUBAR, true); window->menu_bar = NULL; - return true; + + return success; } if (menu_bar->common.type != SDL_MENUITEM_MENUBAR) { SDL_SetError("Can't set menu Item that isn't a Menu onto a Window."); return false; } + + if (menu_bar->menu_bar.window) { + bool success = _this->SetWindowMenuBar(NULL); + SDL_SetObjectValid(menu_bar->menu_bar.window->menu_bar, SDL_OBJECT_TYPE_MENUBAR, true); + window->menu_bar = NULL; + menu_bar->menu_bar.window->menu_bar + + menu_bar->menu_bar.window = NULL; + } SDL_MenuBar *menu_bar_real = (SDL_MenuBar*)menu_bar; menu_bar_real->window = window; @@ -6284,11 +6303,6 @@ Uint32 SDL_GetIndexInMenu(SDL_MenuItem *menu_item) return i; } -SDL_MenuItem *SDL_CreateMenuItemWithProperties(SDL_MenuItem *menu_bar_as_item, SDL_PropertiesID props) -{ - return NULL; -} - SDL_MenuItem *SDL_CreateMenuItem(SDL_MenuItem *menu_bar_as_item, const char *label, SDL_MenuItemType type, Uint16 event_type) { CHECK_MENUITEM_MAGIC(menu_bar_as_item, NULL); @@ -6301,6 +6315,18 @@ SDL_MenuItem *SDL_CreateMenuItem(SDL_MenuItem *menu_bar_as_item, const char *lab return SDL_CreateMenuItemAt(menu_bar_as_item, menu->num_children, label, type, event_type); } + +SDL_MenuItem *SDL_GetMenuBarAppMenu(SDL_MenuBar *menu_bar) +{ + CHECK_MENUITEM_MAGIC(menu_bar, NULL); + + if (!menu_bar->app_menu) { + SDL_SetError("This platform doesn't support an Application menu."); + } + + return menu_bar->app_menu; +} + Sint64 SDL_GetMenuChildItems(SDL_MenuItem *menu_as_item) { CHECK_MENUITEM_MAGIC(menu_as_item, -1); From 9b2a5f9562a17f0f9dfba625b6970ee73ef6e4db Mon Sep 17 00:00:00 2001 From: "Joshua T. Fisher" Date: Sun, 5 Oct 2025 23:42:39 -0700 Subject: [PATCH 27/35] Rewrote SetWindowMenuBar, need to test it. --- src/video/SDL_sysvideo.h | 2 +- src/video/SDL_video.c | 26 ++++++++++++-------------- src/video/cocoa/SDL_cocoavideo.m | 7 ++++++- src/video/cocoa/SDL_cocoawindow.h | 2 +- src/video/windows/SDL_windowsvideo.c | 13 ++++++++++++- src/video/windows/SDL_windowsvideo.h | 20 ++++++++++---------- 6 files changed, 42 insertions(+), 28 deletions(-) diff --git a/src/video/SDL_sysvideo.h b/src/video/SDL_sysvideo.h index a29505a4dfc9a..f6c150aa424eb 100644 --- a/src/video/SDL_sysvideo.h +++ b/src/video/SDL_sysvideo.h @@ -372,7 +372,7 @@ struct SDL_VideoDevice bool (*ReconfigureWindow)(SDL_VideoDevice *_this, SDL_Window *window, SDL_WindowFlags flags); bool (*CreateMenuBar)(SDL_MenuBar *menu_bar); - bool (*SetWindowMenuBar)(SDL_MenuBar *menu_bar); + bool (*SetWindowMenuBar)(SDL_Window *window, SDL_MenuBar *menu_bar); bool (*CreateMenuItemAt)(SDL_MenuItem *menu_item, size_t index, const char *name, Uint16 event_type); bool (*SetMenuItemLabel)(SDL_MenuItem *menu_item, const char *label); bool (*GetMenuItemChecked)(SDL_MenuItem *menu_item, bool *checked); diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c index 4ea5ab3a70c08..2064ec34a3051 100644 --- a/src/video/SDL_video.c +++ b/src/video/SDL_video.c @@ -6164,15 +6164,10 @@ bool SDL_SetWindowMenuBar(SDL_Window* window, SDL_MenuItem* menu_bar) if (!_this) { return SDL_UninitializedVideo(); } - - // Same Window/MenuBar combination, no need to do anything. - if (menu_bar->menu_bar.window == window) { - return true; - } // Passed NULL to the Window, user wants to retake ownership of the menubar. if (!menu_bar && window->menu_bar) { - bool success = _this->SetWindowMenuBar(NULL); + bool success = _this->SetWindowMenuBar(window, NULL); SDL_SetObjectValid(window->menu_bar, SDL_OBJECT_TYPE_MENUBAR, true); window->menu_bar = NULL; @@ -6180,23 +6175,26 @@ bool SDL_SetWindowMenuBar(SDL_Window* window, SDL_MenuItem* menu_bar) return success; } + // Same Window/MenuBar combination, no need to do anything. + if (menu_bar->menu_bar.window == window) { + return true; + } + if (menu_bar->common.type != SDL_MENUITEM_MENUBAR) { SDL_SetError("Can't set menu Item that isn't a Menu onto a Window."); return false; } - + + // menu_bar is already on another window, null out the menubar on that window + // before we add this menubar to the given window. if (menu_bar->menu_bar.window) { - bool success = _this->SetWindowMenuBar(NULL); - SDL_SetObjectValid(menu_bar->menu_bar.window->menu_bar, SDL_OBJECT_TYPE_MENUBAR, true); - window->menu_bar = NULL; - menu_bar->menu_bar.window->menu_bar - - menu_bar->menu_bar.window = NULL; + _this->SetWindowMenuBar(menu_bar->menu_bar.window, NULL); } SDL_MenuBar *menu_bar_real = (SDL_MenuBar*)menu_bar; menu_bar_real->window = window; + // Window has an existing MenuBar, release it back to the user. if (window->menu_bar) { SDL_SetObjectValid(window->menu_bar, SDL_OBJECT_TYPE_MENUBAR, true); } @@ -6205,7 +6203,7 @@ bool SDL_SetWindowMenuBar(SDL_Window* window, SDL_MenuItem* menu_bar) SDL_SetObjectValid(menu_bar_real, SDL_OBJECT_TYPE_MENUBAR, false); - return _this->SetWindowMenuBar(menu_bar_real); + return _this->SetWindowMenuBar(window, menu_bar_real); } void SDL_CleanupMenubars() diff --git a/src/video/cocoa/SDL_cocoavideo.m b/src/video/cocoa/SDL_cocoavideo.m index 6fa873be8de38..ba419622367af 100644 --- a/src/video/cocoa/SDL_cocoavideo.m +++ b/src/video/cocoa/SDL_cocoavideo.m @@ -370,8 +370,13 @@ bool Cocoa_CreateMenuBar(SDL_MenuBar *menu_bar) return true; } -bool Cocoa_SetWindowMenuBar(SDL_MenuBar *menu_bar) +bool Cocoa_SetWindowMenuBar(SDL_Window *window, SDL_MenuBar *menu_bar) { + if (menu_bar == NULL) { + [NSApp setMainMenu:nil]; + return true; + } + // We don't actually set the menubar until the window is in focus if (!menu_bar->window->keyboard_focus) { return true; diff --git a/src/video/cocoa/SDL_cocoawindow.h b/src/video/cocoa/SDL_cocoawindow.h index 0eb0fa760d2df..25164d5477838 100644 --- a/src/video/cocoa/SDL_cocoawindow.h +++ b/src/video/cocoa/SDL_cocoawindow.h @@ -197,7 +197,7 @@ extern bool Cocoa_SyncWindow(SDL_VideoDevice *_this, SDL_Window *window); extern void Cocoa_MenuVisibilityCallback(void *userdata, const char *name, const char *oldValue, const char *newValue); extern bool Cocoa_CreateMenuBar(SDL_MenuBar *menu_bar); -extern bool Cocoa_SetWindowMenuBar(SDL_MenuBar *menu_bar); +extern bool Cocoa_SetWindowMenuBar(SDL_Window *window, SDL_MenuBar *menu_bar); extern bool Cocoa_CreateMenuItemAt(SDL_MenuItem *menu_item, size_t index, const char *name, Uint16 event_type); extern bool Cocoa_SetMenuItemLabel(SDL_MenuItem *menu_item, const char *label); extern bool Cocoa_GetMenuItemChecked(SDL_MenuItem *menu_item, bool *checked); diff --git a/src/video/windows/SDL_windowsvideo.c b/src/video/windows/SDL_windowsvideo.c index d3c2d7546c73f..9b5433b66fcec 100644 --- a/src/video/windows/SDL_windowsvideo.c +++ b/src/video/windows/SDL_windowsvideo.c @@ -920,8 +920,19 @@ bool Win32_CreateMenuBar(SDL_MenuBar *menu_bar) return true; } -bool Win32_SetWindowMenuBar(SDL_MenuBar *menu_bar) +bool Win32_SetWindowMenuBar(SDL_Window *window, SDL_MenuBar *menu_bar) { + if (!menu_bar) { + const SDL_WindowData *data = window->internal; + + if (!SetMenu(data->hwnd, (HMENU)NULL)) { + WIN_SetError("Unable to unset MenuBar"); + return false; + } + + return true; + } + const PlatformMenuData *menu_platform_data = (PlatformMenuData *)menu_bar->common.item_common.platform_data; const SDL_WindowData *data = menu_bar->common.item_common.menu_bar->window->internal; diff --git a/src/video/windows/SDL_windowsvideo.h b/src/video/windows/SDL_windowsvideo.h index d673d6c6d35ce..93d26fa45746b 100644 --- a/src/video/windows/SDL_windowsvideo.h +++ b/src/video/windows/SDL_windowsvideo.h @@ -672,15 +672,15 @@ typedef struct PlatformMenuData Uint16 user_event_type; } PlatformMenuData; -extern bool Cocoa_CreateMenuBar(SDL_MenuBar *menu_bar); -extern bool Cocoa_SetWindowMenuBar(SDL_MenuBar *menu_bar); -extern bool Cocoa_CreateMenuItemAt(SDL_MenuItem *menu_item, size_t index, const char *name, Uint16 event_type); - -extern bool Cocoa_SetMenuItemLabel(SDL_MenuItem *menu_item, const char *label); -extern bool Cocoa_GetMenuItemChecked(SDL_MenuItem *menu_item, bool *checked); -extern bool Cocoa_SetMenuItemChecked(SDL_MenuItem *menu_item, bool checked); -extern bool Cocoa_GetMenuItemEnabled(SDL_MenuItem *menu_item, bool *enabled); -extern bool Cocoa_SetMenuItemEnabled(SDL_MenuItem *menu_item, bool enabled); -extern bool Cocoa_DestroyMenuItem(SDL_MenuItem *menu_item); +extern bool Win32_CreateMenuBar(SDL_MenuBar *menu_bar); +extern bool Win32_SetWindowMenuBar(SDL_Window *window, SDL_MenuBar *menu_bar); +extern bool Win32_CreateMenuItemAt(SDL_MenuItem *menu_item, size_t index, const char *name, Uint16 event_type); + +extern bool Win32_SetMenuItemLabel(SDL_MenuItem *menu_item, const char *label); +extern bool Win32_GetMenuItemChecked(SDL_MenuItem *menu_item, bool *checked); +extern bool Win32_SetMenuItemChecked(SDL_MenuItem *menu_item, bool checked); +extern bool Win32_GetMenuItemEnabled(SDL_MenuItem *menu_item, bool *enabled); +extern bool Win32_SetMenuItemEnabled(SDL_MenuItem *menu_item, bool enabled); +extern bool Win32_DestroyMenuItem(SDL_MenuItem *menu_item); #endif // SDL_windowsvideo_h_ From 527b8d4a79f77b1512e9c11d3f0f467fe2bdeffb Mon Sep 17 00:00:00 2001 From: "Joshua T. Fisher" Date: Mon, 6 Oct 2025 00:56:50 -0700 Subject: [PATCH 28/35] Added some relevant testing to the example. --- examples/video/01-menubar/menubar.c | 303 ++++++++++++++++---------- include/SDL3/SDL_events.h | 1 + include/SDL3/SDL_video.h | 3 +- src/dynapi/SDL_dynapi_overrides.h | 3 +- src/dynapi/SDL_dynapi_procs.h | 13 +- src/video/SDL_sysvideo.h | 4 +- src/video/SDL_video.c | 73 +++++-- src/video/cocoa/SDL_cocoavideo.m | 15 -- src/video/cocoa/SDL_cocoawindow.h | 2 - src/video/windows/SDL_windowsevents.c | 1 + src/video/windows/SDL_windowsvideo.c | 37 +--- src/video/windows/SDL_windowsvideo.h | 2 - 12 files changed, 260 insertions(+), 197 deletions(-) diff --git a/examples/video/01-menubar/menubar.c b/examples/video/01-menubar/menubar.c index 67fce763e09e6..2d349423dcacc 100644 --- a/examples/video/01-menubar/menubar.c +++ b/examples/video/01-menubar/menubar.c @@ -8,36 +8,48 @@ #include #include -SDL_Window* window = NULL; -SDL_Renderer* renderer = NULL; +SDL_Window* window_1 = NULL; +SDL_Renderer *renderer_1 = NULL; -SDL_MenuItem* checkable = NULL; -SDL_MenuItem* new_window = NULL; +SDL_Window *window_2 = NULL; +SDL_Renderer *renderer_2 = NULL; -SDL_MenuItem *menu_bar; +SDL_MenuItem *checkable[2] = { + NULL, + NULL +}; + +SDL_MenuItem *null_out_button[2] = { + NULL, + NULL +}; + +SDL_MenuItem *menu_bar_1; +SDL_MenuItem *menu_bar_2; typedef enum SDL_EventType_MenuExt { - MENU_BAR_FILE, - MENU_BAR_FILE_NEW_WINDOW, - MENU_BAR_FILE_DISABLE_NEW_WINDOW, - MENU_BAR_BOOKMARKS, - MENU_BAR_BOOKMARKS_TOOLBAR, - MENU_BAR_BOOKMARKS_TOOLBAR_GITHUB, - MENU_BAR_BOOKMARKS_TOOLBAR_WIKI, - MENU_BAR_BOOKMARKS_TOOLBAR_DISCORD, - MENU_BAR_BOOKMARKS_OTHER_BOOKMARKS, - MENU_BAR_BOOKMARKS_OTHER_BOOKMARKS_STACKOVERFLOW, - MENU_BAR_INCOGNITO, - MENU_BAR_TOP_LEVEL_BUTTON, - MENU_BAR_EXIT, - - MENU_BAR_LAST + MENU_BAR_FILE, + MENU_BAR_FILE_SWAP_BARS, + MENU_BAR_FILE_NULL_OUT_BAR, + MENU_BAR_FILE_DISABLE_NULL_OUT_BAR, + MENU_BAR_BOOKMARKS, + MENU_BAR_BOOKMARKS_TOOLBAR, + MENU_BAR_BOOKMARKS_TOOLBAR_GITHUB, + MENU_BAR_BOOKMARKS_TOOLBAR_WIKI, + MENU_BAR_BOOKMARKS_TOOLBAR_DISCORD, + MENU_BAR_BOOKMARKS_OTHER_BOOKMARKS, + MENU_BAR_BOOKMARKS_OTHER_BOOKMARKS_STACKOVERFLOW, + MENU_BAR_INCOGNITO, + MENU_BAR_TOP_LEVEL_BUTTON, + MENU_BAR_EXIT, + + MENU_BAR_LAST } SDL_EventType_MenuExt; static SDL_EventType_MenuExt EVENT_START = (SDL_EventType_MenuExt)0; -void PrintMenuItems(SDL_MenuItem *menu_item, int indent, int *total_index) +void PrintMenuItems(SDL_Renderer* renderer, SDL_MenuItem *menu_item, int indent, int *total_index) { if (!menu_item) { return; @@ -56,130 +68,197 @@ void PrintMenuItems(SDL_MenuItem *menu_item, int indent, int *total_index) size_t item_count = SDL_GetMenuChildItems(menu_item); for (size_t i = 0; i < item_count; ++i) { - PrintMenuItems(SDL_GetMenuChildItem(menu_item, i), indent + 1, total_index); + PrintMenuItems(renderer, SDL_GetMenuChildItem(menu_item, i), indent + 1, total_index); } } -void CreateMenuBar() +void CreateMenuBar_1() { - menu_bar = SDL_CreateMenuBar(window); + menu_bar_1 = SDL_CreateMenuBar(); - { - SDL_MenuItem* menu = SDL_CreateMenuItem(menu_bar, "File", SDL_MENUITEM_SUBMENU, MENU_BAR_LAST); - new_window = SDL_CreateMenuItem(menu, "New Window", SDL_MENUITEM_BUTTON, MENU_BAR_FILE_NEW_WINDOW); - checkable = SDL_CreateMenuItem(menu, "Enable New Window", SDL_MENUITEM_CHECKABLE, MENU_BAR_FILE_DISABLE_NEW_WINDOW); + { + SDL_MenuItem *menu = SDL_CreateMenuItem(menu_bar_1, "File_1", SDL_MENUITEM_SUBMENU, MENU_BAR_LAST); + SDL_CreateMenuItem(menu, "Swap Bars", SDL_MENUITEM_BUTTON, MENU_BAR_FILE_SWAP_BARS); + null_out_button[0] = SDL_CreateMenuItem(menu, "Null Out Bar", SDL_MENUITEM_BUTTON, MENU_BAR_FILE_NULL_OUT_BAR); + SDL_SetMenuItemEnabled(null_out_button[0], false); + checkable[0] = SDL_CreateMenuItem(menu, "Enable Null Out Bar", SDL_MENUITEM_CHECKABLE, MENU_BAR_FILE_DISABLE_NULL_OUT_BAR); + SDL_SetMenuItemChecked(checkable[0], false); + } - SDL_SetMenuItemChecked(checkable, true); - } + { + SDL_MenuItem *menu = SDL_CreateMenuItem(menu_bar_1, "Bookmarks_1", SDL_MENUITEM_SUBMENU, MENU_BAR_LAST); + SDL_MenuItem *main_bookmarks = SDL_CreateMenuItem(menu, "Bookmarks Toolbar_1", SDL_MENUITEM_SUBMENU, MENU_BAR_LAST); + SDL_MenuItem *discord = SDL_CreateMenuItem(main_bookmarks, "SDL Discord_1", SDL_MENUITEM_BUTTON, MENU_BAR_BOOKMARKS_TOOLBAR_DISCORD); + SDL_CreateMenuItem(main_bookmarks, "SDL GitHub_1", SDL_MENUITEM_BUTTON, MENU_BAR_BOOKMARKS_TOOLBAR_GITHUB); + SDL_CreateMenuItemAt(main_bookmarks, 0, "SDL Wiki_1", SDL_MENUITEM_BUTTON, MENU_BAR_BOOKMARKS_TOOLBAR_WIKI); - { - SDL_MenuItem* menu = SDL_CreateMenuItem(menu_bar, "Bookmarks", SDL_MENUITEM_SUBMENU, MENU_BAR_LAST); - SDL_MenuItem* main_bookmarks = SDL_CreateMenuItem(menu, "Bookmarks Toolbar", SDL_MENUITEM_SUBMENU, MENU_BAR_LAST); - SDL_MenuItem* discord = SDL_CreateMenuItem(main_bookmarks, "SDL Discord", SDL_MENUITEM_BUTTON, MENU_BAR_BOOKMARKS_TOOLBAR_DISCORD); - SDL_CreateMenuItem(main_bookmarks, "SDL GitHub", SDL_MENUITEM_BUTTON, MENU_BAR_BOOKMARKS_TOOLBAR_GITHUB); - SDL_CreateMenuItemAt(main_bookmarks, 0, "SDL Wiki", SDL_MENUITEM_BUTTON, MENU_BAR_BOOKMARKS_TOOLBAR_WIKI); + SDL_MenuItem *other_bookmarks = SDL_CreateMenuItem(main_bookmarks, "Other Bookmarks_1", SDL_MENUITEM_SUBMENU, MENU_BAR_LAST); + SDL_MenuItem *stack_overflow = SDL_CreateMenuItem(other_bookmarks, "Stack Overflow-test", SDL_MENUITEM_BUTTON, MENU_BAR_BOOKMARKS_OTHER_BOOKMARKS_STACKOVERFLOW); + SDL_SetMenuItemLabel(stack_overflow, "Stack Overflow_1"); - SDL_MenuItem *other_bookmarks = SDL_CreateMenuItem(main_bookmarks, "Other Bookmarks", SDL_MENUITEM_SUBMENU, MENU_BAR_LAST); - SDL_MenuItem *stack_overflow = SDL_CreateMenuItem(other_bookmarks, "Stack Overflow-test", SDL_MENUITEM_BUTTON, MENU_BAR_BOOKMARKS_OTHER_BOOKMARKS_STACKOVERFLOW); - SDL_SetMenuItemLabel(stack_overflow, "Stack Overflow"); - - SDL_DestroyMenuItem(discord); + SDL_DestroyMenuItem(discord); + SDL_SetMenuItemChecked(other_bookmarks, false); + } - SDL_SetMenuItemChecked(other_bookmarks, false); - } + { + // We can't create a top level checkable . + SDL_assert(!SDL_CreateMenuItem(menu_bar_1, "Incognito", SDL_MENUITEM_CHECKABLE, MENU_BAR_INCOGNITO)); - { - // We can't create a top level checkable . - SDL_assert(!SDL_CreateMenuItem(menu_bar, "Incognito", SDL_MENUITEM_CHECKABLE, MENU_BAR_INCOGNITO)); - - SDL_CreateMenuItem(menu_bar, "Exit", SDL_MENUITEM_BUTTON, MENU_BAR_EXIT); - } + SDL_CreateMenuItem(menu_bar_1, "Exit", SDL_MENUITEM_BUTTON, MENU_BAR_EXIT); + } - SDL_SetWindowMenuBar(window, menu_bar); + SDL_SetWindowMenuBar(window_1, menu_bar_1); - EVENT_START = (SDL_EventType_MenuExt)SDL_RegisterEvents(MENU_BAR_LAST); + EVENT_START = (SDL_EventType_MenuExt)SDL_RegisterEvents(MENU_BAR_LAST); +} + +void CreateMenuBar_2() +{ + menu_bar_2 = SDL_CreateMenuBar(); + + { + SDL_MenuItem *menu = SDL_CreateMenuItem(menu_bar_2, "File_2", SDL_MENUITEM_SUBMENU, MENU_BAR_LAST); + SDL_CreateMenuItem(menu, "Swap Bars", SDL_MENUITEM_BUTTON, MENU_BAR_FILE_SWAP_BARS); + null_out_button[1] = SDL_CreateMenuItem(menu, "Null Out Bar", SDL_MENUITEM_BUTTON, MENU_BAR_FILE_NULL_OUT_BAR); + SDL_SetMenuItemEnabled(null_out_button[1], false); + checkable[1] = SDL_CreateMenuItem(menu, "Enable Null Out Bar", SDL_MENUITEM_CHECKABLE, MENU_BAR_FILE_DISABLE_NULL_OUT_BAR); + SDL_SetMenuItemChecked(checkable[1], false); + } + + { + SDL_MenuItem *menu = SDL_CreateMenuItem(menu_bar_2, "Bookmarks_2", SDL_MENUITEM_SUBMENU, MENU_BAR_LAST); + SDL_MenuItem *main_bookmarks = SDL_CreateMenuItem(menu, "Bookmarks Toolbar_2", SDL_MENUITEM_SUBMENU, MENU_BAR_LAST); + SDL_MenuItem *discord = SDL_CreateMenuItem(main_bookmarks, "SDL Discord_2", SDL_MENUITEM_BUTTON, MENU_BAR_BOOKMARKS_TOOLBAR_DISCORD); + SDL_CreateMenuItem(main_bookmarks, "SDL GitHub_2", SDL_MENUITEM_BUTTON, MENU_BAR_BOOKMARKS_TOOLBAR_GITHUB); + SDL_CreateMenuItemAt(main_bookmarks, 0, "SDL Wiki_2", SDL_MENUITEM_BUTTON, MENU_BAR_BOOKMARKS_TOOLBAR_WIKI); + + SDL_MenuItem *other_bookmarks = SDL_CreateMenuItem(main_bookmarks, "Other Bookmarks_2", SDL_MENUITEM_SUBMENU, MENU_BAR_LAST); + SDL_MenuItem *stack_overflow = SDL_CreateMenuItem(other_bookmarks, "Stack Overflow-test_2", SDL_MENUITEM_BUTTON, MENU_BAR_BOOKMARKS_OTHER_BOOKMARKS_STACKOVERFLOW); + SDL_SetMenuItemLabel(stack_overflow, "Stack Overflow_2"); + + SDL_DestroyMenuItem(discord); + + SDL_SetMenuItemChecked(other_bookmarks, false); + } + + { + // We can't create a top level checkable . + SDL_assert(!SDL_CreateMenuItem(menu_bar_2, "Incognito_2", SDL_MENUITEM_CHECKABLE, MENU_BAR_INCOGNITO)); + + SDL_CreateMenuItem(menu_bar_2, "Exit", SDL_MENUITEM_BUTTON, MENU_BAR_EXIT); + } + + SDL_SetWindowMenuBar(window_2, menu_bar_2); + + EVENT_START = (SDL_EventType_MenuExt)SDL_RegisterEvents(MENU_BAR_LAST); } SDL_AppResult SDL_AppInit(void** appstate, int argc, char** argv) { - SDL_CreateWindowAndRenderer("menu bar test", 640, 480, 0, &window, &renderer); + SDL_CreateWindowAndRenderer("Window 1", 640, 480, 0, &window_1, &renderer_1); + SDL_CreateWindowAndRenderer("Window 2", 640, 480, 0, &window_2, &renderer_2); - CreateMenuBar(); + CreateMenuBar_1(); + CreateMenuBar_2(); - //return SDL_APP_SUCCESS; - return SDL_APP_CONTINUE; + //return SDL_APP_SUCCESS; + return SDL_APP_CONTINUE; } SDL_AppResult SDL_AppIterate(void* appstate) { - SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); - SDL_RenderClear(renderer); - + // Window 1 + SDL_SetRenderDrawColor(renderer_1, 180, 180, 180, 255); + SDL_RenderClear(renderer_1); - SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); - int total_index = 0; - PrintMenuItems(menu_bar, 0, &total_index); - SDL_RenderPresent(renderer); + SDL_SetRenderDrawColor(renderer_1, 0, 0, 0, 255); + int total_index = 0; + PrintMenuItems(renderer_1, SDL_GetWindowMenuBar(window_1), 0, &total_index); + SDL_RenderPresent(renderer_1); + + // Window 2 + SDL_SetRenderDrawColor(renderer_2, 255, 255, 255, 255); + SDL_RenderClear(renderer_2); + + SDL_SetRenderDrawColor(renderer_2, 0, 0, 0, 255); + total_index = 0; + PrintMenuItems(renderer_2, SDL_GetWindowMenuBar(window_2), 0, &total_index); + SDL_RenderPresent(renderer_2); - return SDL_APP_CONTINUE; + return SDL_APP_CONTINUE; } SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) { - switch (event->common.type) - { - case SDL_EVENT_QUIT: + switch (event->common.type) { - return SDL_APP_SUCCESS; - } - case SDL_EVENT_MENU_BUTTON_CLICKED: - case SDL_EVENT_MENU_CHECKABLE_CLICKED: - { - switch (event->menu.user_event_type) { - case MENU_BAR_BOOKMARKS_TOOLBAR_GITHUB: - { - SDL_OpenURL("https://github.com/libsdl-org/SDL"); - break; - } - case MENU_BAR_BOOKMARKS_TOOLBAR_WIKI: - { - SDL_OpenURL("https://wiki.libsdl.org/SDL3/FrontPage"); - break; - } - case MENU_BAR_BOOKMARKS_TOOLBAR_DISCORD: - { - SDL_OpenURL("https://discord.gg/BwpFGBWsv8"); - break; - } - case MENU_BAR_BOOKMARKS_OTHER_BOOKMARKS_STACKOVERFLOW: - { - SDL_OpenURL("https://stackoverflow.com/questions"); - break; - } - case MENU_BAR_FILE_DISABLE_NEW_WINDOW: - { - bool is_checked = false; - SDL_GetMenuItemChecked(checkable, &is_checked); - SDL_SetMenuItemChecked(checkable, !is_checked); - - bool is_enabled = false; - SDL_GetMenuItemEnabled(new_window, &is_enabled); - SDL_SetMenuItemEnabled(new_window, !is_enabled); - - break; - } - case MENU_BAR_EXIT: - { - return SDL_APP_SUCCESS; - } + case SDL_EVENT_QUIT: + { + return SDL_APP_SUCCESS; + } + case SDL_EVENT_MENU_BUTTON_CLICKED: + case SDL_EVENT_MENU_CHECKABLE_CLICKED: + { + switch (event->menu.user_event_type) { + case MENU_BAR_BOOKMARKS_TOOLBAR_GITHUB: + { + SDL_OpenURL("https://github.com/libsdl-org/SDL"); + break; + } + case MENU_BAR_BOOKMARKS_TOOLBAR_WIKI: + { + SDL_OpenURL("https://wiki.libsdl.org/SDL3/FrontPage"); + break; + } + case MENU_BAR_BOOKMARKS_TOOLBAR_DISCORD: + { + SDL_OpenURL("https://discord.gg/BwpFGBWsv8"); + break; + } + case MENU_BAR_BOOKMARKS_OTHER_BOOKMARKS_STACKOVERFLOW: + { + SDL_OpenURL("https://stackoverflow.com/questions"); + break; + } + case MENU_BAR_FILE_DISABLE_NULL_OUT_BAR: + { + bool is_checked = false; + SDL_GetMenuItemChecked(checkable[0], &is_checked); + SDL_SetMenuItemChecked(checkable[0], !is_checked); + SDL_SetMenuItemChecked(checkable[1], !is_checked); + + bool is_enabled = false; + SDL_GetMenuItemEnabled(null_out_button[0], &is_enabled); + SDL_SetMenuItemEnabled(null_out_button[0], !is_enabled); + SDL_SetMenuItemEnabled(null_out_button[1], !is_enabled); + + break; + } + case MENU_BAR_FILE_SWAP_BARS: + { + SDL_MenuItem *menu_bar1 = SDL_GetWindowMenuBar(window_1); + SDL_MenuItem *menu_bar2 = SDL_GetWindowMenuBar(window_2); + SDL_SetWindowMenuBar(window_1, menu_bar2); + SDL_SetWindowMenuBar(window_2, menu_bar1); + break; + } + case MENU_BAR_FILE_NULL_OUT_BAR: + { + SDL_Window *window = SDL_GetWindowFromID(event->menu.windowID); + SDL_SetWindowMenuBar(window, NULL); + break; + } + case MENU_BAR_EXIT: + { + return SDL_APP_SUCCESS; + } + } + SDL_Log("%d\n", event->menu.user_event_type); } - SDL_Log("%d\n", event->menu.user_event_type); } - } - - return SDL_APP_CONTINUE; + return SDL_APP_CONTINUE; } void SDL_AppQuit(void* appstate, SDL_AppResult result) { diff --git a/include/SDL3/SDL_events.h b/include/SDL3/SDL_events.h index 45aaeeaac5a8a..f4aa91bc46ad8 100644 --- a/include/SDL3/SDL_events.h +++ b/include/SDL3/SDL_events.h @@ -990,6 +990,7 @@ typedef struct SDL_MenuEvent Uint32 type; /**< SDL_EVENT_MENU_BUTTON_CLICKED or SDL_EVENT_MENU_CHECKABLE_CLICKED */ Uint32 reserved; Uint64 timestamp; /**< In nanoseconds, populated using SDL_GetTicksNS() */ + SDL_WindowID windowID; /**< The associated window if any */ Uint16 user_event_type; } SDL_MenuEvent; diff --git a/include/SDL3/SDL_video.h b/include/SDL3/SDL_video.h index c2e50fa0eabd5..43bd5d23fa14b 100644 --- a/include/SDL3/SDL_video.h +++ b/include/SDL3/SDL_video.h @@ -3060,7 +3060,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_EnableScreenSaver(void); */ extern SDL_DECLSPEC bool SDLCALL SDL_DisableScreenSaver(void); -extern SDL_DECLSPEC SDL_MenuItem *SDL_CreateMenuBar(SDL_Window *window); +extern SDL_DECLSPEC SDL_MenuItem *SDL_CreateMenuBar(); #define SDL_PROP_MENUITEM_CREATE_LABEL "SDL.menuitem.create.label" @@ -3087,6 +3087,7 @@ extern SDL_DECLSPEC SDL_MenuItem *SDL_GetMenuChildItem(SDL_MenuItem *menu_as_ite extern SDL_DECLSPEC const char *SDL_GetMenuItemLabel(SDL_MenuItem *menu_item); extern SDL_DECLSPEC bool SDL_SetMenuItemLabel(SDL_MenuItem *menu_item, const char *label); extern SDL_DECLSPEC SDL_MenuItemType SDL_GetMenuItemType(SDL_MenuItem *menu_item); +extern SDL_DECLSPEC Sint32 SDL_GetMenuItemEventType(SDL_MenuItem *menu_item); /** * Must be a SDL_MENUITEM_CHECKABLE diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index 0e395c51c9566..ac2d6e72a7c8c 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -1299,7 +1299,8 @@ #define SDL_GetMenuChildItems SDL_GetMenuChildItems_REAL #define SDL_GetMenuChildItem SDL_GetMenuChildItem_REAL #define SDL_GetMenuItemLabel SDL_GetMenuItemLabel_REAL -#define SDL_GetMenuItemType SDL_GetMenuItemType_REAL +#define SDL_GetMenuItemType SDL_GetMenuItemType_REAL +#define SDL_GetMenuItemEventType SDL_GetMenuItemEventType_REAL #define SDL_SetMenuItemLabel SDL_SetMenuItemLabel_REAL #define SDL_SetMenuItemChecked SDL_SetMenuItemChecked_REAL #define SDL_GetMenuItemChecked SDL_GetMenuItemChecked_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index ccb9039153732..c46fe4d8a88d2 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -1298,17 +1298,18 @@ SDL_DYNAPI_PROC(SDL_Surface*,SDL_LoadPNG_IO,(SDL_IOStream *a,bool b),(a,b),retur SDL_DYNAPI_PROC(SDL_Surface*,SDL_LoadPNG,(const char *a),(a),return) SDL_DYNAPI_PROC(bool,SDL_SavePNG_IO,(SDL_Surface *a,SDL_IOStream *b,bool c),(a,b,c),return) SDL_DYNAPI_PROC(bool,SDL_SavePNG,(SDL_Surface *a,const char *b),(a,b),return) -SDL_DYNAPI_PROC(SDL_MenuItem*,SDL_CreateMenuBar,(SDL_Window *a),(a),return) +SDL_DYNAPI_PROC(SDL_MenuItem*,SDL_CreateMenuBar,(),(),return) SDL_DYNAPI_PROC(SDL_MenuItem*,SDL_GetWindowMenuBar, (SDL_Window * a), (a),return) SDL_DYNAPI_PROC(bool,SDL_SetWindowMenuBar,(SDL_Window *a, SDL_MenuItem *b),(a, b),return) SDL_DYNAPI_PROC(SDL_MenuItem*,SDL_GetMenuBarAppMenu,(SDL_MenuItem *a),(a),return) SDL_DYNAPI_PROC(SDL_MenuItem*,SDL_CreateMenuItemAt,(SDL_MenuItem *a,size_t b,const char *c,SDL_MenuItemType d, Uint16 e), (a,b,c,d,e), return) SDL_DYNAPI_PROC(SDL_MenuItem*,SDL_CreateMenuItem,(SDL_MenuItem *a, const char *b, SDL_MenuItemType c, Uint16 d),(a,b,c,d), return) -SDL_DYNAPI_PROC(Sint64,SDL_GetMenuChildItems, (SDL_MenuItem *a),(a),return) -SDL_DYNAPI_PROC(SDL_MenuItem*, SDL_GetMenuChildItem, (SDL_MenuItem *a, size_t b), (a,b), return) -SDL_DYNAPI_PROC(const char *, SDL_GetMenuItemLabel, (SDL_MenuItem *a), (a), return) -SDL_DYNAPI_PROC(bool, SDL_SetMenuItemLabel, (SDL_MenuItem *a,const char *b), (a,b), return) -SDL_DYNAPI_PROC(SDL_MenuItemType, SDL_GetMenuItemType, (SDL_MenuItem *a), (a), return) +SDL_DYNAPI_PROC(Sint64,SDL_GetMenuChildItems,(SDL_MenuItem *a),(a),return) +SDL_DYNAPI_PROC(SDL_MenuItem*,SDL_GetMenuChildItem,(SDL_MenuItem *a, size_t b), (a,b), return) +SDL_DYNAPI_PROC(const char *,SDL_GetMenuItemLabel,(SDL_MenuItem *a), (a), return) +SDL_DYNAPI_PROC(bool, SDL_SetMenuItemLabel,(SDL_MenuItem *a,const char *b), (a,b), return) +SDL_DYNAPI_PROC(SDL_MenuItemType,SDL_GetMenuItemType,(SDL_MenuItem * a), (a), return) +SDL_DYNAPI_PROC(Sint32,SDL_GetMenuItemEventType,(SDL_MenuItem * a), (a), return) SDL_DYNAPI_PROC(bool,SDL_GetMenuItemChecked,(SDL_MenuItem *a,bool *b),(a,b),return) SDL_DYNAPI_PROC(bool,SDL_SetMenuItemChecked,(SDL_MenuItem *a,bool b),(a,b),return) SDL_DYNAPI_PROC(bool,SDL_GetMenuItemEnabled,(SDL_MenuItem *a,bool *b),(a,b),return) diff --git a/src/video/SDL_sysvideo.h b/src/video/SDL_sysvideo.h index f6c150aa424eb..23c79635e3e2e 100644 --- a/src/video/SDL_sysvideo.h +++ b/src/video/SDL_sysvideo.h @@ -223,6 +223,8 @@ typedef struct SDL_MenuItem_CommonData SDL_MenuItem *next; const char *label; SDL_MenuItemType type; + Uint16 event_type; + bool enabled; } SDL_MenuItem_CommonData; typedef struct SDL_Menu_CommonData @@ -375,9 +377,7 @@ struct SDL_VideoDevice bool (*SetWindowMenuBar)(SDL_Window *window, SDL_MenuBar *menu_bar); bool (*CreateMenuItemAt)(SDL_MenuItem *menu_item, size_t index, const char *name, Uint16 event_type); bool (*SetMenuItemLabel)(SDL_MenuItem *menu_item, const char *label); - bool (*GetMenuItemChecked)(SDL_MenuItem *menu_item, bool *checked); bool (*SetMenuItemChecked)(SDL_MenuItem *menu_item, bool checked); - bool (*GetMenuItemEnabled)(SDL_MenuItem *menu_item, bool *enabled); bool (*SetMenuItemEnabled)(SDL_MenuItem *menu_item, bool enabled); bool (*DestroyMenuItem)(SDL_MenuItem *menu_item); diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c index 2064ec34a3051..565da6cc9f6f5 100644 --- a/src/video/SDL_video.c +++ b/src/video/SDL_video.c @@ -6122,10 +6122,8 @@ void SDL_OnApplicationDidEnterForeground(void) } } -SDL_MenuItem *SDL_CreateMenuBar(SDL_Window *window) +SDL_MenuItem *SDL_CreateMenuBar() { - CHECK_WINDOW_MAGIC(window, NULL); - if (!_this) { return NULL; } @@ -6137,6 +6135,7 @@ SDL_MenuItem *SDL_CreateMenuBar(SDL_Window *window) SDL_MenuBar *menu_bar = SDL_calloc(1, sizeof(SDL_MenuBar)); menu_bar->common.item_common.menu_bar = menu_bar; menu_bar->common.item_common.type = SDL_MENUITEM_MENUBAR; + menu_bar->common.item_common.enabled = true; if (!_this->CreateMenuBar(menu_bar)) { SDL_free(menu_bar); @@ -6165,14 +6164,20 @@ bool SDL_SetWindowMenuBar(SDL_Window* window, SDL_MenuItem* menu_bar) return SDL_UninitializedVideo(); } - // Passed NULL to the Window, user wants to retake ownership of the menubar. - if (!menu_bar && window->menu_bar) { - bool success = _this->SetWindowMenuBar(window, NULL); - - SDL_SetObjectValid(window->menu_bar, SDL_OBJECT_TYPE_MENUBAR, true); - window->menu_bar = NULL; - - return success; + if (!menu_bar) { + // Passed NULL to the Window, user wants to retake ownership of the menubar. + if (window->menu_bar) { + bool success = _this->SetWindowMenuBar(window, NULL); + + SDL_SetObjectValid(window->menu_bar, SDL_OBJECT_TYPE_MENUBAR, true); + window->menu_bar->window = NULL; + window->menu_bar = NULL; + + return success; + } + + // It's valid to pass a NULL menu_bar even if the Window doesn't currently have one. + return true; } // Same Window/MenuBar combination, no need to do anything. @@ -6188,7 +6193,7 @@ bool SDL_SetWindowMenuBar(SDL_Window* window, SDL_MenuItem* menu_bar) // menu_bar is already on another window, null out the menubar on that window // before we add this menubar to the given window. if (menu_bar->menu_bar.window) { - _this->SetWindowMenuBar(menu_bar->menu_bar.window, NULL); + SDL_SetWindowMenuBar(menu_bar->menu_bar.window, NULL); } SDL_MenuBar *menu_bar_real = (SDL_MenuBar*)menu_bar; @@ -6196,7 +6201,7 @@ bool SDL_SetWindowMenuBar(SDL_Window* window, SDL_MenuItem* menu_bar) // Window has an existing MenuBar, release it back to the user. if (window->menu_bar) { - SDL_SetObjectValid(window->menu_bar, SDL_OBJECT_TYPE_MENUBAR, true); + SDL_SetWindowMenuBar(window, NULL); } window->menu_bar = menu_bar_real; @@ -6250,6 +6255,8 @@ SDL_MenuItem *SDL_CreateMenuItemAt(SDL_MenuItem *menu_as_item, size_t index, con menu_item->common.parent = (SDL_MenuItem *)menu; menu_item->common.menu_bar = menu->item_common.menu_bar; menu_item->common.type = type; + menu_item->common.event_type = event_type; + menu_item->common.enabled = true; if (!_this->CreateMenuItemAt(menu_item, index, label, event_type)) { SDL_free(menu_item); @@ -6387,6 +6394,18 @@ SDL_MenuItemType SDL_GetMenuItemType(SDL_MenuItem *menu_item) return menu_item->common.type; } +Sint32 SDL_GetMenuItemEventType(SDL_MenuItem *menu_item) +{ + CHECK_MENUITEM_MAGIC(menu_item, -1); + + if (menu_item->common.type == SDL_MENUITEM_MENUBAR) { + SDL_SetError("menu_item can't be a SDL_MENUITEM_MENUBAR."); + return -1; + } + + return menu_item->common.event_type; +} + bool SDL_GetMenuItemChecked(SDL_MenuItem *menu_item, bool *checked) { CHECK_MENUITEM_MAGIC(menu_item, false); @@ -6395,35 +6414,47 @@ bool SDL_GetMenuItemChecked(SDL_MenuItem *menu_item, bool *checked) return false; } - return _this->GetMenuItemChecked(menu_item, checked); + return menu_item->checkable.is_checked; } -bool SDL_SetMenuItemChecked(SDL_MenuItem *menu_item, bool enabled) +bool SDL_SetMenuItemChecked(SDL_MenuItem *menu_item, bool checked) { CHECK_MENUITEM_MAGIC(menu_item, false); if (menu_item->common.type != SDL_MENUITEM_CHECKABLE) { - SDL_SetError("menu_item isn't a checkable."); + SDL_SetError("menu_item isn't a SDL_MENUITEM_CHECKABLE."); return false; } - return _this->SetMenuItemChecked(menu_item, enabled); + if (!_this->SetMenuItemChecked(menu_item, checked)) { + return false; + } + + menu_item->checkable.is_checked = checked; + + return true; } bool SDL_GetMenuItemEnabled(SDL_MenuItem *menu_item, bool *enabled) { CHECK_MENUITEM_MAGIC(menu_item, false); - return _this->GetMenuItemEnabled(menu_item, enabled); + return menu_item->common.enabled; } bool SDL_SetMenuItemEnabled(SDL_MenuItem *menu_item, bool enabled) { CHECK_MENUITEM_MAGIC(menu_item, false); - if (menu_item->common.type == SDL_MENUITEM_SUBMENU || menu_item->common.type == SDL_MENUITEM_MENUBAR) { - SDL_SetError("menu_item can't be a Menu."); + if (menu_item->common.type == SDL_MENUITEM_MENUBAR) { + SDL_SetError("A SDL_MENUITEM_MENUBAR can't be disabled."); + return false; + } + + if (!_this->SetMenuItemEnabled(menu_item, enabled)) { return false; } - return _this->SetMenuItemEnabled(menu_item, enabled); + menu_item->common.enabled = enabled; + + return true; } bool SDL_DestroyMenuItem(SDL_MenuItem *menu_item) diff --git a/src/video/cocoa/SDL_cocoavideo.m b/src/video/cocoa/SDL_cocoavideo.m index ba419622367af..2167aed1113e6 100644 --- a/src/video/cocoa/SDL_cocoavideo.m +++ b/src/video/cocoa/SDL_cocoavideo.m @@ -431,14 +431,6 @@ bool Cocoa_SetMenuItemLabel(SDL_MenuItem *menu_item, const char *label) return true; } -bool Cocoa_GetMenuItemChecked(SDL_MenuItem *menu_item, bool *checked) -{ - PlatformMenuData* platform_data = (__bridge PlatformMenuData*)menu_item->common.platform_data; - NSControlStateValue state = [platform_data->menu_item state]; - *checked = state == NSControlStateValueOn; - return true; -} - bool Cocoa_SetMenuItemChecked(SDL_MenuItem *menu_item, bool checked) { NSControlStateValue flag = checked ? NSControlStateValueOn : NSControlStateValueOff; @@ -448,13 +440,6 @@ bool Cocoa_SetMenuItemChecked(SDL_MenuItem *menu_item, bool checked) return true; } -bool Cocoa_GetMenuItemEnabled(SDL_MenuItem *menu_item, bool *enabled) -{ - PlatformMenuData* platform_data = (__bridge PlatformMenuData*)menu_item->common.platform_data; - *enabled = [platform_data->menu_item isEnabled]; - return true; -} - bool Cocoa_SetMenuItemEnabled(SDL_MenuItem *menu_item, bool enabled) { PlatformMenuData* platform_data = (__bridge PlatformMenuData*)menu_item->common.platform_data; diff --git a/src/video/cocoa/SDL_cocoawindow.h b/src/video/cocoa/SDL_cocoawindow.h index 25164d5477838..d283317e1df7d 100644 --- a/src/video/cocoa/SDL_cocoawindow.h +++ b/src/video/cocoa/SDL_cocoawindow.h @@ -200,9 +200,7 @@ extern bool Cocoa_CreateMenuBar(SDL_MenuBar *menu_bar); extern bool Cocoa_SetWindowMenuBar(SDL_Window *window, SDL_MenuBar *menu_bar); extern bool Cocoa_CreateMenuItemAt(SDL_MenuItem *menu_item, size_t index, const char *name, Uint16 event_type); extern bool Cocoa_SetMenuItemLabel(SDL_MenuItem *menu_item, const char *label); -extern bool Cocoa_GetMenuItemChecked(SDL_MenuItem *menu_item, bool *checked); extern bool Cocoa_SetMenuItemChecked(SDL_MenuItem *menu_item, bool checked); -extern bool Cocoa_GetMenuItemEnabled(SDL_MenuItem *menu_item, bool *enabled); extern bool Cocoa_SetMenuItemEnabled(SDL_MenuItem *menu_item, bool enabled); extern bool Cocoa_DestroyMenuItem(SDL_MenuItem *menu_item); diff --git a/src/video/windows/SDL_windowsevents.c b/src/video/windows/SDL_windowsevents.c index 425270de22e3f..faace53645813 100644 --- a/src/video/windows/SDL_windowsevents.c +++ b/src/video/windows/SDL_windowsevents.c @@ -2439,6 +2439,7 @@ LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara event.type = SDL_EVENT_MENU_BUTTON_CLICKED; event.menu.timestamp = SDL_GetTicksNS(); event.menu.user_event_type = (Uint16)command_id; + event.menu.windowID = data->window->id; SDL_PushEvent(&event); } diff --git a/src/video/windows/SDL_windowsvideo.c b/src/video/windows/SDL_windowsvideo.c index 9b5433b66fcec..88fb57c023255 100644 --- a/src/video/windows/SDL_windowsvideo.c +++ b/src/video/windows/SDL_windowsvideo.c @@ -364,9 +364,7 @@ static SDL_VideoDevice *WIN_CreateDevice(void) device->SetWindowMenuBar = Win32_SetWindowMenuBar; device->CreateMenuItemAt = Win32_CreateMenuItemAt; device->SetMenuItemLabel = Win32_SetMenuItemLabel; - device->GetMenuItemChecked = Win32_GetMenuItemChecked; device->SetMenuItemChecked = Win32_SetMenuItemChecked; - device->GetMenuItemEnabled = Win32_GetMenuItemEnabled; device->SetMenuItemEnabled = Win32_SetMenuItemEnabled; device->DestroyMenuItem = Win32_DestroyMenuItem; #endif @@ -1027,21 +1025,6 @@ bool Win32_SetMenuItemLabel(SDL_MenuItem* menu_item, const char* label) return success; } -bool Win32_GetMenuItemChecked(SDL_MenuItem *menu_item, bool *checked) -{ - PlatformMenuData *platform_data = (PlatformMenuData *)menu_item->common.platform_data; - Uint32 i = SDL_GetIndexInMenu(menu_item); - - UINT flags = GetMenuState(platform_data->owner_handle, i, MF_BYPOSITION); - - if (flags == -1) { - return WIN_SetError("Unable to get menu_item check state."); - } - - *checked = flags &MF_CHECKED; - return true; -} - bool Win32_SetMenuItemChecked(SDL_MenuItem *menu_item, bool checked) { PlatformMenuData *platform_data = (PlatformMenuData *)menu_item->common.platform_data; @@ -1049,29 +1032,13 @@ bool Win32_SetMenuItemChecked(SDL_MenuItem *menu_item, bool checked) UINT32 flag = checked ? MF_CHECKED : MF_UNCHECKED; - if (!CheckMenuItem(platform_data->owner_handle, i, MF_BYPOSITION | flag)) { + if (CheckMenuItem(platform_data->owner_handle, i, MF_BYPOSITION | flag) == -1) { return WIN_SetError("Unable to check menu item."); } return true; } -bool Win32_GetMenuItemEnabled(SDL_MenuItem *menu_item, bool *enabled) -{ - PlatformMenuData *platform_data = (PlatformMenuData *)menu_item->common.platform_data; - Uint32 i = SDL_GetIndexInMenu(menu_item); - - UINT flags = GetMenuState(platform_data->owner_handle, i, MF_BYPOSITION); - - if (flags == -1) { - return WIN_SetError("Unable to get menu_item check state."); - } - - *enabled = !(flags & MF_GRAYED); - - return true; -} - bool Win32_SetMenuItemEnabled(SDL_MenuItem *menu_item, bool enabled) { PlatformMenuData *platform_data = (PlatformMenuData *)menu_item->common.platform_data; @@ -1079,7 +1046,7 @@ bool Win32_SetMenuItemEnabled(SDL_MenuItem *menu_item, bool enabled) UINT32 flag = enabled ? MF_ENABLED : MF_GRAYED; - if (!EnableMenuItem(platform_data->owner_handle, i, MF_BYPOSITION | flag)) { + if (EnableMenuItem(platform_data->owner_handle, i, MF_BYPOSITION | flag) == -1) { return WIN_SetError("Unable to enable menu item."); } diff --git a/src/video/windows/SDL_windowsvideo.h b/src/video/windows/SDL_windowsvideo.h index 93d26fa45746b..ff060460628c3 100644 --- a/src/video/windows/SDL_windowsvideo.h +++ b/src/video/windows/SDL_windowsvideo.h @@ -677,9 +677,7 @@ extern bool Win32_SetWindowMenuBar(SDL_Window *window, SDL_MenuBar *menu_bar); extern bool Win32_CreateMenuItemAt(SDL_MenuItem *menu_item, size_t index, const char *name, Uint16 event_type); extern bool Win32_SetMenuItemLabel(SDL_MenuItem *menu_item, const char *label); -extern bool Win32_GetMenuItemChecked(SDL_MenuItem *menu_item, bool *checked); extern bool Win32_SetMenuItemChecked(SDL_MenuItem *menu_item, bool checked); -extern bool Win32_GetMenuItemEnabled(SDL_MenuItem *menu_item, bool *enabled); extern bool Win32_SetMenuItemEnabled(SDL_MenuItem *menu_item, bool enabled); extern bool Win32_DestroyMenuItem(SDL_MenuItem *menu_item); From 60c0c443fcbb4b216180816cbb90c7e04dc0f9f8 Mon Sep 17 00:00:00 2001 From: "Joshua T. Fisher" Date: Mon, 6 Oct 2025 00:59:41 -0700 Subject: [PATCH 29/35] Forgot to delete these when on windows. --- src/video/cocoa/SDL_cocoavideo.m | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/video/cocoa/SDL_cocoavideo.m b/src/video/cocoa/SDL_cocoavideo.m index 2167aed1113e6..ca59606230e84 100644 --- a/src/video/cocoa/SDL_cocoavideo.m +++ b/src/video/cocoa/SDL_cocoavideo.m @@ -130,9 +130,7 @@ static void Cocoa_DeleteDevice(SDL_VideoDevice *device) device->SetWindowMenuBar = Cocoa_SetWindowMenuBar; device->CreateMenuItemAt = Cocoa_CreateMenuItemAt; device->SetMenuItemLabel = Cocoa_SetMenuItemLabel; - device->GetMenuItemChecked = Cocoa_GetMenuItemChecked; device->SetMenuItemChecked = Cocoa_SetMenuItemChecked; - device->GetMenuItemEnabled = Cocoa_GetMenuItemEnabled; device->SetMenuItemEnabled = Cocoa_SetMenuItemEnabled; device->DestroyMenuItem = Cocoa_DestroyMenuItem; From cdbf61924099baf11780c1af4cc6e700aa1ac25c Mon Sep 17 00:00:00 2001 From: "Joshua T. Fisher" Date: Mon, 6 Oct 2025 01:12:18 -0700 Subject: [PATCH 30/35] Swapping is just kind of weird on MacOS. Doesn't change until clicking away and back. --- src/video/cocoa/SDL_cocoavideo.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/video/cocoa/SDL_cocoavideo.m b/src/video/cocoa/SDL_cocoavideo.m index ca59606230e84..0e4368191bc6b 100644 --- a/src/video/cocoa/SDL_cocoavideo.m +++ b/src/video/cocoa/SDL_cocoavideo.m @@ -376,7 +376,8 @@ bool Cocoa_SetWindowMenuBar(SDL_Window *window, SDL_MenuBar *menu_bar) } // We don't actually set the menubar until the window is in focus - if (!menu_bar->window->keyboard_focus) { + + if (!(menu_bar->window->flags & SDL_WINDOW_INPUT_FOCUS)) { return true; } From 1e01ce454984b2d676449c05f22828afe96fb556 Mon Sep 17 00:00:00 2001 From: "Joshua T. Fisher" Date: Mon, 6 Oct 2025 01:21:00 -0700 Subject: [PATCH 31/35] Fix up missing symbols. --- src/dynapi/SDL_dynapi.sym | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index 64fe4daa8cf33..c4f155ff357a7 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -1279,6 +1279,8 @@ SDL3_0.0.0 { SDL_GetMenuItemChecked; SDL_GetMenuItemEnabled; SDL_SetMenuItemEnabled; + SDL_GetMenuItemLabel; + SDL_SetMenuItemLabel; SDL_DestroyMenuItem; # extra symbols go here (don't modify this line) local: *; From 934adb57b57adc434816c60593a82e37027bc07e Mon Sep 17 00:00:00 2001 From: "Joshua T. Fisher" Date: Mon, 6 Oct 2025 01:43:48 -0700 Subject: [PATCH 32/35] MacOS: Send window with event, fixed non-updating flip flop issue. --- src/video/cocoa/SDL_cocoavideo.h | 3 ++- src/video/cocoa/SDL_cocoavideo.m | 22 ++++++++++++++++------ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/video/cocoa/SDL_cocoavideo.h b/src/video/cocoa/SDL_cocoavideo.h index c50783c2eff1b..0358a379c1bac 100644 --- a/src/video/cocoa/SDL_cocoavideo.h +++ b/src/video/cocoa/SDL_cocoavideo.h @@ -66,9 +66,10 @@ typedef enum @interface PlatformMenuData : NSObject { @public - Uint16 user_event_type; + SDL_MenuBar *menu_bar; NSMenu *menu; NSMenuItem *menu_item; + Uint16 user_event_type; } - (void) Cocoa_PlatformMenuData_MenuButtonClicked: (id)sender; diff --git a/src/video/cocoa/SDL_cocoavideo.m b/src/video/cocoa/SDL_cocoavideo.m index 0e4368191bc6b..cebfef1015921 100644 --- a/src/video/cocoa/SDL_cocoavideo.m +++ b/src/video/cocoa/SDL_cocoavideo.m @@ -344,6 +344,7 @@ - (void) Cocoa_PlatformMenuData_MenuButtonClicked: (id)sender;{ event.type = SDL_EVENT_MENU_BUTTON_CLICKED; event.menu.timestamp = SDL_GetTicksNS(); event.menu.user_event_type = user_event_type; + event.menu.windowID = menu_bar->window->id; SDL_PushEvent(&event); } @@ -355,13 +356,20 @@ bool Cocoa_CreateMenuBar(SDL_MenuBar *menu_bar) PlatformMenuData* platform_menu =[PlatformMenuData new]; platform_menu->menu = [NSMenu new]; - NSMenuItem *appMenuItem = [NSMenuItem new]; - NSMenu *appMenu = [NSMenu new]; - [appMenu setAutoenablesItems:false]; + SDL_MenuItem* app_menu = SDL_calloc_REAL(1, sizeof(SDL_MenuItem)); + app_menu->common.type = SDL_MENUITEM_SUBMENU; + app_menu->common.enabled = true; + app_menu->common.parent = (SDL_MenuItem*)menu_bar; - [appMenuItem setSubmenu:appMenu]; + PlatformMenuData* app_menu_platform_data = [PlatformMenuData new]; + app_menu->common.platform_data = (void*)CFBridgingRetain(app_menu_platform_data); - [platform_menu->menu addItem:appMenuItem]; + app_menu_platform_data->menu = [NSMenu new]; + app_menu_platform_data->menu_item = [NSMenuItem new]; + [app_menu_platform_data->menu setAutoenablesItems:false]; + [app_menu_platform_data->menu_item setSubmenu:app_menu_platform_data->menu]; + + [platform_menu->menu addItem:app_menu_platform_data->menu_item]; menu_bar->common.item_common.platform_data = (void*)CFBridgingRetain(platform_menu); @@ -376,11 +384,12 @@ bool Cocoa_SetWindowMenuBar(SDL_Window *window, SDL_MenuBar *menu_bar) } // We don't actually set the menubar until the window is in focus - if (!(menu_bar->window->flags & SDL_WINDOW_INPUT_FOCUS)) { return true; } + // If we don't set it to nil first, it won't change menu bars until you switch off and back on focus. + [NSApp setMainMenu:nil]; PlatformMenuData* platform_data = (__bridge PlatformMenuData*)menu_bar->common.item_common.platform_data; [NSApp setMainMenu:platform_data->menu]; @@ -392,6 +401,7 @@ bool Cocoa_CreateMenuItemAt(SDL_MenuItem *menu_item, size_t index, const char *n PlatformMenuData* platform_data = [PlatformMenuData new]; menu_item->common.platform_data = (void*)CFBridgingRetain(platform_data); platform_data->user_event_type = event_type; + platform_data->menu_bar = menu_item->common.menu_bar; PlatformMenuData* parent_platform_data = (__bridge id _Nullable)(menu_item->common.parent->common.platform_data); NSString* name_ns = [NSString stringWithUTF8String:name]; From ffabd7514c7dbf521d71c60de0719cca928b78e1 Mon Sep 17 00:00:00 2001 From: "Joshua T. Fisher" Date: Mon, 6 Oct 2025 02:02:27 -0700 Subject: [PATCH 33/35] Most of the App Menu Stuff, need to think about destruction. --- examples/video/01-menubar/menubar.c | 16 ++++++++++++++-- include/SDL3/SDL_video.h | 2 +- src/video/SDL_video.c | 6 +++--- src/video/cocoa/SDL_cocoavideo.m | 16 ++++++++-------- 4 files changed, 26 insertions(+), 14 deletions(-) diff --git a/examples/video/01-menubar/menubar.c b/examples/video/01-menubar/menubar.c index 2d349423dcacc..052f096dccb24 100644 --- a/examples/video/01-menubar/menubar.c +++ b/examples/video/01-menubar/menubar.c @@ -106,7 +106,13 @@ void CreateMenuBar_1() // We can't create a top level checkable . SDL_assert(!SDL_CreateMenuItem(menu_bar_1, "Incognito", SDL_MENUITEM_CHECKABLE, MENU_BAR_INCOGNITO)); - SDL_CreateMenuItem(menu_bar_1, "Exit", SDL_MENUITEM_BUTTON, MENU_BAR_EXIT); + SDL_MenuItem* app_menu = SDL_GetMenuBarAppMenu(menu_bar_1); + if (app_menu) { + SDL_assert(!SDL_CreateMenuItem(menu_bar_1, "Exit", SDL_MENUITEM_BUTTON, MENU_BAR_EXIT)); + SDL_CreateMenuItem(app_menu, "Exit", SDL_MENUITEM_BUTTON, MENU_BAR_EXIT); + } else { + SDL_CreateMenuItem(menu_bar_1, "Exit", SDL_MENUITEM_BUTTON, MENU_BAR_EXIT); + } } SDL_SetWindowMenuBar(window_1, menu_bar_1); @@ -147,7 +153,13 @@ void CreateMenuBar_2() // We can't create a top level checkable . SDL_assert(!SDL_CreateMenuItem(menu_bar_2, "Incognito_2", SDL_MENUITEM_CHECKABLE, MENU_BAR_INCOGNITO)); - SDL_CreateMenuItem(menu_bar_2, "Exit", SDL_MENUITEM_BUTTON, MENU_BAR_EXIT); + SDL_MenuItem* app_menu = SDL_GetMenuBarAppMenu(menu_bar_2); + if (app_menu) { + SDL_assert(!SDL_CreateMenuItem(menu_bar_2, "Exit", SDL_MENUITEM_BUTTON, MENU_BAR_EXIT)); + SDL_CreateMenuItem(app_menu, "Exit", SDL_MENUITEM_BUTTON, MENU_BAR_EXIT); + } else { + SDL_CreateMenuItem(menu_bar_2, "Exit", SDL_MENUITEM_BUTTON, MENU_BAR_EXIT); + } } SDL_SetWindowMenuBar(window_2, menu_bar_2); diff --git a/include/SDL3/SDL_video.h b/include/SDL3/SDL_video.h index 43bd5d23fa14b..d205a5ef72543 100644 --- a/include/SDL3/SDL_video.h +++ b/include/SDL3/SDL_video.h @@ -3062,7 +3062,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_DisableScreenSaver(void); extern SDL_DECLSPEC SDL_MenuItem *SDL_CreateMenuBar(); -#define SDL_PROP_MENUITEM_CREATE_LABEL "SDL.menuitem.create.label" +extern SDL_DECLSPEC SDL_MenuItem *SDL_GetMenuBarAppMenu(SDL_MenuItem *menu_bar); /** * menu_as_item must be a SDL_MENUITEM_MENUBAR or SDL_MENUITEM_SUBMENU diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c index 565da6cc9f6f5..8b0dc1e69171d 100644 --- a/src/video/SDL_video.c +++ b/src/video/SDL_video.c @@ -6321,15 +6321,15 @@ SDL_MenuItem *SDL_CreateMenuItem(SDL_MenuItem *menu_bar_as_item, const char *lab } -SDL_MenuItem *SDL_GetMenuBarAppMenu(SDL_MenuBar *menu_bar) +SDL_MenuItem *SDL_GetMenuBarAppMenu(SDL_MenuItem *menu_bar) { CHECK_MENUITEM_MAGIC(menu_bar, NULL); - if (!menu_bar->app_menu) { + if (!menu_bar->menu_bar.app_menu) { SDL_SetError("This platform doesn't support an Application menu."); } - return menu_bar->app_menu; + return menu_bar->menu_bar.app_menu; } Sint64 SDL_GetMenuChildItems(SDL_MenuItem *menu_as_item) diff --git a/src/video/cocoa/SDL_cocoavideo.m b/src/video/cocoa/SDL_cocoavideo.m index cebfef1015921..72bad60a634fa 100644 --- a/src/video/cocoa/SDL_cocoavideo.m +++ b/src/video/cocoa/SDL_cocoavideo.m @@ -360,6 +360,7 @@ bool Cocoa_CreateMenuBar(SDL_MenuBar *menu_bar) app_menu->common.type = SDL_MENUITEM_SUBMENU; app_menu->common.enabled = true; app_menu->common.parent = (SDL_MenuItem*)menu_bar; + app_menu->common.menu_bar = menu_bar; PlatformMenuData* app_menu_platform_data = [PlatformMenuData new]; app_menu->common.platform_data = (void*)CFBridgingRetain(app_menu_platform_data); @@ -372,6 +373,7 @@ bool Cocoa_CreateMenuBar(SDL_MenuBar *menu_bar) [platform_menu->menu addItem:app_menu_platform_data->menu_item]; menu_bar->common.item_common.platform_data = (void*)CFBridgingRetain(platform_menu); + menu_bar->app_menu = app_menu; return true; } @@ -398,6 +400,11 @@ bool Cocoa_SetWindowMenuBar(SDL_Window *window, SDL_MenuBar *menu_bar) bool Cocoa_CreateMenuItemAt(SDL_MenuItem *menu_item, size_t index, const char *name, Uint16 event_type) { + if ((menu_item->common.parent->common.type == SDL_MENUITEM_MENUBAR) && (menu_item->common.type != SDL_MENUITEM_SUBMENU)) { + SDL_SetError("No top level Checkables or Buttons on this platform"); + return false; + } + PlatformMenuData* platform_data = [PlatformMenuData new]; menu_item->common.platform_data = (void*)CFBridgingRetain(platform_data); platform_data->user_event_type = event_type; @@ -420,14 +427,7 @@ bool Cocoa_CreateMenuItemAt(SDL_MenuItem *menu_item, size_t index, const char *n [platform_data->menu_item setAction:@selector(Cocoa_PlatformMenuData_MenuButtonClicked:)]; [platform_data->menu_item setTarget:platform_data]; [platform_data->menu_item setEnabled:true]; - - if ((menu_item->common.parent->common.type == SDL_MENUITEM_MENUBAR) && (menu_item->common.type != SDL_MENUITEM_SUBMENU)) { - NSMenu* app_menu = [[parent_platform_data->menu itemAtIndex:(NSInteger)0] submenu]; - [app_menu addItem:platform_data->menu_item]; - - } else { - [parent_platform_data->menu insertItem:platform_data->menu_item atIndex:(NSInteger)index]; - } + [parent_platform_data->menu insertItem:platform_data->menu_item atIndex:(NSInteger)index]; } return true; } From 17ce1d7f012d54effb1ace9e97200430b407f681 Mon Sep 17 00:00:00 2001 From: "Joshua T. Fisher" Date: Mon, 6 Oct 2025 21:03:22 -0700 Subject: [PATCH 34/35] Did a documentation pass, worked on appmenu destruction, fixed bugs. --- examples/video/01-menubar/menubar.c | 6 + include/SDL3/SDL_video.h | 225 ++++++++++++++++++++++++++-- src/video/SDL_video.c | 32 +++- src/video/cocoa/SDL_cocoavideo.m | 12 ++ 4 files changed, 260 insertions(+), 15 deletions(-) diff --git a/examples/video/01-menubar/menubar.c b/examples/video/01-menubar/menubar.c index 052f096dccb24..09eddd7582fe2 100644 --- a/examples/video/01-menubar/menubar.c +++ b/examples/video/01-menubar/menubar.c @@ -55,6 +55,12 @@ void PrintMenuItems(SDL_Renderer* renderer, SDL_MenuItem *menu_item, int indent, return; } + SDL_MenuItem *app_menu = SDL_GetMenuBarAppMenu(menu_item); + if (app_menu) { + SDL_RenderDebugText(renderer, (float)(8 * indent * 2), (float)(*total_index * 8), " -> AppMenu"); + ++(*total_index); + } + const char* label = SDL_GetMenuItemLabel(menu_item); if (!label) { diff --git a/include/SDL3/SDL_video.h b/include/SDL3/SDL_video.h index d205a5ef72543..887169ce1cab1 100644 --- a/include/SDL3/SDL_video.h +++ b/include/SDL3/SDL_video.h @@ -2975,11 +2975,34 @@ extern SDL_DECLSPEC float SDLCALL SDL_GetWindowProgressValue(SDL_Window *window) * Assigns a menu_bar to the given window, which will take ownership of it's destruction. NULL * releases the menu_bar without destroying it. */ + +/** + * Gets the value of the menu bar for the given window. + * + * \param window the window to retrieve the Menu Bar of. + * \returns A pointer to an SDL_MenuBar on success or NULL on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should only be called on the main thread. + * + * \since This function is available since SDL 3.6.0. + */ extern SDL_DECLSPEC SDL_MenuItem *SDL_GetWindowMenuBar(SDL_Window *window); /** - * Assigns a menu_bar to the given window, which will take ownership of it's destruction. NULL - * releases the menu_bar without destroying it. + * Gets a menu bar for the given window, a NULL menu_bar will unset a menu_bar on the given window. + * + * The window will take ownership over this menu_bar, handling destruction of it it the window + * is destroyed. Ownership will be released back to the user if this function is called again, + * so make sure to keep a reference to it if you plan to call this function more than once. + * + * \param window the window to assign the menu_bar to. + * \param menu_bar the window to retrieve the Menu Bar of. + * \returns true on success, or false on failure; call SDL_GetError() for more information. + * + * \threadsafety This function should only be called on the main thread. + * + * \since This function is available since SDL 3.6.0. */ extern SDL_DECLSPEC bool SDL_SetWindowMenuBar(SDL_Window *window, SDL_MenuItem *menu_bar); @@ -3060,49 +3083,223 @@ extern SDL_DECLSPEC bool SDLCALL SDL_EnableScreenSaver(void); */ extern SDL_DECLSPEC bool SDLCALL SDL_DisableScreenSaver(void); +/** + * Creates an empty menu bar, on platforms that support it, also creates an empty app menu. + * + * \returns a pointer to a menu bar, or -1 on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should only be called on the main thread. + * + * \since This function is available since SDL 3.6.0. + */ extern SDL_DECLSPEC SDL_MenuItem *SDL_CreateMenuBar(); +/** + * Gets the app menu of the given menu bar, must be SDL_MENUITEM_MENUBAR + * + * Most platforms do not have an App Menu, notably MacOS does. + * + * \param menu_bar the menu item to get the app menu of. + * \returns a pointer to the app menu, or NULL on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should only be called on the main thread. + * + * \since This function is available since SDL 3.6.0. + */ extern SDL_DECLSPEC SDL_MenuItem *SDL_GetMenuBarAppMenu(SDL_MenuItem *menu_bar); /** - * menu_as_item must be a SDL_MENUITEM_MENUBAR or SDL_MENUITEM_SUBMENU - * event_type will be ignored if type == SDL_MENUITEM_SUBMENU - * On MacOS, buttoms created under a menubar will go into the "App" submenu + * Gets the app menu of the given menu bar, must be SDL_MENUITEM_MENUBAR + * + * \param menu_as_item must be a SDL_MENUITEM_MENUBAR or SDL_MENUITEM_SUBMENU + * \param index place to put the new menu item under the menu_as_item. + * \param label label to use for the given menu_item. + * \param type type of menu item to create, cannot create an SDL_MENUITEM_MENUBAR. + * \param event_type the event number you'll be passed in SDL_MenuEvent when this menu + * item is clicked, ignored when type == SDL_MENUITEM_SUBMENU. + * \returns a pointer to the app menu, or NULL on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should only be called on the main thread. + * + * \since This function is available since SDL 3.6.0. */ -extern SDL_DECLSPEC SDL_MenuItem *SDL_CreateMenuItemAt(SDL_MenuItem *menu_as_item, size_t index, const char *Label, SDL_MenuItemType type, Uint16 event_type); +extern SDL_DECLSPEC SDL_MenuItem *SDL_CreateMenuItemAt(SDL_MenuItem *menu_as_item, size_t index, const char *label, SDL_MenuItemType type, Uint16 event_type); /** - * menu_as_item must be a SDL_MENUITEM_MENUBAR or SDL_MENUITEM_SUBMENU - * event_type will be ignored if type == SDL_MENUITEM_SUBMENU - * On MacOS, buttoms created under a menubar will go into the "App" submenu + * Gets the app menu of the given menu bar, must be SDL_MENUITEM_MENUBAR + * + * Item will be placed at the end of the list of children of the menu_as_item. + * + * \param menu_as_item must be a SDL_MENUITEM_MENUBAR or SDL_MENUITEM_SUBMENU + * \param label label to use for the given menu_item. + * \param type type of menu item to create, cannot create an SDL_MENUITEM_MENUBAR. + * \param event_type the event number you'll be passed in SDL_MenuEvent when this menu + * item is clicked, ignored when type == SDL_MENUITEM_SUBMENU. + * \returns a pointer to the app menu, or NULL on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should only be called on the main thread. + * + * \since This function is available since SDL 3.6.0. */ -extern SDL_DECLSPEC SDL_MenuItem *SDL_CreateMenuItem(SDL_MenuItem *menu_as_item, const char *Label, SDL_MenuItemType type, Uint16 event_type); +extern SDL_DECLSPEC SDL_MenuItem *SDL_CreateMenuItem(SDL_MenuItem *menu_as_item, const char *label, SDL_MenuItemType type, Uint16 event_type); /** - * -1 on error + * Gets the number of children of the given menu, must be SDL_MENUITEM_MENUBAR or SDL_MENUITEM_SUBMENU + * + * \param menu_item the menu item to get the label of. + * \returns the number of children of the given menu, or -1 on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should only be called on the main thread. + * + * \since This function is available since SDL 3.6.0. */ extern SDL_DECLSPEC Sint64 SDL_GetMenuChildItems(SDL_MenuItem *menu_as_item); + +/** + * Gets child of the given menu at index, menu_as_item must be SDL_MENUITEM_MENUBAR or + * SDL_MENUITEM_SUBMENU. + * + * \param menu_as_item the menu item to get the child of. + * \param index of the child to get. + * \returns the number of children of the given menu, or -1 on failure; call SDL_GetError() for more + * information. + * + * \threadsafety This function should only be called on the main thread. + * + * \since This function is available since SDL 3.6.0. + */ extern SDL_DECLSPEC SDL_MenuItem *SDL_GetMenuChildItem(SDL_MenuItem *menu_as_item, size_t index); + +/** + * Gets the given menu_items label. + * + * If called on an SDL_MENUITEM_MENUBAR or an App Menu, the return will be an empty string. + * + * \param menu_item the menu item to get the label of. + * \returns a pointer to the label of the given menu_item, or NULL on failure; + * call SDL_GetError() for more information. + * + * \threadsafety This function should only be called on the main thread. + * + * \since This function is available since SDL 3.6.0. + */ extern SDL_DECLSPEC const char *SDL_GetMenuItemLabel(SDL_MenuItem *menu_item); + +/** + * Sets the given menu_items label. + * + * Cannot set the label of a SDL_MENUITEM_MENUBAR or an App Menu. + * + * \param menu_item the menu item to set the label of. + * \param label the string to set for the menu_items label.. + * \returns a pointer to the label of the given menu_item, or NULL on failure; + * call SDL_GetError() for more information. + * + * \threadsafety This function should only be called on the main thread. + * + * \since This function is available since SDL 3.6.0. + */ extern SDL_DECLSPEC bool SDL_SetMenuItemLabel(SDL_MenuItem *menu_item, const char *label); + + +/** + * Gets the given menu_items SDL_MenuItemType. + * + * \param menu_item the menu item to get the SDL_MenuItemType of. + * \returns A valid SDL_MenuItemType on success, or SDL_MENUITEM_INVALID on failure; + * call SDL_GetError() for more information. + * + * \threadsafety This function should only be called on the main thread. + * + * \since This function is available since SDL 3.6.0. + */ extern SDL_DECLSPEC SDL_MenuItemType SDL_GetMenuItemType(SDL_MenuItem *menu_item); + +/** + * Gets the given menu_items user event type. + * + * \param menu_item the menu item to get the user type of. + * \returns >= 0 on success, or -1 on failure; call SDL_GetError() for more information. + * + * \threadsafety This function should only be called on the main thread. + * + * \since This function is available since SDL 3.6.0. + */ extern SDL_DECLSPEC Sint32 SDL_GetMenuItemEventType(SDL_MenuItem *menu_item); /** - * Must be a SDL_MENUITEM_CHECKABLE + * Gets the given menu_items (which must be an SDL_MENUITEM_CHECKABLE) checked state. + * + * \param menu_item the menu item to check the state of. + * \param checked pointer to variable to populate with the menu_items checked state. + * \returns true on success, or false on failure; call SDL_GetError() for more information. + * + * \threadsafety This function should only be called on the main thread. + * + * \since This function is available since SDL 3.6.0. */ extern SDL_DECLSPEC bool SDL_GetMenuItemChecked(SDL_MenuItem *menu_item, bool *checked); /** - * Must be a SDL_MENUITEM_CHECKABLE + * Sets the given menu_item (which must be an SDL_MENUITEM_CHECKABLE) to be checked or unchecked. + * + * \param menu_item the menu item to have it's state changed. + * \param checked the value to set for if the menu_item is checked or unchecked. + * \returns true on success, or false on failure; call SDL_GetError() for more information. + * + * \threadsafety This function should only be called on the main thread. + * + * \since This function is available since SDL 3.6.0. */ extern SDL_DECLSPEC bool SDL_SetMenuItemChecked(SDL_MenuItem *menu_item, bool checked); - +/** + * Gets the given menu_items enabled or disabled state. + * + * \param menu_item the menu item to check the state of. + * \param enabled pointer to variable to populate with the menu_items enabled state. + * \returns true on success, or false on failure; call SDL_GetError() for more information. + * + * \threadsafety This function should only be called on the main thread. + * + * \since This function is available since SDL 3.6.0. + */ extern SDL_DECLSPEC bool SDL_GetMenuItemEnabled(SDL_MenuItem *menu_item, bool *enabled); + +/** + * Sets the given menu_item to be enabled or disabled. + * + * \param menu_item the menu item to have it's state changed. + * \param enabled the value to set for if the menu_item is enabled or disabled. + * \returns true on success, or false on failure; call SDL_GetError() for more information. + * + * \threadsafety This function should only be called on the main thread. + * + * \since This function is available since SDL 3.6.0. + */ extern SDL_DECLSPEC bool SDL_SetMenuItemEnabled(SDL_MenuItem *menu_item, bool enabled); +/** + * Destroys the given menu_item and all of it's children. + * + * If called on an SDL_MENUITEM_MENUBAR, the menu bar will be unset from a window it may be + * set to. If called on an SDL_MENUITEM_SUBMENU that happens to be the AppMenu for a menu + * bar, only it's children will be destroyed, not the AppMenu itself. If called on anything + * else, the item will be removed from menu tree it's part of. + * + * \param menu_item the menu item to be destroyed. + * \returns true on success, or false on failure; call SDL_GetError() for more information. + * + * \threadsafety This function should only be called on the main thread. + * + * \since This function is available since SDL 3.6.0. + */ extern SDL_DECLSPEC bool SDL_DestroyMenuItem(SDL_MenuItem *menu_item); /** diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c index 8b0dc1e69171d..a1c5c4e928bf5 100644 --- a/src/video/SDL_video.c +++ b/src/video/SDL_video.c @@ -4471,7 +4471,10 @@ void SDL_DestroyWindow(SDL_Window *window) SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_DESTROYED, 0, 0); - SDL_DestroyMenuItem((SDL_MenuItem*)window->menu_bar); + if (window->menu_bar) { + SDL_DestroyMenuItem((SDL_MenuItem *)window->menu_bar); + window->menu_bar = NULL; + } SDL_DestroyWindowSurface(window); @@ -6244,6 +6247,11 @@ SDL_MenuItem *SDL_CreateMenuItemAt(SDL_MenuItem *menu_as_item, size_t index, con return false; } + if (!label) { + SDL_SetError("Label cannot be null"); + return NULL; + } + SDL_Menu_CommonData *menu = (SDL_Menu_CommonData *)menu_as_item; if (menu->num_children < (Sint64)index) { @@ -6370,12 +6378,29 @@ SDL_MenuItem *SDL_GetMenuChildItem(SDL_MenuItem *menu_as_item, size_t index) const char *SDL_GetMenuItemLabel(SDL_MenuItem *menu_item) { CHECK_MENUITEM_MAGIC(menu_item, NULL); + + if (menu_item->common.menu_bar->app_menu == menu_item || + menu_item->common.type == SDL_MENUITEM_MENUBAR) { + return ""; + } + return menu_item->common.label; } bool SDL_SetMenuItemLabel(SDL_MenuItem *menu_item, const char *label) { CHECK_MENUITEM_MAGIC(menu_item, false); + + if (menu_item->common.menu_bar->app_menu == menu_item) { + SDL_SetError("Cannot set a label on an app menu."); + return false; + } + + if (menu_item->common.type == SDL_MENUITEM_MENUBAR) { + SDL_SetError("Cannot set a label on an SDL_MENUITEM_MENUBAR."); + return false; + } + if (_this->SetMenuItemLabel(menu_item, label)) { if (menu_item->common.label) { SDL_free((void*)menu_item->common.label); @@ -6481,6 +6506,11 @@ bool SDL_DestroyMenuItem(SDL_MenuItem *menu_item) } } + // App Menus cannot be destroyed by the user, only their children can, so we can exit out here. + if (menu_item == menu_item->common.menu_bar->app_menu) { + return true; + } + if (!_this->DestroyMenuItem(menu_item)) { SDL_SetError("Failed to destroy Menu Item"); return false; diff --git a/src/video/cocoa/SDL_cocoavideo.m b/src/video/cocoa/SDL_cocoavideo.m index 72bad60a634fa..6179d68623261 100644 --- a/src/video/cocoa/SDL_cocoavideo.m +++ b/src/video/cocoa/SDL_cocoavideo.m @@ -459,6 +459,18 @@ bool Cocoa_SetMenuItemEnabled(SDL_MenuItem *menu_item, bool enabled) bool Cocoa_DestroyMenuItem(SDL_MenuItem *menu_item) { + if (menu_item->common.type == SDL_MENUITEM_MENUBAR) { + SDL_DestroyMenuItem(menu_item->menu_bar.app_menu); + + // The abstract funtion above won't actually delete the app_menu, so take care of the + // platform side of it here. + Cocoa_DestroyMenuItem(menu_item->menu_bar.app_menu); + + // And now we're safe to free the app_menu itself and NULL it out. + SDL_free(menu_item->menu_bar.app_menu); + menu_item->menu_bar.app_menu = NULL; + } + PlatformMenuData* platform_data = CFBridgingRelease(menu_item->common.platform_data); menu_item->common.platform_data = NULL; PlatformMenuData* parent_platform_data = (__bridge PlatformMenuData*)(menu_item->common.parent->common.platform_data); From b563968a17d827f0a016a426173ac7e25aaf369b Mon Sep 17 00:00:00 2001 From: "Joshua T. Fisher" Date: Mon, 6 Oct 2025 22:44:49 -0700 Subject: [PATCH 35/35] Fixup CI --- examples/video/01-menubar/menubar.c | 45 +++++++++++++++++---------- include/SDL3/SDL_video.h | 2 +- src/dynapi/SDL_dynapi.sym | 1 - src/video/SDL_video.c | 6 ++++ src/video/cocoa/SDL_cocoavideo.m | 2 -- src/video/windows/SDL_windowsevents.c | 1 + 6 files changed, 37 insertions(+), 20 deletions(-) diff --git a/examples/video/01-menubar/menubar.c b/examples/video/01-menubar/menubar.c index 09eddd7582fe2..966e8ab0f7da5 100644 --- a/examples/video/01-menubar/menubar.c +++ b/examples/video/01-menubar/menubar.c @@ -51,16 +51,14 @@ static SDL_EventType_MenuExt EVENT_START = (SDL_EventType_MenuExt)0; void PrintMenuItems(SDL_Renderer* renderer, SDL_MenuItem *menu_item, int indent, int *total_index) { + SDL_MenuItem *app_menu = NULL; + Sint64 item_count = 0; + Sint64 i = 0; + if (!menu_item) { return; } - SDL_MenuItem *app_menu = SDL_GetMenuBarAppMenu(menu_item); - if (app_menu) { - SDL_RenderDebugText(renderer, (float)(8 * indent * 2), (float)(*total_index * 8), " -> AppMenu"); - ++(*total_index); - } - const char* label = SDL_GetMenuItemLabel(menu_item); if (!label) { @@ -68,13 +66,28 @@ void PrintMenuItems(SDL_Renderer* renderer, SDL_MenuItem *menu_item, int indent, } SDL_RenderDebugText(renderer, (float)(8 * indent * 2), (float)(*total_index * 8), label); - ++(*total_index); + + + if (SDL_GetMenuItemType(menu_item) == SDL_MENUITEM_MENUBAR) { + app_menu = SDL_GetMenuBarAppMenu(menu_item); + + if (app_menu) { + SDL_RenderDebugText(renderer, (float)(8 * (indent + 1) * 2), (float)(*total_index * 8), " -> AppMenu"); + ++(*total_index); + + item_count = SDL_GetMenuChildItems(app_menu); + + for (i = 0; i < item_count; ++i) { + PrintMenuItems(renderer, SDL_GetMenuChildItem(app_menu, (size_t)i), indent + 2, total_index); + } + } + } - size_t item_count = SDL_GetMenuChildItems(menu_item); + item_count = SDL_GetMenuChildItems(menu_item); - for (size_t i = 0; i < item_count; ++i) { - PrintMenuItems(renderer, SDL_GetMenuChildItem(menu_item, i), indent + 1, total_index); + for (i = 0; i < item_count; ++i) { + PrintMenuItems(renderer, SDL_GetMenuChildItem(menu_item, (size_t)i), indent + 1, total_index); } } @@ -114,10 +127,10 @@ void CreateMenuBar_1() SDL_MenuItem* app_menu = SDL_GetMenuBarAppMenu(menu_bar_1); if (app_menu) { - SDL_assert(!SDL_CreateMenuItem(menu_bar_1, "Exit", SDL_MENUITEM_BUTTON, MENU_BAR_EXIT)); - SDL_CreateMenuItem(app_menu, "Exit", SDL_MENUITEM_BUTTON, MENU_BAR_EXIT); + SDL_assert(!SDL_CreateMenuItem(menu_bar_1, "Exit 1", SDL_MENUITEM_BUTTON, MENU_BAR_EXIT)); + SDL_CreateMenuItem(app_menu, "Exit 1", SDL_MENUITEM_BUTTON, MENU_BAR_EXIT); } else { - SDL_CreateMenuItem(menu_bar_1, "Exit", SDL_MENUITEM_BUTTON, MENU_BAR_EXIT); + SDL_CreateMenuItem(menu_bar_1, "Exit 1", SDL_MENUITEM_BUTTON, MENU_BAR_EXIT); } } @@ -161,10 +174,10 @@ void CreateMenuBar_2() SDL_MenuItem* app_menu = SDL_GetMenuBarAppMenu(menu_bar_2); if (app_menu) { - SDL_assert(!SDL_CreateMenuItem(menu_bar_2, "Exit", SDL_MENUITEM_BUTTON, MENU_BAR_EXIT)); - SDL_CreateMenuItem(app_menu, "Exit", SDL_MENUITEM_BUTTON, MENU_BAR_EXIT); + SDL_assert(!SDL_CreateMenuItem(menu_bar_2, "Exit 2", SDL_MENUITEM_BUTTON, MENU_BAR_EXIT)); + SDL_CreateMenuItem(app_menu, "Exit 2", SDL_MENUITEM_BUTTON, MENU_BAR_EXIT); } else { - SDL_CreateMenuItem(menu_bar_2, "Exit", SDL_MENUITEM_BUTTON, MENU_BAR_EXIT); + SDL_CreateMenuItem(menu_bar_2, "Exit 2", SDL_MENUITEM_BUTTON, MENU_BAR_EXIT); } } diff --git a/include/SDL3/SDL_video.h b/include/SDL3/SDL_video.h index 887169ce1cab1..f06664cd2a0d1 100644 --- a/include/SDL3/SDL_video.h +++ b/include/SDL3/SDL_video.h @@ -3150,7 +3150,7 @@ extern SDL_DECLSPEC SDL_MenuItem *SDL_CreateMenuItem(SDL_MenuItem *menu_as_item, /** * Gets the number of children of the given menu, must be SDL_MENUITEM_MENUBAR or SDL_MENUITEM_SUBMENU * - * \param menu_item the menu item to get the label of. + * \param menu_as_item the menu item to get the label of. * \returns the number of children of the given menu, or -1 on failure; call SDL_GetError() for more * information. * diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index c4f155ff357a7..a351c51352567 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -1272,7 +1272,6 @@ SDL3_0.0.0 { SDL_CreateMenuItem; SDL_GetMenuChildItems; SDL_GetMenuChildItem; - SDL_GetMenuItemLabel; SDL_GetMenuItemType; SDL_SetMenuItemLabel; SDL_SetMenuItemChecked; diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c index a1c5c4e928bf5..41930cae9c6bd 100644 --- a/src/video/SDL_video.c +++ b/src/video/SDL_video.c @@ -6332,9 +6332,15 @@ SDL_MenuItem *SDL_CreateMenuItem(SDL_MenuItem *menu_bar_as_item, const char *lab SDL_MenuItem *SDL_GetMenuBarAppMenu(SDL_MenuItem *menu_bar) { CHECK_MENUITEM_MAGIC(menu_bar, NULL); + + if (menu_bar->common.type != SDL_MENUITEM_MENUBAR) { + SDL_SetError("menu_bar must be a SDL_MENUITEM_MENUBAR."); + return NULL; + } if (!menu_bar->menu_bar.app_menu) { SDL_SetError("This platform doesn't support an Application menu."); + return NULL; } return menu_bar->menu_bar.app_menu; diff --git a/src/video/cocoa/SDL_cocoavideo.m b/src/video/cocoa/SDL_cocoavideo.m index 6179d68623261..31da3b4dc0468 100644 --- a/src/video/cocoa/SDL_cocoavideo.m +++ b/src/video/cocoa/SDL_cocoavideo.m @@ -390,8 +390,6 @@ bool Cocoa_SetWindowMenuBar(SDL_Window *window, SDL_MenuBar *menu_bar) return true; } - // If we don't set it to nil first, it won't change menu bars until you switch off and back on focus. - [NSApp setMainMenu:nil]; PlatformMenuData* platform_data = (__bridge PlatformMenuData*)menu_bar->common.item_common.platform_data; [NSApp setMainMenu:platform_data->menu]; diff --git a/src/video/windows/SDL_windowsevents.c b/src/video/windows/SDL_windowsevents.c index faace53645813..3751422fc902c 100644 --- a/src/video/windows/SDL_windowsevents.c +++ b/src/video/windows/SDL_windowsevents.c @@ -2442,6 +2442,7 @@ LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara event.menu.windowID = data->window->id; SDL_PushEvent(&event); + break; } #endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)