basalt-win32 0.0.0-rc5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/cli/runWindows.js +35 -0
- package/cli/shared.js +75 -0
- package/native/CMakeLists.txt +398 -0
- package/native/tests/test_win32_accessibility.cpp +271 -0
- package/native/tests/test_win32_clipboard.cpp +121 -0
- package/native/tests/test_win32_focus.cpp +195 -0
- package/native/tests/test_win32_hittest.cpp +410 -0
- package/native/tests/test_win32_image.cpp +239 -0
- package/native/tests/test_win32_imageloader.cpp +208 -0
- package/native/tests/test_win32_input.cpp +361 -0
- package/native/tests/test_win32_key_events.cpp +84 -0
- package/native/tests/test_win32_mounting.cpp +410 -0
- package/native/tests/test_win32_paint.cpp +505 -0
- package/native/tests/test_win32_scrollview.cpp +385 -0
- package/native/tests/test_win32_text.cpp +258 -0
- package/native/tests/test_win32_textinput.cpp +609 -0
- package/native/tests/test_win32_titlebar.cpp +111 -0
- package/native/tests/test_win32_titlebar_actions.cpp +62 -0
- package/native/tests/test_win32_view.cpp +222 -0
- package/native/win32/ColorSchemeWin32.cpp +56 -0
- package/native/win32/ComponentRegistryWin32.cpp +124 -0
- package/native/win32/DirectWriteLayout.cpp +116 -0
- package/native/win32/DirectWriteLayout.h +36 -0
- package/native/win32/DirectWriteLayoutManager.cpp +124 -0
- package/native/win32/FontRegistryDirectWrite.cpp +117 -0
- package/native/win32/PlatformServicesWin32.cpp +643 -0
- package/native/win32/RnWin32Accessible.cpp +258 -0
- package/native/win32/RnWin32Accessible.h +91 -0
- package/native/win32/RnWin32Image.cpp +265 -0
- package/native/win32/RnWin32Image.h +93 -0
- package/native/win32/RnWin32TextLayout.cpp +310 -0
- package/native/win32/RnWin32TextLayout.h +160 -0
- package/native/win32/RnWin32View.cpp +1281 -0
- package/native/win32/RnWin32View.h +506 -0
- package/native/win32/Win32AnimationChoreographer.cpp +76 -0
- package/native/win32/Win32AnimationChoreographer.h +61 -0
- package/native/win32/Win32Clip.h +173 -0
- package/native/win32/Win32DropTarget.cpp +567 -0
- package/native/win32/Win32DropTarget.h +42 -0
- package/native/win32/Win32Focus.cpp +262 -0
- package/native/win32/Win32Focus.h +109 -0
- package/native/win32/Win32ImageLoader.cpp +167 -0
- package/native/win32/Win32ImageLoader.h +104 -0
- package/native/win32/Win32KeyEvents.cpp +126 -0
- package/native/win32/Win32KeyEvents.h +20 -0
- package/native/win32/Win32MenuBar.cpp +352 -0
- package/native/win32/Win32MenuBar.h +16 -0
- package/native/win32/Win32MountingManager.cpp +727 -0
- package/native/win32/Win32MountingManager.h +203 -0
- package/native/win32/Win32Packaging.cpp +161 -0
- package/native/win32/Win32Packaging.h +56 -0
- package/native/win32/Win32RunLoopObserver.cpp +48 -0
- package/native/win32/Win32RunLoopObserver.h +42 -0
- package/native/win32/Win32ScrollView.cpp +515 -0
- package/native/win32/Win32ScrollView.h +208 -0
- package/native/win32/Win32SkiaContext.cpp +221 -0
- package/native/win32/Win32SkiaContext.h +53 -0
- package/native/win32/Win32SkiaModule.cpp +80 -0
- package/native/win32/Win32SkiaModule.h +50 -0
- package/native/win32/Win32SkiaPeer.cpp +358 -0
- package/native/win32/Win32SkiaPeer.h +66 -0
- package/native/win32/Win32SkiaUnicode.cpp +42 -0
- package/native/win32/Win32Snapshot.cpp +201 -0
- package/native/win32/Win32Snapshot.h +66 -0
- package/native/win32/Win32Strings.h +54 -0
- package/native/win32/Win32TextInput.cpp +811 -0
- package/native/win32/Win32TextInput.h +232 -0
- package/native/win32/Win32TitleBar.cpp +562 -0
- package/native/win32/Win32TitleBar.h +146 -0
- package/native/win32/Win32TitleBarLayout.cpp +46 -0
- package/native/win32/Win32TitleBarLayout.h +58 -0
- package/native/win32/Win32TouchDispatcher.cpp +443 -0
- package/native/win32/Win32TouchDispatcher.h +153 -0
- package/native/win32/Win32UiThread.h +27 -0
- package/native/win32/Win32WindowControl.cpp +329 -0
- package/native/win32/Win32WindowModule.cpp +209 -0
- package/native/win32/Win32WindowModule.h +72 -0
- package/native/win32/demo_layout_win32.cpp +232 -0
- package/native/win32/main_win32.cpp +2481 -0
- package/native/win32/mount_harness_win32.cpp +316 -0
- package/package.json +26 -0
- package/react-native.config.js +22 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
#include "Win32KeyEvents.h"
|
|
2
|
+
|
|
3
|
+
#include <windows.h>
|
|
4
|
+
|
|
5
|
+
#include <cwctype>
|
|
6
|
+
#include <string>
|
|
7
|
+
|
|
8
|
+
namespace basalt {
|
|
9
|
+
namespace {
|
|
10
|
+
|
|
11
|
+
const char *specialNameFor(unsigned int virtualKey) {
|
|
12
|
+
switch (virtualKey) {
|
|
13
|
+
case VK_UP:
|
|
14
|
+
return kKeyArrowUp;
|
|
15
|
+
case VK_DOWN:
|
|
16
|
+
return kKeyArrowDown;
|
|
17
|
+
case VK_LEFT:
|
|
18
|
+
return kKeyArrowLeft;
|
|
19
|
+
case VK_RIGHT:
|
|
20
|
+
return kKeyArrowRight;
|
|
21
|
+
// Named for paging rather than for priority, which is what PRIOR and NEXT
|
|
22
|
+
// are about: VK_PRIOR is Page Up.
|
|
23
|
+
case VK_PRIOR:
|
|
24
|
+
return kKeyPageUp;
|
|
25
|
+
case VK_NEXT:
|
|
26
|
+
return kKeyPageDown;
|
|
27
|
+
case VK_HOME:
|
|
28
|
+
return kKeyHome;
|
|
29
|
+
case VK_END:
|
|
30
|
+
return kKeyEnd;
|
|
31
|
+
case VK_DELETE:
|
|
32
|
+
return kKeyDelete;
|
|
33
|
+
case VK_BACK:
|
|
34
|
+
return kKeyBackspace;
|
|
35
|
+
case VK_RETURN:
|
|
36
|
+
return kKeyEnter;
|
|
37
|
+
// Shift-Tab is VK_TAB with shift held here, unlike the other two hosts where
|
|
38
|
+
// it arrives as its own character or keyval. So there is nothing to fold
|
|
39
|
+
// together -- it already reports as Tab with the modifier set.
|
|
40
|
+
case VK_TAB:
|
|
41
|
+
return kKeyTab;
|
|
42
|
+
case VK_ESCAPE:
|
|
43
|
+
return kKeyEscape;
|
|
44
|
+
case VK_SPACE:
|
|
45
|
+
// A space is a character named " ", and the layout below would give it
|
|
46
|
+
// anyway; named here so all three hosts answer it from the same table.
|
|
47
|
+
return kKeySpace;
|
|
48
|
+
default:
|
|
49
|
+
return nullptr;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
bool held(int virtualKey) {
|
|
54
|
+
// The high bit is "down now"; the low bit is a toggle and means Caps Lock is
|
|
55
|
+
// on rather than that Shift is held.
|
|
56
|
+
return (GetKeyState(virtualKey) & 0x8000) != 0;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
} // namespace
|
|
60
|
+
|
|
61
|
+
std::optional<KeyCombination> keyCombinationFrom(unsigned int virtualKey) {
|
|
62
|
+
KeyModifiers modifiers;
|
|
63
|
+
// VK_MENU is Alt, which is a name from a time when Alt opened menus.
|
|
64
|
+
modifiers.alt = held(VK_MENU);
|
|
65
|
+
modifiers.ctrl = held(VK_CONTROL);
|
|
66
|
+
modifiers.shift = held(VK_SHIFT);
|
|
67
|
+
// Either Windows key is `meta`, as Command is on macOS and Super is on Linux:
|
|
68
|
+
// W3C names the key in that position Meta, so an app binding metaKey presses
|
|
69
|
+
// the same physical key on all three desktops.
|
|
70
|
+
modifiers.meta = held(VK_LWIN) || held(VK_RWIN);
|
|
71
|
+
|
|
72
|
+
// A bare modifier is not a combination. Offering one would fire a shortcut on
|
|
73
|
+
// Ctrl alone, and unlike the other two hosts there is no empty character here
|
|
74
|
+
// to detect it by -- the virtual key has to be recognised.
|
|
75
|
+
switch (virtualKey) {
|
|
76
|
+
case VK_SHIFT:
|
|
77
|
+
case VK_LSHIFT:
|
|
78
|
+
case VK_RSHIFT:
|
|
79
|
+
case VK_CONTROL:
|
|
80
|
+
case VK_LCONTROL:
|
|
81
|
+
case VK_RCONTROL:
|
|
82
|
+
case VK_MENU:
|
|
83
|
+
case VK_LMENU:
|
|
84
|
+
case VK_RMENU:
|
|
85
|
+
case VK_LWIN:
|
|
86
|
+
case VK_RWIN:
|
|
87
|
+
case VK_CAPITAL:
|
|
88
|
+
return std::nullopt;
|
|
89
|
+
default:
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (const char *special = specialNameFor(virtualKey)) {
|
|
94
|
+
return KeyCombination{std::string(special), modifiers};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// The character this key produces on the current layout. MapVirtualKeyW gives
|
|
98
|
+
// the *unshifted* character and gives letters in upper case, so the case has to
|
|
99
|
+
// be applied here -- where GDK's keyval already carries it and AppKit's
|
|
100
|
+
// `characters` already applies it. Third host, third arrangement, same answer.
|
|
101
|
+
const UINT mapped = MapVirtualKeyW(virtualKey, MAPVK_VK_TO_CHAR);
|
|
102
|
+
if (mapped == 0) {
|
|
103
|
+
return std::nullopt;
|
|
104
|
+
}
|
|
105
|
+
// The top bit means a dead key -- part-way through composing an accent -- which
|
|
106
|
+
// is not a combination any more than a bare modifier is.
|
|
107
|
+
if ((mapped & 0x80000000u) != 0) {
|
|
108
|
+
return std::nullopt;
|
|
109
|
+
}
|
|
110
|
+
wchar_t character = static_cast<wchar_t>(mapped & 0xFFFFu);
|
|
111
|
+
if (!modifiers.shift) {
|
|
112
|
+
// Lowercased rather than left as the layout gave it, so an unshifted A is
|
|
113
|
+
// "a". Only letters change; punctuation is unaffected by towlower.
|
|
114
|
+
character = static_cast<wchar_t>(towlower(character));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const int needed = WideCharToMultiByte(CP_UTF8, 0, &character, 1, nullptr, 0, nullptr, nullptr);
|
|
118
|
+
if (needed <= 0) {
|
|
119
|
+
return std::nullopt;
|
|
120
|
+
}
|
|
121
|
+
std::string utf8(static_cast<size_t>(needed), '\0');
|
|
122
|
+
WideCharToMultiByte(CP_UTF8, 0, &character, 1, utf8.data(), needed, nullptr, nullptr);
|
|
123
|
+
return KeyCombination{utf8, modifiers};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
} // namespace basalt
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// A Win32 key press as a key combination, in core/KeyEvents.h's vocabulary.
|
|
2
|
+
//
|
|
3
|
+
// The third of three translations, and the one that has to work hardest.
|
|
4
|
+
// `WM_KEYDOWN` carries a virtual key and nothing else: no modifier state, and no
|
|
5
|
+
// character. So the modifiers come from `GetKeyState` and the character from the
|
|
6
|
+
// keyboard layout, where the other two hosts are handed both.
|
|
7
|
+
|
|
8
|
+
#pragma once
|
|
9
|
+
|
|
10
|
+
#include "KeyEvents.h"
|
|
11
|
+
|
|
12
|
+
#include <optional>
|
|
13
|
+
|
|
14
|
+
namespace basalt {
|
|
15
|
+
|
|
16
|
+
// Nothing for a press with no key to speak of: a bare modifier, or a dead key.
|
|
17
|
+
// Reads the modifier state itself, because the message does not carry it.
|
|
18
|
+
std::optional<KeyCombination> keyCombinationFrom(unsigned int virtualKey);
|
|
19
|
+
|
|
20
|
+
} // namespace basalt
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
// The Windows half of core/MenuModel.h: a real menu bar, in the window.
|
|
2
|
+
//
|
|
3
|
+
// Windows is the middle case. A menu bar is conventional rather than required,
|
|
4
|
+
// it lives inside the window rather than in a system bar, and -- unlike macOS
|
|
5
|
+
// -- nothing about editing depends on it: an EDIT control implements Ctrl+C,
|
|
6
|
+
// Ctrl+V and Ctrl+Z itself, so the Edit menu is a convenience rather than the
|
|
7
|
+
// thing that makes copying work.
|
|
8
|
+
//
|
|
9
|
+
// Which is why the roles here are posted to the focused control rather than
|
|
10
|
+
// sent down a responder chain. Windows has no responder chain; what it has is
|
|
11
|
+
// `WM_COPY` and friends, which every standard edit control answers and which
|
|
12
|
+
// reach whichever one has focus.
|
|
13
|
+
//
|
|
14
|
+
// The host forwards `WM_COMMAND` here. A menu bar costs client area, so
|
|
15
|
+
// installing one resizes the surface -- which the host already handles, because
|
|
16
|
+
// the same thing happens when a window is resized for any other reason.
|
|
17
|
+
|
|
18
|
+
#include "Win32MenuBar.h"
|
|
19
|
+
|
|
20
|
+
#include "MenuModel.h"
|
|
21
|
+
#include "WindowControl.h"
|
|
22
|
+
#include "Win32Strings.h"
|
|
23
|
+
|
|
24
|
+
#include <windows.h>
|
|
25
|
+
|
|
26
|
+
#include <iterator>
|
|
27
|
+
#include <string>
|
|
28
|
+
#include <utility>
|
|
29
|
+
#include <vector>
|
|
30
|
+
|
|
31
|
+
namespace basalt {
|
|
32
|
+
|
|
33
|
+
namespace {
|
|
34
|
+
|
|
35
|
+
// Command ids. An app's own item carries its own id and a role needs one of
|
|
36
|
+
// these, so they are kept apart by range: an app's ids come from JavaScript and
|
|
37
|
+
// start at 1, and roles start high enough that the two cannot meet.
|
|
38
|
+
constexpr UINT kRoleBase = 0xE000;
|
|
39
|
+
|
|
40
|
+
struct Role {
|
|
41
|
+
const char *name;
|
|
42
|
+
UINT command;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
constexpr Role kRoles[] = {
|
|
46
|
+
{"quit", kRoleBase + 1},
|
|
47
|
+
{"undo", kRoleBase + 2},
|
|
48
|
+
{"redo", kRoleBase + 3},
|
|
49
|
+
{"cut", kRoleBase + 4},
|
|
50
|
+
{"copy", kRoleBase + 5},
|
|
51
|
+
{"paste", kRoleBase + 6},
|
|
52
|
+
{"delete", kRoleBase + 7},
|
|
53
|
+
{"selectAll", kRoleBase + 8},
|
|
54
|
+
{"minimize", kRoleBase + 9},
|
|
55
|
+
{"zoom", kRoleBase + 10},
|
|
56
|
+
{"close", kRoleBase + 11},
|
|
57
|
+
{"togglefullscreen", kRoleBase + 12},
|
|
58
|
+
// "about" has no standard Windows action. An app that wants one shows its
|
|
59
|
+
// own window, which is an app's own item rather than a role.
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
UINT commandForRole(const std::string &role) {
|
|
63
|
+
for (const Role &known : kRoles) {
|
|
64
|
+
if (role == known.name) {
|
|
65
|
+
return known.command;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return 0;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// The platform's own word, plus the shortcut it answers to. Windows draws an
|
|
72
|
+
// accelerator as the text after a tab; nothing here registers one, because the
|
|
73
|
+
// EDIT control already answers these keys.
|
|
74
|
+
std::wstring labelForRole(const std::string &role, const std::string &label) {
|
|
75
|
+
if (!label.empty()) {
|
|
76
|
+
return win32::widen(label);
|
|
77
|
+
}
|
|
78
|
+
if (role == "quit") {
|
|
79
|
+
return L"Exit";
|
|
80
|
+
}
|
|
81
|
+
if (role == "undo") {
|
|
82
|
+
return L"Undo\tCtrl+Z";
|
|
83
|
+
}
|
|
84
|
+
if (role == "redo") {
|
|
85
|
+
return L"Redo\tCtrl+Y";
|
|
86
|
+
}
|
|
87
|
+
if (role == "cut") {
|
|
88
|
+
return L"Cut\tCtrl+X";
|
|
89
|
+
}
|
|
90
|
+
if (role == "copy") {
|
|
91
|
+
return L"Copy\tCtrl+C";
|
|
92
|
+
}
|
|
93
|
+
if (role == "paste") {
|
|
94
|
+
return L"Paste\tCtrl+V";
|
|
95
|
+
}
|
|
96
|
+
if (role == "delete") {
|
|
97
|
+
return L"Delete";
|
|
98
|
+
}
|
|
99
|
+
if (role == "selectAll") {
|
|
100
|
+
return L"Select All\tCtrl+A";
|
|
101
|
+
}
|
|
102
|
+
if (role == "minimize") {
|
|
103
|
+
return L"Minimize";
|
|
104
|
+
}
|
|
105
|
+
if (role == "zoom") {
|
|
106
|
+
return L"Maximize";
|
|
107
|
+
}
|
|
108
|
+
if (role == "close") {
|
|
109
|
+
return L"Close";
|
|
110
|
+
}
|
|
111
|
+
if (role == "togglefullscreen") {
|
|
112
|
+
return L"Full Screen";
|
|
113
|
+
}
|
|
114
|
+
return {};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
std::function<void(int)> &handler() {
|
|
118
|
+
static std::function<void(int)> value;
|
|
119
|
+
return value;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
HMENU &installed() {
|
|
123
|
+
static HMENU menu = nullptr;
|
|
124
|
+
return menu;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
void addItems(HMENU menu, const std::vector<MenuItemModel> &items);
|
|
128
|
+
|
|
129
|
+
void addItem(HMENU menu, const MenuItemModel &item) {
|
|
130
|
+
if (item.separator) {
|
|
131
|
+
AppendMenuW(menu, MF_SEPARATOR, 0, nullptr);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const UINT flags = item.enabled ? MF_ENABLED : MF_GRAYED;
|
|
136
|
+
|
|
137
|
+
if (!item.submenu.empty()) {
|
|
138
|
+
HMENU submenu = CreatePopupMenu();
|
|
139
|
+
if (submenu == nullptr) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
addItems(submenu, item.submenu);
|
|
143
|
+
AppendMenuW(menu,
|
|
144
|
+
MF_POPUP | flags,
|
|
145
|
+
reinterpret_cast<UINT_PTR>(submenu),
|
|
146
|
+
win32::widen(item.label).c_str());
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (!item.role.empty()) {
|
|
151
|
+
const UINT command = commandForRole(item.role);
|
|
152
|
+
const std::wstring label = labelForRole(item.role, item.label);
|
|
153
|
+
// An unknown role is dropped: a menu item that does nothing is worse than
|
|
154
|
+
// one that is not there.
|
|
155
|
+
if (command != 0 && !label.empty()) {
|
|
156
|
+
AppendMenuW(menu, MF_STRING | flags, command, label.c_str());
|
|
157
|
+
}
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (item.id <= 0) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
std::wstring label = win32::widen(item.label);
|
|
165
|
+
if (!item.accelerator.empty()) {
|
|
166
|
+
label += L"\t";
|
|
167
|
+
label += win32::widen(item.accelerator);
|
|
168
|
+
}
|
|
169
|
+
AppendMenuW(menu, MF_STRING | flags, static_cast<UINT_PTR>(item.id), label.c_str());
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
void addItems(HMENU menu, const std::vector<MenuItemModel> &items) {
|
|
173
|
+
for (const MenuItemModel &item : items) {
|
|
174
|
+
addItem(menu, item);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
} // namespace
|
|
179
|
+
|
|
180
|
+
bool applicationMenuSupported() {
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
namespace {
|
|
185
|
+
|
|
186
|
+
void describeInto(std::string &out, HMENU menu, int depth) {
|
|
187
|
+
const int count = GetMenuItemCount(menu);
|
|
188
|
+
for (int i = 0; i < count; i++) {
|
|
189
|
+
wchar_t text[512] = {0};
|
|
190
|
+
MENUITEMINFOW info{};
|
|
191
|
+
info.cbSize = sizeof(info);
|
|
192
|
+
info.fMask = MIIM_STRING | MIIM_SUBMENU | MIIM_STATE | MIIM_FTYPE;
|
|
193
|
+
info.dwTypeData = text;
|
|
194
|
+
info.cch = static_cast<UINT>(std::size(text) - 1);
|
|
195
|
+
if (!GetMenuItemInfoW(menu, static_cast<UINT>(i), TRUE, &info)) {
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
out.append(static_cast<size_t>(depth) * 2, ' ');
|
|
200
|
+
if ((info.fType & MFT_SEPARATOR) != 0) {
|
|
201
|
+
out += "-\n";
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
// The accelerator is the text after a tab, which is how Windows stores one;
|
|
205
|
+
// it reads the same way the macOS dump's bracket does.
|
|
206
|
+
std::wstring label(text);
|
|
207
|
+
const size_t tab = label.find(L'\t');
|
|
208
|
+
if (tab != std::wstring::npos) {
|
|
209
|
+
out += win32::narrow(label.substr(0, tab));
|
|
210
|
+
out += " [";
|
|
211
|
+
out += win32::narrow(label.substr(tab + 1));
|
|
212
|
+
out += "]";
|
|
213
|
+
} else {
|
|
214
|
+
out += win32::narrow(label);
|
|
215
|
+
}
|
|
216
|
+
if ((info.fState & MFS_DISABLED) != 0) {
|
|
217
|
+
out += " (disabled)";
|
|
218
|
+
}
|
|
219
|
+
out += "\n";
|
|
220
|
+
if (info.hSubMenu != nullptr) {
|
|
221
|
+
describeInto(out, info.hSubMenu, depth + 1);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
} // namespace
|
|
227
|
+
|
|
228
|
+
std::string describeApplicationMenu() {
|
|
229
|
+
std::string out;
|
|
230
|
+
if (installed() != nullptr) {
|
|
231
|
+
describeInto(out, installed(), 0);
|
|
232
|
+
}
|
|
233
|
+
return out;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
void setApplicationMenu(const MenuModel &menu, std::function<void(int)> onChosen) {
|
|
237
|
+
HWND window = GetActiveWindow();
|
|
238
|
+
if (window == nullptr) {
|
|
239
|
+
window = GetForegroundWindow();
|
|
240
|
+
}
|
|
241
|
+
if (window == nullptr) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
handler() = std::move(onChosen);
|
|
246
|
+
|
|
247
|
+
// An empty menu takes the bar away, which is what Windows' own default is:
|
|
248
|
+
// unlike macOS there is nothing an application needs a menu bar to do.
|
|
249
|
+
if (menu.empty()) {
|
|
250
|
+
SetMenu(window, nullptr);
|
|
251
|
+
if (installed() != nullptr) {
|
|
252
|
+
DestroyMenu(installed());
|
|
253
|
+
installed() = nullptr;
|
|
254
|
+
}
|
|
255
|
+
DrawMenuBar(window);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
HMENU bar = CreateMenu();
|
|
260
|
+
if (bar == nullptr) {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
for (const MenuItemModel &top : menu) {
|
|
264
|
+
// A bare command in a menu bar is not a thing any desktop has.
|
|
265
|
+
if (top.submenu.empty()) {
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
HMENU submenu = CreatePopupMenu();
|
|
269
|
+
if (submenu == nullptr) {
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
addItems(submenu, top.submenu);
|
|
273
|
+
AppendMenuW(bar,
|
|
274
|
+
MF_POPUP,
|
|
275
|
+
reinterpret_cast<UINT_PTR>(submenu),
|
|
276
|
+
win32::widen(top.label).c_str());
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
SetMenu(window, bar);
|
|
280
|
+
// Destroyed after the new one is in: a menu still attached to a window
|
|
281
|
+
// cannot be freed, and freeing the old one first would leave the window
|
|
282
|
+
// pointing at nothing for the length of a call.
|
|
283
|
+
if (installed() != nullptr) {
|
|
284
|
+
DestroyMenu(installed());
|
|
285
|
+
}
|
|
286
|
+
installed() = bar;
|
|
287
|
+
DrawMenuBar(window);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
namespace win32 {
|
|
291
|
+
|
|
292
|
+
bool handleMenuCommand(unsigned int command) {
|
|
293
|
+
if (command < kRoleBase) {
|
|
294
|
+
// An app's own item. Zero is "not from a menu", which WM_COMMAND also uses
|
|
295
|
+
// for accelerators and controls.
|
|
296
|
+
if (command == 0 || !handler()) {
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
handler()(static_cast<int>(command));
|
|
300
|
+
return true;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
HWND window = GetActiveWindow();
|
|
304
|
+
HWND focused = GetFocus();
|
|
305
|
+
switch (command) {
|
|
306
|
+
case kRoleBase + 1:
|
|
307
|
+
PostMessageW(window, WM_CLOSE, 0, 0);
|
|
308
|
+
return true;
|
|
309
|
+
// Posted to whatever has focus, which for these is a real EDIT control and
|
|
310
|
+
// answers every one of them itself. Windows has no responder chain; this is
|
|
311
|
+
// the equivalent.
|
|
312
|
+
case kRoleBase + 2:
|
|
313
|
+
SendMessageW(focused, WM_UNDO, 0, 0);
|
|
314
|
+
return true;
|
|
315
|
+
case kRoleBase + 3:
|
|
316
|
+
// EDIT has no redo. Reported as handled anyway: the alternative is the
|
|
317
|
+
// key travelling on to something that will do something else with it.
|
|
318
|
+
return true;
|
|
319
|
+
case kRoleBase + 4:
|
|
320
|
+
SendMessageW(focused, WM_CUT, 0, 0);
|
|
321
|
+
return true;
|
|
322
|
+
case kRoleBase + 5:
|
|
323
|
+
SendMessageW(focused, WM_COPY, 0, 0);
|
|
324
|
+
return true;
|
|
325
|
+
case kRoleBase + 6:
|
|
326
|
+
SendMessageW(focused, WM_PASTE, 0, 0);
|
|
327
|
+
return true;
|
|
328
|
+
case kRoleBase + 7:
|
|
329
|
+
SendMessageW(focused, WM_CLEAR, 0, 0);
|
|
330
|
+
return true;
|
|
331
|
+
case kRoleBase + 8:
|
|
332
|
+
SendMessageW(focused, EM_SETSEL, 0, -1);
|
|
333
|
+
return true;
|
|
334
|
+
case kRoleBase + 9:
|
|
335
|
+
ShowWindow(window, SW_MINIMIZE);
|
|
336
|
+
return true;
|
|
337
|
+
case kRoleBase + 10:
|
|
338
|
+
ShowWindow(window, IsZoomed(window) ? SW_RESTORE : SW_MAXIMIZE);
|
|
339
|
+
return true;
|
|
340
|
+
case kRoleBase + 11:
|
|
341
|
+
PostMessageW(window, WM_CLOSE, 0, 0);
|
|
342
|
+
return true;
|
|
343
|
+
case kRoleBase + 12:
|
|
344
|
+
setWindowFullScreen(!windowBounds().fullScreen);
|
|
345
|
+
return true;
|
|
346
|
+
default:
|
|
347
|
+
return false;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
} // namespace win32
|
|
352
|
+
} // namespace basalt
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// The host's half of the Windows menu bar: forwarding WM_COMMAND.
|
|
2
|
+
//
|
|
3
|
+
// A menu bar's items arrive as WM_COMMAND on the window that owns it, and the
|
|
4
|
+
// window procedure is the host's. This is the one line it has to pass on. See
|
|
5
|
+
// Win32MenuBar.cpp for what happens to it.
|
|
6
|
+
|
|
7
|
+
#pragma once
|
|
8
|
+
|
|
9
|
+
namespace basalt::win32 {
|
|
10
|
+
|
|
11
|
+
// Handles a WM_COMMAND from the application menu. Returns false when the
|
|
12
|
+
// command was not one of its own, so the host can go on treating it as whatever
|
|
13
|
+
// else a WM_COMMAND is -- a control's notification, in this host's case.
|
|
14
|
+
bool handleMenuCommand(unsigned int command);
|
|
15
|
+
|
|
16
|
+
} // namespace basalt::win32
|