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..966e8ab0f7da5 --- /dev/null +++ b/examples/video/01-menubar/menubar.c @@ -0,0 +1,299 @@ +/* + * 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_1 = NULL; +SDL_Renderer *renderer_1 = NULL; + +SDL_Window *window_2 = NULL; +SDL_Renderer *renderer_2 = NULL; + +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_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_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; + } + + 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); + + + 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); + } + } + } + + item_count = SDL_GetMenuChildItems(menu_item); + + for (i = 0; i < item_count; ++i) { + PrintMenuItems(renderer, SDL_GetMenuChildItem(menu_item, (size_t)i), indent + 1, total_index); + } +} + + +void CreateMenuBar_1() +{ + menu_bar_1 = SDL_CreateMenuBar(); + + { + 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_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 *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_DestroyMenuItem(discord); + + 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)); + + SDL_MenuItem* app_menu = SDL_GetMenuBarAppMenu(menu_bar_1); + if (app_menu) { + 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 1", SDL_MENUITEM_BUTTON, MENU_BAR_EXIT); + } + } + + SDL_SetWindowMenuBar(window_1, menu_bar_1); + + 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_MenuItem* app_menu = SDL_GetMenuBarAppMenu(menu_bar_2); + if (app_menu) { + 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 2", 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("Window 1", 640, 480, 0, &window_1, &renderer_1); + SDL_CreateWindowAndRenderer("Window 2", 640, 480, 0, &window_2, &renderer_2); + + CreateMenuBar_1(); + CreateMenuBar_2(); + + //return SDL_APP_SUCCESS; + return SDL_APP_CONTINUE; +} + +SDL_AppResult SDL_AppIterate(void* appstate) { + + // Window 1 + SDL_SetRenderDrawColor(renderer_1, 180, 180, 180, 255); + SDL_RenderClear(renderer_1); + + 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; +} + +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: + { + 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); + } + } + + 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..f4aa91bc46ad8 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,14 @@ typedef struct SDL_UserEvent void *data2; /**< User defined data pointer */ } SDL_UserEvent; +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; /** * The structure for all events in SDL. @@ -1030,6 +1042,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..f06664cd2a0d1 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,41 @@ 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. + */ + +/** + * 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); + +/** + * 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); + /** * Destroy a window. * @@ -3034,6 +3083,224 @@ 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); + +/** + * 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); + +/** + * 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); + +/** + * Gets the number of children of the given menu, must be SDL_MENUITEM_MENUBAR or SDL_MENUITEM_SUBMENU + * + * \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. + * + * \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); + +/** + * 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); + +/** + * 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); /** * \name OpenGL support functions 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 3aa7a822e61cc..a351c51352567 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -1264,6 +1264,23 @@ SDL3_0.0.0 { SDL_LoadPNG; SDL_SavePNG_IO; SDL_SavePNG; + SDL_CreateMenuBar; + SDL_GetMenuBarAppMenu; + SDL_GetWindowMenuBar; + SDL_SetWindowMenuBar; + SDL_CreateMenuItemAt; + SDL_CreateMenuItem; + SDL_GetMenuChildItems; + SDL_GetMenuChildItem; + SDL_GetMenuItemType; + SDL_SetMenuItemLabel; + SDL_SetMenuItemChecked; + SDL_GetMenuItemChecked; + SDL_GetMenuItemEnabled; + SDL_SetMenuItemEnabled; + SDL_GetMenuItemLabel; + SDL_SetMenuItemLabel; + 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 f0693ad980589..ac2d6e72a7c8c 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -1290,3 +1290,20 @@ #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_GetMenuBarAppMenu SDL_GetMenuBarAppMenu_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_GetMenuChildItems SDL_GetMenuChildItems_REAL +#define SDL_GetMenuChildItem SDL_GetMenuChildItem_REAL +#define SDL_GetMenuItemLabel SDL_GetMenuItemLabel_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 +#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 4e3ca27e3ed2f..c46fe4d8a88d2 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -1298,3 +1298,20 @@ 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,(),(),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(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) +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 433eb72f153c7..23c79635e3e2e 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,6 +214,59 @@ typedef enum SDL_FULLSCREEN_PENDING } SDL_FullscreenResult; +typedef struct SDL_MenuItem_CommonData +{ + void *platform_data; + SDL_MenuBar *menu_bar; + SDL_MenuItem *parent; + SDL_MenuItem *prev; + SDL_MenuItem *next; + const char *label; + SDL_MenuItemType type; + Uint16 event_type; + bool enabled; +} SDL_MenuItem_CommonData; + +typedef struct SDL_Menu_CommonData +{ + SDL_MenuItem_CommonData item_common; + SDL_MenuItem *children; + Sint64 num_children; +} SDL_Menu_CommonData; + +typedef struct SDL_MenuBar +{ + SDL_Menu_CommonData common; + SDL_Window *window; + SDL_MenuItem *app_menu; +} SDL_MenuBar; + +typedef struct SDL_SubMenu +{ + SDL_Menu_CommonData common; +} SDL_SubMenu; + +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_Menu_CommonData menu_common; + SDL_MenuBar menu_bar; + SDL_SubMenu sub_menu; + SDL_MenuItem_Button button; + SDL_MenuItem_Checkable checkable; +} SDL_MenuItem; + struct SDL_VideoDevice { /* * * */ @@ -315,6 +373,14 @@ 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 (*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 (*SetMenuItemChecked)(SDL_MenuItem *menu_item, bool checked); + bool (*SetMenuItemEnabled)(SDL_MenuItem *menu_item, bool enabled); + bool (*DestroyMenuItem)(SDL_MenuItem *menu_item); + /* * * */ /* * OpenGL support @@ -422,6 +488,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 @@ -613,4 +680,8 @@ 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); + +void SDL_CleanupMenubars(); + #endif // SDL_sysvideo_h_ diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c index d961db578945e..41930cae9c6bd 100644 --- a/src/video/SDL_video.c +++ b/src/video/SDL_video.c @@ -177,6 +177,12 @@ static VideoBootStrap *bootstrap[] = { return result; \ } +#define CHECK_MENUITEM_MAGIC(menuitem, result) \ + if (!menuitem) { \ + SDL_SetError("Invalid menu_item"); \ + 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); @@ -4465,6 +4471,11 @@ void SDL_DestroyWindow(SDL_Window *window) SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_DESTROYED, 0, 0); + if (window->menu_bar) { + SDL_DestroyMenuItem((SDL_MenuItem *)window->menu_bar); + window->menu_bar = NULL; + } + SDL_DestroyWindowSurface(window); SDL_Renderer *renderer = SDL_GetRenderer(window); @@ -4627,6 +4638,9 @@ void SDL_VideoQuit(void) while (_this->windows) { SDL_DestroyWindow(_this->windows); } + + SDL_CleanupMenubars(); + _this->VideoQuit(_this); for (i = _this->num_displays; i--; ) { @@ -6111,6 +6125,423 @@ void SDL_OnApplicationDidEnterForeground(void) } } +SDL_MenuItem *SDL_CreateMenuBar() +{ + if (!_this) { + return NULL; + } + + if (!_this->CreateMenuBar) { + return NULL; + } + + 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); + return NULL; + } + + ++_this->num_menu_bars; + + SDL_SetObjectValid(menu_bar, SDL_OBJECT_TYPE_MENUBAR, true); + + return (SDL_MenuItem *)menu_bar; +} + + +SDL_MenuItem *SDL_GetWindowMenuBar(SDL_Window *window) +{ + 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 SDL_UninitializedVideo(); + } + + 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. + 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) { + SDL_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_SetWindowMenuBar(window, NULL); + } + + window->menu_bar = menu_bar_real; + + SDL_SetObjectValid(menu_bar_real, SDL_OBJECT_TYPE_MENUBAR, false); + + return _this->SetWindowMenuBar(window, 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_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; + } + + 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) { + 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.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); + 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->children) { + if (index == 0) { + menu_item->common.next = menu->children; + menu->children->common.prev = menu_item; + menu->children = menu_item; + } else { + SDL_MenuItem *current = menu->children; + for (size_t i = 1; (i < index) && current; ++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; + } + + current->common.next = menu_item; + menu_item->common.prev = current; + } + } else { + menu->children = menu_item; + } + + ++menu->num_children; + + 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_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_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->num_children, label, type, event_type); +} + + +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; +} + +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; +} + +SDL_MenuItem *SDL_GetMenuChildItem(SDL_MenuItem *menu_as_item, size_t index) +{ + 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_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); + + 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); + } + + menu_item->common.label = SDL_strdup(label); + return true; + } + + return false; +} + +SDL_MenuItemType SDL_GetMenuItemType(SDL_MenuItem *menu_item) +{ + CHECK_MENUITEM_MAGIC(menu_item, SDL_MENUITEM_INVALID); + 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); + if (menu_item->common.type != SDL_MENUITEM_CHECKABLE) { + SDL_SetError("menu_item isn't a checkable."); + return false; + } + + return menu_item->checkable.is_checked; +} + +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 SDL_MENUITEM_CHECKABLE."); + return false; + } + + 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 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_MENUBAR) { + SDL_SetError("A SDL_MENUITEM_MENUBAR can't be disabled."); + return false; + } + + if (!_this->SetMenuItemEnabled(menu_item, enabled)) { + return false; + } + + menu_item->common.enabled = enabled; + + return true; +} + +bool SDL_DestroyMenuItem(SDL_MenuItem *menu_item) +{ + 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; + } + } + } + + // 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; + } + + if (menu_item->common.prev) { + menu_item->common.prev->common.next = menu_item->common.next; + } + + if (menu_item->common.next) { + menu_item->common.next->common.prev = menu_item->common.prev; + } + + 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; +} + #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/cocoa/SDL_cocoavideo.h b/src/video/cocoa/SDL_cocoavideo.h index 353fb43509d2c..0358a379c1bac 100644 --- a/src/video/cocoa/SDL_cocoavideo.h +++ b/src/video/cocoa/SDL_cocoavideo.h @@ -64,6 +64,19 @@ typedef enum @property(nonatomic) OptionAsAlt option_as_alt; @end +@interface PlatformMenuData : NSObject { +@public + SDL_MenuBar *menu_bar; + NSMenu *menu; + NSMenuItem *menu_item; + Uint16 user_event_type; +} + +- (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 d77080779d848..31da3b4dc0468 100644 --- a/src/video/cocoa/SDL_cocoavideo.m +++ b/src/video/cocoa/SDL_cocoavideo.m @@ -126,6 +126,13 @@ static void Cocoa_DeleteDevice(SDL_VideoDevice *device) device->SetWindowParent = Cocoa_SetWindowParent; device->SetWindowModal = Cocoa_SetWindowModal; device->SyncWindow = Cocoa_SyncWindow; + device->CreateMenuBar = Cocoa_CreateMenuBar; + device->SetWindowMenuBar = Cocoa_SetWindowMenuBar; + device->CreateMenuItemAt = Cocoa_CreateMenuItemAt; + device->SetMenuItemLabel = Cocoa_SetMenuItemLabel; + device->SetMenuItemChecked = Cocoa_SetMenuItemChecked; + device->SetMenuItemEnabled = Cocoa_SetMenuItemEnabled; + device->DestroyMenuItem = Cocoa_DestroyMenuItem; #ifdef SDL_VIDEO_OPENGL_CGL device->GL_LoadLibrary = Cocoa_GL_LoadLibrary; @@ -330,4 +337,144 @@ void SDL_NSLog(const char *prefix, const char *text) } } +@implementation PlatformMenuData + +- (void) Cocoa_PlatformMenuData_MenuButtonClicked: (id)sender;{ + SDL_Event event; + 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); +} + +@end + +bool Cocoa_CreateMenuBar(SDL_MenuBar *menu_bar) +{ + PlatformMenuData* platform_menu =[PlatformMenuData new]; + platform_menu->menu = [NSMenu new]; + + 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; + 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); + + 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); + menu_bar->app_menu = app_menu; + + return true; +} + +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->flags & SDL_WINDOW_INPUT_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) +{ + 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; + 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]; + + 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]; + [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 = [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]; + [parent_platform_data->menu insertItem:platform_data->menu_item atIndex:(NSInteger)index]; + } + return true; +} + +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 setTitle:label_ns]; + return true; +} + +bool Cocoa_SetMenuItemChecked(SDL_MenuItem *menu_item, bool checked) +{ + NSControlStateValue flag = checked ? NSControlStateValueOn : NSControlStateValueOff; + PlatformMenuData* platform_data = (__bridge PlatformMenuData*)menu_item->common.platform_data; + [platform_data->menu_item setState:flag]; + [platform_data->menu update]; + return true; +} + +bool Cocoa_SetMenuItemEnabled(SDL_MenuItem *menu_item, bool enabled) +{ + PlatformMenuData* platform_data = (__bridge PlatformMenuData*)menu_item->common.platform_data; + [platform_data->menu_item setEnabled:enabled]; + [platform_data->menu update]; + return true; +} + +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); + [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 e4ab6efed4e19..d283317e1df7d 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 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_SetMenuItemChecked(SDL_MenuItem *menu_item, bool checked); +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_windowsevents.c b/src/video/windows/SDL_windowsevents.c index 9c8bf6d103bc2..3751422fc902c 100644 --- a/src/video/windows/SDL_windowsevents.c +++ b/src/video/windows/SDL_windowsevents.c @@ -2427,6 +2427,24 @@ 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; + event.menu.windowID = data->window->id; + + SDL_PushEvent(&event); + break; + } + #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..88fb57c023255 100644 --- a/src/video/windows/SDL_windowsvideo.c +++ b/src/video/windows/SDL_windowsvideo.c @@ -359,8 +359,17 @@ static SDL_VideoDevice *WIN_CreateDevice(void) device->ShowWindowSystemMenu = WIN_ShowWindowSystemMenu; device->SetWindowFocusable = WIN_SetWindowFocusable; device->UpdateWindowShape = WIN_UpdateWindowShape; + + device->CreateMenuBar = Win32_CreateMenuBar; + device->SetWindowMenuBar = Win32_SetWindowMenuBar; + device->CreateMenuItemAt = Win32_CreateMenuItemAt; + device->SetMenuItemLabel = Win32_SetMenuItemLabel; + device->SetMenuItemChecked = Win32_SetMenuItemChecked; + device->SetMenuItemEnabled = Win32_SetMenuItemEnabled; + device->DestroyMenuItem = Win32_DestroyMenuItem; #endif + #ifdef SDL_VIDEO_OPENGL_WGL device->GL_LoadLibrary = WIN_GL_LoadLibrary; device->GL_GetProcAddress = WIN_GL_GetProcAddress; @@ -878,4 +887,188 @@ bool WIN_IsPerMonitorV2DPIAware(SDL_VideoDevice *_this) return false; } +#define SDL_WIN32_INVALID_MENU_ID 65535 + +static PlatformMenuData *CreatePlatformMenuData(HMENU owner_handle, UINT_PTR self_handle) +{ + PlatformMenuData *platform = SDL_calloc(1, sizeof(PlatformMenuData)); + + platform->owner_handle = owner_handle; + platform->self_handle = self_handle; + + return platform; +} + +bool Win32_CreateMenuBar(SDL_MenuBar *menu_bar) +{ + HMENU menu_handle = CreateMenu(); + + if (!menu_handle) { + WIN_SetError("Unable to create MenuBar"); + return false; + } + + menu_bar->common.item_common.platform_data = (void*)CreatePlatformMenuData(NULL, (UINT_PTR)menu_handle); + + if (!menu_bar->common.item_common.platform_data) { + DestroyMenu(menu_handle); + return false; + } + + return true; +} + +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; + + 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; + 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_MENUITEM_MENUBAR; + + if (!top_level_menu) { + flags = MF_STRING; + } + + if (menu_item->common.type == SDL_MENUITEM_SUBMENU) { + if (top_level_menu) { + platform_data->self_handle = (UINT_PTR)CreateMenu(); + } else { + 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; + } else { + if (top_level_menu) { + flags |= MF_STRING; + } + + flags |= MF_BYPOSITION; + + platform_data->self_handle = (UINT_PTR)event_type; + } + + 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->num_children) { + win_index = (UINT)-1; + } + + 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."); + } + + 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_SetMenuItemLabel(SDL_MenuItem* menu_item, const char* label) +{ + PlatformMenuData *platform_data = (PlatformMenuData *)menu_item->common.platform_data; + Uint32 i = SDL_GetIndexInMenu(menu_item); + + + 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_SetMenuItemChecked(SDL_MenuItem *menu_item, bool checked) +{ + PlatformMenuData *platform_data = (PlatformMenuData *)menu_item->common.platform_data; + Uint32 i = SDL_GetIndexInMenu(menu_item); + + UINT32 flag = checked ? MF_CHECKED : MF_UNCHECKED; + + if (CheckMenuItem(platform_data->owner_handle, i, MF_BYPOSITION | flag) == -1) { + return WIN_SetError("Unable to check menu item."); + } + + return true; +} + +bool Win32_SetMenuItemEnabled(SDL_MenuItem *menu_item, bool enabled) +{ + 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 | flag) == -1) { + return WIN_SetError("Unable to enable menu item."); + } + + return true; +} + +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_MENUITEM_MENUBAR) { + if (!DestroyMenu((HMENU)platform_data->self_handle)) { + return WIN_SetError("Unable to remove menu item."); + } + } else { + DeleteMenu((HMENU)platform_data->owner_handle, i, MF_BYPOSITION); + } + + SDL_free(menu_item->common.platform_data); + menu_item->common.platform_data = NULL; + 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..ff060460628c3 100644 --- a/src/video/windows/SDL_windowsvideo.h +++ b/src/video/windows/SDL_windowsvideo.h @@ -661,4 +661,24 @@ 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 owner_handle; + UINT_PTR self_handle; + Uint16 user_event_type; +} PlatformMenuData; + +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_SetMenuItemChecked(SDL_MenuItem *menu_item, bool checked); +extern bool Win32_SetMenuItemEnabled(SDL_MenuItem *menu_item, bool enabled); +extern bool Win32_DestroyMenuItem(SDL_MenuItem *menu_item); + #endif // SDL_windowsvideo_h_