Skip to content

Commit f274329

Browse files
committed
fix(windows): implement dev menu for New Arch
1 parent be830f4 commit f274329

5 files changed

Lines changed: 283 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: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
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+
auto rememberLastComponent =
53+
Session::ShouldRememberLastComponent() ? MF_CHECKED : MF_UNCHECKED;
54+
AppendMenuW(hReactMenu,
55+
MF_DISABLED | MF_STRING | rememberLastComponent,
56+
ItemID(DevMenuCommand::RememberLastComponent),
57+
kLabelRememberLastComponent);
58+
59+
if (!components.empty()) {
60+
AppendMenuW(hReactMenu, MF_SEPARATOR, 0, nullptr);
61+
constexpr auto offset = ItemID(DevMenuCommand::ComponentsStart);
62+
std::remove_const_t<decltype(offset)> index = 0;
63+
for (auto const &component : components) {
64+
auto const &title = component.displayName.value_or(component.appKey);
65+
// Add keyboard accelerator for the first nine (1-9) components
66+
auto label = index < 8 ? winrt::to_hstring(title) + L"\tCtrl+Shift+" +
67+
std::to_wstring(index + 1)
68+
: winrt::to_hstring(title);
69+
AppendMenuW(hReactMenu, MF_DISABLED | MF_STRING, offset + (++index), label.c_str());
70+
}
71+
}
72+
73+
return hReactMenu;
74+
}
75+
76+
HMENU CreateDebugMenu(ReactInstance const &instance)
77+
{
78+
auto hDebugMenu = CreatePopupMenu();
79+
AppendMenuW(hDebugMenu, //
80+
MF_STRING,
81+
ItemID(DevMenuCommand::ReloadJS),
82+
kLabelReloadJS);
83+
AppendMenuW(hDebugMenu,
84+
MF_STRING,
85+
ItemID(DevMenuCommand::DirectDebugger),
86+
instance.UseDirectDebugger() ? kLabelDisableDirectDebugger
87+
: kLabelEnableDirectDebugger);
88+
AppendMenuW(hDebugMenu,
89+
MF_STRING,
90+
ItemID(DevMenuCommand::BreakOnFirstLine),
91+
instance.BreakOnFirstLine() ? kLabelDisableBreakOnFirstLine
92+
: kLabelEnableBreakOnFirstLine);
93+
AppendMenuW(hDebugMenu,
94+
MF_STRING,
95+
ItemID(DevMenuCommand::FastRefresh),
96+
instance.UseFastRefresh() ? kLabelDisableFastRefresh : kLabelEnableFastRefresh);
97+
AppendMenuW(hDebugMenu, //
98+
MF_STRING,
99+
ItemID(DevMenuCommand::ToggleInspector),
100+
kLabelToggleInspector);
101+
return hDebugMenu;
102+
}
103+
104+
HMENU CreateDevMenu(ReactInstance const &instance, std::vector<Component> const &components)
105+
{
106+
auto hReactMenu = CreateReactMenu(components);
107+
auto hDebugMenu = CreateDebugMenu(instance);
108+
109+
auto hMenuBar = CreateMenu();
110+
AppendMenuW(hMenuBar, MF_POPUP, reinterpret_cast<UINT_PTR>(hReactMenu), L"&React");
111+
AppendMenuW(hMenuBar, MF_POPUP, reinterpret_cast<UINT_PTR>(hDebugMenu), L"&Debug");
112+
113+
return hMenuBar;
114+
}
115+
116+
void SetMenuItemLabel(HMENU hMenu, DevMenuCommand cmd, LPCWSTR label)
117+
{
118+
MENUITEMINFOW info{.cbSize = sizeof(MENUITEMINFO),
119+
.fMask = MIIM_TYPE,
120+
.dwTypeData = const_cast<LPWSTR>(label)};
121+
SetMenuItemInfoW(hMenu, ItemID(cmd), false, &info);
122+
}
123+
} // namespace
124+
125+
DevMenu::DevMenu(ReactInstance &instance, std::vector<Component> const &components)
126+
: instance_(instance), hMenu_(CreateDevMenu(instance, components))
127+
{
128+
}
129+
130+
void DevMenu::OnCommand(DevMenuCommand cmd) const
131+
{
132+
switch (cmd) {
133+
case DevMenuCommand::LoadFromBundle: {
134+
instance_.LoadJSBundleFrom(JSBundleSource::Embedded);
135+
break;
136+
}
137+
case DevMenuCommand::LoadFromDevServer: {
138+
instance_.LoadJSBundleFrom(JSBundleSource::DevServer);
139+
break;
140+
}
141+
case DevMenuCommand::RememberLastComponent: {
142+
auto rememberLastComponent = !Session::ShouldRememberLastComponent();
143+
Session::ShouldRememberLastComponent(rememberLastComponent);
144+
CheckMenuItem(hMenu_, ItemID(cmd), rememberLastComponent ? MF_CHECKED : MF_UNCHECKED);
145+
break;
146+
}
147+
case DevMenuCommand::ReloadJS: {
148+
instance_.Reload();
149+
break;
150+
}
151+
case DevMenuCommand::DirectDebugger: {
152+
auto useDirectDebugger = !instance_.UseDirectDebugger();
153+
instance_.UseDirectDebugger(useDirectDebugger);
154+
SetMenuItemLabel(hMenu_,
155+
cmd,
156+
useDirectDebugger ? kLabelDisableDirectDebugger
157+
: kLabelEnableDirectDebugger);
158+
break;
159+
}
160+
case DevMenuCommand::BreakOnFirstLine: {
161+
auto breakOnFirstLine = !instance_.BreakOnFirstLine();
162+
instance_.BreakOnFirstLine(breakOnFirstLine);
163+
SetMenuItemLabel(hMenu_,
164+
cmd,
165+
breakOnFirstLine ? kLabelDisableBreakOnFirstLine
166+
: kLabelEnableBreakOnFirstLine);
167+
break;
168+
}
169+
case DevMenuCommand::FastRefresh: {
170+
auto useFastRefresh = !instance_.UseFastRefresh();
171+
instance_.UseFastRefresh(useFastRefresh);
172+
SetMenuItemLabel(hMenu_, //
173+
cmd,
174+
useFastRefresh ? kLabelDisableFastRefresh : kLabelEnableFastRefresh);
175+
break;
176+
}
177+
case DevMenuCommand::ToggleInspector: {
178+
instance_.ToggleElementInspector();
179+
break;
180+
}
181+
default: {
182+
if (cmd > DevMenuCommand::ComponentsStart) {
183+
// TODO
184+
}
185+
break;
186+
}
187+
}
188+
}
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)