Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
e650c4a
Initial prototype of Windows menubar
playmer Aug 15, 2025
24005e4
Try to fix CI issues.
playmer Aug 6, 2025
779d22f
Started working on Mac implementation.
playmer Aug 6, 2025
0886be1
more work
playmer Aug 6, 2025
e1088b4
Fix some of the compilation errors I caused on the windows side.
playmer Aug 7, 2025
8f993d2
Fix windows.
playmer Aug 11, 2025
f0e478c
Okay, we've got one of these showing up.
playmer Aug 11, 2025
043009f
Getting closer...
playmer Aug 11, 2025
d7120ea
I think I sorta got it? Need to handle destruction.
playmer Aug 11, 2025
11a1c8e
Improve example
playmer Aug 11, 2025
2ee1b82
Added way more error checking, switched to insertAt API(still appendi…
playmer Aug 13, 2025
f6d85d1
Not working, gotta switch
playmer Aug 14, 2025
fc9b414
I think it's been fixed.
playmer Aug 15, 2025
c46ae08
Fix the temp comments
playmer Aug 15, 2025
fd3c621
Mac is working again, enabling and disabling don't seem to work.
playmer Aug 15, 2025
bf88c08
Mac looks alright.
playmer Aug 15, 2025
392c8ac
Fix the windows backend
playmer Aug 15, 2025
f10a24e
Fix symbol issues
playmer Aug 15, 2025
a8b9174
Delete the WithProperties stub, can be added later.
playmer Aug 15, 2025
9682a7a
Fix one of the docs, although, I think I'm only sending button clicked..
playmer Aug 15, 2025
49b0823
Try to fix Windows CI
playmer Aug 15, 2025
4447d64
Try to fix a couple warnings I missed, I feel like there's a cmake fl…
playmer Aug 15, 2025
0d074ce
Fix the macos bridging errors I think
playmer Oct 3, 2025
6e53105
A bunch of refactoring to address the open questions.
playmer Oct 5, 2025
bb07a98
Mac compiling and seeming to work. App menu work next.
playmer Oct 6, 2025
3d39b39
Started appmenu work, not remotely ready.
playmer Oct 6, 2025
9b2a5f9
Rewrote SetWindowMenuBar, need to test it.
playmer Oct 6, 2025
527b8d4
Added some relevant testing to the example.
playmer Oct 6, 2025
60c0c44
Forgot to delete these when on windows.
playmer Oct 6, 2025
cdbf619
Swapping is just kind of weird on MacOS. Doesn't change until clickin…
playmer Oct 6, 2025
1e01ce4
Fix up missing symbols.
playmer Oct 6, 2025
934adb5
MacOS: Send window with event, fixed non-updating flip flop issue.
playmer Oct 6, 2025
ffabd75
Most of the App Menu Stuff, need to think about destruction.
playmer Oct 6, 2025
17ce1d7
Did a documentation pass, worked on appmenu destruction, fixed bugs.
playmer Oct 7, 2025
b563968
Fixup CI
playmer Oct 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
299 changes: 299 additions & 0 deletions examples/video/01-menubar/menubar.c
Original file line number Diff line number Diff line change
@@ -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 <SDL3/SDL.h>
#include <SDL3/SDL_main.h>

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) {

}


13 changes: 13 additions & 0 deletions include/SDL3/SDL_events.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
Loading
Loading