iPlug 2: IPopupMenuControl.cpp Source File

1

2

3

4

5

6

7

8

9

10

18

19#ifdef IGRAPHICS_NANOVG

20#include "nanovg.h"

21#endif

22

23using namespace iplug;

24using namespace igraphics;

25

27: IControl(collapsedBounds, paramIdx)

28, mSpecifiedCollapsedBounds(collapsedBounds)

29, mSpecifiedExpandedBounds(expandedBounds)

30{

32

33 int duration = DEFAULT_ANIMATION_DURATION;

34

35 if(mState == kSubMenuAppearing)

36 duration = DEFAULT_ANIMATION_DURATION * 2;

37

38#pragma mark animations

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)

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 {

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--;

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

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 {

167 }

168 }

169}

170

172{

174 {

175 mMouseCellBounds = mActiveMenuPanel->HitTestCells(x, y);

176 CollapseEverything();

177 }

178 else

180}

181

183{

184 if(mActiveMenuPanel)

185 {

186 mMouseCellBounds = mActiveMenuPanel->HitTestCells(x, y);

188 }

189}

190

192{

193 mMouseCellBounds = mActiveMenuPanel->HitTestCells(x, y);

194

195

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

235}

236

238{

239 mMouseCellBounds = nullptr;

240}

241

243{

244

245

246

247

248

249

250

251

252

253

254

255

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

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

344{

345 if(sel)

347}

348

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

369{

370 IRECT tickRect = IRECT(bounds.L, bounds.T, bounds.L + TICK_SIZE, bounds.B).GetCentredInside(TICK_SIZE);

372}

373

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

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

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)

455

457 Expand(bounds);

458}

459

460IRECT IPopupMenuControl::GetLargestCellRectForMenu(IPopupMenu& menu, float x, float y) const

461{

463

464 for (auto i = 0; i < menu.NItems(); ++i)

465 {

467 IRECT textBounds;

468

470

471 pGraphics->MeasureText(mText, pItem->GetText(), textBounds);

472 span = span.Union(textBounds);

473 }

474

475 span.HPad(TEXT_HPAD);

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 {

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

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

606

607 if(mAppearingMenuPanel != pMenuPanel)

608 {

609 mState = kSubMenuAppearing;

610 mAppearingMenuPanel = pMenuPanelForThisMenu;

611 }

612

614 break;

615

616 }

617 }

618 }

619 else

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

637}

638

639void IPopupMenuControl::Expand(const IRECT& anchorArea)

640{

642 mState = kExpanding;

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 )

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

796 SetRECT(mActiveMenuPanel->mRECT);

797

798 SetDirty(true);

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;

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

828

829 mSubMenuOpened = false;

830 mActiveMenuPanel = nullptr;

831

832 mState = kFlickering;

834 SetDirty(true);

835}

836

837void IPopupMenuControl::OnEndAnimation()

838{

839

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);

853 return;

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);

876 return;

877 }

878 else if(mState == kIdling)

879 {

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

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 {

913 float right, bottom;

914 float toAddX, toAddY;

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)

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;

928 const float maxH = (maxBottom - maxTop);

929 mScrollMaxRows = static_cast<int>(maxH / (CellHeight() + control.mCellGap));

930

931

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

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

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);

995 mRECT = control.mSpecifiedExpandedBounds.GetPadded(control.mDropShadowSize + control.PAD);

996 }

997 else

998 {

999 mTargetRECT = span.GetPadded(control.PAD);

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.

virtual void OnMouseDown(float x, float y, const IMouseMod &mod)

Implement this method to respond to a mouse down event on this control.

virtual void Hide(bool hide)

Shows or hides the IControl.

void SetTargetRECT(const IRECT &bounds)

Set the rectangular mouse tracking target area, within the graphics context for this control.

double GetAnimationProgress() const

Get the progress in a control's animation, in the range 0-1.

void SetRECT(const IRECT &bounds)

Set the rectangular draw area for this control, within the graphics context.

virtual void SetDirty(bool triggerAction=true, int valIdx=kNoValIdx)

Mark the control as dirty, i.e.

IControl * SetActionFunction(IActionFunction actionFunc)

Set an Action Function for this control.

void SetAnimation(IAnimationFunction func)

Set the animation function.

The lowest level base class of an IGraphics context.

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.

void SetControlValueAfterPopupMenu(IPopupMenu *pMenu)

Called by PopupMenuControl in order to update a control with a new value after returning from the non...

virtual void FillRect(const IColor &color, const IRECT &bounds, const IBlend *pBlend=0)

Fill a rectangular region of the graphics context with a color.

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.

virtual void UpdateTooltips()=0

Call this if you modify control tool tips at runtime to refresh the platform tooltip state.

virtual void FillRoundRect(const IColor &color, const IRECT &bounds, float cornerRadius=5.f, const IBlend *pBlend=0)

Fill a rounded rectangle with a color.

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.

IRECT GetBounds() const

Returns an IRECT that represents the entire UI bounds This is useful for programatically arranging UI...

virtual float MeasureText(const IText &text, const char *str, IRECT &bounds) const

Measure the rectangular region that some text will occupy.

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.

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.

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,...