Skip to content

Commit 9950061

Browse files
committed
Windows: Implement DisplayServer::dialog_show and DisplayServer::dialog_input_text
1 parent f77bc87 commit 9950061

File tree

5 files changed

+335
-4
lines changed

5 files changed

+335
-4
lines changed

doc/classes/DisplayServer.xml

+4-4
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,8 @@
101101
<param index="2" name="existing_text" type="String" />
102102
<param index="3" name="callback" type="Callable" />
103103
<description>
104-
Shows a text input dialog which uses the operating system's native look-and-feel. [param callback] will be called with a [String] argument equal to the text field's contents when the dialog is closed for any reason.
105-
[b]Note:[/b] This method is implemented only on macOS.
104+
Shows a text input dialog which uses the operating system's native look-and-feel. [param callback] should accept a single [String] parameter which contains the text field's contents.
105+
[b]Note:[/b] This method is implemented only on macOS and Windows.
106106
</description>
107107
</method>
108108
<method name="dialog_show">
@@ -112,8 +112,8 @@
112112
<param index="2" name="buttons" type="PackedStringArray" />
113113
<param index="3" name="callback" type="Callable" />
114114
<description>
115-
Shows a text dialog which uses the operating system's native look-and-feel. [param callback] will be called when the dialog is closed for any reason.
116-
[b]Note:[/b] This method is implemented only on macOS.
115+
Shows a text dialog which uses the operating system's native look-and-feel. [param callback] should accept a single [int] parameter which corresponds to the index of the pressed button.
116+
[b]Note:[/b] This method is implemented only on macOS and Windows.
117117
</description>
118118
</method>
119119
<method name="enable_for_stealing_focus">

godot.manifest

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
3+
<dependency>
4+
<dependentAssembly>
5+
<assemblyIdentity
6+
type='win32'
7+
name='Microsoft.Windows.Common-Controls'
8+
version='6.0.0.0' processorArchitecture='*'
9+
publicKeyToken='6595b64144ccf1df'
10+
language='*'/>
11+
</dependentAssembly>
12+
</dependency>
13+
</assembly>

platform/windows/display_server_windows.cpp

+310
Original file line numberDiff line numberDiff line change
@@ -2519,6 +2519,299 @@ void DisplayServerWindows::enable_for_stealing_focus(OS::ProcessID pid) {
25192519
AllowSetForegroundWindow(pid);
25202520
}
25212521

