iPlug2 - C++ Audio Plug-in Framework
Loading...
Searching...
No Matches
IGraphicsLiveEdit.h
Go to the documentation of this file.
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#pragma once
12
18#if !defined(NDEBUG) || defined(IPLUG_LIVE_EDIT)
19
20#include "IControl.h"
21#include <cmath>
22#include <cstdio>
23#include <fstream>
24#include <string>
25#include <vector>
26
27#if defined(IPLUG_LIVE_EDIT_CLASS_NAME)
28#include <cstring>
29#include <typeinfo>
30#if defined(__GNUG__) && !defined(_WIN32)
31#include <cxxabi.h>
32#include <cstdlib>
33#endif
34#endif
35
36BEGIN_IPLUG_NAMESPACE
37BEGIN_IGRAPHICS_NAMESPACE
38
45{
46public:
47 IGraphicsLiveEdit(bool mouseOversEnabled)
48 : IControl(IRECT())
49 , mMouseOversEnabled(mouseOversEnabled)
50 , mGridSize(10)
51 {
52 mTargetRECT = mRECT;
53 }
54
56 {
57 GetUI()->EnableMouseOver(mMouseOversEnabled); // Set it back to what it was
58 }
59
60 void OnInit() override
61 {
62 GetUI()->EnableMouseOver(true);
63 }
64
65 void OnMouseDown(float x, float y, const IMouseMod& mod) override
66 {
67 int c = GetUI()->GetMouseControlIdx(x, y, true);
68
69 if (c > 0)
70 {
71 IControl* pControl = GetUI()->GetControl(c);
72 mMouseDownRECT = pControl->GetRECT();
73 mMouseDownTargetRECT = pControl->GetTargetRECT();
74
75 if (!mod.S)
76 mSelectedControls.Empty();
77
78 mSelectedControls.Add(pControl);
79
80 if (mod.A)
81 {
82 GetUI()->AttachControl(new PlaceHolder(mMouseDownRECT));
83 mClickedOnControl = GetUI()->NControls() - 1;
84 mMouseClickedOnResizeHandle = false;
85 EmitControlAdded(GetUI()->GetControl(mClickedOnControl));
86 }
87 else if (mod.R)
88 {
89 mClickedOnControl = c;
90 GetUI()->CreatePopupMenu(*this, mRightClickOnControlMenu, x, y);
91 }
92 else
93 {
94 mClickedOnControl = c;
95
96 if (GetHandleRect(mMouseDownRECT).Contains(x, y))
97 {
98 mMouseClickedOnResizeHandle = true;
99 }
100 }
101 }
102 else if (mod.R)
103 {
104 GetUI()->CreatePopupMenu(*this, mRightClickOutsideControlMenu, x, y);
105 }
106 else
107 {
108 mSelectedControls.Empty();
109 mDragRegion.L = mDragRegion.R = x;
110 mDragRegion.T = mDragRegion.B = y;
111 }
112
113 EmitSelectionChanged();
114 }
115
116 void OnMouseUp(float x, float y, const IMouseMod& mod) override
117 {
118 // Web popup menus are async, so keep the clicked control index alive
119 // until OnPopupMenuSelection receives either a selection or a cancel.
120 const bool waitingForControlPopup = mod.R && mClickedOnControl > 0;
121
122 if (mClickedOnControl > 0)
123 {
124 IControl* pControl = GetUI()->GetControl(mClickedOnControl);
125 IRECT r = pControl->GetRECT();
126
127 if (mMouseClickedOnResizeHandle)
128 {
129 float w = r.R - r.L;
130 float h = r.B - r.T;
131
132 if (w < 0.f || h < 0.f)
133 {
134 pControl->SetRECT(mMouseDownRECT);
135 pControl->SetTargetRECT(mMouseDownTargetRECT);
136 r = pControl->GetRECT();
137 }
138 }
139
140 if (r.L != mMouseDownRECT.L || r.T != mMouseDownRECT.T ||
141 r.R != mMouseDownRECT.R || r.B != mMouseDownRECT.B)
142 {
143 EmitControlChanged(pControl, mMouseDownRECT);
144 }
145 }
146
147 if (!waitingForControlPopup)
148 mClickedOnControl = -1;
149
150 mMouseClickedOnResizeHandle = false;
152
153 EmitSelectionChanged();
154
155 mDragRegion = IRECT();
156 }
157
158 void OnMouseDblClick(float x, float y, const IMouseMod& mod) override
159 {
160 }
161
162 void OnMouseOver(float x, float y, const IMouseMod& mod) override
163 {
164 int c = GetUI()->GetMouseControlIdx(x, y, true);
165 if (c > 0)
166 {
167 IRECT cr = GetUI()->GetControl(c)->GetRECT();
168 IRECT h = GetHandleRect(cr);
169
170 if (h.Contains(x, y))
171 {
172 GetUI()->SetMouseCursor(ECursor::SIZENWSE);
173 return;
174 }
175 else
176 GetUI()->SetMouseCursor(ECursor::HAND);
177 }
178 else
179 GetUI()->SetMouseCursor(ECursor::ARROW);
180 }
181
182 void OnMouseDrag(float x, float y, float dX, float dY, const IMouseMod& mod) override
183 {
184 float mouseDownX, mouseDownY;
185 GetUI()->GetMouseDownPoint(mouseDownX, mouseDownY);
186
187 if (mClickedOnControl > 0)
188 {
189 IControl* pControl = GetUI()->GetControl(mClickedOnControl);
190
191 if (mMouseClickedOnResizeHandle)
192 {
193 IRECT r = pControl->GetRECT();
194 r.R = SnapToGrid(mMouseDownRECT.R + (x - mouseDownX));
195 r.B = SnapToGrid(mMouseDownRECT.B + (y - mouseDownY));
196
197 if (r.R < mMouseDownRECT.L +mGridSize) r.R = mMouseDownRECT.L+mGridSize;
198 if (r.B < mMouseDownRECT.T +mGridSize) r.B = mMouseDownRECT.T+mGridSize;
199
200 GetUI()->SetControlSize(pControl, r.W(), r.H());
201 }
202 else
203 {
204 const float x1 = SnapToGrid(mMouseDownRECT.L + (x - mouseDownX));
205 const float y1 = SnapToGrid(mMouseDownRECT.T + (y - mouseDownY));
206
207 GetUI()->SetControlPosition(pControl, x1, y1);
208 }
209 }
210 else
211 {
212 float mouseDownX, mouseDownY;
213 GetUI()->GetMouseDownPoint(mouseDownX, mouseDownY);
214 mDragRegion.L = x < mouseDownX ? x : mouseDownX;
215 mDragRegion.R = x < mouseDownX ? mouseDownX : x;
216 mDragRegion.T = y < mouseDownY ? y : mouseDownY;
217 mDragRegion.B = y < mouseDownY ? mouseDownY : y;
218
219 GetUI()->ForStandardControlsFunc([&](IControl* pControl) {
220 if (mDragRegion.Contains(pControl->GetRECT())) {
221 if (mSelectedControls.FindR(pControl) == -1)
222 mSelectedControls.Add(pControl);
223 }
224 else {
225 int idx = mSelectedControls.FindR(pControl);
226 if (idx > -1)
227 mSelectedControls.Delete(idx);
228 }
229 });
230 }
231 }
232
233 bool OnKeyDown(float x, float y, const IKeyPress& key) override
234 {
236
237 if (key.VK == kVK_BACK || key.VK == kVK_DELETE)
238 {
239 if (mSelectedControls.GetSize())
240 {
241 std::vector<IControl*> snapshot;
242 snapshot.reserve(mSelectedControls.GetSize());
243 for (int i = 0; i < mSelectedControls.GetSize(); i++)
244 snapshot.push_back(mSelectedControls.Get(i));
245 EmitControlsDeleted(snapshot.data(), static_cast<int>(snapshot.size()));
246 for (int i = 0; i < mSelectedControls.GetSize(); i++)
247 {
248 IControl* pControl = mSelectedControls.Get(i);
249 GetUI()->RemoveControl(pControl);
250 }
251
252 mSelectedControls.Empty();
254
255 EmitSelectionChanged();
256
257 return true;
258 }
259 }
260
261 return false;
262 }
263
264 void OnPopupMenuSelection(IPopupMenu* pSelectedMenu, int valIdx) override
265 {
266 if (!pSelectedMenu)
267 {
268 mClickedOnControl = -1;
269 return;
270 }
271
272 if (pSelectedMenu && pSelectedMenu == &mRightClickOutsideControlMenu)
273 {
274 auto idx = pSelectedMenu->GetChosenItemIdx();
275 float x, y;
276 GetUI()->GetMouseDownPoint(x, y);
277 IRECT b = IRECT(x, y, x + 100.f, y + 100.f);
278
279 switch(idx)
280 {
281 case 0:
282 {
284 EmitControlAdded(GetUI()->GetControl(GetUI()->NControls() - 1));
285 break;
286 }
287 default:
288 break;
289 }
290 }
291
292 if (pSelectedMenu && pSelectedMenu == &mRightClickOnControlMenu)
293 {
294 auto idx = pSelectedMenu->GetChosenItemIdx();
295
296 switch (idx)
297 {
298 case 0:
299 {
300 IControl* pToDelete = mClickedOnControl > 0 ? GetUI()->GetControl(mClickedOnControl) : nullptr;
301 IControl* controls[1] = { pToDelete };
302 if (pToDelete)
303 EmitControlsDeleted(controls, 1);
304 mSelectedControls.Empty();
305 if (mClickedOnControl > 0)
306 GetUI()->RemoveControl(mClickedOnControl);
307 EmitSelectionChanged();
308 break;
309 }
310 default:
311 break;
312 }
313
314 mClickedOnControl = -1;
315 }
316 }
317
318 void Draw(IGraphics& g) override
319 {
320 IBlend b {EBlend::Add, 0.25f};
321 g.DrawGrid(mGridColor, g.GetBounds(), mGridSize, mGridSize, &b);
322
323 for (int i = 1; i < g.NControls(); i++)
324 {
325 IControl* pControl = g.GetControl(i);
326 IRECT cr = pControl->GetRECT();
327
328 if (!pControl->GetParent()) // don't allow reszing sub controls
329 {
330 if (pControl->IsHidden())
331 g.DrawDottedRect(COLOR_RED, cr);
332 else if (pControl->IsDisabled())
333 g.DrawDottedRect(COLOR_GREEN, cr);
334 else
335 g.DrawDottedRect(COLOR_BLUE, cr);
336
337 IRECT h = GetHandleRect(cr);
338 g.FillTriangle(mRectColor, h.L, h.B, h.R, h.B, h.R, h.T);
339 g.DrawTriangle(COLOR_BLACK, h.L, h.B, h.R, h.B, h.R, h.T);
340 }
341 }
342
343 for (int i = 0; i< mSelectedControls.GetSize(); i++)
344 {
345 g.DrawDottedRect(COLOR_WHITE, mSelectedControls.Get(i)->GetRECT());
346 }
347
348 if (!mDragRegion.Empty())
349 {
350 g.DrawDottedRect(COLOR_RED, mDragRegion);
351 }
352 }
353
354 void OnResize() override
355 {
356 mSelectedControls.Empty();
357 mRECT = GetUI()->GetBounds();
358 SetTargetRECT(mRECT);
359 }
360
361 bool IsDirty() override { return true; }
362
363 inline IRECT GetHandleRect(const IRECT& r)
364 {
365 return IRECT(r.R - RESIZE_HANDLE_SIZE, r.B - RESIZE_HANDLE_SIZE, r.R, r.B);
366 }
367
368 inline float SnapToGrid(float input)
369 {
370 if (mGridSize > 1)
371 return (float) std::round(input / (float) mGridSize) * mGridSize;
372 else
373 return input;
374 }
375
376 static void AppendJsonString(std::string& out, const char* str)
377 {
378 out.push_back('"');
379
380 if (str)
381 {
382 for (const char* p = str; *p; ++p)
383 {
384 unsigned char c = static_cast<unsigned char>(*p);
385
386 switch (c)
387 {
388 case '\\': out += "\\\\"; break;
389 case '"': out += "\\\""; break;
390 case '\b': out += "\\b"; break;
391 case '\f': out += "\\f"; break;
392 case '\n': out += "\\n"; break;
393 case '\r': out += "\\r"; break;
394 case '\t': out += "\\t"; break;
395 default:
396 {
397 if (c < 0x20)
398 {
399 char esc[8];
400 std::snprintf(esc, sizeof(esc), "\\u%04x", c);
401 out += esc;
402 }
403 else
404 {
405 out.push_back(static_cast<char>(c));
406 }
407 break;
408 }
409 }
410 }
411 }
412
413 out.push_back('"');
414 }
415
416#if defined(IPLUG_LIVE_EDIT_CLASS_NAME)
417 static std::string DemangledClassName(IControl* pControl)
418 {
419 if (!pControl)
420 return "";
421
422 const char* mangled = typeid(*pControl).name();
423#if defined(__GNUG__) && !defined(_WIN32)
424 int status = 0;
425 char* demangled = abi::__cxa_demangle(mangled, nullptr, nullptr, &status);
426 const char* name = (status == 0 && demangled) ? demangled : (mangled ? mangled : "");
427#else
428 const char* name = mangled ? mangled : "";
429#endif
430 static const char prefix[] = "iplug::igraphics::";
431 const std::size_t prefixLen = sizeof(prefix) - 1;
432 const char* compact = std::strncmp(name, prefix, prefixLen) == 0 ? name + prefixLen : name;
433 std::string result(compact);
434
435#if defined(__GNUG__) && !defined(_WIN32)
436 if (demangled)
437 std::free(demangled);
438#endif
439
440 return result;
441 }
442#endif
443
444 void AppendControlDescriptor(std::string& out, IControl* pControl)
445 {
446 if (!pControl)
447 {
448 out += "null";
449 return;
450 }
451
452 const IRECT r = pControl->GetRECT();
453 out += "{\"idx\":";
454 out += std::to_string(GetUI()->GetControlIdx(pControl));
455#if defined(IPLUG_LIVE_EDIT_CLASS_NAME)
456 out += ",\"className\":";
457 AppendJsonString(out, DemangledClassName(pControl).c_str());
458#endif
459 out += ",\"tag\":";
460 out += std::to_string(pControl->GetTag());
461 out += ",\"paramIdx\":";
462 out += std::to_string(pControl->GetParamIdx());
463 out += ",\"l\":";
464 out += std::to_string(static_cast<int>(std::round(r.L)));
465 out += ",\"t\":";
466 out += std::to_string(static_cast<int>(std::round(r.T)));
467 out += ",\"r\":";
468 out += std::to_string(static_cast<int>(std::round(r.R)));
469 out += ",\"b\":";
470 out += std::to_string(static_cast<int>(std::round(r.B)));
471 out += "}";
472 }
473
474 static void AppendRect(std::string& out, const IRECT& r)
475 {
476 out += "{\"l\":";
477 out += std::to_string(static_cast<int>(std::round(r.L)));
478 out += ",\"t\":";
479 out += std::to_string(static_cast<int>(std::round(r.T)));
480 out += ",\"r\":";
481 out += std::to_string(static_cast<int>(std::round(r.R)));
482 out += ",\"b\":";
483 out += std::to_string(static_cast<int>(std::round(r.B)));
484 out += "}";
485 }
486
487 void EmitControlChanged(IControl* pControl, const IRECT& previousRECT)
488 {
489 if (!GetUI()->HasLiveEditEventFunc())
490 return;
491
492 std::string json = "{\"type\":\"iplug:live-edit:control-changed\",\"control\":";
493 AppendControlDescriptor(json, pControl);
494 json += ",\"prev\":";
495 AppendRect(json, previousRECT);
496 json += "}";
497 GetUI()->EmitLiveEditEvent(json.c_str());
498 }
499
500 void EmitControlAdded(IControl* pControl)
501 {
502 if (!GetUI()->HasLiveEditEventFunc())
503 return;
504
505 std::string json = "{\"type\":\"iplug:live-edit:control-added\",\"control\":";
506 AppendControlDescriptor(json, pControl);
507 json += "}";
508 GetUI()->EmitLiveEditEvent(json.c_str());
509 }
510
511 void EmitControlsDeleted(IControl* const* controls, int count)
512 {
513 if (!GetUI()->HasLiveEditEventFunc())
514 return;
515
516 if (count <= 0)
517 return;
518
519 std::string json = "{\"type\":\"iplug:live-edit:controls-deleted\",\"deleted\":[";
520
521 for (int i = 0; i < count; i++)
522 {
523 if (i > 0)
524 json += ",";
525
526 AppendControlDescriptor(json, controls[i]);
527 }
528
529 json += "]}";
530 GetUI()->EmitLiveEditEvent(json.c_str());
531 }
532
533 void EmitSelectionChanged()
534 {
535 if (!GetUI()->HasLiveEditEventFunc())
536 return;
537
538 std::string json = "{\"type\":\"iplug:live-edit:selection-changed\",\"selection\":[";
539
540 for (int i = 0; i < mSelectedControls.GetSize(); i++)
541 {
542 if (i > 0)
543 json += ",";
544
545 AppendControlDescriptor(json, mSelectedControls.Get(i));
546 }
547
548 json += "]}";
549 GetUI()->EmitLiveEditEvent(json.c_str());
550 }
551
552private:
553 IPopupMenu mRightClickOutsideControlMenu {"Outside Control", {"Add Place Holder"}};
554 IPopupMenu mRightClickOnControlMenu{ "On Control", {"Delete Control"} };
555
556 bool mMouseOversEnabled = false;
557 bool mMouseClickedOnResizeHandle = false;
558 bool mMouseIsDragging = false;
559 WDL_String mErrorMessage;
560 WDL_PtrList<IControl> mSelectedControls;
561
562 IColor mGridColor = COLOR_WHITE;
563 IColor mRectColor = COLOR_WHITE;
564 static const int RESIZE_HANDLE_SIZE = 10;
565
566 IRECT mMouseDownRECT;
567 IRECT mMouseDownTargetRECT;
568 IRECT mDragRegion;
569
570 float mGridSize = 10;
571 int mClickedOnControl = -1;
572};
573
574END_IGRAPHICS_NAMESPACE
575END_IPLUG_NAMESPACE
576
577#endif // !NDEBUG || IPLUG_LIVE_EDIT
This file contains the base IControl implementation, along with some base classes for specific types ...
The lowest level base class of an IGraphics control.
Definition: IControl.h:49
IGraphics * GetUI()
Definition: IControl.h:472
bool IsDisabled() const
Definition: IControl.h:367
const IRECT & GetTargetRECT() const
Get the rectangular mouse tracking target area, within the graphics context for this control.
Definition: IControl.h:324
int GetParamIdx(int valIdx=0) const
Get the index of a parameter that the control is linked to Normaly controls are either linked to a si...
Definition: IControl.cpp:109
const IRECT & GetRECT() const
Get the rectangular draw area for this control, within the graphics context.
Definition: IControl.h:316
void SetTargetRECT(const IRECT &bounds)
Set the rectangular mouse tracking target area, within the graphics context for this control.
Definition: IControl.h:328
int GetTag() const
Get the control's tag.
Definition: IControl.h:426
void SetRECT(const IRECT &bounds)
Set the rectangular draw area for this control, within the graphics context.
Definition: IControl.h:320
IContainerBase * GetParent() const
Definition: IControl.h:467
bool IsHidden() const
Definition: IControl.h:360
The lowest level base class of an IGraphics context.
Definition: IGraphics.h:86
void RemoveControl(int idx)
Remove a control at a particular index, (frees memory).
Definition: IGraphics.cpp:169
void CreatePopupMenu(IControl &control, IPopupMenu &menu, const IRECT &bounds, int valIdx=0)
Shows a pop up/contextual menu in relation to a rectangular region of the graphics context.
Definition: IGraphics.cpp:1978
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
virtual void DrawDottedRect(const IColor &color, const IRECT &bounds, const IBlend *pBlend=0, float thickness=1.f, float dashLen=2.f)
Draw a dotted rectangle to the graphics context.
Definition: IGraphics.cpp:2559
void ReleaseMouseCapture()
Used to tell the graphics context to stop tracking mouse interaction with a control.
Definition: IGraphics.cpp:1296
void GetMouseDownPoint(float &x, float &y) const
Get the x, y position of the last mouse down message.
Definition: IGraphics.h:1621
int NControls() const
Definition: IGraphics.h:1464
virtual void FillTriangle(const IColor &color, float x1, float y1, float x2, float y2, float x3, float y3, const IBlend *pBlend=0)
Fill a triangle with a color.
Definition: IGraphics.cpp:2582
virtual void DrawGrid(const IColor &color, const IRECT &bounds, float gridSizeH, float gridSizeV, const IBlend *pBlend=0, float thickness=1.f)
Draw a grid to the graphics context.
Definition: IGraphics.cpp:2444
void SetAllControlsDirty()
Calls SetDirty() on every control.
Definition: IGraphics.cpp:597
void SetControlSize(IControl *pControl, float w, float h)
Resize a control, redrawing the interface correctly.
Definition: IGraphics.cpp:223
IControl * GetControl(int idx)
Get the control at a certain index in the control stack.
Definition: IGraphics.h:1381
void ForStandardControlsFunc(IControlFunction func)
For all standard controls in the main control stack perform a function.
Definition: IGraphics.cpp:549
virtual void DrawTriangle(const IColor &color, float x1, float y1, float x2, float y2, float x3, float y3, const IBlend *pBlend=0, float thickness=1.f)
Draw a triangle to the graphics context.
Definition: IGraphics.cpp:2510
void SetControlPosition(IControl *pControl, float x, float y)
Reposition a control, redrawing the interface correctly.
Definition: IGraphics.cpp:216
void EnableMouseOver(bool enable)
Definition: IGraphics.h:1607
IRECT GetBounds() const
Returns an IRECT that represents the entire UI bounds This is useful for programatically arranging UI...
Definition: IGraphics.h:1214
IControl * AttachControl(IControl *pControl, int ctrlTag=kNoTag, const char *group="")
Attach an IControl to the graphics context and add it to the top of the control stack.
Definition: IGraphics.cpp:311
A control to enable live modification of control layout in an IGraphics context in debug builds This ...
void OnResize() override
Called when IControl is constructed or resized using SetRect().
bool OnKeyDown(float x, float y, const IKeyPress &key) override
Implement this method to respond to a key down event on this control.
void OnMouseOver(float x, float y, const IMouseMod &mod) override
Implement this method to respond to a mouseover event on this control.
void OnMouseDrag(float x, float y, float dX, float dY, const IMouseMod &mod) override
Implement this method to respond to a mouse drag event on this control.
void OnPopupMenuSelection(IPopupMenu *pSelectedMenu, int valIdx) override
Implement this method to handle popup menu selection after IGraphics::CreatePopupMenu/IControlPromptU...
void OnInit() override
Called just prior to when the control is attached, after its delegate and graphics member variable se...
void OnMouseUp(float x, float y, const IMouseMod &mod) override
Implement this method to respond to a mouse up event on this control.
void OnMouseDown(float x, float y, const IMouseMod &mod) override
Implement this method to respond to a mouse down event on this control.
void OnMouseDblClick(float x, float y, const IMouseMod &mod) override
Implement this method to respond to a mouse double click event on this control.
void Draw(IGraphics &g) override
Draw the control to the graphics context.
bool IsDirty() override
Called at each display refresh by the IGraphics draw loop, after IControl::Animate(),...
A class for setting the contents of a pop up menu.
A control to use as a placeholder during development.
Definition: IControl.h:2289
Used to manage composite/blend operations, independent of draw class/platform.
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.
bool Empty() const
float W() const
float H() const
bool Contains(const IRECT &rhs) const
Returns true if this IRECT completely contains rhs.