iPlug2 - C++ Audio Plug-in Framework
IBubbleControl.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 
19 #include "IControl.h"
20 
21 BEGIN_IPLUG_NAMESPACE
22 BEGIN_IGRAPHICS_NAMESPACE
23 
27 class IBubbleControl : public IControl
28 {
29 public:
32  {
33  kCollapsed = 0,
34  kExpanding,
35  kExpanded,
36  kCollapsing,
37  };
38 
39  enum EArrowDir
40  {
41  kNorth,
42  kEast,
43  kSouth,
44  kWest
45  };
46 
51  IBubbleControl(const IText& text = DEFAULT_TEXT.WithAlign(EAlign::Center), const IColor& fillColor = COLOR_WHITE, const IColor& strokeColor = COLOR_BLACK, float roundness = 5.f)
52  : IControl(IRECT())
53  , mRoundness(roundness)
54  , mFillColor(fillColor)
55  , mStrokeColor(strokeColor)
56  {
57  mText = text;
58  mHide = true;
59  mIgnoreMouse = true;
60 
61  auto animationFunc = [&](IControl* pCaller) {
62  auto progress = pCaller->GetAnimationProgress();
63 
64  if(progress > 1.) {
65  if(mState == kExpanding)
66  {
67  mBlend.mWeight = mOpacity;
68  mState = kExpanded;
69  }
70  else if(mState == kExpanded)
71  {
72  if(GetUI()->ControlIsCaptured(mCaller))
73  {
74  mState = kExpanded;
75  SetDirty(true); // triggers animation again
76  }
77  else
78  {
79  mBlend.mWeight = mOpacity;
80  mState = kCollapsing;
81  SetDirty(true); // triggers animation again
82  }
83 
84  return; // don't remove animation
85  }
86  else if(mState == kCollapsing)
87  {
88  mBlend.mWeight = 0.;
89  Hide(true);
90  mState = kCollapsed;
91  mTouchId = 0;
92  SetDirty(false);
93  }
94 
95  pCaller->OnEndAnimation();
96  return;
97  }
98 
99  switch (mState) {
100  case kExpanding: mBlend.mWeight = (float) progress * mOpacity; break;
101  case kExpanded: mBlend.mWeight = mOpacity; break;
102  case kCollapsing: mBlend.mWeight = (float) (1.-progress) * mOpacity; break;
103  default:
104  break;
105  }
106  };
107 
108  SetActionFunction([animationFunc, this](IControl* pCaller) {
109  SetAnimation(animationFunc, 200);
110  });
111 
112  SetAnimationEndActionFunction([animationFunc, this](IControl* pCaller) {
113  if(mState == kExpanded) {
114  SetAnimation(animationFunc, 1000);
115  }
116  });
117  }
118 
119  virtual ~IBubbleControl()
120  {
121  }
122 
123  void Draw(IGraphics& g) override
124  {
125  IRECT r = mBubbleBounds.GetPadded(-mDropShadowSize);
126 
127  DrawDropShadow(g, r);
128  DrawBubble(g, r);
129  DrawContent(g, r);
130  }
131 
133  void ResetBounds()
134  {
136  mBubbleBounds = mRECT;
137  mCaller = nullptr;
138  }
139 
141  void SetMaxBounds(const IRECT& bounds) { mMaxBounds = bounds; }
142 
144  void SetFillColor(const IColor& color) { mFillColor = color; }
145 
147  void SetStrokeColor(const IColor& color) { mStrokeColor = color; }
148 
149 protected:
150  virtual void DrawContent(IGraphics& g, const IRECT& r)
151  {
152  g.DrawText(mText, mStr.Get(), r, &mBlend);
153  }
154 
155  virtual void DrawBubble(IGraphics& g, const IRECT& r)
156  {
157  float halfCalloutSize = mCalloutSize/2.f;
158 
159  g.PathMoveTo(r.L, r.T + mRoundness);
160  g.PathArc(r.L + mRoundness, r.T + mRoundness, mRoundness, 270.f, 360.f);
161 
162  if(mArrowDir == kWest)
163  {
164  g.PathArc(r.R - mRoundness, r.T + mRoundness, mRoundness, 0.f, 90.f);
165  g.PathArc(r.R - mRoundness, r.B - mRoundness, mRoundness, 90.f, 180.f);
166  g.PathArc(r.L + mRoundness, r.B - mRoundness, mRoundness, 180.f, 270.f);
167  g.PathLineTo(r.L, r.MH() + halfCalloutSize);
168  g.PathLineTo(r.L - mCalloutSize, r.MH());
169  g.PathLineTo(r.L, r.MH() - halfCalloutSize);
170  }
171  else if(mArrowDir == kEast)
172  {
173  g.PathArc(r.R - mRoundness, r.T + mRoundness, mRoundness, 0.f, 90.f);
174  g.PathLineTo(r.R, r.MH() - halfCalloutSize);
175  g.PathLineTo(r.R + mCalloutSize, r.MH());
176  g.PathLineTo(r.R, r.MH() + halfCalloutSize);
177  g.PathArc(r.R - mRoundness, r.B - mRoundness, mRoundness, 90.f, 180.f);
178  g.PathArc(r.L + mRoundness, r.B - mRoundness, mRoundness, 180.f, 270.f);
179  }
180  else if(mArrowDir == kNorth)
181  {
182  g.PathLineTo(r.MW() - halfCalloutSize, r.T);
183  g.PathLineTo(r.MW(), r.T - mCalloutSize);
184  g.PathLineTo(r.MW() + halfCalloutSize, r.T);
185  g.PathArc(r.R - mRoundness, r.T + mRoundness, mRoundness, 0.f, 90.f);
186  g.PathArc(r.R - mRoundness, r.B - mRoundness, mRoundness, 90.f, 180.f);
187  g.PathArc(r.L + mRoundness, r.B - mRoundness, mRoundness, 180.f, 270.f);
188  }
189  else if(mArrowDir == kSouth)
190  {
191  g.PathArc(r.R - mRoundness, r.T + mRoundness, mRoundness, 0.f, 90.f);
192  g.PathArc(r.R - mRoundness, r.B - mRoundness, mRoundness, 90.f, 180.f);
193  g.PathLineTo(r.MW() + halfCalloutSize, r.B);
194  g.PathLineTo(r.MW(), r.B + mCalloutSize);
195  g.PathLineTo(r.MW() - halfCalloutSize, r.B);
196  g.PathArc(r.L + mRoundness, r.B - mRoundness, mRoundness, 180.f, 270.f);
197  }
198  g.PathClose();
199 
200  g.PathFill(mFillColor, true, &mBlend);
201  g.PathStroke(mStrokeColor, mStrokeThickness, IStrokeOptions(), &mBlend);
202  }
203 
204  void DrawDropShadow(IGraphics& g, const IRECT& r)
205  {
206  g.DrawFastDropShadow(r, mBubbleBounds, 2.0, mRoundness, 10.f, &mBlend);
207  }
208 
209  virtual void MeasureText(const char* str, IRECT& contentBounds)
210  {
211  GetUI()->MeasureText(mText, str, contentBounds);
212  }
213 
214  void ShowBubble(IControl* pCaller, float x, float y, const char* str, EDirection dir, IRECT minimumContentBounds, ITouchID touchID = 0)
215  {
216  if(mMaxBounds.W() == 0)
217  mMaxBounds = GetUI()->GetBounds();
218 
219  mDirection = dir;
220  mTouchId = touchID;
221 
222  mStr.Set(str);
223  IRECT contentBounds;
224  MeasureText(str, contentBounds);
225 
226  if (!minimumContentBounds.Empty())
227  {
228  if(minimumContentBounds.W() > contentBounds.W())
229  {
230  contentBounds.R = contentBounds.L + minimumContentBounds.W();
231  }
232 
233  if(minimumContentBounds.H() > contentBounds.H())
234  {
235  contentBounds.B = contentBounds.T + minimumContentBounds.H();
236  }
237  }
238 
239  contentBounds.HPad(mHorizontalPadding);
240  contentBounds.Pad(mDropShadowSize);
241  const float halfHeight = contentBounds.H() / 2.f;
242  const float halfWidth = contentBounds.W() / 2.f;
243 
244  mBubbleBounds = IRECT(x, y - halfHeight, x + contentBounds.W(), y + halfHeight);
245  IRECT controlBounds = pCaller->GetRECT();
246 
247  if(mDirection == EDirection::Horizontal)
248  {
249  const float maxR = mMaxBounds.R - mHorizontalPadding - mDropShadowSize;
250 
251  // check if it's gone off the right hand side
252  if(mBubbleBounds.R > maxR)
253  {
254  const float shiftLeft = mBubbleBounds.R-controlBounds.L;
255  mBubbleBounds.Translate(-shiftLeft, 0.f);
256  mArrowDir = EArrowDir::kEast;
257  }
258  else
259  mArrowDir = EArrowDir::kWest;
260  }
261  else
262  {
263  mBubbleBounds.Translate(-halfWidth, -halfHeight);
264 
265  // check if it's gone off the top
266  if(mBubbleBounds.T < mMaxBounds.T)
267  {
268  mArrowDir = EArrowDir::kNorth;
269  const float shiftDown = mBubbleBounds.H() + controlBounds.H();
270  mBubbleBounds.Translate(0.f, shiftDown);
271  }
272  else
273  mArrowDir = EArrowDir::kSouth;
274  }
275 
276  SetRECT(mRECT.Union(mBubbleBounds));
277 
278  if(mState == kCollapsed)
279  {
280  Hide(false);
281  mState = kExpanding;
282  SetDirty(true);
283  }
284 
285  if(pCaller != mCaller)
286  {
287  mRECT = mBubbleBounds;
289  }
290 
291  mCaller = pCaller;
292  }
293 
294  bool GetActive() const
295  {
296  return mState > EPopupState::kCollapsed;
297  }
298 
299  ITouchID GetTouchID() const
300  {
301  return mTouchId;
302  }
303 
304 protected:
305  friend class IGraphics;
306 
307  ITouchID mTouchId = 0;
308  EDirection mDirection = EDirection::Horizontal;
309  IRECT mMaxBounds; // if view is only showing a part of the graphics context, we need to know because menus can't go there
310  IRECT mBubbleBounds;
311  IControl* mCaller = nullptr;
312  EArrowDir mArrowDir = EArrowDir::kWest;
313  WDL_String mStr;
314  IBlend mBlend = { EBlend::Default, 0.f }; // blend for sub panels appearing
315  float mRoundness = 5.f; // The roundness of the corners of the menu panel backgrounds
316  float mDropShadowSize = 10.f; // The size in pixels of the drop shadow
317  float mCalloutSize = 10.f;
318  float mOpacity = 0.95f; // The opacity of the menu panel backgrounds when fully faded in
319  float mStrokeThickness = 2.f;
320  float mHorizontalPadding = 5.f;
321  IColor mFillColor = COLOR_WHITE;
322  IColor mStrokeColor = COLOR_BLACK;
323  EPopupState mState = kCollapsed; // The state of the pop-up, mainly used for animation
324 };
325 
326 END_IGRAPHICS_NAMESPACE
327 END_IPLUG_NAMESPACE
virtual void PathMoveTo(float x, float y)=0
Move the current point in the current path.
float MW() const
float MH() const
virtual void PathClose()=0
Close the path that is being specified.
The lowest level base class of an IGraphics control.
Definition: IControl.h:42
Used to manage a rectangular area, independent of draw class/platform.
virtual void PathArc(float cx, float cy, float r, float a1, float a2, EWinding winding=EWinding::CW)=0
Add an arc to the current path.
Used to manage composite/blend operations, independent of draw class/platform.
void SetMaxBounds(const IRECT &bounds)
Set the bounds that the menu can potentially occupy, if not the full graphics context.
virtual void PathLineTo(float x, float y)=0
Add a line to the current path from the current point to the specified location.
virtual void Hide(bool hide)
Shows or hides the IControl.
Definition: IControl.cpp:237
void SetFillColor(const IColor &color)
Set the background color for the bubble.
void Translate(float x, float y)
Translate this rectangle.
IControl * SetActionFunction(IActionFunction actionFunc)
Set an Action Function for this control.
Definition: IControl.h:201
virtual void PathStroke(const IPattern &pattern, float thickness, const IStrokeOptions &options=IStrokeOptions(), const IBlend *pBlend=0)=0
Stroke the current current path.
Used to manage color data, independent of draw class/platform.
void HPad(float padding)
Pad this IRECT in the X-axis N.B.
void DrawText(const IText &text, const char *str, const IRECT &bounds, const IBlend *pBlend=0)
Draw some text to the graphics context in a specific rectangle.
Definition: IGraphics.cpp:631
Used to manage stroke behaviour for path based drawing back ends.
IControl * SetAnimationEndActionFunction(IActionFunction actionFunc)
Set an Action Function to be called at the end of an animation.
Definition: IControl.h:206
This file contains the base IControl implementation, along with some base classes for specific types ...
void SetAllControlsDirty()
Calls SetDirty() on every control.
Definition: IGraphics.cpp:543
float R
Right side of the rectangle (X + W)
void SetRECT(const IRECT &bounds)
Set the rectangular draw area for this control, within the graphics context.
Definition: IControl.h:309
float H() const
const IRECT & GetRECT() const
Get the rectangular draw area for this control, within the graphics context.
Definition: IControl.h:305
float W() const
IText is used to manage font and text/text entry style for a piece of text on the UI...
IControl(const IRECT &bounds, int paramIdx=kNoParameter, IActionFunction actionFunc=nullptr)
Constructor.
Definition: IControl.cpp:81
virtual void PathFill(const IPattern &pattern, const IFillOptions &options=IFillOptions(), const IBlend *pBlend=0)=0
Fill the current current path.
void ResetBounds()
If controls get moved, you may need to call this to avoid the wrong regions getting dirtied...
bool Empty() const
virtual void DrawFastDropShadow(const IRECT &innerBounds, const IRECT &outerBounds, float xyDrop=5.f, float roundness=0.f, float blur=10.f, IBlend *pBlend=nullptr)
NanoVG only.
Definition: IGraphics.h:397
The lowest level base class of an IGraphics context.
Definition: IGraphics.h:86
void SetAnimation(IAnimationFunction func)
Set the animation function.
Definition: IControl.h:477
IRECT GetBounds() const
Returns an IRECT that represents the entire UI bounds This is useful for programatically arranging UI...
Definition: IGraphics.h:1154
EPopupState
An enumerated list, that is used to determine the state of the menu, mainly for animations.
void SetTargetAndDrawRECTs(const IRECT &bounds)
Set BOTH the draw rect and the target area, within the graphics context for this control.
Definition: IControl.h:321
IRECT Union(const IRECT &rhs) const
Create a new IRECT that is a union of this IRECT and rhs.
void Pad(float padding)
Pad this IRECT N.B.
IGraphics * GetUI()
Definition: IControl.h:452
float L
Left side of the rectangle (X)
A special control to draw contextual info as a slider etc is moved If used in the main IControl stack...
void Draw(IGraphics &g) override
Draw the control to the graphics context.
IRECT GetPadded(float padding) const
Get a copy of this IRECT with each value padded by padding N.B.
void SetStrokeColor(const IColor &color)
Set the stroke color for the bubble.
float T
Top of the rectangle (Y)
IBubbleControl(const IText &text=DEFAULT_TEXT.WithAlign(EAlign::Center), const IColor &fillColor=COLOR_WHITE, const IColor &strokeColor=COLOR_BLACK, float roundness=5.f)
Create a new IBubbleControl.
virtual float MeasureText(const IText &text, const char *str, IRECT &bounds) const
Measure the rectangular region that some text will occupy.
Definition: IGraphics.cpp:639
virtual void SetDirty(bool triggerAction=true, int valIdx=kNoValIdx)
Mark the control as dirty, i.e.
Definition: IControl.cpp:196
float B
Bottom of the rectangle (Y + H)