iPlug2 - C++ Audio Plug-in Framework
Loading...
Searching...
No Matches
ReaperExtBase.cpp
1// Helper to stringify macro
2#define IPLUG_STRINGIFY_HELPER(x) #x
3#define IPLUG_STRINGIFY(x) IPLUG_STRINGIFY_HELPER(x)
4
5ReaperExtBase::ReaperExtBase(reaper_plugin_info_t* pRec)
6: EDITOR_DELEGATE_CLASS(0) // zero params
7, mRec(pRec)
8{
9 mTimer = std::unique_ptr<Timer>(Timer::Create(std::bind(&ReaperExtBase::OnTimer, this, std::placeholders::_1), IDLE_TIMER_RATE));
10 mDockId.Set(IPLUG_STRINGIFY(PLUG_CLASS_NAME));
11 memset(&mDockState, 0, sizeof(ReaperExtDockState));
12 // Note: LoadDockState() is called lazily in CreateMainWindow() after API imports
13}
14
15ReaperExtBase::~ReaperExtBase()
16{
17 mTimer->Stop();
18 if (gHWND)
19 {
20 mSaveStateOnDestroy = false;
21 DestroyWindow(gHWND);
22 }
23}
24
25void ReaperExtBase::OnTimer(Timer& t)
26{
27 OnIdle();
28}
29
30auto ClientResize = [](HWND hWnd, int nWidth, int nHeight) {
31 RECT rcClient, rcWindow;
32 POINT ptDiff;
33 int screenwidth, screenheight;
34 int x, y;
35
36 screenwidth = GetSystemMetrics(SM_CXSCREEN);
37 screenheight = GetSystemMetrics(SM_CYSCREEN);
38 x = (screenwidth / 2) - (nWidth / 2);
39 y = (screenheight / 2) - (nHeight / 2);
40
41 GetClientRect(hWnd, &rcClient);
42 GetWindowRect(hWnd, &rcWindow);
43 ptDiff.x = (rcWindow.right - rcWindow.left) - rcClient.right;
44 ptDiff.y = (rcWindow.bottom - rcWindow.top) - rcClient.bottom;
45
46 SetWindowPos(hWnd, 0, x, y, nWidth + ptDiff.x, nHeight + ptDiff.y, 0);
47};
48
49bool ReaperExtBase::EditorResizeFromUI(int viewWidth, int viewHeight, bool needsPlatformResize)
50{
51 if (viewWidth != GetEditorWidth() || viewHeight != GetEditorHeight())
52 {
53 // Don't resize the window when docked — REAPER controls the dock size
54 if (!IsDocked() && needsPlatformResize)
55 {
56#ifdef OS_MAC
57#define TITLEBAR_BODGE 22 //TODO: sort this out
58 RECT r;
59 GetWindowRect(gHWND, &r);
60 SetWindowPos(gHWND, 0, r.left, r.bottom - viewHeight - TITLEBAR_BODGE, viewWidth, viewHeight + TITLEBAR_BODGE, 0);
61#endif
62 }
63
64 return true;
65 }
66
67 return false;
68}
69
70void ReaperExtBase::CreateMainWindow()
71{
72 if (gHWND != NULL)
73 return;
74
75 // Lazy load state on first window creation (after API imports are done)
76 if (!mStateLoaded)
77 {
78 LoadDockState();
79 mStateLoaded = true;
80 }
81
82 gHWND = CreateDialog(gHINSTANCE, MAKEINTRESOURCE(IDD_DIALOG_MAIN), gParent, ReaperExtBase::MainDlgProc);
83}
84
85void ReaperExtBase::DestroyMainWindow()
86{
87 if (gHWND == NULL)
88 return;
89
90 SaveDockState();
91 gPlug->CloseWindow();
92 DockWindowRemove(gHWND);
93 DestroyWindow(gHWND);
94 gHWND = NULL;
95}
96
98{
99 if (gHWND == NULL)
100 {
101 CreateMainWindow();
102 if (IsDocked())
103 DockWindowActivate(gHWND);
104 }
105 else
106 {
107 DestroyMainWindow();
108 }
109}
110
112{
113 if (gHWND == NULL)
114 return;
115
116 // Save floating position before toggling
117 if (!IsDocked())
118 GetWindowRect(gHWND, &mDockState.r);
119
120 // Destroy and recreate - this is the SWS pattern for reliable dock toggling
121 mSaveStateOnDestroy = false;
122 gPlug->CloseWindow();
123 DockWindowRemove(gHWND);
124 DestroyWindow(gHWND);
125 gHWND = NULL;
126
127 // Toggle docked bit
128 mDockState.state ^= 2;
129 mSaveStateOnDestroy = true;
130
131 // Recreate window with new state
132 CreateMainWindow();
133 if (IsDocked())
134 DockWindowActivate(gHWND);
135}
136
137void ReaperExtBase::SaveDockState()
138{
139 const char* iniFile = get_ini_file();
140 if (!iniFile)
141 return;
142
143 if (gHWND != NULL)
144 {
145 int dockIdx = DockIsChildOfDock(gHWND, NULL);
146 if (dockIdx >= 0)
147 mDockState.whichdock = dockIdx;
148 else
149 GetWindowRect(gHWND, &mDockState.r);
150 }
151
152 // Set visible bit based on window state
153 if (gHWND != NULL && IsWindowVisible(gHWND))
154 mDockState.state |= 1;
155 else
156 mDockState.state &= ~1;
157
158 // Convert to little-endian for cross-platform compatibility
159 ReaperExtDockState stateLE;
160 memcpy(&stateLE, &mDockState, sizeof(ReaperExtDockState));
161 for (int i = 0; i < (int)(sizeof(ReaperExtDockState) / sizeof(int)); i++)
162 REAPER_MAKELEINTMEM(&((int*)&stateLE)[i]);
163
164 WritePrivateProfileStruct("iPlug2", mDockId.Get(), &stateLE, sizeof(ReaperExtDockState), iniFile);
165}
166
167void ReaperExtBase::LoadDockState()
168{
169 const char* iniFile = get_ini_file();
170 if (!iniFile)
171 return;
172
173 ReaperExtDockState stateLE;
174 if (GetPrivateProfileStruct("iPlug2", mDockId.Get(), &stateLE, sizeof(ReaperExtDockState), iniFile))
175 {
176 // Convert from little-endian
177 for (int i = 0; i < (int)(sizeof(ReaperExtDockState) / sizeof(int)); i++)
178 REAPER_MAKELEINTMEM(&((int*)&stateLE)[i]);
179 memcpy(&mDockState, &stateLE, sizeof(ReaperExtDockState));
180 }
181}
182
183void ReaperExtBase::RegisterAction(const char* actionName, std::function<void()> func, bool addMenuItem, int* pToggle/*, IKeyPress keyCmd*/)
184{
185 ReaperAction action;
186
187 int commandID = mRec->Register("command_id", (void*) actionName /* ?? */);
188
189 assert(commandID);
190
191 action.func = func;
192 action.accel.accel.cmd = commandID;
193 action.accel.desc = actionName;
194 action.addMenuItem = addMenuItem;
195 action.pToggle = pToggle;
196
197 gActions.push_back(action);
198
199 mRec->Register("gaccel", (void*) &gActions.back().accel);
200}
201
202//static
203bool ReaperExtBase::HookCommandProc(int command, int flag)
204{
205 std::vector<ReaperAction>::iterator it = std::find_if (gActions.begin(), gActions.end(), [&](const auto& e) { return e.accel.accel.cmd == command; });
206
207 if(it != gActions.end())
208 {
209 it->func();
210 }
211
212 return false;
213}
214
215//static
216int ReaperExtBase::ToggleActionCallback(int command)
217{
218 std::vector<ReaperAction>::iterator it = std::find_if (gActions.begin(), gActions.end(), [&](const auto& e) { return e.accel.accel.cmd == command; });
219
220 if(it != gActions.end())
221 {
222 if(it->pToggle == nullptr)
223 return -1;
224 else
225 return *it->pToggle;
226 }
227
228 return 0;
229}
230
231//static
232WDL_DLGRET ReaperExtBase::MainDlgProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
233{
234 extern float GetScaleForHWND(HWND hWnd);
235
236 switch (uMsg)
237 {
238 case WM_INITDIALOG:
239 {
240 auto scale = GetScaleForHWND(hwnd);
241
242 if (gPlug->IsDocked())
243 {
244 // Docked: register with dock system
245 DockWindowAddEx(hwnd, (char*)gPlug->mDockId.Get(), gPlug->mDockId.Get(), true);
246 }
247 else
248 {
249 // Floating: restore position and show
250 if (gPlug->mDockState.r.left || gPlug->mDockState.r.top ||
251 gPlug->mDockState.r.right || gPlug->mDockState.r.bottom)
252 {
253 EnsureNotCompletelyOffscreen(&gPlug->mDockState.r);
254 SetWindowPos(hwnd, NULL,
255 gPlug->mDockState.r.left, gPlug->mDockState.r.top,
256 gPlug->mDockState.r.right - gPlug->mDockState.r.left,
257 gPlug->mDockState.r.bottom - gPlug->mDockState.r.top,
258 SWP_NOZORDER);
259 }
260 else
261 {
262 ClientResize(hwnd, static_cast<int>(PLUG_WIDTH * scale), static_cast<int>(PLUG_HEIGHT * scale));
263 }
264 AttachWindowTopmostButton(hwnd);
265 ShowWindow(hwnd, SW_SHOW);
266 }
267
268 gPlug->OpenWindow(hwnd);
269
270 // Trigger initial resize now that IGraphics exists
271 // (WM_SIZE during SetWindowPos/DockWindowAddEx above fires before OpenWindow)
272 {
273 RECT r;
274 GetClientRect(hwnd, &r);
275 int w = r.right - r.left;
276 int h = r.bottom - r.top;
277 if (w > 0 && h > 0)
278 gPlug->OnParentWindowResize(w, h);
279 }
280
281 GetWindowRect(hwnd, &gPrevBounds);
282
283 return 0;
284 }
285 case WM_DESTROY:
286 {
287 if (gPlug->mSaveStateOnDestroy)
288 gPlug->SaveDockState();
289
290 DockWindowRemove(hwnd);
291 gHWND = NULL;
292 return 0;
293 }
294 case WM_SIZE:
295 {
296 if (gPlug->GetUI())
297 {
298 RECT r;
299 GetClientRect(hwnd, &r);
300 int w = r.right - r.left;
301 int h = r.bottom - r.top;
302 if (w > 0 && h > 0)
303 gPlug->OnParentWindowResize(w, h);
304 }
305 return 0;
306 }
307 case WM_CLOSE:
308 gPlug->CloseWindow();
309 DestroyWindow(hwnd);
310 return 0;
311 }
312 return 0;
313}
State structure for dock window persistence - matches SWS pattern.
Definition: ReaperExtBase.h:30
void ShowHideMainWindow()
Toggles the visibility of the main extension window.
bool IsDocked() const
Returns true if the window is currently docked.
Definition: ReaperExtBase.h:69
void ToggleDocking()
Toggles between docked and floating state.
void RegisterAction(const char *actionName, std::function< void()> func, bool addMenuItem=false, int *pToggle=nullptr)
Registers an action with the REAPER extension system.
virtual void OnIdle()
Called during idle processing - override to perform periodic tasks.
Definition: ReaperExtBase.h:53
Helper struct for registering Reaper Actions.
Base class for timer.
Definition: IPlugTimer.h:40