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 <emscripten/key_codes.h>
15
16#include "IGraphicsWeb.h"
17
18BEGIN_IPLUG_NAMESPACE
19BEGIN_IGRAPHICS_NAMESPACE
20
21void GetScreenDimensions(int& width, int& height)
22{
23 width = val::global("window")["innerWidth"].as<int>();
24 height = val::global("window")["innerHeight"].as<int>();
25}
26
27END_IPLUG_NAMESPACE
28END_IGRAPHICS_NAMESPACE
29
30using namespace iplug;
31using namespace igraphics;
32using namespace emscripten;
33
34extern IGraphicsWeb* gGraphics;
35double gPrevMouseDownTime = 0.;
36bool gFirstClick = false;
37
38#pragma mark - Private Classes and Structs
39
40// Fonts
41
42class IGraphicsWeb::Font : public PlatformFont
43{
44public:
45 Font(const char* fontName, const char* fontStyle)
46 : PlatformFont(true), mDescriptor{fontName, fontStyle}
47 {}
48
49 FontDescriptor GetDescriptor() override { return &mDescriptor; }
50
51private:
52 std::pair<WDL_String, WDL_String> mDescriptor;
53};
54
55class IGraphicsWeb::FileFont : public Font
56{
57public:
58 FileFont(const char* fontName, const char* fontStyle, const char* fontPath)
59 : Font(fontName, fontStyle), mPath(fontPath)
60 {
61 mSystem = false;
62 }
63
64 IFontDataPtr GetFontData() override;
65
66private:
67 WDL_String mPath;
68};
69
70IFontDataPtr IGraphicsWeb::FileFont::GetFontData()
71{
72 IFontDataPtr fontData(new IFontData());
73 FILE* fp = fopen(mPath.Get(), "rb");
74
75 // Read in the font data.
76 if (!fp)
77 return fontData;
78
79 fseek(fp,0,SEEK_END);
80 fontData = std::make_unique<IFontData>((int) ftell(fp));
81
82 if (!fontData->GetSize())
83 return fontData;
84
85 fseek(fp,0,SEEK_SET);
86 size_t readSize = fread(fontData->Get(), 1, fontData->GetSize(), fp);
87 fclose(fp);
88
89 if (readSize && readSize == fontData->GetSize())
90 fontData->SetFaceIdx(0);
91
92 return fontData;
93}
94
95class IGraphicsWeb::MemoryFont : public Font
96{
97public:
98 MemoryFont(const char* fontName, const char* fontStyle, const void* pData, int dataSize)
99 : Font(fontName, fontStyle)
100 {
101 mSystem = false;
102 mData.Set((const uint8_t*)pData, dataSize);
103 }
104
105 IFontDataPtr GetFontData() override
106 {
107 return IFontDataPtr(new IFontData(mData.Get(), mData.GetSize(), 0));
108 }
109
110private:
111 WDL_TypedBuf<uint8_t> mData;
112};
113
114#pragma mark - Utilities and Callbacks
115
116// Key combos
117
118static int domVKToWinVK(int dom_vk_code)
119{
120 switch(dom_vk_code)
121 {
122// case DOM_VK_CANCEL: return 0; // TODO
123 case DOM_VK_HELP: return kVK_HELP;
124 case DOM_VK_BACK_SPACE: return kVK_BACK;
125 case DOM_VK_TAB: return kVK_TAB;
126 case DOM_VK_CLEAR: return kVK_CLEAR;
127 case DOM_VK_RETURN: return kVK_RETURN;
128 case DOM_VK_ENTER: return kVK_RETURN;
129 case DOM_VK_SHIFT: return kVK_SHIFT;
130 case DOM_VK_CONTROL: return kVK_CONTROL;
131 case DOM_VK_ALT: return kVK_MENU;
132 case DOM_VK_PAUSE: return kVK_PAUSE;
133 case DOM_VK_CAPS_LOCK: return kVK_CAPITAL;
134 case DOM_VK_ESCAPE: return kVK_ESCAPE;
135// case DOM_VK_CONVERT: return 0; // TODO
136// case DOM_VK_NONCONVERT: return 0; // TODO
137// case DOM_VK_ACCEPT: return 0; // TODO
138// case DOM_VK_MODECHANGE: return 0; // TODO
139 case DOM_VK_SPACE: return kVK_SPACE;
140 case DOM_VK_PAGE_UP: return kVK_PRIOR;
141 case DOM_VK_PAGE_DOWN: return kVK_NEXT;
142 case DOM_VK_END: return kVK_END;
143 case DOM_VK_HOME: return kVK_HOME;
144 case DOM_VK_LEFT: return kVK_LEFT;
145 case DOM_VK_UP: return kVK_UP;
146 case DOM_VK_RIGHT: return kVK_RIGHT;
147 case DOM_VK_DOWN: return kVK_DOWN;
148// case DOM_VK_SELECT: return 0; // TODO
149// case DOM_VK_PRINT: return 0; // TODO
150// case DOM_VK_EXECUTE: return 0; // TODO
151// case DOM_VK_PRINTSCREEN: return 0; // TODO
152 case DOM_VK_INSERT: return kVK_INSERT;
153 case DOM_VK_DELETE: return kVK_DELETE;
154 case DOM_VK_0: return kVK_0;
155 case DOM_VK_1: return kVK_1;
156 case DOM_VK_2: return kVK_2;
157 case DOM_VK_3: return kVK_3;
158 case DOM_VK_4: return kVK_4;
159 case DOM_VK_5: return kVK_5;
160 case DOM_VK_6: return kVK_6;
161 case DOM_VK_7: return kVK_7;
162 case DOM_VK_8: return kVK_8;
163 case DOM_VK_9: return kVK_9;
164// case DOM_VK_COLON: return 0; // TODO
165// case DOM_VK_SEMICOLON: return 0; // TODO
166// case DOM_VK_LESS_THAN: return 0; // TODO
167// case DOM_VK_EQUALS: return 0; // TODO
168// case DOM_VK_GREATER_THAN: return 0; // TODO
169// case DOM_VK_QUESTION_MARK: return 0; // TODO
170// case DOM_VK_AT: return 0; // TODO
171 case DOM_VK_A: return kVK_A;
172 case DOM_VK_B: return kVK_B;
173 case DOM_VK_C: return kVK_C;
174 case DOM_VK_D: return kVK_D;
175 case DOM_VK_E: return kVK_E;
176 case DOM_VK_F: return kVK_F;
177 case DOM_VK_G: return kVK_G;
178 case DOM_VK_H: return kVK_H;
179 case DOM_VK_I: return kVK_I;
180 case DOM_VK_J: return kVK_J;
181 case DOM_VK_K: return kVK_K;
182 case DOM_VK_L: return kVK_L;
183 case DOM_VK_M: return kVK_M;
184 case DOM_VK_N: return kVK_N;
185 case DOM_VK_O: return kVK_O;
186 case DOM_VK_P: return kVK_P;
187 case DOM_VK_Q: return kVK_Q;
188 case DOM_VK_R: return kVK_R;
189 case DOM_VK_S: return kVK_S;
190 case DOM_VK_T: return kVK_T;
191 case DOM_VK_U: return kVK_U;
192 case DOM_VK_V: return kVK_V;
193 case DOM_VK_W: return kVK_W;
194 case DOM_VK_X: return kVK_X;
195 case DOM_VK_Y: return kVK_Y;
196 case DOM_VK_Z: return kVK_Z;
197// case DOM_VK_WIN: return 0; // TODO
198// case DOM_VK_CONTEXT_MENU: return 0; // TODO
199// case DOM_VK_SLEEP: return 0; // TODO
200 case DOM_VK_NUMPAD0: return kVK_NUMPAD0;
201 case DOM_VK_NUMPAD1: return kVK_NUMPAD1;
202 case DOM_VK_NUMPAD2: return kVK_NUMPAD2;
203 case DOM_VK_NUMPAD3: return kVK_NUMPAD3;
204 case DOM_VK_NUMPAD4: return kVK_NUMPAD4;
205 case DOM_VK_NUMPAD5: return kVK_NUMPAD5;
206 case DOM_VK_NUMPAD6: return kVK_NUMPAD6;
207 case DOM_VK_NUMPAD7: return kVK_NUMPAD7;
208 case DOM_VK_NUMPAD8: return kVK_NUMPAD8;
209 case DOM_VK_NUMPAD9: return kVK_NUMPAD9;
210 case DOM_VK_MULTIPLY: return kVK_MULTIPLY;
211 case DOM_VK_ADD: return kVK_ADD;
212 case DOM_VK_SEPARATOR: return kVK_SEPARATOR;
213 case DOM_VK_SUBTRACT: return kVK_SUBTRACT;
214 case DOM_VK_DECIMAL: return kVK_DECIMAL;
215 case DOM_VK_DIVIDE: return kVK_DIVIDE;
216 case DOM_VK_F1: return kVK_F1;
217 case DOM_VK_F2: return kVK_F2;
218 case DOM_VK_F3: return kVK_F3;
219 case DOM_VK_F4: return kVK_F4;
220 case DOM_VK_F5: return kVK_F5;
221 case DOM_VK_F6: return kVK_F6;
222 case DOM_VK_F7: return kVK_F7;
223 case DOM_VK_F8: return kVK_F8;
224 case DOM_VK_F9: return kVK_F9;
225 case DOM_VK_F10: return kVK_F10;
226 case DOM_VK_F11: return kVK_F11;
227 case DOM_VK_F12: return kVK_F12;
228 case DOM_VK_F13: return kVK_F13;
229 case DOM_VK_F14: return kVK_F14;
230 case DOM_VK_F15: return kVK_F15;
231 case DOM_VK_F16: return kVK_F16;
232 case DOM_VK_F17: return kVK_F17;
233 case DOM_VK_F18: return kVK_F18;
234 case DOM_VK_F19: return kVK_F19;
235 case DOM_VK_F20: return kVK_F20;
236 case DOM_VK_F21: return kVK_F21;
237 case DOM_VK_F22: return kVK_F22;
238 case DOM_VK_F23: return kVK_F23;
239 case DOM_VK_F24: return kVK_F24;
240 case DOM_VK_NUM_LOCK: return kVK_NUMLOCK;
241 case DOM_VK_SCROLL_LOCK: return kVK_SCROLL;
242// case DOM_VK_WIN_OEM_FJ_JISHO: return 0; // TODO
243// case DOM_VK_WIN_OEM_FJ_MASSHOU: return 0; // TODO
244// case DOM_VK_WIN_OEM_FJ_TOUROKU: return 0; // TODO
245// case DOM_VK_WIN_OEM_FJ_LOYA: return 0; // TODO
246// case DOM_VK_WIN_OEM_FJ_ROYA: return 0; // TODO
247// case DOM_VK_CIRCUMFLEX: return 0; // TODO
248// case DOM_VK_EXCLAMATION: return 0; // TODO
249// case DOM_VK_HASH: return 0; // TODO
250// case DOM_VK_DOLLAR: return 0; // TODO
251// case DOM_VK_PERCENT: return 0; // TODO
252// case DOM_VK_AMPERSAND: return 0; // TODO
253// case DOM_VK_UNDERSCORE: return 0; // TODO
254// case DOM_VK_OPEN_PAREN: return 0; // TODO
255// case DOM_VK_CLOSE_PAREN: return 0; // TODO
256// case DOM_VK_ASTERISK: return 0; // TODO
257// case DOM_VK_PLUS: return 0; // TODO
258// case DOM_VK_PIPE: return 0; // TODO
259// case DOM_VK_HYPHEN_MINUS: return 0; // TODO
260// case DOM_VK_OPEN_CURLY_BRACKET: return 0; // TODO
261// case DOM_VK_CLOSE_CURLY_BRACKET: return 0; // TODO
262// case DOM_VK_TILDE: return 0; // TODO
263// case DOM_VK_VOLUME_MUTE: return 0; // TODO
264// case DOM_VK_VOLUME_DOWN: return 0; // TODO
265// case DOM_VK_VOLUME_UP: return 0; // TODO
266// case DOM_VK_COMMA: return 0; // TODO
267// case DOM_VK_PERIOD: return 0; // TODO
268// case DOM_VK_SLASH: return 0; // TODO
269// case DOM_VK_BACK_QUOTE: return 0; // TODO
270// case DOM_VK_OPEN_BRACKET: return 0; // TODO
271// case DOM_VK_BACK_SLASH: return 0; // TODO
272// case DOM_VK_CLOSE_BRACKET: return 0; // TODO
273// case DOM_VK_QUOTE: return 0; // TODO
274// case DOM_VK_META: return 0; // TODO
275// case DOM_VK_ALTGR: return 0; // TODO
276// case DOM_VK_WIN_ICO_HELP: return 0; // TODO
277// case DOM_VK_WIN_ICO_00: return 0; // TODO
278// case DOM_VK_WIN_ICO_CLEAR: return 0; // TODO
279// case DOM_VK_WIN_OEM_RESET: return 0; // TODO
280// case DOM_VK_WIN_OEM_JUMP: return 0; // TODO
281// case DOM_VK_WIN_OEM_PA1: return 0; // TODO
282// case DOM_VK_WIN_OEM_PA2: return 0; // TODO
283// case DOM_VK_WIN_OEM_PA3: return 0; // TODO
284// case DOM_VK_WIN_OEM_WSCTRL: return 0; // TODO
285// case DOM_VK_WIN_OEM_CUSEL: return 0; // TODO
286// case DOM_VK_WIN_OEM_ATTN: return 0; // TODO
287// case DOM_VK_WIN_OEM_FINISH: return 0; // TODO
288// case DOM_VK_WIN_OEM_COPY: return 0; // TODO
289// case DOM_VK_WIN_OEM_AUTO: return 0; // TODO
290// case DOM_VK_WIN_OEM_ENLW: return 0; // TODO
291// case DOM_VK_WIN_OEM_BACKTAB: return 0; // TODO
292// case DOM_VK_ATTN: return 0; // TODO
293// case DOM_VK_CRSEL: return 0; // TODO
294// case DOM_VK_EXSEL: return 0; // TODO
295// case DOM_VK_EREOF: return 0; // TODO
296// case DOM_VK_PLAY: return 0; // TODO
297// case DOM_VK_ZOOM: return 0; // TODO
298// case DOM_VK_PA1: return 0; // TODO
299// case DOM_VK_WIN_OEM_CLEAR: return 0; // TODO
300 default: return kVK_NONE;
301 }
302}
303
304static EM_BOOL key_callback(int eventType, const EmscriptenKeyboardEvent* pEvent, void* pUserData)
305{
306 IGraphicsWeb* pGraphicsWeb = (IGraphicsWeb*) pUserData;
307
308 int VK = domVKToWinVK(pEvent->keyCode);
309 WDL_String keyUTF8;
310
311 // filter utf8 for non ascii keys
312 if((VK >= kVK_0 && VK <= kVK_Z) || VK == kVK_NONE)
313 keyUTF8.Set(pEvent->key);
314 else
315 keyUTF8.Set("");
316
317 IKeyPress keyPress {keyUTF8.Get(),
318 domVKToWinVK(pEvent->keyCode),
319 static_cast<bool>(pEvent->shiftKey),
320 static_cast<bool>(pEvent->ctrlKey || pEvent->metaKey),
321 static_cast<bool>(pEvent->altKey)};
322
323 switch (eventType)
324 {
325 case EMSCRIPTEN_EVENT_KEYDOWN:
326 {
327 return pGraphicsWeb->OnKeyDown(pGraphicsWeb->mPrevX, pGraphicsWeb->mPrevY, keyPress);
328 }
329 case EMSCRIPTEN_EVENT_KEYUP:
330 {
331 return pGraphicsWeb->OnKeyUp(pGraphicsWeb->mPrevX, pGraphicsWeb->mPrevY, keyPress);
332 }
333 default:
334 break;
335 }
336
337 return 0;
338}
339
340static EM_BOOL outside_mouse_callback(int eventType, const EmscriptenMouseEvent* pEvent, void* pUserData)
341{
342 IGraphicsWeb* pGraphics = (IGraphicsWeb*) pUserData;
343
344 IMouseInfo info;
345 val rect = GetCanvas().call<val>("getBoundingClientRect");
346 info.x = (pEvent->targetX - rect["left"].as<double>()) / pGraphics->GetDrawScale();
347 info.y = (pEvent->targetY - rect["top"].as<double>()) / pGraphics->GetDrawScale();
348 info.dX = pEvent->movementX;
349 info.dY = pEvent->movementY;
350 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)};
351 std::vector<IMouseInfo> list {info};
352
353 switch (eventType)
354 {
355 case EMSCRIPTEN_EVENT_MOUSEUP:
356 {
357 // Get button states based on what caused the mouse up (nothing in buttons)
358 list[0].ms.L = pEvent->button == 0;
359 list[0].ms.R = pEvent->button == 2;
360 pGraphics->OnMouseUp(list);
361 emscripten_set_mousemove_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, pGraphics, 1, nullptr);
362 emscripten_set_mouseup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, pGraphics, 1, nullptr);
363 break;
364 }
365 case EMSCRIPTEN_EVENT_MOUSEMOVE:
366 {
367 if(pEvent->buttons != 0 && !pGraphics->IsInPlatformTextEntry())
368 pGraphics->OnMouseDrag(list);
369 break;
370 }
371 default:
372 break;
373 }
374
375 pGraphics->mPrevX = info.x;
376 pGraphics->mPrevY = info.y;
377
378 return true;
379}
380
381static EM_BOOL mouse_callback(int eventType, const EmscriptenMouseEvent* pEvent, void* pUserData)
382{
383 IGraphicsWeb* pGraphics = (IGraphicsWeb*) pUserData;
384
385 IMouseInfo info;
386 info.x = pEvent->targetX / pGraphics->GetDrawScale();
387 info.y = pEvent->targetY / pGraphics->GetDrawScale();
388 info.dX = pEvent->movementX;
389 info.dY = pEvent->movementY;
390 info.ms = {(pEvent->buttons & 1) != 0,
391 (pEvent->buttons & 2) != 0,
392 static_cast<bool>(pEvent->shiftKey),
393 static_cast<bool>(pEvent->ctrlKey),
394 static_cast<bool>(pEvent->altKey)};
395
396 std::vector<IMouseInfo> list {info};
397 switch (eventType)
398 {
399 case EMSCRIPTEN_EVENT_MOUSEDOWN:
400 {
401 const double timestamp = GetTimestamp();
402 const double timeDiff = timestamp - gPrevMouseDownTime;
403
404 if (gFirstClick && timeDiff < 0.3)
405 {
406 gFirstClick = false;
407 pGraphics->OnMouseDblClick(info.x, info.y, info.ms);
408 }
409 else
410 {
411 gFirstClick = true;
412 pGraphics->OnMouseDown(list);
413 }
414
415 gPrevMouseDownTime = timestamp;
416
417 break;
418 }
419 case EMSCRIPTEN_EVENT_MOUSEUP:
420 {
421 // Get button states based on what caused the mouse up (nothing in buttons)
422 list[0].ms.L = pEvent->button == 0;
423 list[0].ms.R = pEvent->button == 2;
424 pGraphics->OnMouseUp(list);
425 break;
426 }
427 case EMSCRIPTEN_EVENT_MOUSEMOVE:
428 {
429 gFirstClick = false;
430
431 if(pEvent->buttons == 0)
432 pGraphics->OnMouseOver(info.x, info.y, info.ms);
433 else
434 {
435 if(!pGraphics->IsInPlatformTextEntry())
436 pGraphics->OnMouseDrag(list);
437 }
438 break;
439 }
440 case EMSCRIPTEN_EVENT_MOUSEENTER:
441 pGraphics->OnSetCursor();
442 pGraphics->OnMouseOver(info.x, info.y, info.ms);
443 emscripten_set_mousemove_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, pGraphics, 1, nullptr);
444 break;
445 case EMSCRIPTEN_EVENT_MOUSELEAVE:
446 if(pEvent->buttons != 0)
447 {
448 emscripten_set_mousemove_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, pGraphics, 1, outside_mouse_callback);
449 emscripten_set_mouseup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, pGraphics, 1, outside_mouse_callback);
450 }
451 pGraphics->OnMouseOut(); break;
452 default:
453 break;
454 }
455
456 pGraphics->mPrevX = info.x;
457 pGraphics->mPrevY = info.y;
458
459 return true;
460}
461
462static EM_BOOL wheel_callback(int eventType, const EmscriptenWheelEvent* pEvent, void* pUserData)
463{
464 IGraphics* pGraphics = (IGraphics*) pUserData;
465
466 IMouseMod modifiers(false, false, pEvent->mouse.shiftKey, pEvent->mouse.ctrlKey, pEvent->mouse.altKey);
467
468 double x = pEvent->mouse.targetX;
469 double y = pEvent->mouse.targetY;
470
471 x /= pGraphics->GetDrawScale();
472 y /= pGraphics->GetDrawScale();
473
474 switch (eventType) {
475 case EMSCRIPTEN_EVENT_WHEEL: pGraphics->OnMouseWheel(x, y, modifiers, pEvent->deltaY);
476 default:
477 break;
478 }
479
480 return true;
481}
482
483EM_BOOL touch_callback(int eventType, const EmscriptenTouchEvent* pEvent, void* pUserData)
484{
485 IGraphics* pGraphics = (IGraphics*) pUserData;
486 const float drawScale = pGraphics->GetDrawScale();
487
488 std::vector<IMouseInfo> points;
489
490 static EmscriptenTouchPoint previousTouches[32];
491
492 for (auto i = 0; i < pEvent->numTouches; i++)
493 {
494 IMouseInfo info;
495 info.x = pEvent->touches[i].targetX / drawScale;
496 info.y = pEvent->touches[i].targetY / drawScale;
497 info.dX = info.x - (previousTouches[i].targetX / drawScale);
498 info.dY = info.y - (previousTouches[i].targetY / drawScale);
499 info.ms = {true,
500 false,
501 static_cast<bool>(pEvent->shiftKey),
502 static_cast<bool>(pEvent->ctrlKey),
503 static_cast<bool>(pEvent->altKey),
504 static_cast<ITouchID>(pEvent->touches[i].identifier)
505 };
506
507 if(pEvent->touches[i].isChanged)
508 points.push_back(info);
509 }
510
511 memcpy(previousTouches, pEvent->touches, sizeof(previousTouches));
512
513 switch (eventType)
514 {
515 case EMSCRIPTEN_EVENT_TOUCHSTART:
516 pGraphics->OnMouseDown(points);
517 return true;
518 case EMSCRIPTEN_EVENT_TOUCHEND:
519 pGraphics->OnMouseUp(points);
520 return true;
521 case EMSCRIPTEN_EVENT_TOUCHMOVE:
522 pGraphics->OnMouseDrag(points);
523 return true;
524 case EMSCRIPTEN_EVENT_TOUCHCANCEL:
525 pGraphics->OnTouchCancelled(points);
526 return true;
527 default:
528 return false;
529 }
530}
531
532static EM_BOOL complete_text_entry(int eventType, const EmscriptenFocusEvent* focusEvent, void* pUserData)
533{
534 IGraphicsWeb* pGraphics = (IGraphicsWeb*) pUserData;
535
536 val input = val::global("document").call<val>("getElementById", std::string("textEntry"));
537 std::string str = input["value"].as<std::string>();
538 val::global("document")["body"].call<void>("removeChild", input);
539 pGraphics->SetControlValueAfterTextEdit(str.c_str());
540
541 return true;
542}
543
544static EM_BOOL text_entry_keydown(int eventType, const EmscriptenKeyboardEvent* pEvent, void* pUserData)
545{
546 IGraphicsWeb* pGraphicsWeb = (IGraphicsWeb*) pUserData;
547
548 IKeyPress keyPress {pEvent->key, domVKToWinVK(pEvent->keyCode),
549 static_cast<bool>(pEvent->shiftKey),
550 static_cast<bool>(pEvent->ctrlKey),
551 static_cast<bool>(pEvent->altKey)};
552
553 if (keyPress.VK == kVK_RETURN || keyPress.VK == kVK_TAB)
554 return complete_text_entry(0, nullptr, pUserData);
555
556 return false;
557}
558
559static EM_BOOL uievent_callback(int eventType, const EmscriptenUiEvent* pEvent, void* pUserData)
560{
561 IGraphicsWeb* pGraphics = (IGraphicsWeb*) pUserData;
562
563 if (eventType == EMSCRIPTEN_EVENT_RESIZE)
564 {
565 pGraphics->GetDelegate()->OnParentWindowResize(pEvent->windowInnerWidth, pEvent->windowInnerHeight);
566
567 return true;
568 }
569
570 return false;
571}
572
573IColorPickerHandlerFunc gColorPickerHandlerFunc = nullptr;
574
575static void color_picker_callback(val e)
576{
577 if(gColorPickerHandlerFunc)
578 {
579 std::string colorStrHex = e["target"]["value"].as<std::string>();
580
581 if (colorStrHex[0] == '#')
582 colorStrHex = colorStrHex.erase(0, 1);
583
584 IColor result;
585 result.A = 255;
586 sscanf(colorStrHex.c_str(), "%02x%02x%02x", &result.R, &result.G, &result.B);
587
588 gColorPickerHandlerFunc(result);
589 }
590}
591
592static void file_dialog_callback(val e)
593{
594 // DBGMSG(e["files"].as<std::string>().c_str());
595}
596
597EMSCRIPTEN_BINDINGS(events) {
598 function("color_picker_callback", color_picker_callback);
599 function("file_dialog_callback", file_dialog_callback);
600}
601
602#pragma mark -
603
604IGraphicsWeb::IGraphicsWeb(IGEditorDelegate& dlg, int w, int h, int fps, float scale)
605: IGRAPHICS_DRAW_CLASS(dlg, w, h, fps, scale)
606{
607 val keys = val::global("Object").call<val>("keys", GetPreloadedImages());
608
609 DBGMSG("Preloaded %i images\n", keys["length"].as<int>());
610
611 emscripten_set_mousedown_callback("#canvas", this, 1, mouse_callback);
612 emscripten_set_mouseup_callback("#canvas", this, 1, mouse_callback);
613 emscripten_set_mousemove_callback("#canvas", this, 1, mouse_callback);
614 emscripten_set_mouseenter_callback("#canvas", this, 1, mouse_callback);
615 emscripten_set_mouseleave_callback("#canvas", this, 1, mouse_callback);
616 emscripten_set_wheel_callback("#canvas", this, 1, wheel_callback);
617 emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, this, 1, key_callback);
618 emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, this, 1, key_callback);
619 emscripten_set_touchstart_callback("#canvas", this, 1, touch_callback);
620 emscripten_set_touchend_callback("#canvas", this, 1, touch_callback);
621 emscripten_set_touchmove_callback("#canvas", this, 1, touch_callback);
622 emscripten_set_touchcancel_callback("#canvas", this, 1, touch_callback);
623 emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, this, 1, uievent_callback);
624}
625
626IGraphicsWeb::~IGraphicsWeb()
627{
628}
629
630void* IGraphicsWeb::OpenWindow(void* pHandle)
631{
632#ifdef IGRAPHICS_GL
633 EmscriptenWebGLContextAttributes attr;
634 emscripten_webgl_init_context_attributes(&attr);
635 attr.stencil = true;
636 attr.depth = true;
637// attr.explicitSwapControl = 1;
638
639 EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_create_context("#canvas", &attr);
640 emscripten_webgl_make_context_current(ctx);
641#endif
642
643 OnViewInitialized(nullptr /* not used */);
644
645 SetScreenScale(std::ceil(std::max(emscripten_get_device_pixel_ratio(), 1.)));
646
647 GetDelegate()->LayoutUI(this);
648 GetDelegate()->OnUIOpen();
649
650 return nullptr;
651}
652
653void IGraphicsWeb::HideMouseCursor(bool hide, bool lock)
654{
655 if (mCursorHidden == hide)
656 return;
657
658 if (hide)
659 {
660#ifdef IGRAPHICS_WEB_POINTERLOCK
661 if (lock)
662 emscripten_request_pointerlock("#canvas", EM_FALSE);
663 else
664#endif
665 val::global("document")["body"]["style"].set("cursor", "none");
666
667 mCursorHidden = true;
668 mCursorLock = lock;
669 }
670 else
671 {
672#ifdef IGRAPHICS_WEB_POINTERLOCK
673 if (mCursorLock)
674 emscripten_exit_pointerlock();
675 else
676#endif
677 OnSetCursor();
678
679 mCursorHidden = false;
680 mCursorLock = false;
681 }
682}
683
684ECursor IGraphicsWeb::SetMouseCursor(ECursor cursorType)
685{
686 std::string cursor("pointer");
687
688 switch (cursorType)
689 {
690 case ECursor::ARROW: cursor = "default"; break;
691 case ECursor::IBEAM: cursor = "text"; break;
692 case ECursor::WAIT: cursor = "wait"; break;
693 case ECursor::CROSS: cursor = "crosshair"; break;
694 case ECursor::UPARROW: cursor = "n-resize"; break;
695 case ECursor::SIZENWSE: cursor = "nwse-resize"; break;
696 case ECursor::SIZENESW: cursor = "nesw-resize"; break;
697 case ECursor::SIZEWE: cursor = "ew-resize"; break;
698 case ECursor::SIZENS: cursor = "ns-resize"; break;
699 case ECursor::SIZEALL: cursor = "move"; break;
700 case ECursor::INO: cursor = "not-allowed"; break;
701 case ECursor::HAND: cursor = "pointer"; break;
702 case ECursor::APPSTARTING: cursor = "progress"; break;
703 case ECursor::HELP: cursor = "help"; break;
704 }
705
706 val::global("document")["body"]["style"].set("cursor", cursor);
707 return IGraphics::SetMouseCursor(cursorType);
708}
709
710void IGraphicsWeb::GetMouseLocation(float& x, float&y) const
711{
712 x = mPrevX;
713 y = mPrevY;
714}
715
716//static
717void IGraphicsWeb::OnMainLoopTimer()
718{
719 IRECTList rects;
720 int screenScale = (int) std::ceil(std::max(emscripten_get_device_pixel_ratio(), 1.));
721
722 // Don't draw if there are no graphics or if assets are still loading
723 if (!gGraphics || !gGraphics->AssetsLoaded())
724 return;
725
726 if (screenScale != gGraphics->GetScreenScale())
727 {
728 gGraphics->SetScreenScale(screenScale);
729 }
730
731 if (gGraphics->IsDirty(rects))
732 {
733 gGraphics->SetAllControlsClean();
734 gGraphics->Draw(rects);
735 }
736}
737
738EMsgBoxResult IGraphicsWeb::ShowMessageBox(const char* str, const char* /*title*/, EMsgBoxType type, IMsgBoxCompletionHandlerFunc completionHandler)
739{
740 ReleaseMouseCapture();
741
742 EMsgBoxResult result = kNoResult;
743
744 switch (type)
745 {
746 case kMB_OK:
747 {
748 val::global("window").call<val>("alert", std::string(str));
749 result = EMsgBoxResult::kOK;
750 break;
751 }
752 case kMB_YESNO:
753 case kMB_OKCANCEL:
754 {
755 result = static_cast<EMsgBoxResult>(val::global("window").call<val>("confirm", std::string(str)).as<int>());
756 }
757 // case MB_CANCEL:
758 // break;
759 default:
760 return result = kNoResult;
761 }
762
763 if(completionHandler)
764 completionHandler(result);
765
766 return result;
767}
768
769void IGraphicsWeb::PromptForFile(WDL_String& filename, WDL_String& path, EFileAction action, const char* ext, IFileDialogCompletionHandlerFunc completionHandler)
770{
771 //TODO
772 // val inputEl = val::global("document").call<val>("createElement", std::string("input"));
773
774 // inputEl.call<void>("setAttribute", std::string("type"), std::string("file"));
775 // inputEl.call<void>("setAttribute", std::string("accept"), std::string(ext));
776 // inputEl.call<void>("click");
777 // inputEl.call<void>("addEventListener", std::string("input"), val::module_property("file_dialog_callback"), false);
778 // inputEl.call<void>("addEventListener", std::string("onChange"), val::module_property("file_dialog_callback"), false);
779}
780
781void IGraphicsWeb::PromptForDirectory(WDL_String& path, IFileDialogCompletionHandlerFunc completionHandler)
782{
783 //TODO
784 // val inputEl = val::global("document").call<val>("createElement", std::string("input"));
785
786 // inputEl.call<void>("setAttribute", std::string("type"), std::string("file"));
787 // inputEl.call<void>("setAttribute", std::string("directory"), true);
788 // inputEl.call<void>("setAttribute", std::string("webkitdirectory"), true);
789 // inputEl.call<void>("click");
790 // inputEl.call<void>("addEventListener", std::string("input"), val::module_property("file_dialog_callback"), false);
791 // inputEl.call<void>("addEventListener", std::string("onChange"), val::module_property("file_dialog_callback"), false);
792}
793
794bool IGraphicsWeb::PromptForColor(IColor& color, const char* str, IColorPickerHandlerFunc func)
795{
796 ReleaseMouseCapture();
797
798 gColorPickerHandlerFunc = func;
799
800 val inputEl = val::global("document").call<val>("createElement", std::string("input"));
801 inputEl.call<void>("setAttribute", std::string("type"), std::string("color"));
802 WDL_String colorStr;
803 colorStr.SetFormatted(64, "#%02x%02x%02x", color.R, color.G, color.B);
804 inputEl.call<void>("setAttribute", std::string("value"), std::string(colorStr.Get()));
805 inputEl.call<void>("click");
806 inputEl.call<void>("addEventListener", std::string("input"), val::module_property("color_picker_callback"), false);
807 inputEl.call<void>("addEventListener", std::string("onChange"), val::module_property("color_picker_callback"), false);
808
809 return false;
810}
811
812void IGraphicsWeb::CreatePlatformTextEntry(int paramIdx, const IText& text, const IRECT& bounds, int length, const char* str)
813{
814 val input = val::global("document").call<val>("createElement", std::string("input"));
815 val rect = GetCanvas().call<val>("getBoundingClientRect");
816
817 auto setDim = [&input](const char *dimName, double pixels)
818 {
819 WDL_String dimstr;
820 dimstr.SetFormatted(32, "%fpx", pixels);
821 input["style"].set(dimName, std::string(dimstr.Get()));
822 };
823
824 auto setColor = [&input](const char *colorName, IColor color)
825 {
826 WDL_String str;
827 str.SetFormatted(64, "rgba(%d, %d, %d, %d)", color.R, color.G, color.B, color.A);
828 input["style"].set(colorName, std::string(str.Get()));
829 };
830
831 input.set("id", std::string("textEntry"));
832 input["style"].set("position", val("fixed"));
833 setDim("left", rect["left"].as<double>() + bounds.L);
834 setDim("top", rect["top"].as<double>() + bounds.T);
835 setDim("width", bounds.W());
836 setDim("height", bounds.H());
837
838 setColor("color", text.mTextEntryFGColor);
839 setColor("background-color", text.mTextEntryBGColor);
840 if (paramIdx > kNoParameter)
841 {
842 const IParam* pParam = GetDelegate()->GetParam(paramIdx);
843
844 switch (pParam->Type())
845 {
846 case IParam::kTypeEnum:
847 case IParam::kTypeInt:
848 case IParam::kTypeBool:
849 input.set("type", val("number")); // TODO
850 break;
851 case IParam::kTypeDouble:
852 input.set("type", val("number"));
853 break;
854 default:
855 break;
856 }
857 }
858 else
859 {
860 input.set("type", val("text"));
861 }
862
863 val::global("document")["body"].call<void>("appendChild", input);
864 input.call<void>("focus");
865 emscripten_set_focusout_callback("textEntry", this, 1, complete_text_entry);
866 emscripten_set_keydown_callback("textEntry", this, 1, text_entry_keydown);
867}
868
869IPopupMenu* IGraphicsWeb::CreatePlatformPopupMenu(IPopupMenu& menu, const IRECT bounds, bool& isAsync)
870{
871 return nullptr;
872}
873
874bool IGraphicsWeb::OpenURL(const char* url, const char* msgWindowTitle, const char* confirmMsg, const char* errMsgOnFailure)
875{
876 val::global("window").call<val>("open", std::string(url), std::string("_blank"));
877
878 return true;
879}
880
881void IGraphicsWeb::DrawResize()
882{
883 val canvas = GetCanvas();
884
885 canvas["style"].set("width", val(Width() * GetDrawScale()));
886 canvas["style"].set("height", val(Height() * GetDrawScale()));
887
888 canvas.set("width", Width() * GetBackingPixelScale());
889 canvas.set("height", Height() * GetBackingPixelScale());
890
891 IGRAPHICS_DRAW_CLASS::DrawResize();
892}
893
894PlatformFontPtr IGraphicsWeb::LoadPlatformFont(const char* fontID, const char* fileNameOrResID)
895{
896 WDL_String fullPath;
897 const EResourceLocation fontLocation = LocateResource(fileNameOrResID, "ttf", fullPath, GetBundleID(), nullptr, nullptr);
898
899 if (fontLocation == kNotFound)
900 return nullptr;
901
902 return PlatformFontPtr(new FileFont(fontID, "", fullPath.Get()));
903}
904
905PlatformFontPtr IGraphicsWeb::LoadPlatformFont(const char* fontID, const char* fontName, ETextStyle style)
906{
907 const char* styles[] = { "normal", "bold", "italic" };
908
909 return PlatformFontPtr(new Font(fontName, styles[static_cast<int>(style)]));
910}
911
912PlatformFontPtr IGraphicsWeb::LoadPlatformFont(const char* fontID, void* pData, int dataSize)
913{
914 return PlatformFontPtr(new MemoryFont(fontID, "", pData, dataSize));
915}
916
917#if defined IGRAPHICS_CANVAS
918#include "IGraphicsCanvas.cpp"
919#elif defined IGRAPHICS_NANOVG
920#include "IGraphicsNanoVG.cpp"
921
922#ifdef IGRAPHICS_FREETYPE
923#define FONS_USE_FREETYPE
924#endif
925
926#include "nanovg.c"
927#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 for a SOMETHING that uses IGraphics for it's 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:1155
void OnMouseUp(const std::vector< IMouseInfo > &points)
Called when the platform class sends mouse up events.
Definition: IGraphics.cpp:1057
void OnTouchCancelled(const std::vector< IMouseInfo > &points)
Called when the platform class sends touch cancel events.
Definition: IGraphics.cpp:1098
void OnMouseDown(const std::vector< IMouseInfo > &points)
Called when the platform class sends mouse down events.
Definition: IGraphics.cpp:971
void OnMouseWheel(float x, float y, const IMouseMod &mod, float delta)
Definition: IGraphics.cpp:1223
float GetDrawScale() const
Gets the graphics context scaling factor.
Definition: IGraphics.h:1101
IGraphics platform class for the web.
Definition: IGraphicsWeb.h:44
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.
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.
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,...