2522+
static HRESULT CALLBACK win32_task_dialog_callback(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, LONG_PTR lpRefData) {
2523+
if (msg == TDN_CREATED) {
2524+
// To match the input text dialog.
2525+
SendMessageW(hwnd, WM_SETICON, ICON_BIG, 0);
2526+
SendMessageW(hwnd, WM_SETICON, ICON_SMALL, 0);
2527+
}
2528+
2529+
return 0;
2530+
}
2531+
2532+
Error DisplayServerWindows::dialog_show(String p_title, String p_description, Vector<String> p_buttons, const Callable &p_callback) {
2533+
_THREAD_SAFE_METHOD_
2534+
2535+
TASKDIALOGCONFIG config;
2536+
ZeroMemory(&config, sizeof(TASKDIALOGCONFIG));
2537+
config.cbSize = sizeof(TASKDIALOGCONFIG);
2538+
2539+
Char16String title = p_title.utf16();
2540+
Char16String message = p_description.utf16();
2541+
List<Char16String> buttons;
2542+
for (String s : p_buttons) {
2543+
buttons.push_back(s.utf16());
2544+
}
2545+
2546+
config.pszWindowTitle = (LPCWSTR)(title.get_data());
2547+
config.pszContent = (LPCWSTR)(message.get_data());
2548+
2549+
const int button_count = MIN(buttons.size(), 8);
2550+
config.cButtons = button_count;
2551+
2552+
// No dynamic stack array size :(
2553+
TASKDIALOG_BUTTON *tbuttons = button_count != 0 ? (TASKDIALOG_BUTTON *)alloca(sizeof(TASKDIALOG_BUTTON) * button_count) : nullptr;
2554+
if (tbuttons) {
2555+
for (int i = 0; i < button_count; i++) {
2556+
tbuttons[i].nButtonID = i;
2557+
tbuttons[i].pszButtonText = (LPCWSTR)(buttons[i].get_data());
2558+
}
2559+
}
2560+
config.pButtons = tbuttons;
2561+
config.pfCallback = win32_task_dialog_callback;
2562+
2563+
HMODULE comctl = LoadLibraryW(L"comctl32.dll");
2564+
if (comctl) {
2565+
typedef HRESULT(WINAPI * TaskDialogIndirectPtr)(const TASKDIALOGCONFIG *pTaskConfig, int *pnButton, int *pnRadioButton, BOOL *pfVerificationFlagChecked);
2566+
2567+
TaskDialogIndirectPtr task_dialog_indirect = (TaskDialogIndirectPtr)GetProcAddress(comctl, "TaskDialogIndirect");
2568+
if (task_dialog_indirect) {
2569+
int button_pressed;
2570+
if (FAILED(task_dialog_indirect(&config, &button_pressed, nullptr, nullptr))) {
2571+
return FAILED;
2572+
}
2573+
2574+
if (!p_callback.is_null()) {
2575+
Variant button = button_pressed;
2576+
const Variant *args[1] = { &button };
2577+
Variant ret;
2578+
Callable::CallError ce;
2579+
p_callback.callp(args, 1, ret, ce);
2580+
if (ce.error != Callable::CallError::CALL_OK) {
2581+
ERR_PRINT(vformat("Failed to execute dialog callback: %s.", Variant::get_callable_error_text(p_callback, args, 1, ce)));
2582+
}
2583+
}
2584+
2585+
return OK;
2586+
}
2587+
FreeLibrary(comctl);
2588+
}
2589+
2590+
ERR_PRINT("Unable to create native dialog.");
2591+
return FAILED;
2592+
}
2593+
2594+
struct Win32InputTextDialogInit {
2595+
const char16_t *title;
2596+
const char16_t *description;
2597+
const char16_t *partial;
2598+
const Callable &callback;
2599+
};
2600+
2601+
static constexpr int scale_with_dpi(int p_pos, int p_dpi) {
2602+
return IsProcessDPIAware() ? (p_pos * p_dpi / 96) : p_pos;
2603+
}
2604+
2605+
static INT_PTR input_text_dialog_init(HWND hWnd, UINT code, WPARAM wParam, LPARAM lParam) {
2606+
Win32InputTextDialogInit init = *(Win32InputTextDialogInit *)lParam;
2607+
SetWindowLongPtrW(hWnd, GWLP_USERDATA, (LONG_PTR)&init.callback); // Set dialog callback.
2608+
2609+
SetWindowTextW(hWnd, (LPCWSTR)init.title);
2610+
2611+
const int dpi = DisplayServerWindows::get_singleton()->screen_get_dpi();
2612+
2613+
const int margin = scale_with_dpi(7, dpi);
2614+
const SIZE dlg_size = { scale_with_dpi(300, dpi), scale_with_dpi(50, dpi) };
2615+
2616+
int str_len = lstrlenW((LPCWSTR)init.description);
2617+
SIZE str_size = { dlg_size.cx, 0 };
2618+
if (str_len > 0) {
2619+
HDC hdc = GetDC(nullptr);
2620+
RECT trect = { margin, margin, margin + dlg_size.cx, margin + dlg_size.cy };
2621+
SelectObject(hdc, (HFONT)SendMessageW(hWnd, WM_GETFONT, 0, 0));
2622+
2623+
// `+ margin` adds some space between the static text and the edit field.
2624+
// Don't scale this with DPI because DPI is already handled by DrawText.
2625+
str_size.cy = DrawTextW(hdc, (LPCWSTR)init.description, str_len, &trect, DT_LEFT | DT_WORDBREAK | DT_CALCRECT) + margin;
2626+
2627+
ReleaseDC(nullptr, hdc);
2628+
}
2629+
2630+
RECT crect, wrect;
2631+
GetClientRect(hWnd, &crect);
2632+
GetWindowRect(hWnd, &wrect);
2633+
int sw = GetSystemMetrics(SM_CXSCREEN);
2634+
int sh = GetSystemMetrics(SM_CYSCREEN);
2635+
int new_width = dlg_size.cx + margin * 2 + wrect.right - wrect.left - crect.right;
2636+
int new_height = dlg_size.cy + margin * 2 + wrect.bottom - wrect.top - crect.bottom + str_size.cy;
2637+
2638+
MoveWindow(hWnd, (sw - new_width) / 2, (sh - new_height) / 2, new_width, new_height, true);
2639+
2640+
HWND ok_button = GetDlgItem(hWnd, 1);
2641+
MoveWindow(ok_button,
2642+
dlg_size.cx + margin - scale_with_dpi(65, dpi),
2643+
dlg_size.cy + str_size.cy + margin - scale_with_dpi(20, dpi),
2644+
scale_with_dpi(65, dpi), scale_with_dpi(20, dpi), true);
2645+
2646+
HWND description = GetDlgItem(hWnd, 3);
2647+
MoveWindow(description, margin, margin, dlg_size.cx, str_size.cy, true);
2648+
SetWindowTextW(description, (LPCWSTR)init.description);
2649+
2650+
HWND text_edit = GetDlgItem(hWnd, 2);
2651+
MoveWindow(text_edit, margin, str_size.cy + margin, dlg_size.cx, scale_with_dpi(20, dpi), true);
2652+
SetWindowTextW(text_edit, (LPCWSTR)init.partial);
2653+
2654+
return TRUE;
2655+
}
2656+
2657+
static INT_PTR input_text_dialog_cmd_proc(HWND hWnd, UINT code, WPARAM wParam, LPARAM lParam) {
2658+
if (LOWORD(wParam) == 1) {
2659+
HWND text_edit = GetDlgItem(hWnd, 2);
2660+
ERR_FAIL_NULL_V(text_edit, false);
2661+
2662+
Char16String text;
2663+
text.resize(GetWindowTextLengthW(text_edit) + 1);
2664+
GetWindowTextW(text_edit, (LPWSTR)text.get_data(), text.size());
2665+
2666+
const Callable *callback = (const Callable *)GetWindowLongPtrW(hWnd, GWLP_USERDATA);
2667+
if (callback && callback->is_valid()) {
2668+
Variant v_result = String((const wchar_t *)text.get_data());
2669+
Variant ret;
2670+
Callable::CallError ce;
2671+
const Variant *args[1] = { &v_result };
2672+
2673+
callback->callp(args, 1, ret, ce);
2674+
if (ce.error != Callable::CallError::CALL_OK) {
2675+
ERR_PRINT(vformat("Failed to execute input dialog callback: %s.", Variant::get_callable_error_text(*callback, args, 1, ce)));
2676+
}
2677+
}
2678+
2679+
return EndDialog(hWnd, 0);
2680+
}
2681+
2682+
return false;
2683+
}
2684+
2685+
static INT_PTR CALLBACK input_text_dialog_proc(HWND hWnd, UINT code, WPARAM wParam, LPARAM lParam) {
2686+
switch (code) {
2687+
case WM_INITDIALOG:
2688+
return input_text_dialog_init(hWnd, code, wParam, lParam);
2689+
2690+
case WM_COMMAND:
2691+
return input_text_dialog_cmd_proc(hWnd, code, wParam, lParam);
2692+
2693+
default:
2694+
return FALSE;
2695+
}
2696+
}
2697+
2698+
Error DisplayServerWindows::dialog_input_text(String p_title, String p_description, String p_partial, const Callable &p_callback) {
2699+
#pragma pack(push, 1)
2700+
2701+
// NOTE: Use default/placeholder coordinates here. Windows uses its own coordinate system
2702+
// specifically for dialogs which relies on font sizes instead of pixels.
2703+
const struct {
2704+
WORD dlgVer; // must be 1
2705+
WORD signature; // must be 0xFFFF
2706+
DWORD helpID;
2707+
DWORD exStyle;
2708+
DWORD style;
2709+
WORD cDlgItems;
2710+
short x;
2711+
short y;
2712+
short cx;
2713+
short cy;
2714+
WCHAR menu[1]; // must be 0
2715+
WCHAR windowClass[7]; // must be "#32770" -- the default window class for dialogs
2716+
WCHAR title[1]; // must be 0
2717+
WORD pointsize;
2718+
WORD weight;
2719+
BYTE italic;
2720+
BYTE charset;
2721+
WCHAR font[13]; // must be "MS Shell Dlg"
2722+
} template_base = {
2723+
1, 0xFFFF, 0, 0,
2724+
DS_SYSMODAL | DS_SETFONT | DS_MODALFRAME | DS_3DLOOK | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU,
2725+
3, 0, 0, 20, 20, L"", L"#32770", L"", 8, FW_NORMAL, 0, DEFAULT_CHARSET, L"MS Shell Dlg"
2726+
};
2727+
2728+
const struct {
2729+
DWORD helpID;
2730+
DWORD exStyle;
2731+
DWORD style;
2732+
short x;
2733+
short y;
2734+
short cx;
2735+
short cy;
2736+
DWORD id;
2737+
WCHAR windowClass[7]; // must be "Button"
2738+
WCHAR title[3]; // must be "OK"
2739+
WORD extraCount;
2740+
} ok_button = {
2741+
0, 0, WS_VISIBLE | BS_DEFPUSHBUTTON, 0, 0, 50, 14, 1, WC_BUTTONW, L"OK", 0
2742+
};
2743+
const struct {
2744+
DWORD helpID;
2745+
DWORD exStyle;
2746+
DWORD style;
2747+
short x;
2748+
short y;
2749+
short cx;
2750+
short cy;
2751+
DWORD id;
2752+
WCHAR windowClass[5]; // must be "Edit"
2753+
WCHAR title[1]; // must be 0
2754+
WORD extraCount;
2755+
} text_field = {
2756+
0, 0, WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL, 0, 0, 250, 14, 2, WC_EDITW, L"", 0
2757+
};
2758+
const struct {
2759+
DWORD helpID;
2760+
DWORD exStyle;
2761+
DWORD style;
2762+
short x;
2763+
short y;
2764+
short cx;
2765+
short cy;
2766+
DWORD id;
2767+
WCHAR windowClass[7]; // must be "Static"
2768+
WCHAR title[1]; // must be 0
2769+
WORD extraCount;
2770+
} static_text = {
2771+
0, 0, WS_VISIBLE, 0, 0, 250, 14, 3, WC_STATICW, L"", 0
2772+
};
2773+
2774+
#pragma pack(pop)
2775+
2776+
// Dialog template
2777+
const size_t data_size = sizeof(template_base) + (sizeof(template_base) % 4) +
2778+
sizeof(ok_button) + (sizeof(ok_button) % 4) +
2779+
sizeof(text_field) + (sizeof(text_field) % 4) +
2780+
sizeof(static_text) + (sizeof(static_text) % 4);
2781+
2782+
void *data_template = memalloc(data_size);
2783+
ERR_FAIL_NULL_V_MSG(data_template, FAILED, "Unable to allocate memory for the dialog template.");
2784+
ZeroMemory(data_template, data_size);
2785+
2786+
char *current_block = (char *)data_template;
2787+
CopyMemory(current_block, &template_base, sizeof(template_base));
2788+
current_block += sizeof(template_base) + (sizeof(template_base) % 4);
2789+
CopyMemory(current_block, &ok_button, sizeof(ok_button));
2790+
current_block += sizeof(ok_button) + (sizeof(ok_button) % 4);
2791+
CopyMemory(current_block, &text_field, sizeof(text_field));
2792+
current_block += sizeof(text_field) + (sizeof(text_field) % 4);
2793+
CopyMemory(current_block, &static_text, sizeof(static_text));
2794+
2795+
Char16String title16 = p_title.utf16();
2796+
Char16String description16 = p_description.utf16();
2797+
Char16String partial16 = p_partial.utf16();
2798+
2799+
Win32InputTextDialogInit init = {
2800+
title16.get_data(), description16.get_data(), partial16.get_data(), p_callback
2801+
};
2802+
2803+
// No modal dialogs for specific windows? Assume main window here.
2804+
INT_PTR ret = DialogBoxIndirectParamW(hInstance, (LPDLGTEMPLATEW)data_template, nullptr, (DLGPROC)input_text_dialog_proc, (LPARAM)(&init));
2805+
2806+
Error result = ret != -1 ? OK : FAILED;
2807+
memfree(data_template);
2808+
2809+
if (result == FAILED) {
2810+
ERR_PRINT("Unable to create native dialog.");
2811+
}
2812+
return result;
2813+
}
2814+
25222815
int DisplayServerWindows::keyboard_get_layout_count() const {
25232816
return GetKeyboardLayoutList(0, nullptr);
25242817
}
@@ -5285,6 +5578,23 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win
52855578
}
52865579
}
52875580

