Skip to content

Commit 8aacfc3

Browse files
authored
fix(windows): implement dev menu for New Arch (#2785)
1 parent 7d80bf5 commit 8aacfc3

5 files changed

Lines changed: 288 additions & 6 deletions

File tree

packages/app/test/pack.test.mts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,8 @@ describe("npm pack", () => {
278278
"windows/UWP/pch.h",
279279
"windows/Win32/AutolinkedNativeModules.g.cpp",
280280
"windows/Win32/AutolinkedNativeModules.g.h",
281+
"windows/Win32/DevMenu.cpp",
282+
"windows/Win32/DevMenu.h",
281283
"windows/Win32/Images/SplashScreen.scale-100.png",
282284
"windows/Win32/Images/SplashScreen.scale-200.png",
283285
"windows/Win32/Images/SplashScreen.scale-400.png",
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
#include "pch.h"
2+
3+
#include "DevMenu.h"
4+
5+
#include <type_traits>
6+
7+
#include "ReactInstance.h"
8+
#include "Session.h"
9+
10+
using ReactApp::Component;
11+
using ReactApp::DevMenu;
12+
using ReactApp::DevMenuCommand;
13+
using ReactTestApp::JSBundleSource;
14+
using ReactTestApp::ReactInstance;
15+
using ReactTestApp::Session;
16+
17+
namespace
18+
{
19+
constexpr wchar_t kLabelLoadFromJSBundle[] = L"Load from &JS bundle";
20+
constexpr wchar_t kLabelLoadFromDevServer[] = L"Load from &dev server";
21+
constexpr wchar_t kLabelRememberLastComponent[] = L"&Remember last opened component";
22+
constexpr wchar_t kLabelReloadJS[] = L"&Reload JavaScript";
23+
24+
constexpr wchar_t kLabelEnableDirectDebugger[] = L"Enable &direct debugging";
25+
constexpr wchar_t kLabelDisableDirectDebugger[] = L"Disable &direct debugging";
26+
27+
constexpr wchar_t kLabelEnableBreakOnFirstLine[] = L"Enable &break on first line";
28+
constexpr wchar_t kLabelDisableBreakOnFirstLine[] = L"Disable &break on first line";
29+
30+
constexpr wchar_t kLabelEnableFastRefresh[] = L"Enable &Fast Refresh";
31+
constexpr wchar_t kLabelDisableFastRefresh[] = L"Disable &Fast Refresh";
32+
33+
constexpr wchar_t kLabelToggleInspector[] = L"Toggle &inspector";
34+
35+
constexpr UINT ItemID(DevMenuCommand cmd)
36+
{
37+
return static_cast<UINT>(cmd);
38+
}
39+
40+
HMENU CreateReactMenu(std::vector<Component> const &components)
41+
{
42+
auto hReactMenu = CreatePopupMenu();
43+
AppendMenuW(hReactMenu, //
44+
MF_STRING,
45+
ItemID(DevMenuCommand::LoadFromBundle),
46+
kLabelLoadFromJSBundle);
47+
AppendMenuW(hReactMenu,
48+
MF_STRING,
49+
ItemID(DevMenuCommand::LoadFromDevServer),
50+
kLabelLoadFromDevServer);
51+
52+
// TODO: We can't easily change components without `ReactNativeWindow`
53+
// https://github.com/microsoft/react-native-windows/commit/1d80287100b4ebc559cd4d6bce62b7cd03c3cf04
54+
if constexpr (REACT_NATIVE_VERSION >= 84000) {
55+
auto rememberLastComponent =
56+
Session::ShouldRememberLastComponent() ? MF_CHECKED : MF_UNCHECKED;
57+
AppendMenuW(hReactMenu,
58+
MF_DISABLED | MF_STRING | rememberLastComponent,
59+
ItemID(DevMenuCommand::RememberLastComponent),
60+
kLabelRememberLastComponent);
61+
62+
if (!components.empty()) {
63+
AppendMenuW(hReactMenu, MF_SEPARATOR, 0, nullptr);
64+
constexpr auto offset = ItemID(DevMenuCommand::ComponentsStart);
65+
std::remove_const_t<decltype(offset)> index = 0;
66+
for (auto const &component : components) {
67+
auto const &title = component.displayName.value_or(component.appKey);
68+
// Add keyboard accelerator for the first nine (1-9) components
69+
auto label = index < 8 ? winrt::to_hstring(title) + L"\tCtrl+Shift+" +
70+
std::to_wstring(index + 1)
71+
: winrt::to_hstring(title);
72+
AppendMenuW(
73+
hReactMenu, MF_DISABLED | MF_STRING, offset + (++index), label.c_str());
74+
}
75+
}
76+
}
77+
78+
return hReactMenu;
79+
}
80+
81+
HMENU CreateDebugMenu(ReactInstance const &instance)
82+
{
83+
auto hDebugMenu = CreatePopupMenu();
84+
AppendMenuW(hDebugMenu, //
85+
MF_STRING,
86+
ItemID(DevMenuCommand::ReloadJS),
87+
kLabelReloadJS);
88+
AppendMenuW(hDebugMenu,
89+
MF_STRING,
90+
ItemID(DevMenuCommand::DirectDebugger),
91+
instance.UseDirectDebugger() ? kLabelDisableDirectDebugger
92+
: kLabelEnableDirectDebugger);
93+
AppendMenuW(hDebugMenu,
94+
MF_STRING,
95+
ItemID(DevMenuCommand::BreakOnFirstLine),
96+
instance.BreakOnFirstLine() ? kLabelDisableBreakOnFirstLine
97+
: kLabelEnableBreakOnFirstLine);
98+
AppendMenuW(hDebugMenu,
99+
MF_STRING,
100+
ItemID(DevMenuCommand::FastRefresh),
101+
instance.UseFastRefresh() ? kLabelDisableFastRefresh : kLabelEnableFastRefresh);
102+
AppendMenuW(hDebugMenu, //
103+
MF_STRING,
104+
ItemID(DevMenuCommand::ToggleInspector),
105+
kLabelToggleInspector);
106+
return hDebugMenu;
107+
}
108+
109+
HMENU CreateDevMenu(ReactInstance const &instance, std::vector<Component> const &components)
110+
{
111+
auto hReactMenu = CreateReactMenu(components);
112+
auto hDebugMenu = CreateDebugMenu(instance);
113+
114+
auto hMenuBar = CreateMenu();
115+
AppendMenuW(hMenuBar, MF_POPUP, reinterpret_cast<UINT_PTR>(hReactMenu), L"&React");
116+
AppendMenuW(hMenuBar, MF_POPUP, reinterpret_cast<UINT_PTR>(hDebugMenu), L"&Debug");
117+
118+
return hMenuBar;
119+
}
120+
121+
void SetMenuItemLabel(HMENU hMenu, DevMenuCommand cmd, LPCWSTR label)
122+
{
123+
MENUITEMINFOW info{.cbSize = sizeof(MENUITEMINFO),
124+
.fMask = MIIM_TYPE,
125+
.dwTypeData = const_cast<LPWSTR>(label)};
126+
SetMenuItemInfoW(hMenu, ItemID(cmd), false, &info);
127+
}
128+
} // namespace
129+
130+
DevMenu::DevMenu(ReactInstance &instance, std::vector<Component> const &components)
131+
: instance_(instance), hMenu_(CreateDevMenu(instance, components))
132+
{
133+
}
134+
135+
void DevMenu::OnCommand(DevMenuCommand cmd) const
136+
{
137+
switch (cmd) {
138+
case DevMenuCommand::LoadFromBundle: {
139+
instance_.LoadJSBundleFrom(JSBundleSource::Embedded);
140+
break;
141+
}
142+
case DevMenuCommand::LoadFromDevServer: {
143+
instance_.LoadJSBundleFrom(JSBundleSource::DevServer);
144+
break;
145+
}
146+
case DevMenuCommand::RememberLastComponent: {
147+
auto rememberLastComponent = !Session::ShouldRememberLastComponent();
148+
Session::ShouldRememberLastComponent(rememberLastComponent);
149+
CheckMenuItem(hMenu_, ItemID(cmd), rememberLastComponent ? MF_CHECKED : MF_UNCHECKED);
150+
break;
151+
}
152+
case DevMenuCommand::ReloadJS: {
153+
instance_.Reload();
154+
break;
155+
}
156+
case DevMenuCommand::DirectDebugger: {
157+
auto useDirectDebugger = !instance_.UseDirectDebugger();
158+
instance_.UseDirectDebugger(useDirectDebugger);
159+
SetMenuItemLabel(hMenu_,
160+
cmd,
161+
useDirectDebugger ? kLabelDisableDirectDebugger
162+
: kLabelEnableDirectDebugger);
163+
break;
164+
}
165+
case DevMenuCommand::BreakOnFirstLine: {
166+
auto breakOnFirstLine = !instance_.BreakOnFirstLine();
167+
instance_.BreakOnFirstLine(breakOnFirstLine);
168+
SetMenuItemLabel(hMenu_,
169+
cmd,
170+
breakOnFirstLine ? kLabelDisableBreakOnFirstLine
171+
: kLabelEnableBreakOnFirstLine);
172+
break;
173+
}
174+
case DevMenuCommand::FastRefresh: {
175+
auto useFastRefresh = !instance_.UseFastRefresh();
176+
instance_.UseFastRefresh(useFastRefresh);
177+
SetMenuItemLabel(hMenu_, //
178+
cmd,
179+
useFastRefresh ? kLabelDisableFastRefresh : kLabelEnableFastRefresh);
180+
break;
181+
}
182+
case DevMenuCommand::ToggleInspector: {
183+
instance_.ToggleElementInspector();
184+
break;
185+
}
186+
default: {
187+
if (cmd > DevMenuCommand::ComponentsStart) {
188+
// TODO
189+
}
190+
break;
191+
}
192+
}
193+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#pragma once
2+
3+
#include <vector>
4+
5+
#include "Manifest.h"
6+
7+
namespace ReactTestApp
8+
{
9+
class ReactInstance;
10+
}
11+
12+
namespace ReactApp
13+
{
14+
enum class DevMenuCommand {
15+
// Custom functions
16+
LoadFromBundle = 1001,
17+
LoadFromDevServer,
18+
RememberLastComponent,
19+
20+
// React Native core functions
21+
ReloadJS = 2001,
22+
DirectDebugger,
23+
BreakOnFirstLine,
24+
FastRefresh,
25+
ToggleInspector,
26+
27+
// User defined components
28+
ComponentsStart = 3000,
29+
};
30+
31+
class DevMenu
32+
{
33+
public:
34+
DevMenu(ReactTestApp::ReactInstance &, std::vector<Component> const &);
35+
36+
HMENU Handle() const
37+
{
38+
return hMenu_;
39+
}
40+
41+
void OnCommand(DevMenuCommand) const;
42+
43+
// DevMenu is non-copyable
44+
DevMenu(DevMenu const &) = delete;
45+
DevMenu &operator=(DevMenu const &) = delete;
46+
47+
private:
48+
ReactTestApp::ReactInstance &instance_;
49+
HMENU hMenu_;
50+
};
51+
} // namespace ReactApp

packages/app/windows/Win32/Main.cpp

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
#include "Main.h"
44

5+
#include <commctrl.h>
6+
7+
#include "DevMenu.h"
58
#include "JSValueWriterHelper.h"
69
#include "Manifest.g.cpp"
710
#include "ReactInstance.h"
@@ -40,12 +43,15 @@ namespace
4043
}
4144
} // namespace
4245

43-
_Use_decl_annotations_ int CALLBACK WinMain(HINSTANCE /* instance */,
44-
HINSTANCE,
45-
PSTR /* commandLine */,
46-
int /* showCmd */)
46+
LRESULT APIENTRY SubclassProc(
47+
HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData);
48+
49+
_Use_decl_annotations_ int CALLBACK WinMain(HINSTANCE /* hInstance */,
50+
HINSTANCE /* hPrevInstance */,
51+
PSTR /* lpCmdLine */,
52+
int /* nShowCmd */)
4753
{
48-
auto manifest = ::ReactApp::GetManifest();
54+
auto manifest = ReactApp::GetManifest();
4955
assert(manifest.components.has_value() && (*manifest.components).size() > 0 &&
5056
"At least one component must be declared");
5157

@@ -91,5 +97,33 @@ _Use_decl_annotations_ int CALLBACK WinMain(HINSTANCE /* instance */,
9197
window.Title(winrt::to_hstring(manifest.displayName));
9298
window.Resize({600, 800});
9399

100+
#if _DEBUG
101+
ReactApp::DevMenu devMenu{instance, manifest.components.value_or({})};
102+
auto hWnd = GetWindowFromWindowId(window.Id());
103+
SetMenu(hWnd, devMenu.Handle());
104+
SetWindowSubclass(hWnd,
105+
SubclassProc,
106+
reinterpret_cast<UINT_PTR>(&devMenu),
107+
reinterpret_cast<DWORD_PTR>(&devMenu));
108+
#endif // _DEBUG
109+
94110
app.Start();
111+
return 0;
112+
}
113+
114+
LRESULT APIENTRY SubclassProc(
115+
HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
116+
{
117+
switch (uMsg) {
118+
case WM_COMMAND: {
119+
auto const &devMenu = *reinterpret_cast<ReactApp::DevMenu *>(dwRefData);
120+
devMenu.OnCommand(static_cast<ReactApp::DevMenuCommand>(LOWORD(wParam)));
121+
return TRUE;
122+
}
123+
case WM_NCDESTROY: {
124+
RemoveWindowSubclass(hWnd, SubclassProc, uIdSubclass);
125+
break;
126+
}
127+
}
128+
return DefSubclassProc(hWnd, uMsg, wParam, lParam);
95129
}

packages/app/windows/Win32/ReactApp.vcxproj

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@
8989
<LanguageStandard>stdcpp20</LanguageStandard>
9090
</ClCompile>
9191
<Link>
92-
<AdditionalDependencies>shell32.lib;user32.lib;windowsapp.lib;%(AdditionalDependenices)</AdditionalDependencies>
92+
<AdditionalDependencies>comctl32.lib;shell32.lib;user32.lib;windowsapp.lib;%(AdditionalDependenices)</AdditionalDependencies>
9393
<SubSystem>Windows</SubSystem>
9494
<GenerateDebugInformation>true</GenerateDebugInformation>
9595
</Link>
@@ -106,6 +106,7 @@
106106
</ItemDefinitionGroup>
107107
<PropertyGroup Label="UserMacros" />
108108
<ItemGroup>
109+
<ClInclude Include="$(ReactAppWin32Dir)\DevMenu.h" />
109110
<ClInclude Include="$(ReactAppSharedDir)\JSValueWriterHelper.h" />
110111
<ClInclude Include="$(ReactAppWin32Dir)\Main.h" />
111112
<ClInclude Include="$(ReactAppSharedDir)\Manifest.h" />
@@ -117,6 +118,7 @@
117118
<ClInclude Include="$(ReactAppWin32Dir)\targetver.h" />
118119
</ItemGroup>
119120
<ItemGroup>
121+
<ClCompile Include="$(ReactAppWin32Dir)\DevMenu.cpp" />
120122
<ClCompile Include="$(ReactAppWin32Dir)\Main.cpp" />
121123
<ClCompile Include="$(ReactAppSharedDir)\ReactInstance.cpp" />
122124
<ClCompile Include="AutolinkedNativeModules.g.cpp" />

0 commit comments

Comments
 (0)