iPlug2 - C++ Audio Plug-in Framework
Loading...
Searching...
No Matches
IGraphicsWin.cpp
1/*
2 ==============================================================================
3
4 This file is part of the iPlug 2 library. Copyright (C) the iPlug 2 developers.
5
6 See LICENSE.txt for more info.
7
8 ==============================================================================
9*/
10
11
12#include <Shlobj.h>
13#include <commctrl.h>
14
15#include "heapbuf.h"
16
17#include "IPlugParameter.h"
18#include "IGraphicsWin.h"
19#include "IGraphicsWin_dnd.h"
20#include "IPopupMenuControl.h"
21#include "IPlugPaths.h"
22
23#include <wininet.h>
24#include <VersionHelpers.h>
25
26#if defined __clang__
27#undef CCSIZEOF_STRUCT
28#define CCSIZEOF_STRUCT(structname, member) (__builtin_offsetof(structname, member) + sizeof(((structname*)0)->member))
29#endif
30
31using namespace iplug;
32using namespace igraphics;
33
34#pragma warning(disable:4244) // Pointer size cast mismatch.
35#pragma warning(disable:4312) // Pointer size cast mismatch.
36#pragma warning(disable:4311) // Pointer size cast mismatch.
37
38static int nWndClassReg = 0;
39static const wchar_t* wndClassName = L"IPlugWndClass";
40static double sFPS = 0.0;
41
42#define PARAM_EDIT_ID 99
43#define IPLUG_TIMER_ID 2
44
45#define TOOLTIPWND_MAXWIDTH 250
46
47#define WM_VBLANK (WM_USER+1)
48
49#ifdef IGRAPHICS_GL3
50typedef HGLRC(WINAPI* PFNWGLCREATECONTEXTATTRIBSARBPROC) (HDC hDC, HGLRC hShareContext, const int* attribList);
51#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091
52#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092
53#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126
54#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001
55#endif
56
57#pragma mark - Static storage
58
59StaticStorage<IGraphicsWin::InstalledFont> IGraphicsWin::sPlatformFontCache;
60StaticStorage<HFontHolder> IGraphicsWin::sHFontCache;
61
62#pragma mark - Mouse and tablet helpers
63
64extern float GetScaleForHWND(HWND hWnd);
65
66inline IMouseInfo IGraphicsWin::GetMouseInfo(LPARAM lParam, WPARAM wParam)
67{
68 IMouseInfo info;
69 const float scale = GetTotalScale();
70 info.x = mCursorX = GET_X_LPARAM(lParam) / scale;
71 info.y = mCursorY = GET_Y_LPARAM(lParam) / scale;
72 info.ms = IMouseMod((wParam & MK_LBUTTON), (wParam & MK_RBUTTON), (wParam & MK_SHIFT), (wParam & MK_CONTROL),
73#ifdef AAX_API
74 GetAsyncKeyState(VK_MENU) < 0
75#else
76 GetKeyState(VK_MENU) < 0
77#endif
78 );
79
80 return info;
81}
82
83void IGraphicsWin::CheckTabletInput(UINT msg)
84{
85 if ((msg == WM_LBUTTONDOWN) || (msg == WM_RBUTTONDOWN) || (msg == WM_MBUTTONDOWN) || (msg == WM_MOUSEMOVE)
86 || (msg == WM_RBUTTONDBLCLK) || (msg == WM_LBUTTONDBLCLK) || (msg == WM_MBUTTONDBLCLK)
87 || (msg == WM_RBUTTONUP) || (msg == WM_LBUTTONUP) || (msg == WM_MBUTTONUP)
88 || (msg == WM_MOUSEHOVER) || (msg == WM_MOUSELEAVE))
89 {
90 const LONG_PTR c_SIGNATURE_MASK = 0xFFFFFF00;
91 const LONG_PTR c_MOUSEEVENTF_FROMTOUCH = 0xFF515700;
92
93 LONG_PTR extraInfo = GetMessageExtraInfo();
94 SetTabletInput(((extraInfo & c_SIGNATURE_MASK) == c_MOUSEEVENTF_FROMTOUCH));
95 mCursorLock &= !mTabletInput;
96 }
97}
98
99void IGraphicsWin::DestroyEditWindow()
100{
101 if (mParamEditWnd)
102 {
103 SetWindowLongPtrW(mParamEditWnd, GWLP_WNDPROC, (LPARAM) mDefEditProc);
104 DestroyWindow(mParamEditWnd);
105 mParamEditWnd = nullptr;
106 mDefEditProc = nullptr;
107 DeleteObject(mEditFont);
108 mEditFont = nullptr;
109 }
110}
111
112void IGraphicsWin::OnDisplayTimer(int vBlankCount)
113{
114 // Check the message vblank with the current one to see if we are way behind. If so, then throw these away.
115 DWORD msgCount = vBlankCount;
116 DWORD curCount = mVBlankCount;
117
118 if (mVSYNCEnabled)
119 {
120 // skip until the actual vblank is at a certain number.
121 if (mVBlankSkipUntil != 0 && mVBlankSkipUntil > mVBlankCount)
122 {
123 return;
124 }
125
126 mVBlankSkipUntil = 0;
127
128 if (msgCount != curCount)
129 {
130 // we are late, just skip it until we can get a message soon after the vblank event.
131 // DBGMSG("vblank is late by %i frames. Skipping.", (mVBlankCount - msgCount));
132 return;
133 }
134 }
135
136 if (mParamEditWnd && mParamEditMsg != kNone)
137 {
138 switch (mParamEditMsg)
139 {
140 case kCommit:
141 {
142 WCHAR strWide[MAX_WIN32_PARAM_LEN];
143 SendMessageW(mParamEditWnd, WM_GETTEXT, MAX_WIN32_PARAM_LEN, (LPARAM) strWide);
144 SetControlValueAfterTextEdit(UTF16AsUTF8(strWide).Get());
145 DestroyEditWindow();
146 break;
147 }
148 case kCancel:
149 DestroyEditWindow();
150 ClearInTextEntryControl();
151 break;
152 }
153
154 mParamEditMsg = kNone;
155
156 return; // TODO: check this!
157 }
158
159 // TODO: move this... listen to the right messages in windows for screen resolution changes, etc.
160 if (!GetCapture()) // workaround Windows issues with window sizing during mouse move
161 {
162 float scale = GetScaleForHWND(mPlugWnd);
163 if (scale != GetScreenScale())
164 SetScreenScale(scale);
165 }
166
167 // TODO: this is far too aggressive for slow drawing animations and data changing. We need to
168 // gate the rate of updates to a certain percentage of the wall clock time.
169 IRECTList rects;
170 const float totalScale = GetTotalScale();
171 if (IsDirty(rects))
172 {
173 SetAllControlsClean();
174
175 for (int i = 0; i < rects.Size(); i++)
176 {
177 IRECT dirtyR = rects.Get(i);
178 dirtyR.Scale(totalScale);
179 dirtyR.PixelAlign();
180 RECT r = { (LONG)dirtyR.L, (LONG)dirtyR.T, (LONG)dirtyR.R, (LONG)dirtyR.B };
181 InvalidateRect(mPlugWnd, &r, FALSE);
182 }
183
184 if (mParamEditWnd)
185 {
186 IRECT notDirtyR = mEditRECT;
187 notDirtyR.Scale(totalScale);
188 notDirtyR.PixelAlign();
189 RECT r2 = { (LONG)notDirtyR.L, (LONG)notDirtyR.T, (LONG)notDirtyR.R, (LONG)notDirtyR.B };
190 ValidateRect(mPlugWnd, &r2); // make sure we dont redraw the edit box area
191 UpdateWindow(mPlugWnd);
192 mParamEditMsg = kUpdate;
193 }
194 else
195 {
196 // Force a redraw right now
197 UpdateWindow(mPlugWnd);
198
199 if (mVSYNCEnabled)
200 {
201 // Check and see if we are still in this frame.
202 curCount = mVBlankCount;
203 if (msgCount != curCount)
204 {
205 // we are late, skip the next vblank to give us a breather.
206 mVBlankSkipUntil = curCount+1;
207 //DBGMSG("vblank painting was late by %i frames.", (mVBlankSkipUntil - msgCount));
208 }
209 }
210 }
211 }
212 return;
213}
214
215// static
216LRESULT CALLBACK IGraphicsWin::WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
217{
218 if (msg == WM_CREATE)
219 {
220 CREATESTRUCTW* lpcs = (CREATESTRUCTW *) lParam;
221 SetWindowLongPtrW(hWnd, GWLP_USERDATA, (LPARAM) lpcs->lpCreateParams);
222 IGraphicsWin* pGraphics = (IGraphicsWin*) GetWindowLongPtrW(hWnd, GWLP_USERDATA);
223
224 if (pGraphics->mVSYNCEnabled) // use VBLANK thread
225 {
226 assert((pGraphics->FPS() == 60) && "If you want to run at frame rates other than 60FPS");
227 pGraphics->StartVBlankThread(hWnd);
228 }
229 else // use WM_TIMER -- its best to get below 16ms because the windows time quanta is slightly above 15ms.
230 {
231 int mSec = static_cast<int>(std::floorf(1000.0f / (pGraphics->FPS())));
232 if (mSec < 20) mSec = 15;
233 SetTimer(hWnd, IPLUG_TIMER_ID, mSec, NULL);
234 }
235
236 SetFocus(hWnd); // gets scroll wheel working straight away
237 DragAcceptFiles(hWnd, true);
238 return 0;
239 }
240
241 IGraphicsWin* pGraphics = (IGraphicsWin*) GetWindowLongPtrW(hWnd, GWLP_USERDATA);
242
243 if (!pGraphics || hWnd != pGraphics->mPlugWnd)
244 {
245 return DefWindowProcW(hWnd, msg, wParam, lParam);
246 }
247
248 if (pGraphics->mParamEditWnd && pGraphics->mParamEditMsg == kEditing)
249 {
250 if (msg == WM_RBUTTONDOWN || (msg == WM_LBUTTONDOWN))
251 {
252 pGraphics->mParamEditMsg = kCancel;
253 return 0;
254 }
255 return DefWindowProcW(hWnd, msg, wParam, lParam);
256 }
257
258 auto IsTouchEvent = []() {
259 const LONG_PTR c_SIGNATURE_MASK = 0xFFFFFF00;
260 const LONG_PTR c_MOUSEEVENTF_FROMTOUCH = 0xFF515700;
261 LONG_PTR extraInfo = GetMessageExtraInfo();
262 return ((extraInfo & c_SIGNATURE_MASK) == c_MOUSEEVENTF_FROMTOUCH);
263 };
264
265 pGraphics->CheckTabletInput(msg);
266
267 switch (msg)
268 {
269 case WM_VBLANK:
270 pGraphics->OnDisplayTimer(wParam);
271 return 0;
272
273 case WM_TIMER:
274 if (wParam == IPLUG_TIMER_ID)
275 pGraphics->OnDisplayTimer(0);
276
277 return 0;
278
279 case WM_ERASEBKGND:
280 return 0;
281
282 case WM_RBUTTONDOWN:
283 case WM_LBUTTONDOWN:
284 case WM_MBUTTONDOWN:
285 {
286 if (IsTouchEvent())
287 return 0;
288
289 pGraphics->HideTooltip();
290 if (pGraphics->mParamEditWnd)
291 {
292 pGraphics->mParamEditMsg = kCommit;
293 return 0;
294 }
295 SetFocus(hWnd); // Added to get keyboard focus again when user clicks in window
296 SetCapture(hWnd);
297 IMouseInfo info = pGraphics->GetMouseInfo(lParam, wParam);
298 std::vector<IMouseInfo> list{ info };
299 pGraphics->OnMouseDown(list);
300 return 0;
301 }
302 case WM_SETCURSOR:
303 {
304 pGraphics->OnSetCursor();
305 return 0;
306 }
307 case WM_MOUSEMOVE:
308 {
309 if (IsTouchEvent())
310 return 0;
311
312 if (!(wParam & (MK_LBUTTON | MK_RBUTTON)))
313 {
314 IMouseInfo info = pGraphics->GetMouseInfo(lParam, wParam);
315 if (pGraphics->OnMouseOver(info.x, info.y, info.ms))
316 {
317 TRACKMOUSEEVENT eventTrack = { sizeof(TRACKMOUSEEVENT), TME_LEAVE, hWnd, HOVER_DEFAULT };
318 if (pGraphics->TooltipsEnabled())
319 {
320 int c = pGraphics->GetMouseOver();
321 if (c != pGraphics->mTooltipIdx)
322 {
323 if (c >= 0) eventTrack.dwFlags |= TME_HOVER;
324 pGraphics->mTooltipIdx = c;
325 pGraphics->HideTooltip();
326 }
327 }
328
329 TrackMouseEvent(&eventTrack);
330 }
331 }
332 else if (GetCapture() == hWnd && !pGraphics->IsInPlatformTextEntry())
333 {
334 float oldX = pGraphics->mCursorX;
335 float oldY = pGraphics->mCursorY;
336
337 IMouseInfo info = pGraphics->GetMouseInfo(lParam, wParam);
338
339 info.dX = info.x - oldX;
340 info.dY = info.y - oldY;
341
342 if (info.dX || info.dY)
343 {
344 std::vector<IMouseInfo> list{ info };
345 pGraphics->OnMouseDrag(list);
346
347 if (pGraphics->MouseCursorIsLocked())
348 {
349 const float x = pGraphics->mHiddenCursorX;
350 const float y = pGraphics->mHiddenCursorY;
351
352 pGraphics->MoveMouseCursor(x, y);
353 pGraphics->mHiddenCursorX = x;
354 pGraphics->mHiddenCursorY = y;
355 }
356 }
357 }
358
359 return 0;
360 }
361 case WM_MOUSEHOVER:
362 {
363 pGraphics->ShowTooltip();
364 return 0;
365 }
366 case WM_MOUSELEAVE:
367 {
368 pGraphics->HideTooltip();
369 pGraphics->OnMouseOut();
370 return 0;
371 }
372 case WM_LBUTTONUP:
373 case WM_RBUTTONUP:
374 {
375 ReleaseCapture();
376 IMouseInfo info = pGraphics->GetMouseInfo(lParam, wParam);
377 std::vector<IMouseInfo> list{ info };
378 pGraphics->OnMouseUp(list);
379 return 0;
380 }
381 case WM_LBUTTONDBLCLK:
382 case WM_RBUTTONDBLCLK:
383 {
384 if (IsTouchEvent())
385 return 0;
386
387 IMouseInfo info = pGraphics->GetMouseInfo(lParam, wParam);
388 if (pGraphics->OnMouseDblClick(info.x, info.y, info.ms))
389 {
390 SetCapture(hWnd);
391 }
392 return 0;
393 }
394 case WM_MOUSEWHEEL:
395 {
396 if (pGraphics->mParamEditWnd)
397 {
398 pGraphics->mParamEditMsg = kCancel;
399 return 0;
400 }
401 else
402 {
403 IMouseInfo info = pGraphics->GetMouseInfo(lParam, wParam);
404 float d = GET_WHEEL_DELTA_WPARAM(wParam) / WHEEL_DELTA;
405 const float scale = pGraphics->GetTotalScale();
406 RECT r;
407 GetWindowRect(hWnd, &r);
408 pGraphics->OnMouseWheel(info.x - (r.left / scale), info.y - (r.top / scale), info.ms, d);
409 return 0;
410 }
411 }
412 case WM_TOUCH:
413 {
414 UINT nTouches = LOWORD(wParam);
415
416 if (nTouches > 0)
417 {
418 WDL_TypedBuf<TOUCHINPUT> touches;
419 touches.Resize(nTouches);
420 HTOUCHINPUT hTouchInput = (HTOUCHINPUT) lParam;
421 std::vector<IMouseInfo> downlist;
422 std::vector<IMouseInfo> uplist;
423 std::vector<IMouseInfo> movelist;
424 const float scale = pGraphics->GetTotalScale();
425
426 GetTouchInputInfo(hTouchInput, nTouches, touches.Get(), sizeof(TOUCHINPUT));
427
428 for (int i = 0; i < nTouches; i++)
429 {
430 TOUCHINPUT* pTI = touches.Get() +i;
431
432 POINT pt;
433 pt.x = TOUCH_COORD_TO_PIXEL(pTI->x);
434 pt.y = TOUCH_COORD_TO_PIXEL(pTI->y);
435 ScreenToClient(pGraphics->mPlugWnd, &pt);
436
437 IMouseInfo info;
438 info.x = static_cast<float>(pt.x) / scale;
439 info.y = static_cast<float>(pt.y) / scale;
440 info.dX = 0.f;
441 info.dY = 0.f;
442 info.ms.touchRadius = 0;
443
444 if (pTI->dwMask & TOUCHINPUTMASKF_CONTACTAREA)
445 {
446 info.ms.touchRadius = pTI->cxContact;
447 }
448
449 info.ms.touchID = static_cast<ITouchID>(pTI->dwID);
450
451 if (pTI->dwFlags & TOUCHEVENTF_DOWN)
452 {
453 downlist.push_back(info);
454 pGraphics->mDeltaCapture.insert(std::make_pair(info.ms.touchID, info));
455 }
456 else if (pTI->dwFlags & TOUCHEVENTF_UP)
457 {
458 pGraphics->mDeltaCapture.erase(info.ms.touchID);
459 uplist.push_back(info);
460 }
461 else if (pTI->dwFlags & TOUCHEVENTF_MOVE)
462 {
463 IMouseInfo previous = pGraphics->mDeltaCapture.find(info.ms.touchID)->second;
464 info.dX = info.x - previous.x;
465 info.dY = info.y - previous.y;
466 movelist.push_back(info);
467 pGraphics->mDeltaCapture[info.ms.touchID] = info;
468 }
469 }
470
471 if (downlist.size())
472 pGraphics->OnMouseDown(downlist);
473
474 if (uplist.size())
475 pGraphics->OnMouseUp(uplist);
476
477 if (movelist.size())
478 pGraphics->OnMouseDrag(movelist);
479
480 CloseTouchInputHandle(hTouchInput);
481 }
482 return 0;
483 }
484 case WM_GETDLGCODE:
485 return DLGC_WANTALLKEYS;
486 case WM_KEYDOWN:
487 case WM_KEYUP:
488 {
489 POINT p;
490 GetCursorPos(&p);
491 ScreenToClient(hWnd, &p);
492
493 BYTE keyboardState[256] = {};
494 GetKeyboardState(keyboardState);
495 const int keyboardScanCode = (lParam >> 16) & 0x00ff;
496 WORD character = 0;
497 const int len = ToAscii(wParam, keyboardScanCode, keyboardState, &character, 0);
498 // TODO: should get unicode?
499 bool handle = false;
500
501 // send when len is 0 because wParam might be something like VK_LEFT or VK_HOME, etc.
502 if (len == 0 || len == 1)
503 {
504 char str[2];
505 str[0] = static_cast<char>(character);
506 str[1] = '\0';
507
508 IKeyPress keyPress{ str, static_cast<int>(wParam),
509 static_cast<bool>(GetKeyState(VK_SHIFT) & 0x8000),
510 static_cast<bool>(GetKeyState(VK_CONTROL) & 0x8000),
511 static_cast<bool>(GetKeyState(VK_MENU) & 0x8000) };
512
513 const float scale = pGraphics->GetTotalScale();
514
515 if (msg == WM_KEYDOWN)
516 handle = pGraphics->OnKeyDown(p.x / scale, p.y / scale, keyPress);
517 else
518 handle = pGraphics->OnKeyUp(p.x / scale, p.y / scale, keyPress);
519 }
520
521 if (!handle)
522 {
523 HWND rootHWnd = GetAncestor( hWnd, GA_ROOT);
524 SendMessageW(rootHWnd, msg, wParam, lParam);
525 return DefWindowProcW(hWnd, msg, wParam, lParam);
526 }
527 else
528 return 0;
529 }
530 case WM_PAINT:
531 {
532 const float scale = pGraphics->GetTotalScale();
533 auto addDrawRect = [pGraphics, scale](IRECTList& rects, RECT r) {
534 IRECT ir(r.left, r.top, r.right, r.bottom);
535 ir.Scale(1.f/scale);
536 ir.PixelAlign();
537 rects.Add(ir);
538 };
539
540 HRGN region = CreateRectRgn(0, 0, 0, 0);
541 int regionType = GetUpdateRgn(hWnd, region, FALSE);
542
543 if ((regionType == COMPLEXREGION) || (regionType == SIMPLEREGION))
544 {
545 IRECTList rects;
546 const int bufferSize = sizeof(RECT) * 64;
547 unsigned char stackBuffer[sizeof(RGNDATA) + bufferSize];
548 RGNDATA* regionData = (RGNDATA*)stackBuffer;
549
550 if (regionType == COMPLEXREGION && GetRegionData(region, bufferSize, regionData))
551 {
552 for (int i = 0; i < regionData->rdh.nCount; i++)
553 addDrawRect(rects, *(((RECT*)regionData->Buffer) + i));
554 }
555 else
556 {
557 RECT r;
558 GetRgnBox(region, &r);
559 addDrawRect(rects, r);
560 }
561
562#if defined IGRAPHICS_GL //|| IGRAPHICS_D2D
563 PAINTSTRUCT ps;
564 BeginPaint(hWnd, &ps);
565#endif
566
567 {
568 ScopedGLContext scopedGLCtx {pGraphics};
569 pGraphics->Draw(rects);
570 SwapBuffers((HDC) pGraphics->GetPlatformContext());
571 }
572
573#if defined IGRAPHICS_GL || IGRAPHICS_D2D
574 EndPaint(hWnd, &ps);
575#endif
576 }
577
578 // For the D2D if we don't call endpaint, then you really need to call ValidateRect otherwise
579 // we are just going to get another WM_PAINT to handle. Bad! It also exibits the odd property
580 // that windows will be popped under the window.
581 ValidateRect(hWnd, 0);
582
583 DeleteObject(region);
584
585 return 0;
586 }
587
588 case WM_CTLCOLOREDIT:
589 {
590 if (!pGraphics->mParamEditWnd)
591 return 0;
592
593 const IText& text = pGraphics->mEditText;
594 HDC dc = (HDC) wParam;
595 SetBkColor(dc, RGB(text.mTextEntryBGColor.R, text.mTextEntryBGColor.G, text.mTextEntryBGColor.B));
596 SetTextColor(dc, RGB(text.mTextEntryFGColor.R, text.mTextEntryFGColor.G, text.mTextEntryFGColor.B));
597 SetBkMode(dc, OPAQUE);
598 SetDCBrushColor(dc, RGB(text.mTextEntryBGColor.R, text.mTextEntryBGColor.G, text.mTextEntryBGColor.B));
599 return (LRESULT)GetStockObject(DC_BRUSH);
600 }
601 case WM_DROPFILES:
602 {
603 HDROP hdrop = (HDROP) wParam;
604
605 int numDroppedFiles = DragQueryFileW(hdrop, -1, nullptr, 0);
606
607 std::vector<std::vector<char>> pathBuffers(numDroppedFiles, std::vector<char>(1025, 0));
608 std::vector<const char*> pathPtrs(numDroppedFiles);
609
610 for (int i = 0; i < numDroppedFiles; i++)
611 {
612 wchar_t pathBufferW[1025] = {'\0'};
613 DragQueryFileW(hdrop, i, pathBufferW, 1024);
614 strncpy(pathBuffers[i].data(), UTF16AsUTF8(pathBufferW).Get(), 1024);
615 pathPtrs[i] = pathBuffers[i].data();
616 }
617 POINT p;
618 DragQueryPoint(hdrop, &p);
619
620 const float scale = pGraphics->GetTotalScale();
621
622 if (numDroppedFiles==1)
623 {
624 pGraphics->OnDrop(&pathPtrs[0][0], p.x / scale, p.y / scale);
625 }
626 else
627 {
628 pGraphics->OnDropMultiple(pathPtrs, p.x / scale, p.y / scale);
629 }
630
631 return 0;
632 }
633 case WM_CLOSE:
634 {
635 pGraphics->CloseWindow();
636 return 0;
637 }
638 case WM_SETFOCUS:
639 {
640 return 0;
641 }
642 case WM_KILLFOCUS:
643 {
644 return 0;
645 }
646 }
647 return DefWindowProcW(hWnd, msg, wParam, lParam);
648}
649
650// static
651LRESULT CALLBACK IGraphicsWin::ParamEditProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
652{
653 IGraphicsWin* pGraphics = (IGraphicsWin*) GetWindowLongPtrW(GetParent(hWnd), GWLP_USERDATA);
654
655 if (pGraphics && pGraphics->mParamEditWnd && pGraphics->mParamEditWnd == hWnd)
656 {
657 pGraphics->HideTooltip();
658
659 switch (msg)
660 {
661 case WM_CHAR:
662 {
663 // limit to numbers for text entry on appropriate parameters
664 if (pGraphics->mEditParam)
665 {
666 char c = wParam;
667
668 if (c == 0x08) break; // backspace
669
670 switch (pGraphics->mEditParam->Type())
671 {
672 case IParam::kTypeEnum:
673 case IParam::kTypeInt:
674 case IParam::kTypeBool:
675 if (c >= '0' && c <= '9') break;
676 else if (c == '-') break;
677 else if (c == '+') break;
678 else return 0;
679 case IParam::kTypeDouble:
680 if (c >= '0' && c <= '9') break;
681 else if (c == '-') break;
682 else if (c == '+') break;
683 else if (c == '.') break;
684 else return 0;
685 default:
686 break;
687 }
688 }
689 break;
690 }
691 case WM_KEYDOWN:
692 {
693 if (wParam == VK_RETURN)
694 {
695 pGraphics->mParamEditMsg = kCommit;
696 return 0;
697 }
698 else if (wParam == VK_ESCAPE)
699 {
700 pGraphics->mParamEditMsg = kCancel;
701 return 0;
702 }
703 break;
704 }
705 case WM_SETFOCUS:
706 {
707 pGraphics->mParamEditMsg = kEditing;
708 break;
709 }
710 case WM_KILLFOCUS:
711 {
712 pGraphics->mParamEditMsg = kCommit;
713 break;
714 }
715 // handle WM_GETDLGCODE so that we can say that we want the return key message
716 // (normally single line edit boxes don't get sent return key messages)
717 case WM_GETDLGCODE:
718 {
719 LPARAM lres;
720 // find out if the original control wants it
721 lres = CallWindowProcW(pGraphics->mDefEditProc, hWnd, WM_GETDLGCODE, wParam, lParam);
722 // add in that we want it if it is a return keydown
723 if (lParam && ((MSG*)lParam)->message == WM_KEYDOWN && wParam == VK_RETURN)
724 {
725 lres |= DLGC_WANTMESSAGE;
726 }
727 return lres;
728 }
729 case WM_COMMAND:
730 {
731 switch HIWORD(wParam)
732 {
733 case CBN_SELCHANGE:
734 {
735 if (pGraphics->mParamEditWnd)
736 {
737 pGraphics->mParamEditMsg = kCommit;
738 return 0;
739 }
740 }
741
742 }
743 break; // Else let the default proc handle it.
744 }
745 }
746 return CallWindowProcW(pGraphics->mDefEditProc, hWnd, msg, wParam, lParam);
747 }
748 return DefWindowProcW(hWnd, msg, wParam, lParam);
749}
750
751IGraphicsWin::IGraphicsWin(IGEditorDelegate& dlg, int w, int h, int fps, float scale)
752 : IGRAPHICS_DRAW_CLASS(dlg, w, h, fps, scale)
753{
754 StaticStorage<InstalledFont>::Accessor fontStorage(sPlatformFontCache);
755 StaticStorage<HFontHolder>::Accessor hfontStorage(sHFontCache);
756 fontStorage.Retain();
757 hfontStorage.Retain();
758
759#ifndef IGRAPHICS_DISABLE_VSYNC
760 mVSYNCEnabled = IsWindows8OrGreater();
761#endif
762}
763
764IGraphicsWin::~IGraphicsWin()
765{
766 StaticStorage<InstalledFont>::Accessor fontStorage(sPlatformFontCache);
767 StaticStorage<HFontHolder>::Accessor hfontStorage(sHFontCache);
768 fontStorage.Release();
769 hfontStorage.Release();
770 DestroyEditWindow();
771 CloseWindow();
772}
773
774static void GetWindowSize(HWND pWnd, int* pW, int* pH)
775{
776 if (pWnd)
777 {
778 RECT r;
779 GetWindowRect(pWnd, &r);
780 *pW = r.right - r.left;
781 *pH = r.bottom - r.top;
782 }
783 else
784 {
785 *pW = *pH = 0;
786 }
787}
788
789static bool IsChildWindow(HWND pWnd)
790{
791 if (pWnd)
792 {
793 int style = GetWindowLongW(pWnd, GWL_STYLE);
794 int exStyle = GetWindowLongW(pWnd, GWL_EXSTYLE);
795 return ((style & WS_CHILD) && !(exStyle & WS_EX_MDICHILD));
796 }
797 return false;
798}
799
800void IGraphicsWin::ForceEndUserEdit()
801{
802 mParamEditMsg = kCancel;
803}
804
805static UINT SETPOS_FLAGS = SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE;
806
807void IGraphicsWin::PlatformResize(bool parentHasResized)
808{
809 if (WindowIsOpen())
810 {
811 HWND pParent = 0, pGrandparent = 0;
812 int dlgW = 0, dlgH = 0, parentW = 0, parentH = 0, grandparentW = 0, grandparentH = 0;
813 GetWindowSize(mPlugWnd, &dlgW, &dlgH);
814 int dw = (WindowWidth() * GetScreenScale()) - dlgW, dh = (WindowHeight()* GetScreenScale()) - dlgH;
815
816 if (IsChildWindow(mPlugWnd))
817 {
818 pParent = GetParent(mPlugWnd);
819 GetWindowSize(pParent, &parentW, &parentH);
820
821 if (IsChildWindow(pParent))
822 {
823 pGrandparent = GetParent(pParent);
824 GetWindowSize(pGrandparent, &grandparentW, &grandparentH);
825 }
826 }
827
828 if (!dw && !dh)
829 return;
830
831 SetWindowPos(mPlugWnd, 0, 0, 0, dlgW + dw, dlgH + dh, SETPOS_FLAGS);
832
833 if (pParent && !parentHasResized)
834 {
835 SetWindowPos(pParent, 0, 0, 0, parentW + dw, parentH + dh, SETPOS_FLAGS);
836 }
837
838 if (pGrandparent && !parentHasResized)
839 {
840 SetWindowPos(pGrandparent, 0, 0, 0, grandparentW + dw, grandparentH + dh, SETPOS_FLAGS);
841 }
842 }
843}
844
845void IGraphicsWin::HideMouseCursor(bool hide, bool lock)
846{
847 if (mCursorHidden == hide)
848 return;
849
850 if (hide)
851 {
852 mHiddenCursorX = mCursorX;
853 mHiddenCursorY = mCursorY;
854
855 ShowCursor(false);
856 mCursorHidden = true;
857 mCursorLock = lock && !mTabletInput;
858 }
859 else
860 {
861 if (mCursorLock)
862 MoveMouseCursor(mHiddenCursorX, mHiddenCursorY);
863
864 ShowCursor(true);
865 mCursorHidden = false;
866 mCursorLock = false;
867 }
868}
869
870void IGraphicsWin::MoveMouseCursor(float x, float y)
871{
872 if (mTabletInput)
873 return;
874
875 const float scale = GetTotalScale();
876
877 POINT p;
878 p.x = std::round(x * scale);
879 p.y = std::round(y * scale);
880
881 ::ClientToScreen(mPlugWnd, &p);
882
883 if (SetCursorPos(p.x, p.y))
884 {
885 GetCursorPos(&p);
886 ScreenToClient(mPlugWnd, &p);
887
888 mHiddenCursorX = mCursorX = p.x / scale;
889 mHiddenCursorY = mCursorY = p.y / scale;
890 }
891}
892
893ECursor IGraphicsWin::SetMouseCursor(ECursor cursorType)
894{
895 HCURSOR cursor;
896
897 switch (cursorType)
898 {
899 case ECursor::ARROW: cursor = LoadCursor(NULL, IDC_ARROW); break;
900 case ECursor::IBEAM: cursor = LoadCursor(NULL, IDC_IBEAM); break;
901 case ECursor::WAIT: cursor = LoadCursor(NULL, IDC_WAIT); break;
902 case ECursor::CROSS: cursor = LoadCursor(NULL, IDC_CROSS); break;
903 case ECursor::UPARROW: cursor = LoadCursor(NULL, IDC_UPARROW); break;
904 case ECursor::SIZENWSE: cursor = LoadCursor(NULL, IDC_SIZENWSE); break;
905 case ECursor::SIZENESW: cursor = LoadCursor(NULL, IDC_SIZENESW); break;
906 case ECursor::SIZEWE: cursor = LoadCursor(NULL, IDC_SIZEWE); break;
907 case ECursor::SIZENS: cursor = LoadCursor(NULL, IDC_SIZENS); break;
908 case ECursor::SIZEALL: cursor = LoadCursor(NULL, IDC_SIZEALL); break;
909 case ECursor::INO: cursor = LoadCursor(NULL, IDC_NO); break;
910 case ECursor::HAND: cursor = LoadCursor(NULL, IDC_HAND); break;
911 case ECursor::APPSTARTING: cursor = LoadCursor(NULL, IDC_APPSTARTING); break;
912 case ECursor::HELP: cursor = LoadCursor(NULL, IDC_HELP); break;
913 default:
914 cursor = LoadCursor(NULL, IDC_ARROW);
915 }
916
917 SetCursor(cursor);
918 return IGraphics::SetMouseCursor(cursorType);
919}
920
921bool IGraphicsWin::MouseCursorIsLocked()
922{
923 return mCursorLock;
924}
925
926void IGraphicsWin::GetMouseLocation(float& x, float&y) const
927{
928 POINT p;
929 GetCursorPos(&p);
930 ScreenToClient(mPlugWnd, &p);
931
932 const float scale = GetTotalScale();
933
934 x = p.x / scale;
935 y = p.y / scale;
936}
937
938#ifdef IGRAPHICS_GL
939void IGraphicsWin::CreateGLContext()
940{
941 PIXELFORMATDESCRIPTOR pfd =
942 {
943 sizeof(PIXELFORMATDESCRIPTOR),
944 1,
945 PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, //Flags
946 PFD_TYPE_RGBA, // The kind of framebuffer. RGBA or palette.
947 32, // Colordepth of the framebuffer.
948 0, 0, 0, 0, 0, 0,
949 0,
950 0,
951 0,
952 0, 0, 0, 0,
953 24, // Number of bits for the depthbuffer
954 8, // Number of bits for the stencilbuffer
955 0, // Number of Aux buffers in the framebuffer.
956 PFD_MAIN_PLANE,
957 0,
958 0, 0, 0
959 };
960
961 HDC dc = GetDC(mPlugWnd);
962 int fmt = ChoosePixelFormat(dc, &pfd);
963 SetPixelFormat(dc, fmt, &pfd);
964 mHGLRC = wglCreateContext(dc);
965 wglMakeCurrent(dc, mHGLRC);
966
967#ifdef IGRAPHICS_GL3
968 // On windows we can't create a 3.3 context directly, since we need the wglCreateContextAttribsARB extension.
969 // We load the extension, then re-create the context.
970 auto wglCreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC) wglGetProcAddress("wglCreateContextAttribsARB");
971
972 if (wglCreateContextAttribsARB)
973 {
974 wglDeleteContext(mHGLRC);
975
976 const int attribList[] = {
977 WGL_CONTEXT_MAJOR_VERSION_ARB, 3,
978 WGL_CONTEXT_MINOR_VERSION_ARB, 3,
979 WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB,
980 0
981 };
982
983 mHGLRC = wglCreateContextAttribsARB(dc, 0, attribList);
984 wglMakeCurrent(dc, mHGLRC);
985 }
986
987#endif
988
989 //TODO: return false if GL init fails?
990 if (!gladLoadGL())
991 DBGMSG("Error initializing glad");
992
993 glGetError();
994
995 ReleaseDC(mPlugWnd, dc);
996}
997
998void IGraphicsWin::DestroyGLContext()
999{
1000 wglMakeCurrent(NULL, NULL);
1001 wglDeleteContext(mHGLRC);
1002}
1003#endif
1004
1005void IGraphicsWin::ActivateGLContext()
1006{
1007#ifdef IGRAPHICS_GL
1008 mStartHDC = wglGetCurrentDC();
1009 mStartHGLRC = wglGetCurrentContext();
1010 HDC dc = GetDC(mPlugWnd);
1011 wglMakeCurrent(dc, mHGLRC);
1012#endif
1013}
1014
1015void IGraphicsWin::DeactivateGLContext()
1016{
1017#ifdef IGRAPHICS_GL
1018 ReleaseDC(mPlugWnd, (HDC) GetPlatformContext());
1019 wglMakeCurrent(mStartHDC, mStartHGLRC); // return current ctxt to start
1020#endif
1021}
1022
1023EMsgBoxResult IGraphicsWin::ShowMessageBox(const char* str, const char* title, EMsgBoxType type, IMsgBoxCompletionHandlerFunc completionHandler)
1024{
1025 ReleaseMouseCapture();
1026
1027 EMsgBoxResult result = static_cast<EMsgBoxResult>(MessageBoxW(GetMainWnd(), UTF8AsUTF16(str).Get(), UTF8AsUTF16(title).Get(), static_cast<int>(type)));
1028
1029 if (completionHandler)
1030 completionHandler(result);
1031
1032 return result;
1033}
1034
1035void* IGraphicsWin::OpenWindow(void* pParent)
1036{
1037 mParentWnd = (HWND) pParent;
1038 int screenScale = GetScaleForHWND(mParentWnd);
1039 int x = 0, y = 0, w = WindowWidth() * screenScale, h = WindowHeight() * screenScale;
1040
1041 if (mPlugWnd)
1042 {
1043 RECT pR, cR;
1044 GetWindowRect((HWND) pParent, &pR);
1045 GetWindowRect(mPlugWnd, &cR);
1046 CloseWindow();
1047 x = cR.left - pR.left;
1048 y = cR.top - pR.top;
1049 w = cR.right - cR.left;
1050 h = cR.bottom - cR.top;
1051 }
1052
1053 if (nWndClassReg++ == 0)
1054 {
1055 WNDCLASSW wndClass = { CS_DBLCLKS | CS_OWNDC, WndProc, 0, 0, mHInstance, 0, 0, 0, 0, wndClassName };
1056 RegisterClassW(&wndClass);
1057 }
1058
1059 mPlugWnd = CreateWindowW(wndClassName, L"IPlug", WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, x, y, w, h, mParentWnd, 0, mHInstance, this);
1060
1061 HDC dc = GetDC(mPlugWnd);
1062 SetPlatformContext(dc);
1063 ReleaseDC(mPlugWnd, dc);
1064
1065#ifdef IGRAPHICS_GL
1066 CreateGLContext();
1067#endif
1068
1069 OnViewInitialized((void*) dc);
1070
1071 SetScreenScale(screenScale); // resizes draw context
1072
1073 GetDelegate()->LayoutUI(this);
1074
1075 if (MultiTouchEnabled() && GetSystemMetrics(SM_DIGITIZER) & NID_MULTI_INPUT)
1076 {
1077 RegisterTouchWindow(mPlugWnd, 0);
1078 }
1079
1080 if (!mPlugWnd && --nWndClassReg == 0)
1081 {
1082 UnregisterClassW(wndClassName, mHInstance);
1083 }
1084 else
1085 {
1086 SetAllControlsDirty();
1087 }
1088
1089 if (mPlugWnd && TooltipsEnabled())
1090 {
1091 static const INITCOMMONCONTROLSEX iccex = { sizeof(INITCOMMONCONTROLSEX), ICC_TAB_CLASSES };
1092
1093 if (InitCommonControlsEx(&iccex))
1094 {
1095 mTooltipWnd = CreateWindowExW(0, TOOLTIPS_CLASSW, NULL, WS_POPUP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | TTS_NOPREFIX | TTS_ALWAYSTIP,
1096 CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, mPlugWnd, NULL, mHInstance, NULL);
1097 if (mTooltipWnd)
1098 {
1099 SetWindowPos(mTooltipWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
1100 TOOLINFOW ti = { TTTOOLINFOW_V2_SIZE, TTF_IDISHWND | TTF_SUBCLASS, mPlugWnd, (UINT_PTR) mPlugWnd, {0, 0, 0, 0}, NULL, NULL, 0, NULL };
1101 SendMessageW(mTooltipWnd, TTM_ADDTOOLW, 0, (LPARAM) &ti);
1102 SendMessageW(mTooltipWnd, TTM_SETMAXTIPWIDTH, 0, TOOLTIPWND_MAXWIDTH);
1103 }
1104 }
1105
1106 if (!mTooltipWnd)
1107 EnableTooltips(false);
1108
1109#ifdef IGRAPHICS_GL
1110 wglMakeCurrent(NULL, NULL);
1111#endif
1112 }
1113
1114 GetDelegate()->OnUIOpen();
1115
1116 return mPlugWnd;
1117}
1118
1119static void GetWndClassName(HWND hWnd, WDL_String* pStr)
1120{
1121 wchar_t cStrW[MAX_CLASSNAME_LEN] = {'\0'};
1122 GetClassNameW(hWnd, cStrW, MAX_CLASSNAME_LEN);
1123 pStr->Set(UTF16AsUTF8(cStrW).Get());
1124}
1125
1126BOOL CALLBACK IGraphicsWin::FindMainWindow(HWND hWnd, LPARAM lParam)
1127{
1128 IGraphicsWin* pGraphics = (IGraphicsWin*) lParam;
1129 if (pGraphics)
1130 {
1131 DWORD wPID;
1132 GetWindowThreadProcessId(hWnd, &wPID);
1133 WDL_String str;
1134 GetWndClassName(hWnd, &str);
1135 if (wPID == pGraphics->mPID && !strcmp(str.Get(), pGraphics->mMainWndClassName.Get()))
1136 {
1137 pGraphics->mMainWnd = hWnd;
1138 return FALSE; // Stop enumerating.
1139 }
1140 }
1141 return TRUE;
1142}
1143
1144HWND IGraphicsWin::GetMainWnd()
1145{
1146 if (!mMainWnd)
1147 {
1148 if (mParentWnd)
1149 {
1150 HWND parentWnd = mParentWnd;
1151 while (parentWnd)
1152 {
1153 mMainWnd = parentWnd;
1154 parentWnd = GetParent(mMainWnd);
1155 }
1156
1157 GetWndClassName(mMainWnd, &mMainWndClassName);
1158 }
1159 else if (CStringHasContents(mMainWndClassName.Get()))
1160 {
1161 mPID = GetCurrentProcessId();
1162 EnumWindows(FindMainWindow, (LPARAM) this);
1163 }
1164 }
1165 return mMainWnd;
1166}
1167
1168IRECT IGraphicsWin::GetWindowRECT()
1169{
1170 if (mPlugWnd)
1171 {
1172 RECT r;
1173 GetWindowRect(mPlugWnd, &r);
1174 r.right -= TOOLWIN_BORDER_W;
1175 r.bottom -= TOOLWIN_BORDER_H;
1176 return IRECT(r.left, r.top, r.right, r.bottom);
1177 }
1178 return IRECT();
1179}
1180
1181void IGraphicsWin::CloseWindow()
1182{
1183 if (mPlugWnd)
1184 {
1185 if (mVSYNCEnabled)
1186 StopVBlankThread();
1187 else
1188 KillTimer(mPlugWnd, IPLUG_TIMER_ID);
1189
1190 {
1191 ScopedGLContext scopedGLCtx {this};
1192 OnViewDestroyed();
1193 }
1194
1195#ifdef IGRAPHICS_GL
1196 DestroyGLContext();
1197#endif
1198
1199 SetPlatformContext(nullptr);
1200
1201 if (mTooltipWnd)
1202 {
1203 DestroyWindow(mTooltipWnd);
1204 mTooltipWnd = 0;
1205 mShowingTooltip = false;
1206 mTooltipIdx = -1;
1207 }
1208
1209 DestroyWindow(mPlugWnd);
1210 mPlugWnd = 0;
1211
1212 if (--nWndClassReg == 0)
1213 {
1214 UnregisterClassW(wndClassName, mHInstance);
1215 }
1216 }
1217}
1218
1219bool IGraphicsWin::PlatformSupportsMultiTouch() const
1220{
1221 return GetSystemMetrics(SM_DIGITIZER) & NID_MULTI_INPUT;
1222}
1223
1224IPopupMenu* IGraphicsWin::GetItemMenu(long idx, long& idxInMenu, long& offsetIdx, IPopupMenu& baseMenu)
1225{
1226 long oldIDx = offsetIdx;
1227 offsetIdx += baseMenu.NItems();
1228
1229 if (idx < offsetIdx)
1230 {
1231 idxInMenu = idx - oldIDx;
1232 return &baseMenu;
1233 }
1234
1235 IPopupMenu* pMenu = nullptr;
1236
1237 for (int i = 0; i< baseMenu.NItems(); i++)
1238 {
1239 IPopupMenu::Item* pMenuItem = baseMenu.GetItem(i);
1240 if (pMenuItem->GetSubmenu())
1241 {
1242 pMenu = GetItemMenu(idx, idxInMenu, offsetIdx, *pMenuItem->GetSubmenu());
1243
1244 if (pMenu)
1245 break;
1246 }
1247 }
1248
1249 return pMenu;
1250}
1251
1252HMENU IGraphicsWin::CreateMenu(IPopupMenu& menu, long* pOffsetIdx)
1253{
1254 HMENU hMenu = ::CreatePopupMenu();
1255
1256 WDL_String escapedText;
1257
1258 int flags = 0;
1259 long offset = *pOffsetIdx;
1260 long nItems = menu.NItems();
1261 *pOffsetIdx += nItems;
1262 long inc = 0;
1263
1264 for (int i = 0; i < nItems; i++)
1265 {
1266 IPopupMenu::Item* pMenuItem = menu.GetItem(i);
1267
1268 if (pMenuItem->GetIsSeparator())
1269 {
1270 AppendMenuW(hMenu, MF_SEPARATOR, 0, 0);
1271 }
1272 else
1273 {
1274 const char* str = pMenuItem->GetText();
1275 int maxlen = strlen(str) + menu.GetPrefix() ? 50 : 0;
1276 WDL_String entryText(str);
1277
1278 if (menu.GetPrefix())
1279 {
1280 switch (menu.GetPrefix())
1281 {
1282 case 1:
1283 {
1284 entryText.SetFormatted(maxlen, "%1d: %s", i+1, str); break;
1285 }
1286 case 2:
1287 {
1288 entryText.SetFormatted(maxlen, "%02d: %s", i+1, str); break;
1289 }
1290 case 3:
1291 {
1292 entryText.SetFormatted(maxlen, "%03d: %s", i+1, str); break;
1293 }
1294 }
1295 }
1296
1297 // Escape ampersands if present
1298
1299 if (strchr(entryText.Get(), '&'))
1300 {
1301 for (int c = 0; c < entryText.GetLength(); c++)
1302 if (entryText.Get()[c] == '&')
1303 entryText.Insert("&", c++);
1304 }
1305
1306 flags = MF_STRING;
1307 if (nItems < 160 && menu.NItemsPerColumn() > 0 && inc && !(inc % menu.NItemsPerColumn()))
1308 flags |= MF_MENUBARBREAK;
1309
1310 if (pMenuItem->GetEnabled())
1311 flags |= MF_ENABLED;
1312 else
1313 flags |= MF_GRAYED;
1314 if (pMenuItem->GetIsTitle())
1315 flags |= MF_DISABLED;
1316 if (pMenuItem->GetChecked())
1317 flags |= MF_CHECKED;
1318 else
1319 flags |= MF_UNCHECKED;
1320
1321 if (pMenuItem->GetSubmenu())
1322 {
1323 HMENU submenu = CreateMenu(*pMenuItem->GetSubmenu(), pOffsetIdx);
1324 if (submenu)
1325 {
1326 AppendMenuW(hMenu, flags|MF_POPUP, (UINT_PTR)submenu, UTF8AsUTF16(entryText).Get());
1327 }
1328 }
1329 else
1330 {
1331 AppendMenuW(hMenu, flags, offset + inc, UTF8AsUTF16(entryText).Get());
1332 }
1333 }
1334 inc++;
1335 }
1336
1337 return hMenu;
1338}
1339
1340IPopupMenu* IGraphicsWin::CreatePlatformPopupMenu(IPopupMenu& menu, const IRECT bounds, bool& isAsync)
1341{
1342 long offsetIdx = 0;
1343 HMENU hMenu = CreateMenu(menu, &offsetIdx);
1344
1345 if (hMenu)
1346 {
1347 IPopupMenu* result = nullptr;
1348
1349 POINT cPos;
1350 const float scale = GetTotalScale();
1351
1352 cPos.x = bounds.L * scale;
1353 cPos.y = bounds.B * scale;
1354
1355 ::ClientToScreen(mPlugWnd, &cPos);
1356
1357 if (TrackPopupMenu(hMenu, TPM_LEFTALIGN, cPos.x, cPos.y, 0, mPlugWnd, 0))
1358 {
1359 MSG msg;
1360 if (PeekMessage(&msg, mPlugWnd, WM_COMMAND, WM_COMMAND, PM_REMOVE))
1361 {
1362 if (HIWORD(msg.wParam) == 0)
1363 {
1364 long res = LOWORD(msg.wParam);
1365 if (res != -1)
1366 {
1367 long idx = 0;
1368 offsetIdx = 0;
1369 IPopupMenu* pReturnMenu = GetItemMenu(res, idx, offsetIdx, menu);
1370 if (pReturnMenu)
1371 {
1372 result = pReturnMenu;
1373 result->SetChosenItemIdx(idx);
1374
1375 //synchronous
1376 if (pReturnMenu && pReturnMenu->GetFunction())
1377 pReturnMenu->ExecFunction();
1378 }
1379 }
1380 }
1381 }
1382 }
1383 DestroyMenu(hMenu);
1384
1385 RECT r = { 0, 0, static_cast<LONG>(WindowWidth() * GetScreenScale()), static_cast<LONG>(WindowHeight() * GetScreenScale()) };
1386 InvalidateRect(mPlugWnd, &r, FALSE);
1387
1388 return result;
1389 }
1390
1391 return nullptr;
1392}
1393
1394void IGraphicsWin::CreatePlatformTextEntry(int paramIdx, const IText& text, const IRECT& bounds, int length, const char* str)
1395{
1396 if (mParamEditWnd)
1397 return;
1398
1399 DWORD editStyle;
1400
1401 switch (text.mAlign)
1402 {
1403 case EAlign::Near: editStyle = ES_LEFT; break;
1404 case EAlign::Far: editStyle = ES_RIGHT; break;
1405 case EAlign::Center:
1406 default: editStyle = ES_CENTER; break;
1407 }
1408
1409 const float scale = GetTotalScale();
1410 IRECT scaledBounds = bounds.GetScaled(scale);
1411
1412 mParamEditWnd = CreateWindowW(L"EDIT", UTF8AsUTF16(str).Get(), ES_AUTOHSCROLL /*only works for left aligned text*/ | WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_VISIBLE | ES_MULTILINE | editStyle,
1413 scaledBounds.L, scaledBounds.T, scaledBounds.W()+1, scaledBounds.H()+1,
1414 mPlugWnd, (HMENU) PARAM_EDIT_ID, mHInstance, 0);
1415
1416 StaticStorage<HFontHolder>::Accessor hfontStorage(sHFontCache);
1417
1418 LOGFONTW lFont = { 0 };
1419 HFontHolder* hfontHolder = hfontStorage.Find(text.mFont);
1420 GetObjectW(hfontHolder->mHFont, sizeof(LOGFONTW), &lFont);
1421 lFont.lfHeight = text.mSize * scale;
1422 mEditFont = CreateFontIndirectW(&lFont);
1423
1424 assert(hfontHolder && "font not found - did you forget to load it?");
1425
1426 mEditParam = paramIdx > kNoParameter ? GetDelegate()->GetParam(paramIdx) : nullptr;
1427 mEditText = text;
1428 mEditRECT = bounds;
1429
1430 SendMessageW(mParamEditWnd, EM_LIMITTEXT, (WPARAM) length, 0);
1431 SendMessageW(mParamEditWnd, WM_SETFONT, (WPARAM) mEditFont, 0);
1432 SendMessageW(mParamEditWnd, EM_SETSEL, 0, -1);
1433
1434 if (text.mVAlign == EVAlign::Middle)
1435 {
1436 double size = text.mSize * scale;
1437 double offset = (scaledBounds.H() - size) / 2.0;
1438 RECT formatRect{0, (LONG) offset, (LONG) scaledBounds.W() + 1, (LONG) scaledBounds.H() + 1};
1439 SendMessageW(mParamEditWnd, EM_SETRECT, 0, (LPARAM) &formatRect);
1440 }
1441
1442 SetFocus(mParamEditWnd);
1443
1444 mDefEditProc = (WNDPROC) SetWindowLongPtrW(mParamEditWnd, GWLP_WNDPROC, (LONG_PTR) ParamEditProc);
1445 SetWindowLongPtrW(mParamEditWnd, GWLP_USERDATA, 0xdeadf00b);
1446}
1447
1448bool IGraphicsWin::RevealPathInExplorerOrFinder(WDL_String& path, bool select)
1449{
1450 bool success = false;
1451
1452 if (path.GetLength())
1453 {
1454 WCHAR winDir[IPLUG_WIN_MAX_WIDE_PATH];
1455 UINT len = GetSystemDirectoryW(winDir, IPLUG_WIN_MAX_WIDE_PATH);
1456
1457 if (len && !(len > MAX_PATH - 2))
1458 {
1459 winDir[len] = L'\\';
1460 winDir[++len] = L'\0';
1461
1462 WDL_String explorerParams;
1463
1464 if (select)
1465 explorerParams.Append("/select,");
1466
1467 explorerParams.Append("\"");
1468 explorerParams.Append(path.Get());
1469 explorerParams.Append("\\\"");
1470
1471 HINSTANCE result;
1472
1473 if ((result=::ShellExecuteW(NULL, L"open", L"explorer.exe", UTF8AsUTF16(explorerParams).Get(), winDir, SW_SHOWNORMAL)) <= (HINSTANCE) 32)
1474 success = true;
1475 }
1476 }
1477
1478 return success;
1479}
1480
1481void IGraphicsWin::PromptForFile(WDL_String& fileName, WDL_String& path, EFileAction action, const char* ext, IFileDialogCompletionHandlerFunc completionHandler)
1482{
1483 if (!WindowIsOpen())
1484 {
1485 fileName.Set("");
1486 return;
1487 }
1488
1489 wchar_t fileNameWide[_MAX_PATH];
1490
1491 UTF8ToUTF16(fileNameWide, fileName.Get(), _MAX_PATH);
1492
1493 //if (!path.GetLength())
1494 // DesktopPath(path);
1495
1496 UTF8AsUTF16 directoryWide(path);
1497
1498 OPENFILENAMEW ofn;
1499 memset(&ofn, 0, sizeof(OPENFILENAMEW));
1500
1501 ofn.lStructSize = sizeof(OPENFILENAMEW);
1502 ofn.hwndOwner = (HWND) GetWindow();
1503 ofn.lpstrFile = fileNameWide;
1504 ofn.nMaxFile = _MAX_PATH - 1;
1505 ofn.lpstrInitialDir = directoryWide.Get();
1506 ofn.Flags = OFN_PATHMUSTEXIST;
1507
1508 if (CStringHasContents(ext))
1509 {
1510 wchar_t extStr[256];
1511 wchar_t defExtStr[16];
1512 int i, p, n = strlen(ext);
1513 bool separator = true;
1514
1515 for (i = 0, p = 0; i < n; ++i)
1516 {
1517 if (separator)
1518 {
1519 if (p)
1520 extStr[p++] = ';';
1521
1522 separator = false;
1523 extStr[p++] = '*';
1524 extStr[p++] = '.';
1525 }
1526
1527 if (ext[i] == ' ')
1528 separator = true;
1529 else
1530 extStr[p++] = ext[i];
1531 }
1532 extStr[p++] = '\0';
1533
1534 wcscpy(&extStr[p], extStr);
1535 extStr[p + p] = '\0';
1536 ofn.lpstrFilter = extStr;
1537
1538 for (i = 0, p = 0; i < n && ext[i] != ' '; ++i)
1539 defExtStr[p++] = ext[i];
1540
1541 defExtStr[p++] = '\0';
1542 ofn.lpstrDefExt = defExtStr;
1543 }
1544
1545 bool rc = false;
1546
1547 switch (action)
1548 {
1549 case EFileAction::Save:
1550 ofn.Flags |= OFN_OVERWRITEPROMPT;
1551 rc = GetSaveFileNameW(&ofn);
1552 break;
1553 case EFileAction::Open:
1554 default:
1555 ofn.Flags |= OFN_FILEMUSTEXIST;
1556 rc = GetOpenFileNameW(&ofn);
1557 break;
1558 }
1559
1560 if (rc)
1561 {
1562 char drive[_MAX_DRIVE];
1563 char directoryOutCStr[_MAX_PATH];
1564
1565 UTF16AsUTF8 tempUTF8(ofn.lpstrFile);
1566
1567 if (_splitpath_s(tempUTF8.Get(), drive, sizeof(drive), directoryOutCStr, sizeof(directoryOutCStr), NULL, 0, NULL, 0) == 0)
1568 {
1569 path.Set(drive);
1570 path.Append(directoryOutCStr);
1571 }
1572
1573 fileName.Set(tempUTF8.Get());
1574 }
1575 else
1576 {
1577 fileName.Set("");
1578 }
1579
1580 // Async is not required on windows, but call the completion handler anyway
1581 if (completionHandler)
1582 {
1583 completionHandler(fileName, path);
1584 }
1585
1586 ReleaseMouseCapture();
1587}
1588
1589void IGraphicsWin::PromptForDirectory(WDL_String& dir, IFileDialogCompletionHandlerFunc completionHandler)
1590{
1591 BROWSEINFOW bi;
1592 memset(&bi, 0, sizeof(bi));
1593
1594 bi.ulFlags = BIF_USENEWUI;
1595 bi.hwndOwner = mPlugWnd;
1596 bi.lpszTitle = L"Choose a Directory";
1597
1598 // must call this if using BIF_USENEWUI
1599 ::OleInitialize(NULL);
1600 LPITEMIDLIST pIDL = ::SHBrowseForFolderW(&bi);
1601
1602 if (pIDL != NULL)
1603 {
1604 wchar_t buffer[_MAX_PATH] = {'\0'};
1605
1606 if (::SHGetPathFromIDListW(pIDL, buffer) != 0)
1607 {
1608 dir.Set(UTF16AsUTF8(buffer).Get());
1609 dir.Append("\\");
1610 }
1611
1612 // free the item id list
1613 CoTaskMemFree(pIDL);
1614 }
1615 else
1616 {
1617 dir.Set("");
1618 }
1619
1620 if (completionHandler)
1621 {
1622 WDL_String fileName; // not used
1623 completionHandler(fileName, dir);
1624 }
1625
1626 ReleaseMouseCapture();
1627
1628 ::OleUninitialize();
1629}
1630
1631static UINT_PTR CALLBACK CCHookProc(HWND hdlg, UINT uiMsg, WPARAM wParam, LPARAM lParam)
1632{
1633 if (uiMsg == WM_INITDIALOG && lParam)
1634 {
1635 CHOOSECOLORW* cc = (CHOOSECOLORW*) lParam;
1636 if (cc && cc->lCustData)
1637 {
1638 const wchar_t* strWide = (const wchar_t*) cc->lCustData;
1639 SetWindowTextW(hdlg, strWide);
1640 UINT uiSetRGB;
1641 uiSetRGB = RegisterWindowMessageW(SETRGBSTRINGW);
1642 SendMessageW(hdlg, uiSetRGB, 0, (LPARAM) cc->rgbResult);
1643 }
1644 }
1645 return 0;
1646}
1647
1648bool IGraphicsWin::PromptForColor(IColor& color, const char* prompt, IColorPickerHandlerFunc func)
1649{
1650 ReleaseMouseCapture();
1651
1652 if (!mPlugWnd)
1653 return false;
1654
1655 UTF8AsUTF16 promptWide(prompt);
1656
1657 const COLORREF w = RGB(255, 255, 255);
1658 static COLORREF customColorStorage[16] = { w, w, w, w, w, w, w, w, w, w, w, w, w, w, w, w };
1659
1660 CHOOSECOLORW cc;
1661 memset(&cc, 0, sizeof(CHOOSECOLORW));
1662 cc.lStructSize = sizeof(CHOOSECOLORW);
1663 cc.hwndOwner = mPlugWnd;
1664 cc.rgbResult = RGB(color.R, color.G, color.B);
1665 cc.lpCustColors = customColorStorage;
1666 cc.lCustData = (LPARAM) promptWide.Get();
1667 cc.lpfnHook = CCHookProc;
1668 cc.Flags = CC_RGBINIT | CC_ANYCOLOR | CC_FULLOPEN | CC_SOLIDCOLOR | CC_ENABLEHOOK;
1669
1670 if (ChooseColorW(&cc))
1671 {
1672 color.R = GetRValue(cc.rgbResult);
1673 color.G = GetGValue(cc.rgbResult);
1674 color.B = GetBValue(cc.rgbResult);
1675
1676 if (func)
1677 func(color);
1678
1679 return true;
1680 }
1681 return false;
1682}
1683
1684bool IGraphicsWin::OpenURL(const char* url, const char* msgWindowTitle, const char* confirmMsg, const char* errMsgOnFailure)
1685{
1686 if (confirmMsg && MessageBoxW(mPlugWnd, UTF8AsUTF16(confirmMsg).Get(), UTF8AsUTF16(msgWindowTitle).Get(), MB_YESNO) != IDYES)
1687 {
1688 return false;
1689 }
1690 DWORD inetStatus = 0;
1691 if (InternetGetConnectedState(&inetStatus, 0))
1692 {
1693 if (ShellExecuteW(mPlugWnd, L"open", UTF8AsUTF16(url).Get(), 0, 0, SW_SHOWNORMAL) > HINSTANCE(32))
1694 {
1695 return true;
1696 }
1697 }
1698 if (errMsgOnFailure)
1699 {
1700 MessageBoxW(mPlugWnd, UTF8AsUTF16(errMsgOnFailure).Get(), UTF8AsUTF16(msgWindowTitle).Get(), MB_OK);
1701 }
1702 return false;
1703}
1704
1705void IGraphicsWin::SetTooltip(const char* tooltip)
1706{
1707 UTF8AsUTF16 tipWide(tooltip);
1708 TOOLINFOW ti = { TTTOOLINFOW_V2_SIZE, 0, mPlugWnd, (UINT_PTR) mPlugWnd, {0, 0, 0, 0}, NULL, NULL, 0, NULL };
1709 ti.lpszText = const_cast<wchar_t*>(tipWide.Get());
1710 SendMessageW(mTooltipWnd, TTM_UPDATETIPTEXTW, 0, (LPARAM) &ti);
1711}
1712
1713void IGraphicsWin::ShowTooltip()
1714{
1715 if (mTooltipIdx > -1)
1716 {
1717 if (auto* pTooltipControl = GetControl(mTooltipIdx))
1718 {
1719 const char* tooltipStr = pTooltipControl->GetTooltip();
1720 if (tooltipStr)
1721 {
1722 SetTooltip(tooltipStr);
1723 mShowingTooltip = true;
1724 }
1725 }
1726 }
1727}
1728
1729void IGraphicsWin::HideTooltip()
1730{
1731 if (mShowingTooltip)
1732 {
1733 SetTooltip(NULL);
1734 mShowingTooltip = false;
1735 }
1736}
1737
1738bool IGraphicsWin::GetTextFromClipboard(WDL_String& str)
1739{
1740 bool result = false;
1741
1742 if (IsClipboardFormatAvailable(CF_UNICODETEXT))
1743 {
1744 if (OpenClipboard(0))
1745 {
1746 HGLOBAL hglb = GetClipboardData(CF_UNICODETEXT);
1747
1748 if (hglb)
1749 {
1750 WCHAR *origStr = (WCHAR*) GlobalLock(hglb);
1751
1752 if (origStr)
1753 {
1754 UTF16ToUTF8(str, origStr);
1755 GlobalUnlock(hglb);
1756 result = true;
1757 }
1758 }
1759 }
1760
1761 CloseClipboard();
1762 }
1763
1764 if (!result)
1765 str.Set("");
1766
1767 return result;
1768}
1769
1770bool IGraphicsWin::SetTextInClipboard(const char* str)
1771{
1772 if (!OpenClipboard(mMainWnd))
1773 return false;
1774
1775 EmptyClipboard();
1776
1777 bool result = true;
1778
1779 if (strlen(str))
1780 {
1781 // figure out how many characters we need for the wide version of this string
1782 const int lenWide = UTF8ToUTF16Len(str);
1783
1784 // allocate global memory object for the text
1785 HGLOBAL hglbCopy = GlobalAlloc(GMEM_MOVEABLE, lenWide*sizeof(WCHAR));
1786 if (!hglbCopy)
1787 {
1788 CloseClipboard();
1789 return false;
1790 }
1791
1792 // lock the handle
1793 LPWSTR lpstrCopy = (LPWSTR) GlobalLock(hglbCopy);
1794
1795 if (lpstrCopy)
1796 {
1797 // copy the string into the buffer
1798 UTF8ToUTF16(lpstrCopy, str, lenWide);
1799 GlobalUnlock(hglbCopy);
1800
1801 // place the handle on the clipboard
1802 result = SetClipboardData(CF_UNICODETEXT, hglbCopy);
1803
1804 // free the handle if unsuccessful
1805 if (!result)
1806 GlobalFree(hglbCopy);
1807 }
1808 }
1809
1810 CloseClipboard();
1811
1812 return result;
1813}
1814
1815bool IGraphicsWin::SetFilePathInClipboard(const char* path)
1816{
1817 if (!OpenClipboard(mMainWnd))
1818 return false;
1819
1820 EmptyClipboard();
1821
1822 UTF8AsUTF16 pathWide(path);
1823
1824 // N.B. GHND ensures that the memory is zeroed
1825
1826 HGLOBAL hGlobal = GlobalAlloc(GHND, sizeof(DROPFILES) + (sizeof(wchar_t) * (pathWide.GetLength() + 1)));
1827
1828 if (!hGlobal)
1829 return false;
1830
1831 DROPFILES* pDropFiles = (DROPFILES*) GlobalLock(hGlobal);
1832 bool result = false;
1833
1834 if (pDropFiles)
1835 {
1836 // Populate the dropfile structure and copy the file path
1837
1838 pDropFiles->pFiles = sizeof(DROPFILES);
1839 pDropFiles->pt = { 0, 0 };
1840 pDropFiles->fNC = true;
1841 pDropFiles->fWide = true;
1842
1843 std::copy_n(pathWide.Get(), pathWide.GetLength(), reinterpret_cast<wchar_t*>(&pDropFiles[1]));
1844
1845 GlobalUnlock(hGlobal);
1846
1847 result = SetClipboardData(CF_HDROP, hGlobal);
1848 }
1849
1850 // free the handle if unsuccessful
1851 if (!result)
1852 GlobalFree(hGlobal);
1853
1854 CloseClipboard();
1855 return result;
1856}
1857
1858bool IGraphicsWin::InitiateExternalFileDragDrop(const char* path, const IRECT& /*iconBounds*/)
1859{
1860 using namespace DragAndDropHelpers;
1861 OleInitialize(nullptr);
1862
1863 FORMATETC format = { CF_HDROP, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
1864
1865 DataObject* dataObj = new DataObject(&format, path);
1866 DropSource* dropSource = new DropSource();
1867
1868 DWORD dropEffect;
1869 HRESULT ret = DoDragDrop(dataObj, dropSource, DROPEFFECT_COPY, &dropEffect);
1870 bool success = SUCCEEDED(ret);
1871
1872 dataObj->Release();
1873 dropSource->Release();
1874
1875 OleUninitialize();
1876
1877 ReleaseMouseCapture();
1878
1879 return success;
1880}
1881
1882static HFONT GetHFont(const char* fontName, int weight, bool italic, bool underline, DWORD quality = DEFAULT_QUALITY, bool enumerate = false)
1883{
1884 HDC hdc = GetDC(NULL);
1885 HFONT font = nullptr;
1886 LOGFONTW lFont;
1887
1888 lFont.lfHeight = 0;
1889 lFont.lfWidth = 0;
1890 lFont.lfEscapement = 0;
1891 lFont.lfOrientation = 0;
1892 lFont.lfWeight = weight;
1893 lFont.lfItalic = italic;
1894 lFont.lfUnderline = underline;
1895 lFont.lfStrikeOut = false;
1896 lFont.lfCharSet = DEFAULT_CHARSET;
1897 lFont.lfOutPrecision = OUT_TT_PRECIS;
1898 lFont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
1899 lFont.lfQuality = quality;
1900 lFont.lfPitchAndFamily = DEFAULT_PITCH;
1901
1902 wcsncpy(lFont.lfFaceName, UTF8AsUTF16(fontName).Get(), LF_FACESIZE);
1903
1904 auto enumProc = [](const LOGFONTW* pLFont, const TEXTMETRICW* pTextMetric, DWORD FontType, LPARAM lParam)
1905 {
1906 return -1;
1907 };
1908
1909 if ((!enumerate || EnumFontFamiliesExW(hdc, &lFont, enumProc, NULL, 0) == -1))
1910 font = CreateFontIndirectW(&lFont);
1911
1912 if (font)
1913 {
1914 wchar_t selectedFontName[64] = {'\0'};
1915
1916 SelectFont(hdc, font);
1917 GetTextFaceW(hdc, 64, selectedFontName);
1918 if (strcmp(UTF16AsUTF8(selectedFontName).Get(), fontName))
1919 {
1920 DeleteObject(font);
1921 return nullptr;
1922 }
1923 }
1924
1925 ReleaseDC(NULL, hdc);
1926
1927 return font;
1928}
1929
1930PlatformFontPtr IGraphicsWin::LoadPlatformFont(const char* fontID, const char* fileNameOrResID)
1931{
1932 StaticStorage<InstalledFont>::Accessor fontStorage(sPlatformFontCache);
1933
1934 void* pFontMem = nullptr;
1935 int resSize = 0;
1936 WDL_String fullPath;
1937
1938 const EResourceLocation fontLocation = LocateResource(fileNameOrResID, "ttf", fullPath, GetBundleID(), GetWinModuleHandle(), nullptr);
1939
1940 if (fontLocation == kNotFound)
1941 return nullptr;
1942
1943 switch (fontLocation)
1944 {
1945 case kAbsolutePath:
1946 {
1947 HANDLE file = CreateFileW(UTF8AsUTF16(fullPath).Get(), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1948 PlatformFontPtr ret = nullptr;
1949 if (file)
1950 {
1951 HANDLE mapping = CreateFileMappingW(file, NULL, PAGE_READONLY, 0, 0, NULL);
1952 if (mapping)
1953 {
1954 resSize = (int) GetFileSize(file, nullptr);
1955 pFontMem = MapViewOfFile(mapping, FILE_MAP_READ, 0, 0, 0);
1956 ret = LoadPlatformFont(fontID, pFontMem, resSize);
1957 UnmapViewOfFile(pFontMem);
1958 CloseHandle(mapping);
1959 }
1960 CloseHandle(file);
1961 }
1962 return ret;
1963 }
1964 break;
1965 case kWinBinary:
1966 {
1967 pFontMem = const_cast<void *>(LoadWinResource(fullPath.Get(), "ttf", resSize, GetWinModuleHandle()));
1968 return LoadPlatformFont(fontID, pFontMem, resSize);
1969 }
1970 break;
1971 }
1972
1973 return nullptr;
1974}
1975
1976PlatformFontPtr IGraphicsWin::LoadPlatformFont(const char* fontID, const char* fontName, ETextStyle style)
1977{
1978 int weight = style == ETextStyle::Bold ? FW_BOLD : FW_REGULAR;
1979 bool italic = style == ETextStyle::Italic;
1980 bool underline = false;
1981 DWORD quality = DEFAULT_QUALITY;
1982
1983 HFONT font = GetHFont(fontName, weight, italic, underline, quality, true);
1984
1985 return PlatformFontPtr(font ? new Font(font, TextStyleString(style), true) : nullptr);
1986}
1987
1988PlatformFontPtr IGraphicsWin::LoadPlatformFont(const char* fontID, void* pData, int dataSize)
1989{
1990 StaticStorage<InstalledFont>::Accessor fontStorage(sPlatformFontCache);
1991
1992 std::unique_ptr<InstalledFont> pFont;
1993 void* pFontMem = pData;
1994 int resSize = dataSize;
1995
1996 pFont = std::make_unique<InstalledFont>(pFontMem, resSize);
1997
1998 if (pFontMem && pFont && pFont->IsValid())
1999 {
2000 IFontInfo fontInfo(pFontMem, resSize, 0);
2001 WDL_String family = fontInfo.GetFamily();
2002 int weight = fontInfo.IsBold() ? FW_BOLD : FW_REGULAR;
2003 bool italic = fontInfo.IsItalic();
2004 bool underline = fontInfo.IsUnderline();
2005
2006 HFONT font = GetHFont(family.Get(), weight, italic, underline);
2007
2008 if (font)
2009 {
2010 fontStorage.Add(pFont.release(), fontID);
2011 return PlatformFontPtr(new Font(font, "", false));
2012 }
2013 }
2014
2015 return nullptr;
2016}
2017
2018void IGraphicsWin::CachePlatformFont(const char* fontID, const PlatformFontPtr& font)
2019{
2020 StaticStorage<HFontHolder>::Accessor hfontStorage(sHFontCache);
2021
2022 HFONT hfont = font->GetDescriptor();
2023
2024 if (!hfontStorage.Find(fontID))
2025 hfontStorage.Add(new HFontHolder(hfont), fontID);
2026}
2027
2028DWORD WINAPI VBlankRun(LPVOID lpParam)
2029{
2030 IGraphicsWin* pGraphics = (IGraphicsWin*) lpParam;
2031 return pGraphics->OnVBlankRun();
2032}
2033
2034void IGraphicsWin::StartVBlankThread(HWND hWnd)
2035{
2036 mVBlankWindow = hWnd;
2037 mVBlankShutdown = false;
2038 DWORD threadId = 0;
2039 mVBlankThread = ::CreateThread(NULL, 0, VBlankRun, this, 0, &threadId);
2040}
2041
2042void IGraphicsWin::StopVBlankThread()
2043{
2044 if (mVBlankThread != INVALID_HANDLE_VALUE)
2045 {
2046 mVBlankShutdown = true;
2047 ::WaitForSingleObject(mVBlankThread, 10000);
2048 mVBlankThread = INVALID_HANDLE_VALUE;
2049 mVBlankWindow = 0;
2050 }
2051}
2052
2053// Nasty kernel level definitions for wait for vblank. Including the
2054// proper include file requires "d3dkmthk.h" from the driver development
2055// kit. Instead we define the minimum needed to call the three methods we need.
2056// and use LoadLibrary/GetProcAddress to accomplish the same thing.
2057// See https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/d3dkmthk/
2058//
2059// Heres another link (rant) with a lot of good information about vsync on firefox
2060// https://www.vsynctester.com/firefoxisbroken.html
2061// https://bugs.chromium.org/p/chromium/issues/detail?id=467617
2062
2063// structs to use
2064typedef UINT32 D3DKMT_HANDLE;
2065typedef UINT D3DDDI_VIDEO_PRESENT_SOURCE_ID;
2066
2067typedef struct _D3DKMT_OPENADAPTERFROMHDC
2068{
2069 HDC hDc;
2070 D3DKMT_HANDLE hAdapter;
2071 LUID AdapterLuid;
2072 D3DDDI_VIDEO_PRESENT_SOURCE_ID VidPnSourceId;
2073} D3DKMT_OPENADAPTERFROMHDC;
2074
2075typedef struct _D3DKMT_CLOSEADAPTER
2076{
2077 D3DKMT_HANDLE hAdapter;
2078} D3DKMT_CLOSEADAPTER;
2079
2080typedef struct _D3DKMT_WAITFORVERTICALBLANKEVENT
2081{
2082 D3DKMT_HANDLE hAdapter;
2083 D3DKMT_HANDLE hDevice;
2084 D3DDDI_VIDEO_PRESENT_SOURCE_ID VidPnSourceId;
2085} D3DKMT_WAITFORVERTICALBLANKEVENT;
2086
2087// entry points
2088typedef NTSTATUS(WINAPI* D3DKMTOpenAdapterFromHdc)(D3DKMT_OPENADAPTERFROMHDC* Arg1);
2089typedef NTSTATUS(WINAPI* D3DKMTCloseAdapter)(const D3DKMT_CLOSEADAPTER* Arg1);
2090typedef NTSTATUS(WINAPI* D3DKMTWaitForVerticalBlankEvent)(const D3DKMT_WAITFORVERTICALBLANKEVENT* Arg1);
2091
2092DWORD IGraphicsWin::OnVBlankRun()
2093{
2094 SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
2095
2096 // TODO: get expected vsync value. For now we will use a fallback
2097 // of 60Hz
2098 float rateFallback = 60.0f;
2099 int rateMS = (int)(1000.0f / rateFallback);
2100
2101 // We need to try to load the module and entry points to wait on v blank.
2102 // if anything fails, we try to gracefully fallback to sleeping for some
2103 // number of milliseconds.
2104 //
2105 // TODO: handle low power modes
2106
2107 D3DKMTOpenAdapterFromHdc pOpen = nullptr;
2108 D3DKMTCloseAdapter pClose = nullptr;
2109 D3DKMTWaitForVerticalBlankEvent pWait = nullptr;
2110 HINSTANCE hInst = LoadLibraryW(L"gdi32.dll");
2111
2112 if (hInst != nullptr)
2113 {
2114 pOpen = (D3DKMTOpenAdapterFromHdc) GetProcAddress((HMODULE) hInst, "D3DKMTOpenAdapterFromHdc");
2115 pClose = (D3DKMTCloseAdapter) GetProcAddress((HMODULE) hInst, "D3DKMTCloseAdapter");
2116 pWait = (D3DKMTWaitForVerticalBlankEvent) GetProcAddress((HMODULE) hInst, "D3DKMTWaitForVerticalBlankEvent");
2117 }
2118
2119 // if we don't get bindings to the methods we will fallback
2120 // to a crummy sleep loop for now. This is really just a last
2121 // resort and not expected on modern hardware and Windows OS
2122 // installs.
2123 if (!pOpen || !pClose || !pWait)
2124 {
2125 while (mVBlankShutdown == false)
2126 {
2127 Sleep(rateMS);
2128 VBlankNotify();
2129 }
2130 }
2131 else
2132 {
2133 // we have a good set of functions to call. We need to keep
2134 // track of the adapter and reask for it if the device is lost.
2135 bool adapterIsOpen = false;
2136 DWORD adapterLastFailTime = 0;
2137 _D3DKMT_WAITFORVERTICALBLANKEVENT we = { 0 };
2138
2139 while (mVBlankShutdown == false)
2140 {
2141 if (!adapterIsOpen)
2142 {
2143 // reacquire the adapter (at most once a second).
2144 if (adapterLastFailTime < ::GetTickCount() - 1000)
2145 {
2146 // try to get adapter
2147 D3DKMT_OPENADAPTERFROMHDC openAdapterData = { 0 };
2148 HDC hDC = GetDC(mVBlankWindow);
2149 openAdapterData.hDc = hDC;
2150 NTSTATUS status = (*pOpen)(&openAdapterData);
2151 if (status == S_OK)
2152 {
2153 // success, setup wait request parameters.
2154 adapterLastFailTime = 0;
2155 adapterIsOpen = true;
2156 we.hAdapter = openAdapterData.hAdapter;
2157 we.hDevice = 0;
2158 we.VidPnSourceId = openAdapterData.VidPnSourceId;
2159 }
2160 else
2161 {
2162 // failed
2163 adapterLastFailTime = ::GetTickCount();
2164 }
2165 DeleteDC(hDC);
2166 }
2167 }
2168
2169 if (adapterIsOpen)
2170 {
2171 // Finally we can wait on VBlank
2172 NTSTATUS status = (*pWait)(&we);
2173 if (status != S_OK)
2174 {
2175 // failed, close now and try again on the next pass.
2176 _D3DKMT_CLOSEADAPTER ca;
2177 ca.hAdapter = we.hAdapter;
2178 (*pClose)(&ca);
2179 adapterIsOpen = false;
2180 }
2181 }
2182
2183 // Temporary fallback for lost adapter or failed call.
2184 if (!adapterIsOpen)
2185 {
2186 ::Sleep(rateMS);
2187 }
2188
2189 // notify logic
2190 VBlankNotify();
2191 }
2192
2193 // cleanup adapter before leaving
2194 if (adapterIsOpen)
2195 {
2196 _D3DKMT_CLOSEADAPTER ca;
2197 ca.hAdapter = we.hAdapter;
2198 (*pClose)(&ca);
2199 adapterIsOpen = false;
2200 }
2201 }
2202
2203 // release module resource
2204 if (hInst != nullptr)
2205 {
2206 FreeLibrary((HMODULE)hInst);
2207 hInst = nullptr;
2208 }
2209
2210 return 0;
2211}
2212
2213void IGraphicsWin::VBlankNotify()
2214{
2215 mVBlankCount++;
2216 ::PostMessageW(mVBlankWindow, WM_VBLANK, mVBlankCount, 0);
2217}
2218
2219#ifndef NO_IGRAPHICS
2220#if defined IGRAPHICS_SKIA
2221 #include "IGraphicsSkia.cpp"
2222 #ifdef IGRAPHICS_GL
2223 #include "glad.c"
2224 #endif
2225#elif defined IGRAPHICS_NANOVG
2226 #include "IGraphicsNanoVG.cpp"
2227#ifdef IGRAPHICS_FREETYPE
2228#define FONS_USE_FREETYPE
2229 #pragma comment(lib, "freetype.lib")
2230#endif
2231 #include "nanovg.c"
2232 #include "glad.c"
2233#else
2234 #error
2235#endif
2236#endif
Common paths useful for plug-ins.
const void * LoadWinResource(const char *resID, const char *type, int &sizeInBytes, void *pHInstance)
Load a resource from the binary (windows only).
EResourceLocation LocateResource(const char *fileNameOrResID, const char *type, WDL_String &result, const char *bundleID, void *pHInstance, const char *sharedResourcesSubPath)
Find the absolute path of a resource based on it's file name (e.g.
An editor delegate base class that uses IGraphics for the UI.
virtual ECursor SetMouseCursor(ECursor cursorType=ECursor::ARROW)
Sets the mouse cursor to one of ECursor (implementations should return the result of the base impleme...
Definition: IGraphics.h:828
IGraphics platform class for Windows.
Definition: IGraphicsWin.h:26
EParamType Type() const
Get the parameter's type.
A class to specify an item of a pop up menu.
A class for setting the contents of a pop up menu.
Used to manage a list of rectangular areas and optimize them for drawing to the screen.
int Size() const
const IRECT & Get(int idx) const
Get an IRECT from the list (will crash if idx is invalid)
static const char * TextStyleString(ETextStyle style)
Helper to get a CString based on ETextStyle.
Used to group mouse coordinates with mouse modifier information.
Used to manage color data, independent of draw class/platform.
Used for key press info, such as ASCII representation, virtual key (mapped to win32 codes) and modifi...
Definition: IPlugStructs.h:616
Used to manage mouse modifiers i.e.
Used to manage a rectangular area, independent of draw class/platform.
void PixelAlign()
Pixel aligns the rect in an inclusive manner (moves all points outwards)
IRECT GetScaled(float scale) const
Get a copy of this IRECT with all values multiplied by scale.
float W() const
void Scale(float scale)
Multiply each field of this IRECT by scale.
float H() const
IText is used to manage font and text/text entry style for a piece of text on the UI,...