5581+
HMODULE comctl32 = LoadLibraryW(L"comctl32.dll");
5582+
if (comctl32) {
5583+
typedef BOOL(WINAPI * InitCommonControlsExPtr)(_In_ const INITCOMMONCONTROLSEX *picce);
5584+
InitCommonControlsExPtr init_common_controls_ex = (InitCommonControlsExPtr)GetProcAddress(comctl32, "InitCommonControlsEx");
5585+
5586+
// Fails if the incorrect version was loaded. Probably not a big enough deal to print an error about.
5587+
if (init_common_controls_ex) {
5588+
INITCOMMONCONTROLSEX icc = {};
5589+
icc.dwICC = ICC_STANDARD_CLASSES;
5590+
icc.dwSize = sizeof(INITCOMMONCONTROLSEX);
5591+
if (!init_common_controls_ex(&icc)) {
5592+
WARN_PRINT("Unable to initialize Windows common controls. Native dialogs may not work properly.");
5593+
}
5594+
}
5595+
FreeLibrary(comctl32);
5596+
}
5597+
52885598
memset(&wc, 0, sizeof(WNDCLASSEXW));
52895599
wc.cbSize = sizeof(WNDCLASSEXW);
52905600
wc.style = CS_OWNDC | CS_DBLCLKS;

platform/windows/display_server_windows.h

+3
Original file line numberDiff line numberDiff line change
@@ -642,6 +642,9 @@ class DisplayServerWindows : public DisplayServer {
642642

643643
virtual void enable_for_stealing_focus(OS::ProcessID pid) override;
644644

645+
virtual Error dialog_show(String p_title, String p_description, Vector<String> p_buttons, const Callable &p_callback) override;
646+
virtual Error dialog_input_text(String p_title, String p_description, String p_partial, const Callable &p_callback) override;
647+
645648
virtual int keyboard_get_layout_count() const override;
646649
virtual int keyboard_get_current_layout() const override;
647650
virtual void keyboard_set_current_layout(int p_index) override;

platform/windows/godot_res.rc

+5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
#include "core/version.h"
22

3+
#ifndef RT_MANIFEST
4+
#define RT_MANIFEST 24
5+
#endif
6+
37
GODOT_ICON ICON platform/windows/godot.ico
8+
1 RT_MANIFEST "godot.manifest"
49

510
1 VERSIONINFO
611
FILEVERSION VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH,0

0 commit comments

Comments
 (0)