iPlug2 - C++ Audio Plug-in Framework
Loading...
Searching...
No Matches
IGraphicsWeb.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#include <cstring>
12#include <cstdio>
13#include <cstdint>
14#include <string>
15#include <vector>
16
17#include "IGraphicsWeb.h"
18
19// Helper to create WebGL context for Shadow DOM (CSS selectors don't work)
20EM_JS(int, createWebGLContextForShadowDOM, (), {
21 var canvas = Module.canvas;
22 if (!canvas) return 0;
23 var attrs = { stencil: true, depth: true, antialias: true, alpha: true };
24 var ctx = canvas.getContext("webgl", attrs) || canvas.getContext("experimental-webgl", attrs);
25 if (!ctx) return 0;
26 return GL.registerContext(ctx, attrs);
27});
28
29
30BEGIN_IPLUG_NAMESPACE
31BEGIN_IGRAPHICS_NAMESPACE
32
33void GetScreenDimensions(int& width, int& height)
34{
35 width = val::global("window")["innerWidth"].as<int>();
36 height = val::global("window")["innerHeight"].as<int>();
37}
38
39END_IPLUG_NAMESPACE
40END_IGRAPHICS_NAMESPACE
41
42using namespace iplug;
43using namespace igraphics;
44using namespace emscripten;
45
46extern std::vector<IGraphicsWeb*> gGraphicsInstances;
47extern void UnregisterGraphicsInstance(IGraphicsWeb* pGraphics);
48double gPrevMouseDownTime = 0.;
49bool gFirstClick = false;
50
51#pragma mark - Private Classes and Structs
52
53// Fonts
54
55class IGraphicsWeb::Font : public PlatformFont
56{
57public:
58 Font(const char* fontName, const char* fontStyle)
59 : PlatformFont(true), mDescriptor{fontName, fontStyle}
60 {}
61
62 FontDescriptor GetDescriptor() override { return &mDescriptor; }
63
64private:
65 std::pair<WDL_String, WDL_String> mDescriptor;
66};
67
68class IGraphicsWeb::FileFont : public Font
69{
70public:
71 FileFont(const char* fontName, const char* fontStyle, const char* fontPath)
72 : Font(fontName, fontStyle), mPath(fontPath)
73 {
74 mSystem = false;
75 }
76
77 IFontDataPtr GetFontData() override;
78
79private:
80 WDL_String mPath;
81};
82
83IFontDataPtr IGraphicsWeb::FileFont::GetFontData()
84{
85 IFontDataPtr fontData(new IFontData());
86 FILE* fp = fopen(mPath.Get(), "rb");
87
88 // Read in the font data.
89 if (!fp)
90 return fontData;
91
92 fseek(fp,0,SEEK_END);
93 fontData = std::make_unique<IFontData>((int) ftell(fp));
94
95 if (!fontData->GetSize())
96 return fontData;
97
98 fseek(fp,0,SEEK_SET);
99 size_t readSize = fread(fontData->Get(), 1, fontData->GetSize(), fp);
100 fclose(fp);
101
102 if (readSize && readSize == fontData->GetSize())
103 fontData->SetFaceIdx(0);
104
105 return fontData;
106}
107
108class IGraphicsWeb::MemoryFont : public Font
109{
110public:
111 MemoryFont(const char* fontName, const char* fontStyle, const void* pData, int dataSize)
112 : Font(fontName, fontStyle)
113 {
114 mSystem = false;
115 mData.Set((const uint8_t*)pData, dataSize);
116 }
117
118 IFontDataPtr GetFontData() override
119 {
120 return IFontDataPtr(new IFontData(mData.Get(), mData.GetSize(), 0));
121 }
122
123private:
124 WDL_TypedBuf<uint8_t> mData;
125};
126
127#pragma mark - Utilities and Callbacks
128
129static EM_BOOL key_callback(int eventType, const EmscriptenKeyboardEvent* pEvent, void* pUserData)
130{
131 IGraphicsWeb* pGraphicsWeb = (IGraphicsWeb*) pUserData;
132
133 int VK = DOMKeyToVirtualKey(pEvent->keyCode);
134 WDL_String keyUTF8;
135
136 // filter utf8 for non ascii keys
137 if ((VK >= kVK_0 && VK <= kVK_Z) || VK == kVK_NONE)
138 keyUTF8.Set(pEvent->key);
139 else
140 keyUTF8.Set("");
141
142 IKeyPress keyPress {keyUTF8.Get(),
143 DOMKeyToVirtualKey(pEvent->keyCode),
144 static_cast<bool>(pEvent->shiftKey),
145 static_cast<bool>(pEvent->ctrlKey || pEvent->metaKey),
146 static_cast<bool>(pEvent->altKey)};
147
148 switch (eventType)
149 {
150 case EMSCRIPTEN_EVENT_KEYDOWN:
151 {
152 return pGraphicsWeb->OnKeyDown(pGraphicsWeb->mPrevX, pGraphicsWeb->mPrevY, keyPress);
153 }
154 case EMSCRIPTEN_EVENT_KEYUP:
155 {
156 return pGraphicsWeb->OnKeyUp(pGraphicsWeb->mPrevX, pGraphicsWeb->mPrevY, keyPress);
157 }
158 default:
159 break;
160 }
161
162 return 0;
163}
164
165static EM_BOOL outside_mouse_callback(int eventType, const EmscriptenMouseEvent* pEvent, void* pUserData)
166{
167 IGraphicsWeb* pGraphics = (IGraphicsWeb*) pUserData;
168
169 IMouseInfo info;
170 val rect = pGraphics->GetCanvas().call<val>("getBoundingClientRect");
171 info.x = (pEvent->targetX - rect["left"].as<double>()) / pGraphics->GetDrawScale();
172 info.y = (pEvent->targetY - rect["top"].as<double>()) / pGraphics->GetDrawScale();
173 info.dX = pEvent->movementX;
174 info.dY = pEvent->movementY;
175 info.ms = {(pEvent->buttons & 1) != 0, (pEvent->buttons & 2) != 0, static_cast<bool>(pEvent->shiftKey), static_cast<bool>(pEvent->ctrlKey), static_cast<bool>(pEvent->altKey)};
176 std::vector<IMouseInfo> list {info};
177
178 switch (eventType)
179 {
180 case EMSCRIPTEN_EVENT_MOUSEUP:
181 {
182 // Get button states based on what caused the mouse up (nothing in buttons)
183 list[0].ms.L = pEvent->button == 0;
184 list[0].ms.R = pEvent->button == 2;
185 pGraphics->OnMouseUp(list);
186 emscripten_set_mousemove_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, pGraphics, 1, nullptr);
187 emscripten_set_mouseup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, pGraphics, 1, nullptr);
188 break;
189 }
190 case EMSCRIPTEN_EVENT_MOUSEMOVE:
191 {
192 if(pEvent->buttons != 0 && !pGraphics->IsInPlatformTextEntry())
193 pGraphics->OnMouseDrag(list);
194 break;
195 }
196 default:
197 break;
198 }
199
200 pGraphics->mPrevX = info.x;
201 pGraphics->mPrevY = info.y;
202
203 return true;
204}
205
206static EM_BOOL mouse_callback(int eventType, const EmscriptenMouseEvent* pEvent, void* pUserData)
207{
208 IGraphicsWeb* pGraphics = (IGraphicsWeb*) pUserData;
209
210 IMouseInfo info;
211 info.x = pEvent->targetX / pGraphics->GetDrawScale();
212 info.y = pEvent->targetY / pGraphics->GetDrawScale();
213 info.dX = pEvent->movementX;
214 info.dY = pEvent->movementY;
215 info.ms = {(pEvent->buttons & 1) != 0,
216 (pEvent->buttons & 2) != 0,
217 static_cast<bool>(pEvent->shiftKey),
218 static_cast<bool>(pEvent->ctrlKey),
219 static_cast<bool>(pEvent->altKey)};
220
221 std::vector<IMouseInfo> list {info};
222 switch (eventType)
223 {
224 case EMSCRIPTEN_EVENT_MOUSEDOWN:
225 {
226 const double timestamp = GetTimestamp();
227 const double timeDiff = timestamp - gPrevMouseDownTime;
228
229 if (gFirstClick && timeDiff < 0.3)
230 {
231 gFirstClick = false;
232 pGraphics->OnMouseDblClick(info.x, info.y, info.ms);
233 }
234 else
235 {
236 gFirstClick = true;
237 pGraphics->OnMouseDown(list);
238 }
239
240 gPrevMouseDownTime = timestamp;
241
242 break;
243 }
244 case EMSCRIPTEN_EVENT_MOUSEUP:
245 {
246 // Get button states based on what caused the mouse up (nothing in buttons)
247 list[0].ms.L = pEvent->button == 0;
248 list[0].ms.R = pEvent->button == 2;
249 pGraphics->OnMouseUp(list);
250 break;
251 }
252 case EMSCRIPTEN_EVENT_MOUSEMOVE:
253 {
254 gFirstClick = false;
255
256 if(pEvent->buttons == 0)
257 pGraphics->OnMouseOver(info.x, info.y, info.ms);
258 else
259 {
260 if(!pGraphics->IsInPlatformTextEntry())
261 pGraphics->OnMouseDrag(list);
262 }
263 break;
264 }
265 case EMSCRIPTEN_EVENT_MOUSEENTER:
266 pGraphics->OnSetCursor();
267 pGraphics->OnMouseOver(info.x, info.y, info.ms);
268 emscripten_set_mousemove_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, pGraphics, 1, nullptr);
269 break;
270 case EMSCRIPTEN_EVENT_MOUSELEAVE:
271 if(pEvent->buttons != 0)
272 {
273 emscripten_set_mousemove_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, pGraphics, 1, outside_mouse_callback);
274 emscripten_set_mouseup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, pGraphics, 1, outside_mouse_callback);
275 }
276 pGraphics->OnMouseOut(); break;
277 default:
278 break;
279 }
280
281 pGraphics->mPrevX = info.x;
282 pGraphics->mPrevY = info.y;
283
284 return true;
285}
286
287static EM_BOOL wheel_callback(int eventType, const EmscriptenWheelEvent* pEvent, void* pUserData)
288{
289 IGraphics* pGraphics = (IGraphics*) pUserData;
290
291 IMouseMod modifiers(false, false, pEvent->mouse.shiftKey, pEvent->mouse.ctrlKey, pEvent->mouse.altKey);
292
293 double x = pEvent->mouse.targetX;
294 double y = pEvent->mouse.targetY;
295
296 x /= pGraphics->GetDrawScale();
297 y /= pGraphics->GetDrawScale();
298
299 switch (eventType) {
300 case EMSCRIPTEN_EVENT_WHEEL: pGraphics->OnMouseWheel(x, y, modifiers, pEvent->deltaY);
301 default:
302 break;
303 }
304
305 return true;
306}
307
308EM_BOOL touch_callback(int eventType, const EmscriptenTouchEvent* pEvent, void* pUserData)
309{
310 IGraphics* pGraphics = (IGraphics*) pUserData;
311 const float drawScale = pGraphics->GetDrawScale();
312
313 std::vector<IMouseInfo> points;
314
315 static EmscriptenTouchPoint previousTouches[32];
316
317 for (auto i = 0; i < pEvent->numTouches; i++)
318 {
319 IMouseInfo info;
320 info.x = pEvent->touches[i].targetX / drawScale;
321 info.y = pEvent->touches[i].targetY / drawScale;
322 info.dX = info.x - (previousTouches[i].targetX / drawScale);
323 info.dY = info.y - (previousTouches[i].targetY / drawScale);
324 info.ms = {true,
325 false,
326 static_cast<bool>(pEvent->shiftKey),
327 static_cast<bool>(pEvent->ctrlKey),
328 static_cast<bool>(pEvent->altKey),
329 static_cast<ITouchID>(pEvent->touches[i].identifier)
330 };
331
332 if(pEvent->touches[i].isChanged)
333 points.push_back(info);
334 }
335
336 memcpy(previousTouches, pEvent->touches, sizeof(previousTouches));
337
338 switch (eventType)
339 {
340 case EMSCRIPTEN_EVENT_TOUCHSTART:
341 pGraphics->OnMouseDown(points);
342 return true;
343 case EMSCRIPTEN_EVENT_TOUCHEND:
344 pGraphics->OnMouseUp(points);
345 return true;
346 case EMSCRIPTEN_EVENT_TOUCHMOVE:
347 pGraphics->OnMouseDrag(points);
348 return true;
349 case EMSCRIPTEN_EVENT_TOUCHCANCEL:
350 pGraphics->OnTouchCancelled(points);
351 return true;
352 default:
353 return false;
354 }
355}
356
357static EM_BOOL complete_text_entry(int eventType, const EmscriptenFocusEvent* focusEvent, void* pUserData)
358{
359 IGraphicsWeb* pGraphics = (IGraphicsWeb*) pUserData;
360
361 val input = val::global("document").call<val>("getElementById", std::string("textEntry"));
362 std::string str = input["value"].as<std::string>();
363 val::global("document")["body"].call<void>("removeChild", input);
364 pGraphics->SetControlValueAfterTextEdit(str.c_str());
365
366 return true;
367}
368
369static EM_BOOL text_entry_keydown(int eventType, const EmscriptenKeyboardEvent* pEvent, void* pUserData)
370{
371 IGraphicsWeb* pGraphicsWeb = (IGraphicsWeb*) pUserData;
372
373 IKeyPress keyPress {pEvent->key, DOMKeyToVirtualKey(pEvent->keyCode),
374 static_cast<bool>(pEvent->shiftKey),
375 static_cast<bool>(pEvent->ctrlKey),
376 static_cast<bool>(pEvent->altKey)};
377
378 if (keyPress.VK == kVK_RETURN || keyPress.VK == kVK_TAB)
379 return complete_text_entry(0, nullptr, pUserData);
380
381 return false;
382}
383
384static EM_BOOL uievent_callback(int eventType, const EmscriptenUiEvent* pEvent, void* pUserData)
385{
386 IGraphicsWeb* pGraphics = (IGraphicsWeb*) pUserData;
387
388 if (eventType == EMSCRIPTEN_EVENT_RESIZE)
389 {
390 pGraphics->GetDelegate()->OnParentWindowResize(pEvent->windowInnerWidth, pEvent->windowInnerHeight);
391
392 return true;
393 }
394
395 return false;
396}
397
398IColorPickerHandlerFunc gColorPickerHandlerFunc = nullptr;
399
400static void color_picker_callback(val e)
401{
402 if(gColorPickerHandlerFunc)
403 {
404 std::string colorStrHex = e["target"]["value"].as<std::string>();
405
406 if (colorStrHex[0] == '#')
407 colorStrHex = colorStrHex.erase(0, 1);
408
409 IColor result;
410 result.A = 255;
411 sscanf(colorStrHex.c_str(), "%02x%02x%02x", &result.R, &result.G, &result.B);
412
413 gColorPickerHandlerFunc(result);
414 }
415}
416
417static void file_dialog_callback(val e)
418{
419 // DBGMSG(e["files"].as<std::string>().c_str());
420}
421
422EMSCRIPTEN_BINDINGS(events) {
423 function("color_picker_callback", color_picker_callback);
424 function("file_dialog_callback", file_dialog_callback);
425}
426
427#pragma mark -
428
429IGraphicsWeb::IGraphicsWeb(IGEditorDelegate& dlg, int w, int h, int fps, float scale, val canvas)
430: IGRAPHICS_DRAW_CLASS(dlg, w, h, fps, scale)
431{
432 val keys = val::global("Object").call<val>("keys", GetPreloadedImages());
433
434 DBGMSG("Preloaded %i images\n", keys["length"].as<int>());
435
436 // Initialize canvas - use provided element, Module.canvas, or fall back to getElementById
437 if (canvas.isUndefined() || canvas.isNull())
438 {
439 // Try Module.canvas first (set by web component or HTML template)
440 val moduleCanvas = val::global("Module")["canvas"];
441 if (!moduleCanvas.isUndefined() && !moduleCanvas.isNull())
442 {
443 mCanvas = moduleCanvas;
444 }
445 else
446 {
447 // Fall back to getElementById for legacy templates
448 mCanvas = val::global("document").call<val>("getElementById", std::string("canvas"));
449 }
450 }
451 else
452 {
453 mCanvas = canvas;
454 }
455
456 // Detect Shadow DOM by checking the canvas's root node
457 mRootNode = mCanvas.call<val>("getRootNode");
458 std::string rootNodeType = mRootNode["constructor"]["name"].as<std::string>();
459 mInShadowDOM = (rootNodeType == "ShadowRoot");
460
461 // Generate unique canvas ID for this instance (used by emscripten callbacks)
462 char idBuf[64];
463 snprintf(idBuf, sizeof(idBuf), "iplug-canvas-%p", static_cast<void*>(this));
464 mCanvasSelector = std::string("#") + idBuf;
465 mCanvas.set("id", std::string(idBuf));
466
467 DBGMSG("IGraphicsWeb: Shadow DOM = %s, selector = %s\n", mInShadowDOM ? "true" : "false", mCanvasSelector.c_str());
468
469 RegisterCanvasEvents();
470}
471
472void IGraphicsWeb::RegisterCanvasEvents()
473{
474 // Window-level events always work
475 emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, this, 1, key_callback);
476 emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, this, 1, key_callback);
477 emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, this, 1, uievent_callback);
478
479 if (mInShadowDOM)
480 {
481 // Shadow DOM: emscripten's CSS selector-based callbacks don't work
482 // Set up events via JavaScript on the canvas element directly
483 EM_ASM({
484 var pGraphics = $0;
485 var canvas = Module.canvas;
486 if (!canvas) return;
487
488 // Store reference for cleanup
489 canvas._iplugGraphics = pGraphics;
490
491 canvas.addEventListener('mousedown', function(e) {
492 Module._iGraphicsMouseCallback(pGraphics, 0, e.offsetX, e.offsetY, e.movementX, e.movementY, e.buttons, e.button, e.shiftKey, e.ctrlKey, e.altKey);
493 });
494 canvas.addEventListener('mouseup', function(e) {
495 Module._iGraphicsMouseCallback(pGraphics, 1, e.offsetX, e.offsetY, e.movementX, e.movementY, e.buttons, e.button, e.shiftKey, e.ctrlKey, e.altKey);
496 });
497 canvas.addEventListener('mousemove', function(e) {
498 Module._iGraphicsMouseCallback(pGraphics, 2, e.offsetX, e.offsetY, e.movementX, e.movementY, e.buttons, e.button, e.shiftKey, e.ctrlKey, e.altKey);
499 });
500 canvas.addEventListener('mouseenter', function(e) {
501 Module._iGraphicsMouseCallback(pGraphics, 3, e.offsetX, e.offsetY, e.movementX, e.movementY, e.buttons, e.button, e.shiftKey, e.ctrlKey, e.altKey);
502 });
503 canvas.addEventListener('mouseleave', function(e) {
504 Module._iGraphicsMouseCallback(pGraphics, 4, e.offsetX, e.offsetY, e.movementX, e.movementY, e.buttons, e.button, e.shiftKey, e.ctrlKey, e.altKey);
505 });
506 canvas.addEventListener('wheel', function(e) {
507 Module._iGraphicsWheelCallback(pGraphics, e.offsetX, e.offsetY, e.deltaY, e.shiftKey, e.ctrlKey, e.altKey);
508 e.preventDefault();
509 }, { passive: false });
510 // TODO: Touch events for Shadow DOM
511 }, this);
512 }
513 else
514 {
515 // Regular DOM: use emscripten's callback system
516 const char* target = mCanvasSelector.c_str();
517 emscripten_set_mousedown_callback(target, this, 1, mouse_callback);
518 emscripten_set_mouseup_callback(target, this, 1, mouse_callback);
519 emscripten_set_mousemove_callback(target, this, 1, mouse_callback);
520 emscripten_set_mouseenter_callback(target, this, 1, mouse_callback);
521 emscripten_set_mouseleave_callback(target, this, 1, mouse_callback);
522 emscripten_set_wheel_callback(target, this, 1, wheel_callback);
523 emscripten_set_touchstart_callback(target, this, 1, touch_callback);
524 emscripten_set_touchend_callback(target, this, 1, touch_callback);
525 emscripten_set_touchmove_callback(target, this, 1, touch_callback);
526 emscripten_set_touchcancel_callback(target, this, 1, touch_callback);
527 }
528}
529
530void IGraphicsWeb::UnregisterCanvasEvents()
531{
532 const char* target = mCanvasSelector.c_str();
533
534 emscripten_set_mousedown_callback(target, this, 1, nullptr);
535 emscripten_set_mouseup_callback(target, this, 1, nullptr);
536 emscripten_set_mousemove_callback(target, this, 1, nullptr);
537 emscripten_set_mouseenter_callback(target, this, 1, nullptr);
538 emscripten_set_mouseleave_callback(target, this, 1, nullptr);
539 emscripten_set_wheel_callback(target, this, 1, nullptr);
540 emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, this, 1, nullptr);
541 emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, this, 1, nullptr);
542 emscripten_set_touchstart_callback(target, this, 1, nullptr);
543 emscripten_set_touchend_callback(target, this, 1, nullptr);
544 emscripten_set_touchmove_callback(target, this, 1, nullptr);
545 emscripten_set_touchcancel_callback(target, this, 1, nullptr);
546}
547
548IGraphicsWeb::~IGraphicsWeb()
549{
550 UnregisterCanvasEvents();
551 UnregisterGraphicsInstance(this);
552}
553
554void* IGraphicsWeb::OpenWindow(void* pHandle)
555{
556#ifdef IGRAPHICS_GL
557 EmscriptenWebGLContextAttributes attr;
558 emscripten_webgl_init_context_attributes(&attr);
559 attr.stencil = true;
560 attr.depth = true;
561// attr.explicitSwapControl = 1;
562
563 EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx;
564
565 if (mInShadowDOM)
566 {
567 // Shadow DOM: create context via JS since CSS selectors don't work
568 ctx = createWebGLContextForShadowDOM();
569 }
570 else
571 {
572 // Regular DOM: use standard emscripten API
573 ctx = emscripten_webgl_create_context(mCanvasSelector.c_str(), &attr);
574 }
575
576 emscripten_webgl_make_context_current(ctx);
577#endif
578
579 OnViewInitialized(nullptr /* not used */);
580
581 SetScreenScale(std::ceil(std::max(emscripten_get_device_pixel_ratio(), 1.)));
582
583 GetDelegate()->LayoutUI(this);
584 GetDelegate()->OnUIOpen();
585
586 return nullptr;
587}
588
589void IGraphicsWeb::HideMouseCursor(bool hide, bool lock)
590{
591 if (mCursorHidden == hide)
592 return;
593
594 if (hide)
595 {
596#ifdef IGRAPHICS_WEB_POINTERLOCK
597 if (lock)
598 emscripten_request_pointerlock(mCanvasSelector.c_str(), EM_FALSE);
599 else
600#endif
601 mCanvas["style"].set("cursor", "none");
602
603 mCursorHidden = true;
604 mCursorLock = lock;
605 }
606 else
607 {
608#ifdef IGRAPHICS_WEB_POINTERLOCK
609 if (mCursorLock)
610 emscripten_exit_pointerlock();
611 else
612#endif
613 OnSetCursor();
614
615 mCursorHidden = false;
616 mCursorLock = false;
617 }
618}
619
620ECursor IGraphicsWeb::SetMouseCursor(ECursor cursorType)
621{
622 std::string cursor("pointer");
623
624 switch (cursorType)
625 {
626 case ECursor::ARROW: cursor = "default"; break;
627 case ECursor::IBEAM: cursor = "text"; break;
628 case ECursor::WAIT: cursor = "wait"; break;
629 case ECursor::CROSS: cursor = "crosshair"; break;
630 case ECursor::UPARROW: cursor = "n-resize"; break;
631 case ECursor::SIZENWSE: cursor = "nwse-resize"; break;
632 case ECursor::SIZENESW: cursor = "nesw-resize"; break;
633 case ECursor::SIZEWE: cursor = "ew-resize"; break;
634 case ECursor::SIZENS: cursor = "ns-resize"; break;
635 case ECursor::SIZEALL: cursor = "move"; break;
636 case ECursor::INO: cursor = "not-allowed"; break;
637 case ECursor::HAND: cursor = "pointer"; break;
638 case ECursor::APPSTARTING: cursor = "progress"; break;
639 case ECursor::HELP: cursor = "help"; break;
640 }
641
642 mCanvas["style"].set("cursor", cursor);
643 return IGraphics::SetMouseCursor(cursorType);
644}
645
646void IGraphicsWeb::GetMouseLocation(float& x, float&y) const
647{
648 x = mPrevX;
649 y = mPrevY;
650}
651
652//static
653void IGraphicsWeb::OnMainLoopTimer()
654{
655 int screenScale = (int) std::ceil(std::max(emscripten_get_device_pixel_ratio(), 1.));
656
657 // Iterate over all registered graphics instances
658 for (IGraphicsWeb* pGraphics : gGraphicsInstances)
659 {
660 if (pGraphics == nullptr)
661 continue;
662
663 if (screenScale != pGraphics->GetScreenScale())
664 {
665 pGraphics->SetScreenScale(screenScale);
666 }
667
668 IRECTList rects;
669 if (pGraphics->IsDirty(rects))
670 {
671 pGraphics->SetAllControlsClean();
672 pGraphics->Draw(rects);
673 }
674 }
675}
676
677EMsgBoxResult IGraphicsWeb::ShowMessageBox(const char* str, const char* /*title*/, EMsgBoxType type, IMsgBoxCompletionHandlerFunc completionHandler)
678{
679 ReleaseMouseCapture();
680
681 EMsgBoxResult result = kNoResult;
682
683 switch (type)
684 {
685 case kMB_OK:
686 {
687 val::global("window").call<val>("alert", std::string(str));
688 result = EMsgBoxResult::kOK;
689 break;
690 }
691 case kMB_YESNO:
692 case kMB_OKCANCEL:
693 {
694 result = static_cast<EMsgBoxResult>(val::global("window").call<val>("confirm", std::string(str)).as<int>());
695 }
696 // case MB_CANCEL:
697 // break;
698 default:
699 return result = kNoResult;
700 }
701
702 if(completionHandler)
703 completionHandler(result);
704
705 return result;
706}
707
708void IGraphicsWeb::PromptForFile(WDL_String& filename, WDL_String& path, EFileAction action, const char* ext, IFileDialogCompletionHandlerFunc completionHandler)
709{
710 //TODO
711 // val inputEl = val::global("document").call<val>("createElement", std::string("input"));
712
713 // inputEl.call<void>("setAttribute", std::string("type"), std::string("file"));
714 // inputEl.call<void>("setAttribute", std::string("accept"), std::string(ext));
715 // inputEl.call<void>("click");
716 // inputEl.call<void>("addEventListener", std::string("input"), val::module_property("file_dialog_callback"), false);
717 // inputEl.call<void>("addEventListener", std::string("onChange"), val::module_property("file_dialog_callback"), false);
718}
719
720void IGraphicsWeb::PromptForDirectory(WDL_String& path, IFileDialogCompletionHandlerFunc completionHandler)
721{
722 //TODO
723 // val inputEl = val::global("document").call<val>("createElement", std::string("input"));
724
725 // inputEl.call<void>("setAttribute", std::string("type"), std::string("file"));
726 // inputEl.call<void>("setAttribute", std::string("directory"), true);
727 // inputEl.call<void>("setAttribute", std::string("webkitdirectory"), true);
728 // inputEl.call<void>("click");
729 // inputEl.call<void>("addEventListener", std::string("input"), val::module_property("file_dialog_callback"), false);
730 // inputEl.call<void>("addEventListener", std::string("onChange"), val::module_property("file_dialog_callback"), false);
731}
732
733bool IGraphicsWeb::PromptForColor(IColor& color, const char* str, IColorPickerHandlerFunc func)
734{
735 ReleaseMouseCapture();
736
737 gColorPickerHandlerFunc = func;
738
739 val inputEl = val::global("document").call<val>("createElement", std::string("input"));
740 inputEl.call<void>("setAttribute", std::string("type"), std::string("color"));
741 WDL_String colorStr;
742 colorStr.SetFormatted(64, "#%02x%02x%02x", color.R, color.G, color.B);
743 inputEl.call<void>("setAttribute", std::string("value"), std::string(colorStr.Get()));
744 inputEl.call<void>("click");
745 inputEl.call<void>("addEventListener", std::string("input"), val::module_property("color_picker_callback"), false);
746 inputEl.call<void>("addEventListener", std::string("onChange"), val::module_property("color_picker_callback"), false);
747
748 return false;
749}
750
751void IGraphicsWeb::CreatePlatformTextEntry(int paramIdx, const IText& text, const IRECT& bounds, int length, const char* str)
752{
753 val input = val::global("document").call<val>("createElement", std::string("input"));
754 val rect = mCanvas.call<val>("getBoundingClientRect");
755
756 auto setDim = [&input](const char *dimName, double pixels)
757 {
758 WDL_String dimstr;
759 dimstr.SetFormatted(32, "%fpx", pixels);
760 input["style"].set(dimName, std::string(dimstr.Get()));
761 };
762
763 auto setColor = [&input](const char *colorName, IColor color)
764 {
765 WDL_String str;
766 str.SetFormatted(64, "rgba(%d, %d, %d, %d)", color.R, color.G, color.B, color.A);
767 input["style"].set(colorName, std::string(str.Get()));
768 };
769
770 input.set("id", std::string("textEntry"));
771 input["style"].set("position", val("fixed"));
772 setDim("left", rect["left"].as<double>() + bounds.L);
773 setDim("top", rect["top"].as<double>() + bounds.T);
774 setDim("width", bounds.W());
775 setDim("height", bounds.H());
776
777 setColor("color", text.mTextEntryFGColor);
778 setColor("background-color", text.mTextEntryBGColor);
779 if (paramIdx > kNoParameter)
780 {
781 const IParam* pParam = GetDelegate()->GetParam(paramIdx);
782
783 switch (pParam->Type())
784 {
785 case IParam::kTypeEnum:
786 case IParam::kTypeInt:
787 case IParam::kTypeBool:
788 input.set("type", val("number")); // TODO
789 break;
790 case IParam::kTypeDouble:
791 input.set("type", val("number"));
792 break;
793 default:
794 break;
795 }
796 }
797 else
798 {
799 input.set("type", val("text"));
800 }
801
802 // Append to shadow root or document.body based on mode
803 if (mInShadowDOM)
804 {
805 mRootNode.call<void>("appendChild", input);
806 }
807 else
808 {
809 val::global("document")["body"].call<void>("appendChild", input);
810 }
811
812 input.call<void>("focus");
813 emscripten_set_focusout_callback("textEntry", this, 1, complete_text_entry);
814 emscripten_set_keydown_callback("textEntry", this, 1, text_entry_keydown);
815}
816
817IPopupMenu* IGraphicsWeb::CreatePlatformPopupMenu(IPopupMenu& menu, const IRECT bounds, bool& isAsync)
818{
819 return nullptr;
820}
821
822bool IGraphicsWeb::OpenURL(const char* url, const char* msgWindowTitle, const char* confirmMsg, const char* errMsgOnFailure)
823{
824 val::global("window").call<val>("open", std::string(url), std::string("_blank"));
825
826 return true;
827}
828
829void IGraphicsWeb::DrawResize()
830{
831 // CSS style.width/height need "px" suffix
832 std::string widthPx = std::to_string(static_cast<int>(Width() * GetDrawScale())) + "px";
833 std::string heightPx = std::to_string(static_cast<int>(Height() * GetDrawScale())) + "px";
834 mCanvas["style"].set("width", val(widthPx));
835 mCanvas["style"].set("height", val(heightPx));
836
837 // Canvas element width/height attributes are integers (no px)
838 mCanvas.set("width", Width() * GetBackingPixelScale());
839 mCanvas.set("height", Height() * GetBackingPixelScale());
840
841 IGRAPHICS_DRAW_CLASS::DrawResize();
842}
843
844PlatformFontPtr IGraphicsWeb::LoadPlatformFont(const char* fontID, const char* fileNameOrResID)
845{
846 WDL_String fullPath;
847 const EResourceLocation fontLocation = LocateResource(fileNameOrResID, "ttf", fullPath, GetBundleID(), nullptr, nullptr);
848
849 if (fontLocation == kNotFound)
850 return nullptr;
851
852 return PlatformFontPtr(new FileFont(fontID, "", fullPath.Get()));
853}
854
855PlatformFontPtr IGraphicsWeb::LoadPlatformFont(const char* fontID, const char* fontName, ETextStyle style)
856{
857 const char* styles[] = { "normal", "bold", "italic" };
858
859 return PlatformFontPtr(new Font(fontName, styles[static_cast<int>(style)]));
860}
861
862PlatformFontPtr IGraphicsWeb::LoadPlatformFont(const char* fontID, void* pData, int dataSize)
863{
864 return PlatformFontPtr(new MemoryFont(fontID, "", pData, dataSize));
865}
866
867// Shadow DOM event callback implementations (called from JavaScript)
868extern "C" {
869
870EMSCRIPTEN_KEEPALIVE
871void iGraphicsMouseCallback(void* pGraphics, int eventType, double x, double y, double dx, double dy, int buttons, int button, int shift, int ctrl, int alt)
872{
873 IGraphicsWeb* pG = static_cast<IGraphicsWeb*>(pGraphics);
874 float scale = pG->GetDrawScale();
875
876 IMouseInfo info;
877 info.x = x / scale;
878 info.y = y / scale;
879 info.dX = dx;
880 info.dY = dy;
881 info.ms = {(buttons & 1) != 0, (buttons & 2) != 0, static_cast<bool>(shift), static_cast<bool>(ctrl), static_cast<bool>(alt)};
882 std::vector<IMouseInfo> list{info};
883
884 switch (eventType)
885 {
886 case 0: // mousedown
887 pG->OnMouseDown(list);
888 break;
889 case 1: // mouseup
890 list[0].ms.L = button == 0;
891 list[0].ms.R = button == 2;
892 pG->OnMouseUp(list);
893 break;
894 case 2: // mousemove
895 if (buttons == 0)
896 pG->OnMouseOver(info.x, info.y, info.ms);
897 else if (!pG->IsInPlatformTextEntry())
898 pG->OnMouseDrag(list);
899 break;
900 case 3: // mouseenter
901 pG->OnSetCursor();
902 pG->OnMouseOver(info.x, info.y, info.ms);
903 break;
904 case 4: // mouseleave
905 pG->OnMouseOut();
906 break;
907 }
908
909 pG->mPrevX = info.x;
910 pG->mPrevY = info.y;
911}
912
913EMSCRIPTEN_KEEPALIVE
914void iGraphicsWheelCallback(void* pGraphics, double x, double y, double deltaY, int shift, int ctrl, int alt)
915{
916 IGraphicsWeb* pG = static_cast<IGraphicsWeb*>(pGraphics);
917 float scale = pG->GetDrawScale();
918 IMouseMod mod(false, false, static_cast<bool>(shift), static_cast<bool>(ctrl), static_cast<bool>(alt));
919 pG->OnMouseWheel(x / scale, y / scale, mod, deltaY);
920}
921
922} // extern "C"
923
924#if defined IGRAPHICS_NANOVG
925#include "IGraphicsNanoVG.cpp"
926
927#ifdef IGRAPHICS_FREETYPE
928#define FONS_USE_FREETYPE
929#endif
930
931#include "nanovg.c"
932#endif
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.
The lowest level base class of an IGraphics context.
Definition: IGraphics.h:86
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
void OnMouseDrag(const std::vector< IMouseInfo > &points)
Called when the platform class sends drag events.
Definition: IGraphics.cpp:1168
void OnMouseUp(const std::vector< IMouseInfo > &points)
Called when the platform class sends mouse up events.
Definition: IGraphics.cpp:1070
void OnTouchCancelled(const std::vector< IMouseInfo > &points)
Called when the platform class sends touch cancel events.
Definition: IGraphics.cpp:1111
void OnMouseDown(const std::vector< IMouseInfo > &points)
Called when the platform class sends mouse down events.
Definition: IGraphics.cpp:984
bool OnMouseWheel(float x, float y, const IMouseMod &mod, float delta)
Definition: IGraphics.cpp:1236
float GetDrawScale() const
Gets the graphics context scaling factor.
Definition: IGraphics.h:1118
IGraphics platform class for the web.
Definition: IGraphicsWeb.h:40
IGraphicsWeb(IGEditorDelegate &dlg, int w, int h, int fps, float scale, val canvas=val::undefined())
Constructor.
val GetCanvas() const
Get the canvas element for this instance.
Definition: IGraphicsWeb.h:56
IPlug's parameter class.
EParamType Type() const
Get the parameter's type.
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.
Used to group mouse coordinates with mouse modifier information.
int DOMKeyToVirtualKey(uint32_t domKeyCode)
Converts a DOM virtual key code to an iPlug2 virtual key code.
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:615
Used to manage mouse modifiers i.e.
Used to manage a rectangular area, independent of draw class/platform.
float W() const
float H() const
IText is used to manage font and text/text entry style for a piece of text on the UI,...