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