iPlug2 - C++ Audio Plug-in Framework
IPopupMenuControl.cpp
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 
17 #include "IPopupMenuControl.h"
18 
19 #ifdef IGRAPHICS_NANOVG
20 #include "nanovg.h"
21 #endif
22 
23 using namespace iplug;
24 using namespace igraphics;
25 
26 IPopupMenuControl::IPopupMenuControl(int paramIdx, IText text, IRECT collapsedBounds, IRECT expandedBounds)
27 : IControl(collapsedBounds, paramIdx)
28 , mSpecifiedCollapsedBounds(collapsedBounds)
29 , mSpecifiedExpandedBounds(expandedBounds)
30 {
31  SetActionFunction([&](IControl* pCaller) {
32 
33  int duration = DEFAULT_ANIMATION_DURATION;
34 
35  if(mState == kSubMenuAppearing)
36  duration = DEFAULT_ANIMATION_DURATION * 2;
37 
38 #pragma mark animations
39  SetAnimation([&](IControl* pCaller) {
40  auto progress = pCaller->GetAnimationProgress();
41 
42  if(progress > 1.) {
43  pCaller->OnEndAnimation();
44  return;
45  }
46 
47  if(mState == kExpanding)
48  {
49  if(mAppearingMenuPanel != nullptr)
50  mAppearingMenuPanel->mBlend.mWeight = (float) progress * mOpacity;
51  }
52  else if(mState == kSubMenuAppearing)
53  {
54  if(mAppearingMenuPanel != nullptr)
55  mAppearingMenuPanel->mBlend.mWeight = (float) (progress > 0.9) * mOpacity;
56  }
57  else if(mState == kCollapsing)
58  {
59  for (auto i = 0; i < mMenuPanels.GetSize(); i++) {
60  mMenuPanels.Get(i)->mBlend.mWeight = (float) (1.-progress) * mOpacity;
61  }
62  }
63  else if(mState == kIdling) // Idling is a special stage, to force the menu to redraw, before complete collapse
64  {
65  for (auto i = 0; i < mMenuPanels.GetSize(); i++) {
66  mMenuPanels.Get(i)->mBlend.mWeight = 0.f;
67  }
68  }
69  },
70  duration);
71  });
72 
73  mText = text;
74  mHide = true;
75 }
76 
77 IPopupMenuControl::~IPopupMenuControl()
78 {
79  mMenuPanels.Empty(true);
80 }
81 
83 {
84  assert(mMenu != nullptr);
85 
86  for (auto mr = 0; mr < mMenuPanels.GetSize(); mr++)
87  {
88  MenuPanel* pMenuPanel = mMenuPanels.Get(mr);
89 
90  if(pMenuPanel->mShouldDraw)
91  {
92  DrawPanelShadow(g, pMenuPanel);
93  DrawPanelBackground(g, pMenuPanel);
94 
95  int nItems = pMenuPanel->mMenu.NItems();
96  int nCells = pMenuPanel->mCellBounds.GetSize();
97  int startCell = 0;
98  int endCell = nCells-1;
99  int cellOffset = 0;
100 
101  if(nItems > nCells)
102  {
103  if(pMenuPanel->mScrollItemOffset > 0)
104  {
105  IRECT* pCellRect = pMenuPanel->mCellBounds.Get(0);
106  bool sel = mMouseCellBounds == pCellRect || pCellRect == pMenuPanel->mHighlightedCell;
107 
108  DrawCellBackground(g, *pCellRect, nullptr, sel, &pMenuPanel->mBlend);
109  DrawUpArrow(g, *pCellRect, sel, &pMenuPanel->mBlend);
110 
111  startCell++;
112  }
113 
114  if(pMenuPanel->mScrollItemOffset < nItems-nCells)
115  {
116  IRECT* pCellRect = pMenuPanel->mCellBounds.Get(nCells-1);
117  bool sel = mMouseCellBounds == pCellRect || pCellRect == pMenuPanel->mHighlightedCell;
118 
119  DrawCellBackground(g, *pCellRect, nullptr, sel, &pMenuPanel->mBlend);
120  DrawDownArrow(g, *pCellRect, sel, &pMenuPanel->mBlend);
121 
122  endCell--; // last one is going to be an arrow
123  }
124  }
125 
126  for(auto i = startCell; i <= endCell; i++)
127  {
128  IRECT* pCellRect = pMenuPanel->mCellBounds.Get(i);
129  IPopupMenu::Item* pMenuItem = pMenuPanel->mMenu.GetItem(startCell + pMenuPanel->mScrollItemOffset + cellOffset++);
130 
131  if(!pMenuItem)
132  return;
133 
134  if(pMenuItem->GetIsSeparator())
135  DrawSeparator(g, *pCellRect, &pMenuPanel->mBlend);
136  else
137  {
138  bool sel = mMouseCellBounds == pCellRect || pCellRect == pMenuPanel->mHighlightedCell;
139 
140  if(pMenuPanel->mClickedCell)
141  {
142  if(mState != kFlickering)
143  DrawCellBackground(g, *pCellRect, pMenuItem, sel, &pMenuPanel->mBlend);
144  }
145  else
146  DrawCellBackground(g, *pCellRect, pMenuItem, sel, &pMenuPanel->mBlend);
147 
148  //TODO: Title indent?
149  DrawCellText(g, *pCellRect, pMenuItem, sel, &pMenuPanel->mBlend);
150 
151  if(pMenuItem->GetChecked())
152  DrawTick(g, *pCellRect, pMenuItem, sel, &pMenuPanel->mBlend);
153 
154  if(pMenuItem->GetSubmenu())
155  DrawSubMenuArrow(g, *pCellRect, pMenuItem, sel, &pMenuPanel->mBlend);
156  }
157  }
158  }
159  }
160 
161  if(mCallOut && mMenuPanels.GetSize())
162  {
163  DrawCalloutArrow(g, mCalloutArrowBounds, &mMenuPanels.Get(0)->mBlend);
164  if (mMenuHasSubmenu && mSubMenuOpened)
165  {
166  DrawSubMenuCalloutArrow(g, mSubMenuCalloutArrowBounds, &mMenuPanels.Get(0)->mBlend);
167  }
168  }
169 }
170 
171 void IPopupMenuControl::OnMouseDown(float x, float y, const IMouseMod& mod)
172 {
173  if(GetState() == kExpanded)
174  {
175  mMouseCellBounds = mActiveMenuPanel->HitTestCells(x, y);
176  CollapseEverything();
177  }
178  else
179  IControl::OnMouseDown(x, y, mod);
180 }
181 
182 void IPopupMenuControl::OnMouseDrag(float x, float y, float dX, float dY, const IMouseMod& mod)
183 {
184  if(mActiveMenuPanel)
185  {
186  mMouseCellBounds = mActiveMenuPanel->HitTestCells(x, y);
187  SetDirty(false);
188  }
189 }
190 
191 void IPopupMenuControl::OnMouseOver(float x, float y, const IMouseMod& mod)
192 {
193  mMouseCellBounds = mActiveMenuPanel->HitTestCells(x, y);
194 
195  // if the mouse event was outside of the active MenuPanel - could be on another menu or completely outside
196  if(mMouseCellBounds == nullptr)
197  {
198  MenuPanel* pMousedMenuPanel = nullptr;
199 
200  const int nPanels = mMenuPanels.GetSize();
201 
202  for (auto p = nPanels-1; p >= 0; p--)
203  {
204  MenuPanel* pMenuPanel = mMenuPanels.Get(p);
205 
206  if(pMenuPanel->mShouldDraw && pMenuPanel->mRECT.Contains(x, y))
207  {
208  pMousedMenuPanel = pMenuPanel;
209  break;
210  }
211  }
212 
213  if(pMousedMenuPanel != nullptr)
214  {
215  mActiveMenuPanel = pMousedMenuPanel;
216  mMouseCellBounds = mActiveMenuPanel->HitTestCells(x, y);
217  }
218  }
219 
220  CalculateMenuPanels(x, y);
221 
222  if(mActiveMenuPanel->mScroller)
223  {
224  if(mMouseCellBounds == mActiveMenuPanel->mCellBounds.Get(0))
225  {
226  mActiveMenuPanel->ScrollUp();
227  }
228  else if (mMouseCellBounds == mActiveMenuPanel->mCellBounds.Get((mActiveMenuPanel->mCellBounds.GetSize()-1)))
229  {
230  mActiveMenuPanel->ScrollDown();
231  }
232  }
233 
234  SetDirty(false);
235 }
236 
238 {
239  mMouseCellBounds = nullptr;
240 }
241 
242 void IPopupMenuControl::OnMouseWheel(float x, float y, const IMouseMod& mod, float d)
243 {
244  //FIXME:
245 // if(mActiveMenuPanel)
246 // {
247 // if(mActiveMenuPanel->mScroller)
248 // {
249 // if(d > 0.)
250 // mActiveMenuPanel->ScrollUp();
251 // else
252 // mActiveMenuPanel->ScrollDown();
253 // }
254 //
255 // SetDirty(false);
256 // }
257 }
258 
260 {
261  float trisize = bounds.H();
262  float halftri = trisize * 0.5f;
263  float ax, ay, bx, by, cx, cy;
264 
265  switch (mCalloutArrowDir) {
266  case kNorth:
267  ax = bounds.MW() - halftri;
268  ay = bounds.MH() - halftri;
269  bx = ax + trisize;
270  by = ay;
271  cx = bounds.MW();
272  cy = ay + trisize;
273  break;
274  case kEast:
275  ax = bounds.MW() + halftri;
276  ay = bounds.MH() + halftri;
277  bx = ax;
278  by = ay - trisize;
279  cx = ax - trisize;
280  cy = bounds.MH();
281  break;
282  case kSouth:
283  ax = bounds.MW() - halftri;
284  ay = bounds.MH() + halftri;
285  bx = ax + trisize;
286  by = ay;
287  cx = bounds.MW();
288  cy = ay - trisize;
289  break;
290  case kWest:
291  ax = bounds.MW() - halftri;
292  ay = bounds.MH() + halftri;
293  bx = ax;
294  by = ay - trisize;
295  cx = ax + trisize;
296  cy = bounds.MH();
297  break;
298  default:
299  break;
300  }
301  g.FillTriangle(mPanelBackgroundColor, ax, ay, bx, by, cx, cy, pBlend);
302 }
303 
305 {
306  float trisize = bounds.H();
307  float halftri = trisize * 0.5f;
308  float ax, ay, bx, by, cx, cy;
309 
310  if (mSubmenuOnRight)
311  {
312  ax = bounds.MW() + halftri;
313  ay = bounds.MH() + halftri;
314  bx = ax;
315  by = ay - trisize;
316  cx = ax - trisize;
317  cy = bounds.MH();
318  }
319  else
320  {
321  ax = bounds.MW() - halftri;
322  ay = bounds.MH() + halftri;
323  bx = ax;
324  by = ay - trisize;
325  cx = ax + trisize;
326  cy = bounds.MH();
327  }
328  g.FillTriangle(mPanelBackgroundColor, ax, ay, bx, by, cx, cy, pBlend);
329 }
330 
332 {
333  // mTargetRECT = inner area
334  g.FillRoundRect(mPanelBackgroundColor, panel->mTargetRECT, mRoundness, &panel->mBlend);
335 }
336 
338 {
339  IRECT inner = panel->mRECT.GetPadded(-mDropShadowSize);
340  g.DrawFastDropShadow(inner, panel->mRECT, 2.0, mRoundness, 10.f, &panel->mBlend);
341 }
342 
343 void IPopupMenuControl::DrawCellBackground(IGraphics& g, const IRECT& bounds, const IPopupMenu::Item* pItem, bool sel, IBlend* pBlend)
344 {
345  if(sel)
346  g.FillRect(mCellBackGroundColor, bounds.GetHPadded(PAD), pBlend);
347 }
348 
349 void IPopupMenuControl::DrawCellText(IGraphics& g, const IRECT& bounds, const IPopupMenu::Item* pItem, bool sel, IBlend* pBlend)
350 {
351  IRECT tickRect = IRECT(bounds.L, bounds.T, bounds.L + TICK_SIZE, bounds.B).GetCentredInside(TICK_SIZE);
352  IRECT textRect = IRECT(tickRect.R + TEXT_HPAD, bounds.T, bounds.R - TEXT_HPAD, bounds.B);
353 
354  if(sel)
355  mText.mFGColor = mItemMouseoverColor;
356  else
357  {
358  if(pItem->GetEnabled())
359  mText.mFGColor = mItemColor;
360  else
361  mText.mFGColor = mDisabledItemColor;
362  }
363 
364  mText.mAlign = EAlign::Near;
365  g.DrawText(mText, pItem->GetText(), textRect, pBlend);
366 }
367 
368 void IPopupMenuControl::DrawTick(IGraphics& g, const IRECT& bounds, const IPopupMenu::Item* pItem, bool sel, IBlend* pBlend)
369 {
370  IRECT tickRect = IRECT(bounds.L, bounds.T, bounds.L + TICK_SIZE, bounds.B).GetCentredInside(TICK_SIZE);
371  g.FillRoundRect(sel ? mItemMouseoverColor : mItemColor, tickRect.GetCentredInside(TICK_SIZE/2.f), 2, pBlend);
372 }
373 
374 void IPopupMenuControl::DrawSubMenuArrow(IGraphics& g, const IRECT& bounds, const IPopupMenu::Item* pItem, bool sel, IBlend* pBlend)
375 {
376  float trisize, halftri, ax, ay, bx, by, cx, cy;
377 
378  if (mSubmenuOnRight)
379  {
380  IRECT tri = IRECT(bounds.R + (PAD * 0.5f) - bounds.H(), bounds.T, bounds.R + (PAD * 0.5f), bounds.B);
381  trisize = (tri.R - tri.L) * 0.5f;
382  halftri = trisize * 0.5f;
383  ax = tri.R - trisize;
384  ay = tri.MH() + halftri;
385  bx = ax;
386  by = ay - trisize;
387  cx = tri.R;
388  cy = tri.MH();
389  }
390  else
391  {
392  IRECT tri = IRECT(bounds.L - (PAD * 0.5f), bounds.T, bounds.L - (PAD * 0.5f) + bounds.H(), bounds.B);
393  trisize = (tri.R - tri.L) * 0.5f;
394  halftri = trisize * 0.5f;
395  ax = tri.L + trisize;
396  ay = tri.MH() + halftri;
397  bx = ax;
398  by = ay - trisize;
399  cx = tri.L;
400  cy = tri.MH();
401  }
402  g.FillTriangle(sel ? mItemMouseoverColor : mItemColor, ax, ay, bx, by, cx, cy, pBlend);
403 }
404 
405 void IPopupMenuControl::DrawUpArrow(IGraphics& g, const IRECT& bounds, bool sel, IBlend* pBlend)
406 {
407  IRECT tri = IRECT(bounds.MW() - (bounds.H() * 0.5f), bounds.T, bounds.MW() + (bounds.H() * 0.5f), bounds.B);
408  float trisize = (tri.R - tri.L) * 0.6f;
409  float halftri = trisize * 0.5f;
410  float ax = tri.MW() - halftri;
411  float ay = tri.MH() + halftri;
412  float bx = ax + trisize;
413  float by = ay;
414  float cx = tri.MW();
415  float cy = ay - trisize;
416  g.FillTriangle(sel ? mItemMouseoverColor : mItemColor, ax, ay, bx, by, cx, cy, pBlend);
417 }
418 
419 void IPopupMenuControl::DrawDownArrow(IGraphics& g, const IRECT& bounds, bool sel, IBlend* pBlend)
420 {
421  IRECT tri = IRECT(bounds.MW() - (bounds.H() * 0.5f), bounds.T, bounds.MW() + (bounds.H() * 0.5f), bounds.B);
422  float trisize = (tri.R - tri.L) * 0.6f;
423  float halftri = trisize * 0.5f;
424  float ax = tri.MW() - halftri;
425  float ay = tri.MH() - halftri;
426  float bx = ax + trisize;
427  float by = ay;
428  float cx = tri.MW();
429  float cy = ay + trisize;
430  g.FillTriangle(sel ? mItemMouseoverColor : mItemColor, ax, ay, bx, by, cx, cy, pBlend);
431 }
432 
433 void IPopupMenuControl::DrawSeparator(IGraphics& g, const IRECT& bounds, IBlend* pBlend)
434 {
435  if(pBlend->mWeight > 0.9)
436  g.FillRect(mSeparatorColor, bounds, &BLEND_25);
437 }
438 
440 {
441  mMenu = &menu;
442 
443  for (int i = 0; i< mMenu->NItems(); i++)
444  {
445  if (mMenu->GetItem(i)->GetSubmenu())
446  {
447  mMenuHasSubmenu = true;
448  break;
449  }
450  else mMenuHasSubmenu = false;
451  }
452 
453  if(mMaxBounds.W() == 0)
454  mMaxBounds = GetUI()->GetBounds();
455 
456  if(GetState() == kCollapsed)
457  Expand(bounds);
458 }
459 
460 IRECT IPopupMenuControl::GetLargestCellRectForMenu(IPopupMenu& menu, float x, float y) const
461 {
462  IRECT span;
463 
464  for (auto i = 0; i < menu.NItems(); ++i)
465  {
466  IPopupMenu::Item* pItem = menu.GetItem(i);
467  IRECT textBounds;
468 
469  const IGraphics* pGraphics = GetUI();
470 
471  pGraphics->MeasureText(mText, pItem->GetText(), textBounds);
472  span = span.Union(textBounds);
473  }
474 
475  span.HPad(TEXT_HPAD); // add some padding because we don't want to be flush to the edges
476  span.Pad(TICK_SIZE, 0, ARROW_SIZE, 0);
477 
478  return IRECT(x, y, x + span.W(), y + span.H());
479 }
480 
481 void IPopupMenuControl::GetPanelDimensions(IPopupMenu&menu, float& width, float& height) const
482 {
483  IRECT maxCell = GetLargestCellRectForMenu(menu, 0, 0);
484 
485  int numItems = menu.NItems();
486  int numSeparators = 0;
487  float panelHeight = 0.f;
488 
489  for (auto i = 0; i < numItems; ++i)
490  {
491  IPopupMenu::Item* pItem = menu.GetItem(i);
492  if (pItem->GetIsSeparator())
493  {
494  numSeparators += 1;
495  }
496  }
497  float numCells = float(numItems - numSeparators);
498  panelHeight = (numCells * maxCell.H()) + (numSeparators * mSeparatorSize) + ((numItems - 1) * mCellGap);
499 
500  width = maxCell.W();
501  height = panelHeight;
502 }
503 
505 {
506  float calloutSpace =0.f;
507 
508  if(mCallOut)
509  {
510  calloutSpace = CALLOUT_SPACE;
511  }
512 
513  for(auto i = 0; i < mActiveMenuPanel->mCellBounds.GetSize(); i++)
514  {
515  IRECT* pCellRect = mActiveMenuPanel->mCellBounds.Get(i);
516  IPopupMenu::Item* pMenuItem = mActiveMenuPanel->mMenu.GetItem(i);
517  IPopupMenu* pSubMenu = pMenuItem->GetSubmenu();
518 
519  if(pCellRect == mMouseCellBounds)
520  {
521  if(pSubMenu != nullptr)
522  {
523  MenuPanel* pMenuPanelForThisMenu = nullptr;
524 
525  for (auto mr = 0; mr < mMenuPanels.GetSize(); mr++)
526  {
527  MenuPanel* pMenuPanel = mMenuPanels.Get(mr);
528 
529  if(&pMenuPanel->mMenu == pSubMenu)
530  {
531  pMenuPanelForThisMenu = pMenuPanel;
532  pMenuPanel->mShouldDraw = true;
533  }
534  else
535  pMenuPanel->mShouldDraw = false;
536  }
537 
538  if(pMenuItem->GetEnabled())
539  mActiveMenuPanel->mHighlightedCell = pCellRect;
540  else
541  mActiveMenuPanel->mHighlightedCell = nullptr;
542 
543  // There is no MenuPanel for this menu, make a new one
544  if(pMenuPanelForThisMenu == nullptr) {
545 
546  float panelWidth = 0.f;
547  float panelHeight = 0.f;
548 
549  GetPanelDimensions(*pSubMenu, panelWidth, panelHeight);
550 
551  float minT = mMaxBounds.T + mDropShadowSize;
552  float maxB = mMaxBounds.B - panelHeight - (mDropShadowSize * 2.f);
553  float maxR = mMaxBounds.R - panelWidth - (mDropShadowSize * 2.f);
554  float minL = mMaxBounds.L + mDropShadowSize;
555 
556  float x = 0.f;
557  float y = 0.f;
558 
559  if (mCalloutArrowDir == kSouth)
560  {
561  y = pCellRect->T - PAD;
562  if (y > maxB) y = maxB;
563  if ( y <= minT) y = minT;
564  }
565 
566  if (mCalloutArrowDir == kNorth)
567  {
568  y = (pCellRect->T - (PAD / 2.f) - panelHeight) + (mCellGap * 2.f) + mDropShadowSize;
569  if ( y <= minT) y = minT;
570  if (y > maxB) y = maxB;
571  }
572 
573  if (mSubmenuOnRight) x = pCellRect->R + PAD + calloutSpace;
574  else x = pCellRect->L - PAD - calloutSpace - panelWidth - mDropShadowSize;
575  if ( x <= minL ) x = minL;
576  if ( x > maxR ) x = maxR;
577 
578  pMenuPanelForThisMenu = mMenuPanels.Add(new MenuPanel(*this, *pSubMenu, x, y, mMenuPanels.Find(mActiveMenuPanel)));
579  }
580 
581  for (auto mr = 0; mr < mMenuPanels.GetSize(); mr++)
582  {
583  MenuPanel* pMenuPanel = mMenuPanels.Get(mr);
584 
585  if(pMenuPanel->mShouldDraw)
586  {
587  IRECT drawRECT = pMenuPanel->mRECT;
588  IRECT targetRECT = pMenuPanel->mTargetRECT;
589 
590  MenuPanel* pParentMenuPanel = mMenuPanels.Get(pMenuPanel->mParentIdx);
591 
592  while (pParentMenuPanel != nullptr)
593  {
594  pParentMenuPanel->mShouldDraw = true;
595  drawRECT = drawRECT.Union(pParentMenuPanel->mTargetRECT);
596  targetRECT = targetRECT.Union(pParentMenuPanel->mRECT);
597  pParentMenuPanel = mMenuPanels.Get(pParentMenuPanel->mParentIdx);
598  mSubMenuOpened = true;
599 
600  if (mSubmenuOnRight) mSubMenuCalloutArrowBounds = IRECT(pCellRect->R + PAD , pCellRect->MH() - (calloutSpace / 2.f) , pCellRect->R + PAD + calloutSpace, pCellRect->MH() + (calloutSpace / 2.f));
601  else mSubMenuCalloutArrowBounds = IRECT(pCellRect->L - PAD - calloutSpace, pCellRect->MH() - (calloutSpace / 2.f), pCellRect->L - PAD, pCellRect->MH() + (calloutSpace / 2.f));
602  }
603 
604  SetTargetRECT(mTargetRECT.Union(targetRECT));
605  SetRECT(mRECT.Union(drawRECT));
606 
607  if(mAppearingMenuPanel != pMenuPanel)
608  {
609  mState = kSubMenuAppearing;
610  mAppearingMenuPanel = pMenuPanelForThisMenu;
611  }
612 
613  SetDirty(true);
614  break; // STOP LOOP!
615 
616  }
617  }
618  }
619  else // (pSubMenu == nullptr)
620  {
621  for (auto mr = 0; mr < mMenuPanels.GetSize(); mr++)
622  {
623  MenuPanel* pMenuPanel = mMenuPanels.Get(mr);
624 
625  if(pMenuPanel->mParentIdx == mMenuPanels.Find(mActiveMenuPanel))
626  {
627  mActiveMenuPanel->mHighlightedCell = nullptr;
628  pMenuPanel->mShouldDraw = false;
629  mSubMenuOpened = false;
630  }
631  }
632  }
633  }
634  }
635 
636  SetDirty(false);
637 }
638 
639 void IPopupMenuControl::Expand(const IRECT& anchorArea)
640 {
641  Hide(false);
642  mState = kExpanding;
643  GetUI()->UpdateTooltips(); //will disable
644 
645  mMenuPanels.Empty(true);
646 
647  mAnchorArea = anchorArea;
648 
649  float panelWidth = 0.f;
650  float panelHeight = 0.f;
651 
652  GetPanelDimensions(*mMenu, panelWidth, panelHeight);
653 
654  float minT = mMaxBounds.T + mDropShadowSize;
655  float maxB = mMaxBounds.B - panelHeight - (mDropShadowSize * 2.f);
656  float maxR = mMaxBounds.R - panelWidth - (mDropShadowSize * 2.f);
657  float minL = mMaxBounds.L + mDropShadowSize;
658 
659  float x = 0.f;
660  float y = 0.f;
661 
662  float calloutSpace =0.f;
663  if(mCallOut)
664  {
665  calloutSpace = CALLOUT_SPACE;
666  }
667 
668  if ( anchorArea.MH() <= mMaxBounds.MH())
669  {
670  y = anchorArea.MH() - (calloutSpace + mText.mSize);
671  }
672  else y = (anchorArea.MH() - panelHeight) + (calloutSpace + mText.mSize);
673 
674  if ( anchorArea.MW() <= mMaxBounds.MW() )
675  {
676  x = anchorArea.R + calloutSpace;
677  mCalloutArrowBounds = IRECT( anchorArea.R, anchorArea.MH() - (calloutSpace / 2.f), x, anchorArea.MH() + (calloutSpace / 2.f) );
678  mCalloutArrowDir = kEast;
679  }
680  else
681  {
682  x = anchorArea.L - calloutSpace - panelWidth - mDropShadowSize;
683  mCalloutArrowBounds = IRECT( anchorArea.L - calloutSpace, anchorArea.MH() - (calloutSpace / 2.f), anchorArea.L, anchorArea.MH() + (calloutSpace / 2.f) );
684  mCalloutArrowDir = kWest;
685  }
686 
687  if( y <= minT || y > maxB || x <= minL || x > maxR ) // if we're going off the top, right, left, or bottom
688  {
689  if ( (y <= minT || x <= minL || x > maxR) && anchorArea.MH() <= mMaxBounds.MH() )
690  {
691  x = anchorArea.MW() - (panelWidth / 2.f);
692  y = anchorArea.B + calloutSpace;
693  mCalloutArrowBounds = IRECT(anchorArea.MW() - (calloutSpace/2.f), anchorArea.B, anchorArea.MW() + (calloutSpace/2.f), anchorArea.B + calloutSpace);
694  mCalloutArrowDir = kSouth;
695 
696  if ( y > maxB )
697  {
698  if ( anchorArea.MW() <= mMaxBounds.MW() )
699  {
700  x = anchorArea.R + calloutSpace;
701  mCalloutArrowBounds = IRECT( anchorArea.R, anchorArea.MH() - (calloutSpace / 2.f), x, anchorArea.MH() + (calloutSpace / 2.f) );
702  mCalloutArrowDir = kEast;
703  }
704  else
705  {
706  x = anchorArea.L - calloutSpace - panelWidth - mDropShadowSize;
707  mCalloutArrowBounds = IRECT( anchorArea.L - calloutSpace, anchorArea.MH() - (calloutSpace / 2.f), anchorArea.L, anchorArea.MH() + (calloutSpace / 2.f) );
708  mCalloutArrowDir = kWest;
709  }
710  y = maxB;
711  }
712  }
713 
714  if ( (y > maxB || x <= minL || x > maxR) && anchorArea.MH() > mMaxBounds.MH() )
715  {
716  x = anchorArea.MW() - (panelWidth / 2.f);
717  y = anchorArea.T - calloutSpace - panelHeight - mDropShadowSize;
718  mCalloutArrowBounds = IRECT(anchorArea.MW() - (calloutSpace/2.f), anchorArea.T - calloutSpace, anchorArea.MW() + (calloutSpace/2.f), anchorArea.T);
719  mCalloutArrowDir = kNorth;
720 
721  if ( y <= minT )
722  {
723  if ( anchorArea.MW() <= mMaxBounds.MW() )
724  {
725  x = anchorArea.R + calloutSpace;
726  mCalloutArrowBounds = IRECT( anchorArea.R, anchorArea.MH() - (calloutSpace / 2.f), x, anchorArea.MH() + (calloutSpace / 2.f) );
727  mCalloutArrowDir = kEast;
728  }
729  else
730  {
731  x = anchorArea.L - calloutSpace - panelWidth - mDropShadowSize;
732  mCalloutArrowBounds = IRECT( anchorArea.L - calloutSpace, anchorArea.MH() - (calloutSpace / 2.f), anchorArea.L, anchorArea.MH() + (calloutSpace / 2.f) );
733  mCalloutArrowDir = kWest;
734  }
735  y = minT;
736  }
737  }
738 
739  if ( x <= minL ) x = minL;
740  if ( x > maxR ) x = maxR;
741  if ( y <= minT ) y = minT;
742  if ( y > maxB ) y = maxB;
743  }
744 
745  if (mForcedSouth)
746  {
747  if (anchorArea.B + calloutSpace <= maxB)
748  {
749  x = anchorArea.MW() - (panelWidth / 2.f);
750  y = anchorArea.B + calloutSpace;
751  mCalloutArrowBounds = IRECT(anchorArea.MW() - (calloutSpace/2.f), anchorArea.B, anchorArea.MW() + (calloutSpace/2.f), anchorArea.B + calloutSpace);
752  mCalloutArrowDir = kSouth;
753  }
754  if ( x <= minL ) x = minL;
755  if ( x > maxR ) x = maxR;
756  }
757 
758  if (mMenuHasSubmenu)
759  {
760  float shiftfactor;
761  if ( anchorArea.MW() <= mMaxBounds.MW() )
762  {
763  mSubmenuOnRight = true;
764  shiftfactor = -1.f;
765  }
766  else
767  {
768  mSubmenuOnRight = false;
769  shiftfactor = 1.f;
770  }
771  x = (anchorArea.MW() - (panelWidth / 2.f)) + (mMenuShift * shiftfactor);
772  if ( x <= minL ) x = minL;
773  if ( x > maxR ) x = maxR;
774 
775  if (anchorArea.T - mMaxBounds.T <= mMaxBounds.B - anchorArea.B)
776  {
777  y = anchorArea.B + calloutSpace;
778  if ( y > maxB ) y = maxB;
779  if ( y <= minT ) y = minT;
780  mCalloutArrowBounds = IRECT(anchorArea.MW() - (calloutSpace/2.f), anchorArea.B, anchorArea.MW() + (calloutSpace/2.f), anchorArea.B + calloutSpace);
781  mCalloutArrowDir = kSouth;
782  }
783  else
784  {
785  y = anchorArea.T - calloutSpace - panelHeight - mDropShadowSize;
786  if ( y <= minT ) y = minT;
787  if ( y > maxB ) y = maxB;
788  mCalloutArrowBounds = IRECT(anchorArea.MW() - (calloutSpace/2.f), anchorArea.T - calloutSpace, anchorArea.MW() + (calloutSpace/2.f), anchorArea.T);
789  mCalloutArrowDir = kNorth;
790  }
791  }
792 
793  mActiveMenuPanel = mAppearingMenuPanel = mMenuPanels.Add(new MenuPanel(*this, *mMenu, x, y, -1));
794 
795  SetTargetRECT(mActiveMenuPanel->mTargetRECT);
796  SetRECT(mActiveMenuPanel->mRECT);
797 
798  SetDirty(true); // triggers animation
799 }
800 
801 void IPopupMenuControl::CollapseEverything()
802 {
803  IPopupMenu* pClickedMenu = &mActiveMenuPanel->mMenu;
804 
805  pClickedMenu->SetChosenItemIdx(-1);
806 
807  for(auto i = 0; i < mActiveMenuPanel->mCellBounds.GetSize(); i++)
808  {
809  IRECT* pR = mActiveMenuPanel->mCellBounds.Get(i);
810 
811  if(mMouseCellBounds == pR)
812  {
813  int itemChosen = mActiveMenuPanel->mScrollItemOffset + i;
814  IPopupMenu::Item* pItem = pClickedMenu->GetItem(itemChosen);
815 
816  if(pItem->GetIsChoosable())
817  {
818  pClickedMenu->SetChosenItemIdx(itemChosen);
819  mActiveMenuPanel->mClickedCell = pR;
820  }
821  }
822  }
823 
824  if(pClickedMenu->GetFunction())
825  pClickedMenu->ExecFunction();
826 
827  GetUI()->SetControlValueAfterPopupMenu(pClickedMenu);
828 
829  mSubMenuOpened = false;
830  mActiveMenuPanel = nullptr;
831 
832  mState = kFlickering;
833  Hide(true);
834  SetDirty(true); // triggers animation
835 }
836 
837 void IPopupMenuControl::OnEndAnimation()
838 {
839 // DBGMSG("state %i\n", mState);
840 
841  if(mState == kExpanding)
842  {
843  for (auto i = 0; i < mMenuPanels.GetSize(); i++) {
844  mMenuPanels.Get(i)->mBlend.mWeight = mOpacity;
845  }
846 
847  mState = kExpanded;
848  }
849  else if (mState == kFlickering)
850  {
851  mState = kCollapsing;
852  SetDirty(true); // triggers animation again
853  return; // don't cancel animation
854  }
855  else if (mState == kSubMenuAppearing)
856  {
857  for (auto i = 0; i < mMenuPanels.GetSize(); i++) {
858  mMenuPanels.Get(i)->mBlend.mWeight = mOpacity;
859  }
860 
861  mState = kExpanded;
862  }
863  else if(mState == kCollapsing)
864  {
865  mTargetRECT = mSpecifiedCollapsedBounds;
866 
867  for (auto i = 0; i < mMenuPanels.GetSize(); i++) {
868  mMenuPanels.Get(i)->mBlend.mWeight = 0.;
869  }
870 
871  mState = kIdling;
872  mMouseCellBounds = nullptr;
873  mAnchorArea = IRECT();
874 
875  SetDirty(true); // triggers animation again
876  return; // don't cancel animation
877  }
878  else if(mState == kIdling)
879  {
880  GetUI()->UpdateTooltips(); // will enable the tooltips
881 
882  mMenuPanels.Empty(true);
883  mRECT = mSpecifiedCollapsedBounds;
884  mState = kCollapsed;
885  }
886 
887  IControl::OnEndAnimation();
888 }
889 
890 IPopupMenuControl::MenuPanel::MenuPanel(IPopupMenuControl& control, IPopupMenu& menu, float x, float y, int parentIdx)
891 : mMenu(menu)
892 , mParentIdx(parentIdx)
893 {
894  mSingleCellBounds = control.GetLargestCellRectForMenu(menu, x, y);
895 
896  float left = x + control.PAD;
897  float top = y + control.PAD;
898 
899  // cell height can change depending on if cell is a separator or not
900  auto GetIncrements = [&](IPopupMenu::Item* pMenuItem, float& incX, float& incY)
901  {
902  incX = CellWidth();
903 
904  if (pMenuItem->GetIsSeparator())
905  incY = control.mSeparatorSize;
906  else
907  incY = CellHeight();
908  };
909 
910  for (auto i = 0; i < menu.NItems(); ++i)
911  {
912  IPopupMenu::Item* pMenuItem = menu.GetItem(i);
913  float right, bottom;
914  float toAddX, toAddY; // the increments, different depending on item type
915  bool newColumn = false;
916 
917  GetIncrements(pMenuItem, toAddX, toAddY);
918 
919  if(control.mMaxColumnItems > 0 && i > 1)
920  newColumn = !(i % control.mMaxColumnItems);
921 
922  if((top + toAddY + control.PAD) > control.mMaxBounds.B || newColumn) // it's gonna go off the bottom
923  {
924  if(control.mScrollIfTooBig)
925  {
926  const float maxTop = control.mMaxBounds.T + control.PAD + control.mDropShadowSize;
927  const float maxBottom = control.mMaxBounds.B - control.PAD;// - control.mDropShadowSize;
928  const float maxH = (maxBottom - maxTop);
929  mScrollMaxRows = static_cast<int>(maxH / (CellHeight() + control.mCellGap)); // maximum cell rows (full height, not with separators)
930 
931  // clear everything added so far
932  mCellBounds.Empty(true);
933  GetIncrements(menu.GetItem(0), toAddX, toAddY);
934 
935  if(menu.NItems() < mScrollMaxRows)
936  {
937  top = (y + control.PAD + CellHeight()) - (menu.NItems() * CellHeight());
938 
939  for (auto r = 0; r < menu.NItems(); r++)
940  {
941  GetIncrements(menu.GetItem(r), toAddX, toAddY);
942  bottom = top + toAddY;
943  right = left + toAddX;
944  mCellBounds.Add(new IRECT(left, top, right, bottom));
945  top = bottom + control.mCellGap;
946  bottom = top + toAddY;
947  }
948  }
949  else
950  {
951  mScroller = true;
952  top = maxTop;
953 
954  for (auto r = 0; r < mScrollMaxRows; r++)
955  {
956  GetIncrements(menu.GetItem(r), toAddX, toAddY);
957  bottom = top + toAddY;
958  right = left + toAddX;
959  mCellBounds.Add(new IRECT(left, top, right, bottom));
960  top = bottom + control.mCellGap;
961  bottom = top + toAddY;
962  }
963  }
964  break;
965  }
966  else // new column
967  {
968  left += mSingleCellBounds.W() + control.mCellGap;
969  top = mSingleCellBounds.T + control.PAD;
970  }
971  }
972 
973  right = left + toAddX;
974  bottom = top + toAddY;
975 
976  mCellBounds.Add(new IRECT(left, top, right, bottom));
977  top = bottom + control.mCellGap;
978  }
979 
980  IRECT span;
981 
982  if(mCellBounds.GetSize())
983  {
984  span = *mCellBounds.Get(0);
985 
986  for(auto i = 1; i < mCellBounds.GetSize(); i++)
987  {
988  span = span.Union(*mCellBounds.Get(i));
989  }
990  }
991 
992  if (control.mSpecifiedExpandedBounds.W())
993  {
994  mTargetRECT = control.mSpecifiedExpandedBounds.GetPadded(control.PAD); // pad the unioned cell rects)
995  mRECT = control.mSpecifiedExpandedBounds.GetPadded(control.mDropShadowSize + control.PAD);
996  }
997  else
998  {
999  mTargetRECT = span.GetPadded(control.PAD); // pad the unioned cell rects)
1000  mRECT = span.GetPadded(control.mDropShadowSize + control.PAD);
1001  }
1002 }
1003 
1004 IPopupMenuControl::MenuPanel::~MenuPanel()
1005 {
1006  mCellBounds.Empty(true);
1007 }
1008 
1009 IRECT* IPopupMenuControl::MenuPanel::HitTestCells(float x, float y) const
1010 {
1011  for(auto i = 0; i < mCellBounds.GetSize(); i++)
1012  {
1013  IRECT* pR = mCellBounds.Get(i);
1014  if(pR->Contains(x, y) && mMenu.GetItem(i)->GetEnabled())
1015  return pR;
1016  }
1017  return nullptr;
1018 }
virtual void DrawUpArrow(IGraphics &g, const IRECT &bounds, bool sel, IBlend *pBlend)
Override this method to change the way a scroll up cell&#39;s arrow is drawn.
bool Contains(const IRECT &rhs) const
Returns true if this IRECT completely contains rhs.
float MW() const
float MH() const
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 OnMouseDown(float x, float y, const IMouseMod &mod)
Implement this method to respond to a mouse down event on this control.
Definition: IControl.cpp:250
Used to manage composite/blend operations, independent of draw class/platform.
Used to manage mouse modifiers i.e.
virtual void Hide(bool hide)
Shows or hides the IControl.
Definition: IControl.cpp:237
void OnMouseDown(float x, float y, const IMouseMod &mod) override
Implement this method to respond to a mouse down event on this control.
void OnMouseOut() override
Implement this method to respond to a mouseout event on this control.
IControl * SetActionFunction(IActionFunction actionFunc)
Set an Action Function for this control.
Definition: IControl.h:201
void HPad(float padding)
Pad this IRECT in the X-axis N.B.
A class to specify an item of a pop up menu.
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
virtual void DrawTick(IGraphics &g, const IRECT &bounds, const IPopupMenu::Item *pItem, bool sel, IBlend *pBlend)
Override this method to change the way a checked cell&#39;s "tick" is drawn.
float R
Right side of the rectangle (X + W)
void CreatePopupMenu(IPopupMenu &menu, const IRECT &anchorArea)
Call this to create a pop-up menu.
void SetRECT(const IRECT &bounds)
Set the rectangular draw area for this control, within the graphics context.
Definition: IControl.h:309
float H() const
double GetAnimationProgress() const
Get the progress in a control&#39;s animation, in the range 0-1.
Definition: IControl.cpp:429
EPopupState GetState() const
virtual void DrawPanelBackground(IGraphics &g, MenuPanel *panel)
Override this method to change the background of the pop-up menu panel.
float W() const
A base control for a pop-up menu/drop-down list that stays within the bounds of the IGraphics context...
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...
void OnMouseWheel(float x, float y, const IMouseMod &mod, float d) override
Implement this method to respond to a mouse wheel event on this control.
virtual void DrawSubMenuCalloutArrow(IGraphics &g, const IRECT &bounds, IBlend *pBlend)
Override this method to change the way Callout arrows for Submenus are drawn.
virtual void DrawCellText(IGraphics &g, const IRECT &bounds, const IPopupMenu::Item *pItem, bool sel, IBlend *pBlend)
Override this method to change the way a cell&#39;s text is drawn.
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
A base control for a pop-up menu/drop-down list that stays within the bounds of the IGraphics context...
virtual void FillRoundRect(const IColor &color, const IRECT &bounds, float cornerRadius=5.f, const IBlend *pBlend=0)
Fill a rounded rectangle with a color.
Definition: IGraphics.cpp:2554
virtual void FillRect(const IColor &color, const IRECT &bounds, const IBlend *pBlend=0)
Fill a rectangular region of the graphics context with a color.
Definition: IGraphics.cpp:2547
void CalculateMenuPanels(float x, float y)
Called as the user moves the mouse around, in order to work out which menu panel should be on the scr...
virtual void DrawPanelShadow(IGraphics &g, MenuPanel *panel)
Override this method to change the shadow of the pop-up menu panel.
virtual void DrawSubMenuArrow(IGraphics &g, const IRECT &bounds, const IPopupMenu::Item *pItem, bool sel, IBlend *pBlend)
Override this method to change the way a submenu cell&#39;s arrow is drawn.
void Draw(IGraphics &g) override
Draw the control to the graphics context.
IRECT Union(const IRECT &rhs) const
Create a new IRECT that is a union of this IRECT and rhs.
virtual void DrawCalloutArrow(IGraphics &g, const IRECT &bounds, IBlend *pBlend)
Override this method to change the way Callout arrows are drawn.
void Pad(float padding)
Pad this IRECT N.B.
void OnMouseOver(float x, float y, const IMouseMod &mod) override
Implement this method to respond to a mouseover event on this control.
void SetTargetRECT(const IRECT &bounds)
Set the rectangular mouse tracking target area, within the graphics context for this control...
Definition: IControl.h:317
IGraphics * GetUI()
Definition: IControl.h:452
float L
Left side of the rectangle (X)
IRECT GetHPadded(float padding) const
Get a copy of this IRECT padded in the X-axis N.B.
IRECT GetCentredInside(const IRECT &sr) const
Get a rectangle the size of sr but with the same center point as this rectangle.
IRECT GetPadded(float padding) const
Get a copy of this IRECT with each value padded by padding N.B.
virtual void DrawSeparator(IGraphics &g, const IRECT &bounds, IBlend *pBlend)
Override this method to change the way a cell separator is drawn.
virtual void DrawDownArrow(IGraphics &g, const IRECT &bounds, bool sel, IBlend *pBlend)
Override this method to change the way a scroll Down cell&#39;s arrow is drawn.
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 SetControlValueAfterPopupMenu(IPopupMenu *pMenu)
Called by PopupMenuControl in order to update a control with a new value after returning from the non...
Definition: IGraphics.cpp:252
IPopupMenuControl(int paramIdx=kNoParameter, IText text=IText(16), IRECT collapsedBounds=IRECT(), IRECT expandedBounds=IRECT())
Create a new IPopupMenuControl.
float T
Top of the rectangle (Y)
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:2540
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
virtual void DrawCellBackground(IGraphics &g, const IRECT &bounds, const IPopupMenu::Item *pItem, bool sel, IBlend *pBlend)
Override this method to change the way a cell&#39;s background is drawn.
float B
Bottom of the rectangle (Y + H)
virtual void UpdateTooltips()=0
Call this if you modify control tool tips at runtime.