iPlug2 - C++ Audio Plug-in Framework
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 
18 BEGIN_IPLUG_NAMESPACE
19 BEGIN_IGRAPHICS_NAMESPACE
20 
21 void GetScreenDimensions(int& width, int& height)
22 {
23  width = val::global("window")["innerWidth"].as<int>();
24  height = val::global("window")["innerHeight"].as<int>();
25 }
26 
27 END_IPLUG_NAMESPACE
28 END_IGRAPHICS_NAMESPACE
29 
30 using namespace iplug;
31 using namespace igraphics;
32 using namespace emscripten;
33 
34 extern IGraphicsWeb* gGraphics;
35 double gPrevMouseDownTime = 0.;
36 bool gFirstClick = false;
37 
38 #pragma mark - Private Classes and Structs
39 
40 // Fonts
41 
42 class IGraphicsWeb::Font : public PlatformFont
43 {
44 public:
45  Font(const char* fontName, const char* fontStyle)
46  : PlatformFont(true), mDescriptor{fontName, fontStyle}
47  {}
48 
49  FontDescriptor GetDescriptor() override { return &mDescriptor; }
50 
51 private:
52  std::pair<WDL_String, WDL_String> mDescriptor;
53 };
54 
55 class IGraphicsWeb::FileFont : public Font
56 {
57 public:
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 
66 private:
67  WDL_String mPath;
68 };
69 
70 IFontDataPtr 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 
95 class IGraphicsWeb::MemoryFont : public Font
96 {
97 public:
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 
110 private:
111  WDL_TypedBuf<uint8_t> mData;
112 };
113 
114 #pragma mark - Utilities and Callbacks
115 
116 // Key combos
117 
118 static 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 
304 static 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 
340 static 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 
381 static 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 
462 static 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 
483 EM_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 
532 static 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 
544 static 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 
559 static 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 
573 IColorPickerHandlerFunc gColorPickerHandlerFunc = nullptr;
574 
575 static 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 
592 static void file_dialog_callback(val e)
593 {
594  // DBGMSG(e["files"].as<std::string>().c_str());
595 }
596 
597 EMSCRIPTEN_BINDINGS(events) {
598  function("color_picker_callback", color_picker_callback);
599  function("file_dialog_callback", file_dialog_callback);
600 }
601 
602 #pragma mark -
603 
604 IGraphicsWeb::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 
626 IGraphicsWeb::~IGraphicsWeb()
627 {
628 }
629 
630 void* 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 
653 void 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 
684 ECursor 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 
710 void IGraphicsWeb::GetMouseLocation(float& x, float&y) const
711 {
712  x = mPrevX;
713  y = mPrevY;
714 }
715 
716 //static
717 void 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 
738 EMsgBoxResult IGraphicsWeb::ShowMessageBox(const char* str, const char* caption, EMsgBoxType type, IMsgBoxCompletionHanderFunc 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 
769 void IGraphicsWeb::PromptForFile(WDL_String& filename, WDL_String& path, EFileAction action, const char* ext)
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 
781 void IGraphicsWeb::PromptForDirectory(WDL_String& path)
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 
794 bool 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 
812 void 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 
869 IPopupMenu* IGraphicsWeb::CreatePlatformPopupMenu(IPopupMenu& menu, const IRECT& bounds, bool& isAsync)
870 {
871  return nullptr;
872 }
873 
874 bool 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 
881 void 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 
894 PlatformFontPtr 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 
905 PlatformFontPtr 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 
912 PlatformFontPtr 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
Used to manage a list of rectangular areas and optimize them for drawing to the screen.
void OnMouseDrag(const std::vector< IMouseInfo > &points)
Called when the platform class sends drag events.
Definition: IGraphics.cpp:1128
Used to manage a rectangular area, independent of draw class/platform.
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:805
Used to manage mouse modifiers i.e.
IPlug&#39;s parameter class.
void OnMouseUp(const std::vector< IMouseInfo > &points)
Called when the platform class sends mouse up events.
Definition: IGraphics.cpp:1014
Used to manage color data, independent of draw class/platform.
void OnMouseDown(const std::vector< IMouseInfo > &points)
Called when the platform class sends mouse down events.
Definition: IGraphics.cpp:913
float H() const
An editor delegate base class for a SOMETHING that uses IGraphics for it&#39;s UI.
float W() const
A class for setting the contents of a pop up menu.
IText is used to manage font and text/text entry style for a piece of text on the UI...
float GetDrawScale() const
Gets the graphics context scaling factor.
Definition: IGraphics.h:1064
Used to group mouse coordinates with mouse modifier information.
IGraphics platform class for the web.
Definition: IGraphicsWeb.h:43
void OnMouseWheel(float x, float y, const IMouseMod &mod, float delta)
Definition: IGraphics.cpp:1208
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&#39;s file name (e.g.
The lowest level base class of an IGraphics context.
Definition: IGraphics.h:86
void OnTouchCancelled(const std::vector< IMouseInfo > &points)
Called when the platform class sends touch cancel events.
Definition: IGraphics.cpp:1066
EParamType Type() const
Get the parameter&#39;s type.
float L
Left side of the rectangle (X)
Used for key press info, such as ASCII representation, virtual key (mapped to win32 codes) and modifi...
Definition: IPlugStructs.h:612
float T
Top of the rectangle (Y)