From a54c8b4f4b2f1fa8f501a2f06b4011bf63c1f6c8 Mon Sep 17 00:00:00 2001 From: martinfouilleul Date: Tue, 23 May 2023 14:50:31 +0200 Subject: [PATCH] [win32] alert popup using TaskDialogIndirect(): automatically handles icons, dpi, and text/buttons layout (but requires Vista or higher) --- build.bat | 22 ++-- examples/ui/main.c | 11 +- src/win32_app.c | 281 +++++++---------------------------------- src/win32_manifest.xml | 22 ++++ 4 files changed, 87 insertions(+), 249 deletions(-) create mode 100644 src/win32_manifest.xml diff --git a/build.bat b/build.bat index e11b7d3..335b7c3 100644 --- a/build.bat +++ b/build.bat @@ -1,11 +1,11 @@ - -if not exist bin mkdir bin - -set glsl_shaders=src\glsl_shaders\common.glsl src\glsl_shaders\blit_vertex.glsl src\glsl_shaders\blit_fragment.glsl src\glsl_shaders\clear_counters.glsl src\glsl_shaders\tile.glsl src\glsl_shaders\sort.glsl src\glsl_shaders\draw.glsl - -call python3 scripts\embed_text.py %glsl_shaders% --prefix=glsl_ --output src\glsl_shaders.h - -set INCLUDES=/I src /I src/util /I src/platform /I ext /I ext/angle_headers -set LIBS=user32.lib opengl32.lib gdi32.lib shcore.lib delayimp.lib dwmapi.lib /LIBPATH:./bin libEGL.dll.lib libGLESv2.dll.lib /DELAYLOAD:libEGL.dll /DELAYLOAD:libGLESv2.dll - -cl /we4013 /Zi /Zc:preprocessor /DMP_BUILD_DLL /std:c11 %INCLUDES% src/milepost.c /Fo:bin/milepost.o /LD /link %LIBS% /OUT:bin/milepost.dll /IMPLIB:bin/milepost.dll.lib + +if not exist bin mkdir bin + +set glsl_shaders=src\glsl_shaders\common.glsl src\glsl_shaders\blit_vertex.glsl src\glsl_shaders\blit_fragment.glsl src\glsl_shaders\clear_counters.glsl src\glsl_shaders\tile.glsl src\glsl_shaders\sort.glsl src\glsl_shaders\draw.glsl + +call python3 scripts\embed_text.py %glsl_shaders% --prefix=glsl_ --output src\glsl_shaders.h + +set INCLUDES=/I src /I src/util /I src/platform /I ext /I ext/angle_headers +set LIBS=user32.lib opengl32.lib gdi32.lib shcore.lib delayimp.lib dwmapi.lib comctl32.lib /LIBPATH:./bin libEGL.dll.lib libGLESv2.dll.lib /DELAYLOAD:libEGL.dll /DELAYLOAD:libGLESv2.dll + +cl /we4013 /Zi /Zc:preprocessor /DMP_BUILD_DLL /std:c11 %INCLUDES% src/milepost.c /Fo:bin/milepost.o /LD /link /MANIFEST:EMBED /MANIFESTINPUT:src/win32_manifest.xml %LIBS% /OUT:bin/milepost.dll /IMPLIB:bin/milepost.dll.lib diff --git a/examples/ui/main.c b/examples/ui/main.c index b615173..1d820be 100644 --- a/examples/ui/main.c +++ b/examples/ui/main.c @@ -412,9 +412,16 @@ int main() if(ui_button("Test Dialog").clicked) { - char* options[] = {"OK", "Cancel"}; + char* options[] = {"Accept", "Reject"}; int res = mp_alert_popup("test dialog", "dialog message", 2, options); - printf("selected options %i\n", res); + if(res >= 0) + { + printf("selected options %i: %s\n", res, options[res]); + } + else + { + printf("no options selected\n"); + } } } diff --git a/src/win32_app.c b/src/win32_app.c index 667ddb0..1656280 100644 --- a/src/win32_app.c +++ b/src/win32_app.c @@ -1100,257 +1100,66 @@ MP_API str8 mp_save_dialog(mem_arena* arena, int filterCount, const char** filters); -//TODO: MessageBox() doesn't offer custom buttons? - #include -BOOL CALLBACK mp_dialog_proc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) -{ - switch(message) - { - case WM_INITDIALOG: - { - // Get the owner window and dialog box rectangles. - HWND hWndOwner = GetParent(hWnd); - if(hWndOwner == NULL) - { - hWndOwner = GetDesktopWindow(); - } - - //TODO Get all child items, get their ideal sizes, set size, and resize dialog accordingly - - vec2 buttonsTotalSize = {0, 20}; - vec2 messageSize = {400, 200}; - - for(HWND hWndChild = GetWindow(hWnd, GW_CHILD); - hWndChild != NULL; - hWndChild = GetWindow(hWndChild, GW_HWNDNEXT)) - { - int id = GetDlgCtrlID(hWndChild); - if(id == 0xffff) - { - //message - //TODO compute size with an "ideal" aspect ratio - SetWindowPos(hWndChild, - HWND_TOP, - 0, - 0, - messageSize.x, - messageSize.y, - SWP_NOMOVE | SWP_NOZORDER); - } - else - { - //button - SIZE size = {0}; - Button_GetIdealSize(hWndChild, &size); - - size.cx = maximum((int)size.cx, 100); - size.cy = maximum((int)size.cy, 40); - - SetWindowPos(hWndChild, - HWND_TOP, - 0, - 0, - size.cx, - size.cy, - SWP_NOMOVE | SWP_NOZORDER); - - buttonsTotalSize.x += size.cx + 10; - buttonsTotalSize.y = maximum(buttonsTotalSize.y, size.cy); - } - } - buttonsTotalSize.x -= 10; - - vec2 dialogSize = {maximum(messageSize.x, buttonsTotalSize.x) + 20, - 10 + messageSize.y + 10 + buttonsTotalSize.y + 10}; - - RECT dialogRect = {0, 0, dialogSize.x, dialogSize.y}; - - AdjustWindowRect(&dialogRect, WS_POPUP | WS_BORDER | WS_SYSMENU | DS_MODALFRAME | WS_CAPTION, FALSE); - - SetWindowPos(hWnd, - HWND_TOP, - 0, 0, - dialogRect.right - dialogRect.left, - dialogRect.bottom - dialogRect.top, - SWP_NOMOVE | SWP_NOZORDER); - - vec2 buttonPos = {dialogSize.x - buttonsTotalSize.x - 10, - dialogSize.y - buttonsTotalSize.y - 10}; - - for(HWND hWndChild = GetWindow(hWnd, GW_CHILD); - hWndChild != NULL; - hWndChild = GetWindow(hWndChild, GW_HWNDNEXT)) - { - int id = GetDlgCtrlID(hWndChild); - if(id == 0xffff) - { - //message - SetWindowPos(hWndChild, - HWND_TOP, - 0.5*(dialogSize.x - messageSize.x), - 10, - 0, 0, - SWP_NOSIZE | SWP_NOZORDER); - } - else - { - //button - SIZE size; - Button_GetIdealSize(hWndChild, &size); - size.cx = maximum((int)size.cx, 100); - - SetWindowPos(hWndChild, - HWND_TOP, - buttonPos.x, - buttonPos.y, - 0, 0, - SWP_NOSIZE | SWP_NOZORDER); - - buttonPos.x += size.cx + 10; - } - } - - RECT rc, rcDlg, rcOwner; - - GetWindowRect(hWndOwner, &rcOwner); - GetWindowRect(hWnd, &rcDlg); - CopyRect(&rc, &rcOwner); - - // Offset the owner and dialog box rectangles so that right and bottom - // values represent the width and height, and then offset the owner again - // to discard space taken up by the dialog box. - - OffsetRect(&rcDlg, -rcDlg.left, -rcDlg.top); - OffsetRect(&rc, -rc.left, -rc.top); - OffsetRect(&rc, -rcDlg.right, -rcDlg.bottom); - - // The new position is the sum of half the remaining space and the owner's - // original position. - - SetWindowPos(hWnd, - HWND_TOP, - rcOwner.left + (rc.right / 2), - rcOwner.top + (rc.bottom / 2), - 0, 0, // Ignores size arguments. - SWP_NOSIZE); - - return TRUE; - } - - case WM_COMMAND: - EndDialog(hWnd, wParam); - return TRUE; - } - return(FALSE); -} - MP_API int mp_alert_popup(const char* title, const char* message, u32 count, const char** options) { - //NOTE compute size needed - int size = sizeof(DLGTEMPLATE); // template struct - size += 2*sizeof(WORD); // menu and box class - - int titleWideSize = 1 + MultiByteToWideChar(CP_UTF8, 0, title, -1, NULL, 0); - size += titleWideSize; - - size = AlignUpOnPow2(size, sizeof(DWORD)); - size += sizeof(DLGITEMTEMPLATE); - size += 2*sizeof(WORD); // menu and box class - size += 1 + MultiByteToWideChar(CP_UTF8, 0, message, -1, NULL, 0); // dialog message - size++; // no creation data - - for(int i=0; istyle = WS_POPUP | WS_BORDER | WS_SYSMENU | DS_MODALFRAME | WS_CAPTION; - template->cdit = count + 1; - template->x = 10; - template->y = 10; - template->cx = 100; - template->cy = 100; - - LPWORD lpw = (LPWORD)(template + 1); - *lpw = 0; - lpw++; - *lpw = 0; - lpw++; - - MultiByteToWideChar(CP_UTF8, 0, title, -1, (LPWSTR)lpw, titleWideSize); - lpw += titleWideSize; - - { - lpw = (LPWORD)AlignUpOnPow2((uintptr_t)lpw, sizeof(DWORD)); - - LPDLGITEMTEMPLATE item = (LPDLGITEMTEMPLATE)lpw; - item->x = 10; - item->y = 10; - item->cx = 80; - item->cy = 40; - item->id = 0xffff; - item->style = WS_CHILD | WS_VISIBLE; - - lpw = (LPWORD)(item+1); - *lpw = 0xffff; - lpw++; - *lpw = 0x0082; - lpw++; - - int wideSize = 1 + MultiByteToWideChar(CP_UTF8, 0, message, -1, NULL, 0); - MultiByteToWideChar(CP_UTF8, 0, message, -1, (LPWSTR)lpw, wideSize); - lpw += wideSize; - - *lpw = 0; - lpw++; - } + TASKDIALOG_BUTTON* buttons = mem_arena_alloc_array(scratch, TASKDIALOG_BUTTON, count); for(int i=0; ix = 10; - item->y = 70; - item->cx = 80; - item->cy = 20; - item->id = i+1; - item->style = WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON; - - lpw = (LPWORD)(item+1); - *lpw = 0xffff; - lpw++; - *lpw = 0x0080; - lpw++; - - int wideSize = 1 + MultiByteToWideChar(CP_UTF8, 0, options[i], -1, NULL, 0); - MultiByteToWideChar(CP_UTF8, 0, options[i], -1, (LPWSTR)lpw, wideSize); - lpw += wideSize; - - *lpw = 0; - lpw++; + buttons[i].nButtonID = i+1; + buttons[i].pszButtonText = textWide; } - LRESULT ret = DialogBoxIndirect(NULL, template, NULL, (DLGPROC)mp_dialog_proc); + int titleWideSize = MultiByteToWideChar(CP_UTF8, 0, title, -1, NULL, 0); + wchar_t* titleWide = mem_arena_alloc_array(scratch, wchar_t, titleWideSize); + MultiByteToWideChar(CP_UTF8, 0, title, -1, titleWide, titleWideSize); + + int messageWideSize = MultiByteToWideChar(CP_UTF8, 0, message, -1, NULL, 0); + wchar_t* messageWide = mem_arena_alloc_array(scratch, wchar_t, messageWideSize); + MultiByteToWideChar(CP_UTF8, 0, message, -1, messageWide, messageWideSize); + + TASKDIALOGCONFIG config = + { + .cbSize = sizeof(TASKDIALOGCONFIG), + .hwndParent = NULL, + .hInstance = NULL, + .dwFlags = 0, + .dwCommonButtons = 0, + .pszWindowTitle = titleWide, + .hMainIcon = 0, + .pszMainIcon = TD_WARNING_ICON, + .pszMainInstruction = messageWide, + .pszContent = NULL, + .cButtons = count, + .pButtons = buttons, + .nDefaultButton = 0, + }; + + int button = -1; + HRESULT hRes = TaskDialogIndirect(&config, &button, NULL, NULL); + if(hRes == S_OK) + { + if(button == IDCANCEL) + { + button = -1; + } + else + { + button--; + } + } mem_arena_clear_to(scratch, marker); - - return((int)ret-1); + return(button); } diff --git a/src/win32_manifest.xml b/src/win32_manifest.xml new file mode 100644 index 0000000..b28a978 --- /dev/null +++ b/src/win32_manifest.xml @@ -0,0 +1,22 @@ + + + +Orca Runtime + + + + + +