[win32] alert popup using TaskDialogIndirect(): automatically handles icons, dpi, and text/buttons layout (but requires Vista or higher)
This commit is contained in:
parent
f1d8dd1ca9
commit
a54c8b4f4b
|
@ -6,6 +6,6 @@ set glsl_shaders=src\glsl_shaders\common.glsl src\glsl_shaders\blit_vertex.glsl
|
||||||
call python3 scripts\embed_text.py %glsl_shaders% --prefix=glsl_ --output src\glsl_shaders.h
|
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 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
|
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 %LIBS% /OUT:bin/milepost.dll /IMPLIB:bin/milepost.dll.lib
|
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
|
||||||
|
|
|
@ -412,9 +412,16 @@ int main()
|
||||||
|
|
||||||
if(ui_button("Test Dialog").clicked)
|
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);
|
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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
281
src/win32_app.c
281
src/win32_app.c
|
@ -1100,257 +1100,66 @@ MP_API str8 mp_save_dialog(mem_arena* arena,
|
||||||
int filterCount,
|
int filterCount,
|
||||||
const char** filters);
|
const char** filters);
|
||||||
|
|
||||||
//TODO: MessageBox() doesn't offer custom buttons?
|
|
||||||
|
|
||||||
#include<commctrl.h>
|
#include<commctrl.h>
|
||||||
|
|
||||||
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,
|
MP_API int mp_alert_popup(const char* title,
|
||||||
const char* message,
|
const char* message,
|
||||||
u32 count,
|
u32 count,
|
||||||
const char** options)
|
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; i<count; i++)
|
|
||||||
{
|
|
||||||
size = AlignUpOnPow2(size, sizeof(DWORD));
|
|
||||||
size += sizeof(DLGITEMTEMPLATE);
|
|
||||||
size += 2*sizeof(WORD); // menu and box class
|
|
||||||
size += 1 + MultiByteToWideChar(CP_UTF8, 0, options[i], -1, NULL, 0); // button text
|
|
||||||
size++; // no creation data
|
|
||||||
}
|
|
||||||
size += sizeof(DWORD);
|
|
||||||
|
|
||||||
mem_arena* scratch = mem_scratch();
|
mem_arena* scratch = mem_scratch();
|
||||||
mem_arena_marker marker = mem_arena_mark(scratch);
|
mem_arena_marker marker = mem_arena_mark(scratch);
|
||||||
|
TASKDIALOG_BUTTON* buttons = mem_arena_alloc_array(scratch, TASKDIALOG_BUTTON, count);
|
||||||
char* buffer = mem_arena_alloc(scratch, size);
|
|
||||||
memset(buffer, 0, size);
|
|
||||||
|
|
||||||
LPDLGTEMPLATE template = (LPDLGTEMPLATE)AlignUpOnPow2((uintptr_t)buffer, sizeof(DWORD));
|
|
||||||
template->style = 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++;
|
|
||||||
}
|
|
||||||
|
|
||||||
for(int i=0; i<count; i++)
|
for(int i=0; i<count; i++)
|
||||||
{
|
{
|
||||||
lpw = (LPWORD)AlignUpOnPow2((uintptr_t)lpw, sizeof(DWORD));
|
int textWideSize = MultiByteToWideChar(CP_UTF8, 0, options[i], -1, NULL, 0);
|
||||||
|
wchar_t* textWide = mem_arena_alloc_array(scratch, wchar_t, textWideSize);
|
||||||
|
MultiByteToWideChar(CP_UTF8, 0, options[i], -1, textWide, textWideSize);
|
||||||
|
|
||||||
LPDLGITEMTEMPLATE item = (LPDLGITEMTEMPLATE)lpw;
|
buttons[i].nButtonID = i+1;
|
||||||
item->x = 10;
|
buttons[i].pszButtonText = textWide;
|
||||||
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++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
mem_arena_clear_to(scratch, marker);
|
||||||
|
return(button);
|
||||||
return((int)ret-1);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||||
|
<assemblyIdentity
|
||||||
|
version="0.0.0.1"
|
||||||
|
processorArchitecture="*"
|
||||||
|
name="forkingpaths.orca.runtime"
|
||||||
|
type="win32"
|
||||||
|
/>
|
||||||
|
<description>Orca Runtime</description>
|
||||||
|
<dependency>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity
|
||||||
|
type="win32"
|
||||||
|
name="Microsoft.Windows.Common-Controls"
|
||||||
|
version="6.0.0.0"
|
||||||
|
processorArchitecture="*"
|
||||||
|
publicKeyToken="6595b64144ccf1df"
|
||||||
|
language="*"
|
||||||
|
/>
|
||||||
|
</dependentAssembly>
|
||||||
|
</dependency>
|
||||||
|
</assembly>
|
Loading…
Reference in New Issue