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 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 const char* tooltip = GetControl(mTooltipIdx)->GetTooltip();
1820 if (tooltip)
1821 {
1822 SetTooltip(tooltip);
1823 mShowingTooltip = true;
1824 }
1825 }
1826}
1827
1828void IGraphicsWin::HideTooltip()
1829{
1830 if (mShowingTooltip)
1831 {
1832 SetTooltip(NULL);
1833 mShowingTooltip = false;
1834 }
1835}
1836
1837bool IGraphicsWin::GetTextFromClipboard(WDL_String& str)
1838{
1839 bool result = false;
1840
1841 if (IsClipboardFormatAvailable(CF_UNICODETEXT))
1842 {
1843 if (OpenClipboard(0))
1844 {
1845 HGLOBAL hglb = GetClipboardData(CF_UNICODETEXT);
1846
1847 if (hglb)
1848 {
1849 WCHAR *origStr = (WCHAR*) GlobalLock(hglb);
1850
1851 if (origStr)
1852 {
1853 UTF16ToUTF8(str, origStr);
1854 GlobalUnlock(hglb);
1855 result = true;
1856 }
1857 }
1858 }
1859
1860 CloseClipboard();
1861 }
1862
1863 if (!result)
1864 str.Set("");
1865
1866 return result;
1867}
1868
1869bool IGraphicsWin::SetTextInClipboard(const char* str)
1870{
1871 if (!OpenClipboard(mMainWnd))
1872 return false;
1873
1874 EmptyClipboard();
1875
1876 bool result = true;
1877
1878 if (strlen(str))
1879 {
1880 // figure out how many characters we need for the wide version of this string
1881 const int lenWide = UTF8ToUTF16Len(str);
1882
1883 // allocate global memory object for the text
1884 HGLOBAL hglbCopy = GlobalAlloc(GMEM_MOVEABLE, lenWide*sizeof(WCHAR));
1885 if (!hglbCopy)
1886 {
1887 CloseClipboard();
1888 return false;
1889 }
1890
1891 // lock the handle
1892 LPWSTR lpstrCopy = (LPWSTR) GlobalLock(hglbCopy);
1893
1894 if (lpstrCopy)
1895 {
1896 // copy the string into the buffer
1897 UTF8ToUTF16(lpstrCopy, str, lenWide);
1898 GlobalUnlock(hglbCopy);
1899
1900 // place the handle on the clipboard
1901 result = SetClipboardData(CF_UNICODETEXT, hglbCopy);
1902
1903 // free the handle if unsuccessful
1904 if (!result)
1905 GlobalFree(hglbCopy);
1906 }
1907 }
1908
1909 CloseClipboard();
1910
1911 return result;
1912}
1913
1914bool IGraphicsWin::SetFilePathInClipboard(const char* path)
1915{
1916 if (!OpenClipboard(mMainWnd))
1917 return false;
1918
1919 EmptyClipboard();
1920
1921 UTF8AsUTF16 pathWide(path);
1922
1923 // N.B. GHND ensures that the memory is zeroed
1924
1925 HGLOBAL hGlobal = GlobalAlloc(GHND, sizeof(DROPFILES) + (sizeof(wchar_t) * (pathWide.GetLength() + 1)));
1926
1927 if (!hGlobal)
1928 return false;
1929
1930 DROPFILES* pDropFiles = (DROPFILES*) GlobalLock(hGlobal);
1931 bool result = false;
1932
1933 if (pDropFiles)
1934 {
1935 // Populate the dropfile structure and copy the file path
1936
1937 pDropFiles->pFiles = sizeof(DROPFILES);
1938 pDropFiles->pt = { 0, 0 };
1939 pDropFiles->fNC = true;
1940 pDropFiles->fWide = true;
1941
1942 std::copy_n(pathWide.Get(), pathWide.GetLength(), reinterpret_cast<wchar_t*>(&pDropFiles[1]));
1943
1944 GlobalUnlock(hGlobal);
1945
1946 result = SetClipboardData(CF_HDROP, hGlobal);
1947 }
1948
1949 // free the handle if unsuccessful
1950 if (!result)
1951 GlobalFree(hGlobal);
1952
1953 CloseClipboard();
1954 return result;
1955}
1956
1957bool IGraphicsWin::InitiateExternalFileDragDrop(const char* path, const IRECT& /*iconBounds*/)
1958{
1959 using namespace DragAndDropHelpers;
1960 OleInitialize(nullptr);
1961
1962 FORMATETC format = { CF_HDROP, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
1963
1964 DataObject* dataObj = new DataObject(&format, path);
1965 DropSource* dropSource = new DropSource();
1966
1967 DWORD dropEffect;
1968 HRESULT ret = DoDragDrop(dataObj, dropSource, DROPEFFECT_COPY, &dropEffect);
1969 bool success = SUCCEEDED(ret);
1970
1971 dataObj->Release();
1972 dropSource->Release();
1973
1974 OleUninitialize();
1975
1976 ReleaseMouseCapture();
1977
1978 return success;
1979}
1980
1981static HFONT GetHFont(const char* fontName, int weight, bool italic, bool underline, DWORD quality = DEFAULT_QUALITY, bool enumerate = false)
1982{
1983 HDC hdc = GetDC(NULL);
1984 HFONT font = nullptr;
1985 LOGFONTW lFont;
1986
1987 lFont.lfHeight = 0;
1988 lFont.lfWidth = 0;
1989 lFont.lfEscapement = 0;
1990 lFont.lfOrientation = 0;
1991 lFont.lfWeight = weight;
1992 lFont.lfItalic = italic;
1993 lFont.lfUnderline = underline;
1994 lFont.lfStrikeOut = false;
1995 lFont.lfCharSet = DEFAULT_CHARSET;
1996 lFont.lfOutPrecision = OUT_TT_PRECIS;
1997 lFont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
1998 lFont.lfQuality = quality;
1999 lFont.lfPitchAndFamily = DEFAULT_PITCH;
2000
2001 wcsncpy(lFont.lfFaceName, UTF8AsUTF16(fontName).Get(), LF_FACESIZE);
2002
2003 auto enumProc = [](const LOGFONTW* pLFont, const TEXTMETRICW* pTextMetric, DWORD FontType, LPARAM lParam)
2004 {
2005 return -1;
2006 };
2007
2008 if ((!enumerate || EnumFontFamiliesExW(hdc, &lFont, enumProc, NULL, 0) == -1))
2009 font = CreateFontIndirectW(&lFont);
2010
2011 if (font)
2012 {
2013 wchar_t selectedFontName[64] = {'\0'};
2014
2015 SelectFont(hdc, font);
2016 GetTextFaceW(hdc, 64, selectedFontName);
2017 if (strcmp(UTF16AsUTF8(selectedFontName).Get(), fontName))
2018 {
2019 DeleteObject(font);
2020 return nullptr;
2021 }
2022 }
2023
2024 ReleaseDC(NULL, hdc);
2025
2026 return font;
2027}
2028
2029PlatformFontPtr IGraphicsWin::LoadPlatformFont(const char* fontID, const char* fileNameOrResID)
2030{
2031 StaticStorage<InstalledFont>::Accessor fontStorage(sPlatformFontCache);
2032
2033 void* pFontMem = nullptr;
2034 int resSize = 0;
2035 WDL_String fullPath;
2036
2037 const EResourceLocation fontLocation = LocateResource(fileNameOrResID, "ttf", fullPath, GetBundleID(), GetWinModuleHandle(), nullptr);
2038
2039 if (fontLocation == kNotFound)
2040 return nullptr;
2041
2042 switch (fontLocation)
2043 {
2044 case kAbsolutePath:
2045 {
2046 HANDLE file = CreateFileW(UTF8AsUTF16(fullPath).Get(), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
2047 PlatformFontPtr ret = nullptr;
2048 if (file)
2049 {
2050 HANDLE mapping = CreateFileMappingW(file, NULL, PAGE_READONLY, 0, 0, NULL);
2051 if (mapping)
2052 {
2053 resSize = (int) GetFileSize(file, nullptr);
2054 pFontMem = MapViewOfFile(mapping, FILE_MAP_READ, 0, 0, 0);
2055 ret = LoadPlatformFont(fontID, pFontMem, resSize);
2056 UnmapViewOfFile(pFontMem);
2057 CloseHandle(mapping);
2058 }
2059 CloseHandle(file);
2060 }
2061 return ret;
2062 }
2063 break;
2064 case kWinBinary:
2065 {
2066 pFontMem = const_cast<void *>(LoadWinResource(fullPath.Get(), "ttf", resSize, GetWinModuleHandle()));
2067 return LoadPlatformFont(fontID, pFontMem, resSize);
2068 }
2069 break;
2070 }
2071
2072 return nullptr;
2073}
2074
2075PlatformFontPtr IGraphicsWin::LoadPlatformFont(const char* fontID, const char* fontName, ETextStyle style)
2076{
2077 int weight = style == ETextStyle::Bold ? FW_BOLD : FW_REGULAR;
2078 bool italic = style == ETextStyle::Italic;
2079 bool underline = false;
2080 DWORD quality = DEFAULT_QUALITY;
2081
2082 HFONT font = GetHFont(fontName, weight, italic, underline, quality, true);
2083
2084 return PlatformFontPtr(font ? new Font(font, TextStyleString(style), true) : nullptr);
2085}
2086
2087PlatformFontPtr IGraphicsWin::LoadPlatformFont(const char* fontID, void* pData, int dataSize)
2088{
2089 StaticStorage<InstalledFont>::Accessor fontStorage(sPlatformFontCache);
2090
2091 std::unique_ptr<InstalledFont> pFont;
2092 void* pFontMem = pData;
2093 int resSize = dataSize;
2094
2095 pFont = std::make_unique<InstalledFont>(pFontMem, resSize);
2096
2097 if (pFontMem && pFont && pFont->IsValid())
2098 {
2099 IFontInfo fontInfo(pFontMem, resSize, 0);
2100 WDL_String family = fontInfo.GetFamily();
2101 int weight = fontInfo.IsBold() ? FW_BOLD : FW_REGULAR;
2102 bool italic = fontInfo.IsItalic();
2103 bool underline = fontInfo.IsUnderline();
2104
2105 HFONT font = GetHFont(family.Get(), weight, italic, underline);
2106
2107 if (font)
2108 {
2109 fontStorage.Add(pFont.release(), fontID);
2110 return PlatformFontPtr(new Font(font, "", false));
2111 }
2112 }
2113
2114 return nullptr;
2115}
2116
2117void IGraphicsWin::CachePlatformFont(const char* fontID, const PlatformFontPtr& font)
2118{
2119 StaticStorage<HFontHolder>::Accessor hfontStorage(sHFontCache);
2120
2121 HFONT hfont = font->GetDescriptor();
2122
2123 if (!hfontStorage.Find(fontID))
2124 hfontStorage.Add(new HFontHolder(hfont), fontID);
2125}
2126
2127DWORD WINAPI VBlankRun(LPVOID lpParam)
2128{
2129 IGraphicsWin* pGraphics = (IGraphicsWin*) lpParam;
2130 return pGraphics->OnVBlankRun();
2131}
2132
2133void IGraphicsWin::StartVBlankThread(HWND hWnd)
2134{
2135 mVBlankWindow = hWnd;
2136 mVBlankShutdown = false;
2137 DWORD threadId = 0;
2138 mVBlankThread = ::CreateThread(NULL, 0, VBlankRun, this, 0, &threadId);
2139}
2140
2141void IGraphicsWin::StopVBlankThread()
2142{
2143 if (mVBlankThread != INVALID_HANDLE_VALUE)
2144 {
2145 mVBlankShutdown = true;
2146 ::WaitForSingleObject(mVBlankThread, 10000);
2147 mVBlankThread = INVALID_HANDLE_VALUE;
2148 mVBlankWindow = 0;
2149 }
2150}
2151
2152// Nasty kernel level definitions for wait for vblank. Including the
2153// proper include file requires "d3dkmthk.h" from the driver development
2154// kit. Instead we define the minimum needed to call the three methods we need.
2155// and use LoadLibrary/GetProcAddress to accomplish the same thing.
2156// See https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/d3dkmthk/
2157//
2158// Heres another link (rant) with a lot of good information about vsync on firefox
2159// https://www.vsynctester.com/firefoxisbroken.html
2160// https://bugs.chromium.org/p/chromium/issues/detail?id=467617
2161
2162// structs to use
2163typedef UINT32 D3DKMT_HANDLE;
2164typedef UINT D3DDDI_VIDEO_PRESENT_SOURCE_ID;
2165
2166typedef struct _D3DKMT_OPENADAPTERFROMHDC
2167{
2168 HDC hDc;
2169 D3DKMT_HANDLE hAdapter;
2170 LUID AdapterLuid;
2171 D3DDDI_VIDEO_PRESENT_SOURCE_ID VidPnSourceId;
2172} D3DKMT_OPENADAPTERFROMHDC;
2173
2174typedef struct _D3DKMT_CLOSEADAPTER
2175{
2176 D3DKMT_HANDLE hAdapter;
2177} D3DKMT_CLOSEADAPTER;
2178
2179typedef struct _D3DKMT_WAITFORVERTICALBLANKEVENT
2180{
2181 D3DKMT_HANDLE hAdapter;
2182 D3DKMT_HANDLE hDevice;
2183 D3DDDI_VIDEO_PRESENT_SOURCE_ID VidPnSourceId;
2184} D3DKMT_WAITFORVERTICALBLANKEVENT;
2185
2186// entry points
2187typedef NTSTATUS(WINAPI* D3DKMTOpenAdapterFromHdc)(D3DKMT_OPENADAPTERFROMHDC* Arg1);
2188typedef NTSTATUS(WINAPI* D3DKMTCloseAdapter)(const D3DKMT_CLOSEADAPTER* Arg1);
2189typedef NTSTATUS(WINAPI* D3DKMTWaitForVerticalBlankEvent)(const D3DKMT_WAITFORVERTICALBLANKEVENT* Arg1);
2190
2191DWORD IGraphicsWin::OnVBlankRun()
2192{
2193 SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
2194
2195 // TODO: get expected vsync value. For now we will use a fallback
2196 // of 60Hz
2197 float rateFallback = 60.0f;
2198 int rateMS = (int)(1000.0f / rateFallback);
2199
2200 // We need to try to load the module and entry points to wait on v blank.
2201 // if anything fails, we try to gracefully fallback to sleeping for some
2202 // number of milliseconds.
2203 //
2204 // TODO: handle low power modes
2205
2206 D3DKMTOpenAdapterFromHdc pOpen = nullptr;
2207 D3DKMTCloseAdapter pClose = nullptr;
2208 D3DKMTWaitForVerticalBlankEvent pWait = nullptr;
2209 HINSTANCE hInst = LoadLibraryW(L"gdi32.dll");
2210
2211 if (hInst != nullptr)
2212 {
2213 pOpen = (D3DKMTOpenAdapterFromHdc) GetProcAddress((HMODULE) hInst, "D3DKMTOpenAdapterFromHdc");
2214 pClose = (D3DKMTCloseAdapter) GetProcAddress((HMODULE) hInst, "D3DKMTCloseAdapter");
2215 pWait = (D3DKMTWaitForVerticalBlankEvent) GetProcAddress((HMODULE) hInst, "D3DKMTWaitForVerticalBlankEvent");
2216 }
2217
2218 // if we don't get bindings to the methods we will fallback
2219 // to a crummy sleep loop for now. This is really just a last
2220 // resort and not expected on modern hardware and Windows OS
2221 // installs.
2222 if (!pOpen || !pClose || !pWait)
2223 {
2224 while (mVBlankShutdown == false)
2225 {
2226 Sleep(rateMS);
2227 VBlankNotify();
2228 }
2229 }
2230 else
2231 {
2232 // we have a good set of functions to call. We need to keep
2233 // track of the adapter and reask for it if the device is lost.
2234 bool adapterIsOpen = false;
2235 DWORD adapterLastFailTime = 0;
2236 _D3DKMT_WAITFORVERTICALBLANKEVENT we = { 0 };
2237
2238 while (mVBlankShutdown == false)
2239 {
2240 if (!adapterIsOpen)
2241 {
2242 // reacquire the adapter (at most once a second).
2243 if (adapterLastFailTime < ::GetTickCount() - 1000)
2244 {
2245 // try to get adapter
2246 D3DKMT_OPENADAPTERFROMHDC openAdapterData = { 0 };
2247 HDC hDC = GetDC(mVBlankWindow);
2248 openAdapterData.hDc = hDC;
2249 NTSTATUS status = (*pOpen)(&openAdapterData);
2250 if (status == S_OK)
2251 {
2252 // success, setup wait request parameters.
2253 adapterLastFailTime = 0;
2254 adapterIsOpen = true;
2255 we.hAdapter = openAdapterData.hAdapter;
2256 we.hDevice = 0;
2257 we.VidPnSourceId = openAdapterData.VidPnSourceId;
2258 }
2259 else
2260 {
2261 // failed
2262 adapterLastFailTime = ::GetTickCount();
2263 }
2264 DeleteDC(hDC);
2265 }
2266 }
2267
2268 if (adapterIsOpen)
2269 {
2270 // Finally we can wait on VBlank
2271 NTSTATUS status = (*pWait)(&we);
2272 if (status != S_OK)
2273 {
2274 // failed, close now and try again on the next pass.
2275 _D3DKMT_CLOSEADAPTER ca;
2276 ca.hAdapter = we.hAdapter;
2277 (*pClose)(&ca);
2278 adapterIsOpen = false;
2279 }
2280 }
2281
2282 // Temporary fallback for lost adapter or failed call.
2283 if (!adapterIsOpen)
2284 {
2285 ::Sleep(rateMS);
2286 }
2287
2288 // notify logic
2289 VBlankNotify();
2290 }
2291
2292 // cleanup adapter before leaving
2293 if (adapterIsOpen)
2294 {
2295 _D3DKMT_CLOSEADAPTER ca;
2296 ca.hAdapter = we.hAdapter;
2297 (*pClose)(&ca);
2298 adapterIsOpen = false;
2299 }
2300 }
2301
2302 // release module resource
2303 if (hInst != nullptr)
2304 {
2305 FreeLibrary((HMODULE)hInst);
2306 hInst = nullptr;
2307 }
2308
2309 return 0;
2310}
2311
2312void IGraphicsWin::VBlankNotify()
2313{
2314 mVBlankCount++;
2315 ::PostMessageW(mVBlankWindow, WM_VBLANK, mVBlankCount, 0);
2316}
2317
2318#ifndef NO_IGRAPHICS
2319#if defined IGRAPHICS_SKIA
2320 #include "IGraphicsSkia.cpp"
2321 #ifdef IGRAPHICS_GL
2322 #include "glad.c"
2323 #endif
2324#elif defined IGRAPHICS_NANOVG
2325 #include "IGraphicsNanoVG.cpp"
2326#ifdef IGRAPHICS_FREETYPE
2327#define FONS_USE_FREETYPE
2328 #pragma comment(lib, "freetype.lib")
2329#endif
2330 #include "nanovg.c"
2331 #include "glad.c"
2332#else
2333 #error
2334#endif
2335#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 for a SOMETHING that uses IGraphics for it's 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,...