iPlug2 - C++ Audio Plug-in Framework
Loading...
Searching...
No Matches
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
23using namespace iplug;
24using namespace igraphics;
25
26IPopupMenuControl::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
77IPopupMenuControl::~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
171void 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
182void 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
191void 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
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
242void 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
343void 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
349void 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
368void 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
374void 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
405void 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
419void 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
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
460IRECT 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
481void 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
639void 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
801void 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
837void 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
890IPopupMenuControl::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
1004IPopupMenuControl::MenuPanel::~MenuPanel()
1005{
1006 mCellBounds.Empty(true);
1007}
1008
1009IRECT* 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}
The lowest level base class of an IGraphics control.
Definition: IControl.h:49
IGraphics * GetUI()
Definition: IControl.h:467
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:252
virtual void Hide(bool hide)
Shows or hides the IControl.
Definition: IControl.cpp:239
void SetTargetRECT(const IRECT &bounds)
Set the rectangular mouse tracking target area, within the graphics context for this control.
Definition: IControl.h:323
double GetAnimationProgress() const
Get the progress in a control's animation, in the range 0-1.
Definition: IControl.cpp:431
void SetRECT(const IRECT &bounds)
Set the rectangular draw area for this control, within the graphics context.
Definition: IControl.h:315
virtual void SetDirty(bool triggerAction=true, int valIdx=kNoValIdx)
Mark the control as dirty, i.e.
Definition: IControl.cpp:198
IControl * SetActionFunction(IActionFunction actionFunc)
Set an Action Function for this control.
Definition: IControl.h:206
void SetAnimation(IAnimationFunction func)
Set the animation function.
Definition: IControl.h:492
The lowest level base class of an IGraphics context.
Definition: IGraphics.h:86
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:670
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:255
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:2569
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:404
virtual void UpdateTooltips()=0
Call this if you modify control tool tips at runtime.
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:2576
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:2562
IRECT GetBounds() const
Returns an IRECT that represents the entire UI bounds This is useful for programatically arranging UI...
Definition: IGraphics.h:1194
virtual float MeasureText(const IText &text, const char *str, IRECT &bounds) const
Measure the rectangular region that some text will occupy.
Definition: IGraphics.cpp:678
A class to specify an item of a pop up menu.
A base control for a pop-up menu/drop-down list that stays within the bounds of the IGraphics context...
void OnMouseOut() override
Implement this method to respond to a mouseout event on this control.
void CreatePopupMenu(IPopupMenu &menu, const IRECT &anchorArea)
Call this to create a pop-up menu.
virtual void DrawUpArrow(IGraphics &g, const IRECT &bounds, bool sel, IBlend *pBlend)
Override this method to change the way a scroll up cell's arrow is drawn.
EPopupState GetState() const
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.
void Draw(IGraphics &g) override
Draw the control to the graphics context.
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...
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.
virtual void DrawSeparator(IGraphics &g, const IRECT &bounds, IBlend *pBlend)
Override this method to change the way a cell separator is drawn
IPopupMenuControl(int paramIdx=kNoParameter, IText text=IText(16), IRECT collapsedBounds=IRECT(), IRECT expandedBounds=IRECT())
Create a new IPopupMenuControl.
void OnMouseOver(float x, float y, const IMouseMod &mod) override
Implement this method to respond to a mouseover event on this control.
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's text is drawn.
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's arrow is drawn.
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's "tick" 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's arrow is drawn.
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's background is drawn.
virtual void DrawPanelBackground(IGraphics &g, MenuPanel *panel)
Override this method to change the background of the pop-up menu panel.
void OnMouseDown(float x, float y, const IMouseMod &mod) override
Implement this method to respond to a mouse down event on this control.
virtual void DrawPanelShadow(IGraphics &g, MenuPanel *panel)
Override this method to change the shadow of the pop-up menu panel.
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 DrawCalloutArrow(IGraphics &g, const IRECT &bounds, IBlend *pBlend)
Override this method to change the way Callout arrows are drawn.
A class for setting the contents of a pop up menu.
Used to manage composite/blend operations, independent of draw class/platform.
Used to manage mouse modifiers i.e.
Used to manage a rectangular area, independent of draw class/platform.
void Pad(float padding)
Pad this IRECT N.B.
IRECT GetCentredInside(const IRECT &sr) const
Get a rectangle the size of sr but with the same center point as this rectangle.
float MH() const
bool Empty() const
float W() const
void HPad(float padding)
Pad this IRECT in the X-axis N.B.
IRECT Union(const IRECT &rhs) const
Create a new IRECT that is a union of this IRECT and rhs.
float H() const
float MW() const
bool Contains(const IRECT &rhs) const
Returns true if this IRECT completely contains rhs.
IRECT GetPadded(float padding) const
Get a copy of this IRECT with each value padded by padding N.B.
IRECT GetHPadded(float padding) const
Get a copy of this IRECT padded in the X-axis N.B.
IText is used to manage font and text/text entry style for a piece of text on the UI,...