From 738f7e87cfe2f6f710c9afb5beb6f3201be5e461 Mon Sep 17 00:00:00 2001 From: EvilDragon Date: Mon, 21 Mar 2022 20:40:24 +0100 Subject: [PATCH] Implement long press touch gesture for OscillatorWaveformDisplay (and derived classes) (#5983) Addresses #5980 --- src/surge-xt/gui/SurgeGUIEditor.cpp | 2 +- .../gui/widgets/OscillatorWaveformDisplay.cpp | 268 ++++++++++-------- .../gui/widgets/OscillatorWaveformDisplay.h | 15 +- src/surge-xt/gui/widgets/WidgetBaseMixin.h | 36 ++- 4 files changed, 199 insertions(+), 122 deletions(-) diff --git a/src/surge-xt/gui/SurgeGUIEditor.cpp b/src/surge-xt/gui/SurgeGUIEditor.cpp index dd559d3a757..9b1a0d33f5a 100644 --- a/src/surge-xt/gui/SurgeGUIEditor.cpp +++ b/src/surge-xt/gui/SurgeGUIEditor.cpp @@ -2217,7 +2217,7 @@ juce::PopupMenu::Options SurgeGUIEditor::popupMenuOptions(const juce::Component where = c->getBounds().getBottomLeft(); } - if (Surge::GUI::isTouchMode(&(synth->storage))) + if (c && Surge::GUI::isTouchMode(&(synth->storage))) where = c->getBounds().getBottomLeft(); return popupMenuOptions(where); diff --git a/src/surge-xt/gui/widgets/OscillatorWaveformDisplay.cpp b/src/surge-xt/gui/widgets/OscillatorWaveformDisplay.cpp index 62b872e3be6..bb0e42818cf 100644 --- a/src/surge-xt/gui/widgets/OscillatorWaveformDisplay.cpp +++ b/src/surge-xt/gui/widgets/OscillatorWaveformDisplay.cpp @@ -526,6 +526,15 @@ void OscillatorWaveformDisplay::populateMenu(juce::PopupMenu &contextMenu, int s createWTMenuItems(contextMenu); } +void OscillatorWaveformDisplay::createWTMenu(const bool useComponentBounds = true) +{ + auto contextMenu = juce::PopupMenu(); + + createWTMenuItems(contextMenu, true, true); + + contextMenu.showMenuAsync(sge->popupMenuOptions(useComponentBounds ? this : nullptr)); +} + void OscillatorWaveformDisplay::createWTMenuItems(juce::PopupMenu &contextMenu, bool centerBold, bool add2D3Dswitch) { @@ -716,6 +725,7 @@ void OscillatorWaveformDisplay::showWavetableMenu() populateMenu(menu, id); auto where = sge->frame->getLocalPoint(this, menuOverlays[0]->getBounds().getBottomLeft()); + menu.showMenuAsync(sge->popupMenuOptions(where)); } } @@ -729,6 +739,8 @@ void OscillatorWaveformDisplay::mouseDown(const juce::MouseEvent &event) return; } + mouseDownLongHold(event); + bool usesWT = uses_wavetabledata(oscdata->type.val.i); if (usesWT) @@ -785,12 +797,7 @@ void OscillatorWaveformDisplay::mouseDown(const juce::MouseEvent &event) { if (event.mods.isPopupMenu()) { - auto contextMenu = juce::PopupMenu(); - - createWTMenuItems(contextMenu, true, true); - - contextMenu.showMenuAsync(sge->popupMenuOptions()); - + createWTMenu(false); return; } @@ -800,6 +807,8 @@ void OscillatorWaveformDisplay::mouseDown(const juce::MouseEvent &event) void OscillatorWaveformDisplay::mouseMove(const juce::MouseEvent &event) { + mouseMoveLongHold(event); + if (supportsCustomEditor()) { auto q = customEditorBox.contains(event.position); @@ -849,6 +858,8 @@ void OscillatorWaveformDisplay::mouseMove(const juce::MouseEvent &event) } } +void OscillatorWaveformDisplay::mouseUp(const juce::MouseEvent &event) { mouseUpLongHold(event); } + std::string OscillatorWaveformDisplay::customEditorActionLabel(bool isActionToOpen) const { if (oscdata->type.val.i == ot_alias && @@ -888,7 +899,12 @@ bool OscillatorWaveformDisplay::supportsCustomEditor() return false; } -struct WaveTable3DEditor : public juce::Component, Surge::GUI::SkinConsumingComponent +struct WaveTable3DEditor; +template <> void LongHoldMixin::onLongHold(); + +struct WaveTable3DEditor : public juce::Component, + public Surge::GUI::SkinConsumingComponent, + public LongHoldMixin { OscillatorStorage *oscdata; SurgeStorage *storage; @@ -1150,14 +1166,11 @@ struct WaveTable3DEditor : public juce::Component, Surge::GUI::SkinConsumingComp void mouseDown(const juce::MouseEvent &event) override { + mouseDownLongHold(event); + if (event.mods.isPopupMenu()) { - auto contextMenu = juce::PopupMenu(); - - parent->createWTMenuItems(contextMenu, true, true); - - contextMenu.showMenuAsync(sge->popupMenuOptions()); - + parent->createWTMenu(false); return; } @@ -1170,9 +1183,18 @@ struct WaveTable3DEditor : public juce::Component, Surge::GUI::SkinConsumingComp } }); } + + void mouseUp(const juce::MouseEvent &event) override { mouseUpLongHold(event); } }; -struct AliasAdditiveEditor : public juce::Component, Surge::GUI::SkinConsumingComponent +template <> void LongHoldMixin::onLongHold() { asT()->parent->createWTMenu(); } + +struct AliasAdditiveEditor; +template <> void LongHoldMixin::onLongHold(); + +struct AliasAdditiveEditor : public juce::Component, + public Surge::GUI::SkinConsumingComponent, + public LongHoldMixin { AliasAdditiveEditor(SurgeStorage *s, OscillatorStorage *osc, SurgeGUIEditor *ed) : storage(s), oscdata(osc), sge(ed) @@ -1288,148 +1310,154 @@ struct AliasAdditiveEditor : public juce::Component, Surge::GUI::SkinConsumingCo g.drawLine(0.f, halfHeight, getWidth(), halfHeight); } - void mouseDown(const juce::MouseEvent &event) override + void createOptionsMenu(const bool useComponentBounds = true) { - if (event.mods.isPopupMenu()) + auto contextMenu = juce::PopupMenu(); + { - auto contextMenu = juce::PopupMenu(); + auto msurl = SurgeGUIEditor::helpURLForSpecial(storage, "alias-shape"); + auto hurl = SurgeGUIEditor::fullyResolvedHelpURL(msurl); + auto tc = std::make_unique( + "Alias Additive Options", hurl); - { - auto msurl = SurgeGUIEditor::helpURLForSpecial(storage, "alias-shape"); - auto hurl = SurgeGUIEditor::fullyResolvedHelpURL(msurl); - auto tc = std::make_unique( - "Alias Additive Options", hurl); + tc->setSkin(skin, associatedBitmapStore); - tc->setSkin(skin, associatedBitmapStore); + contextMenu.addCustomItem(-1, std::move(tc)); - contextMenu.addCustomItem(-1, std::move(tc)); + contextMenu.addSeparator(); + } - contextMenu.addSeparator(); - } + { + auto action = [this]() { + for (int qq = 0; qq < AliasOscillator::n_additive_partials; ++qq) + { + oscdata->extraConfig.data[qq] = (qq == 0) ? 1 : 0; + } - { - auto action = [this]() { - for (int qq = 0; qq < AliasOscillator::n_additive_partials; ++qq) - { - oscdata->extraConfig.data[qq] = (qq == 0) ? 1 : 0; - } + repaint(); + }; - repaint(); - }; + contextMenu.addItem("Sine", action); + } - contextMenu.addItem("Sine", action); - } + { + auto action = [this]() { + for (int qq = 0; qq < AliasOscillator::n_additive_partials; ++qq) + { + oscdata->extraConfig.data[qq] = (qq % 2 == 0) * 1.f / ((qq + 1) * (qq + 1)); - { - auto action = [this]() { - for (int qq = 0; qq < AliasOscillator::n_additive_partials; ++qq) + if (qq % 4 == 2) { - oscdata->extraConfig.data[qq] = (qq % 2 == 0) * 1.f / ((qq + 1) * (qq + 1)); - - if (qq % 4 == 2) - { - oscdata->extraConfig.data[qq] *= -1.f; - } + oscdata->extraConfig.data[qq] *= -1.f; } + } - repaint(); - }; + repaint(); + }; - contextMenu.addItem("Triangle", action); - } + contextMenu.addItem("Triangle", action); + } - { - auto action = [this]() { - for (int qq = 0; qq < AliasOscillator::n_additive_partials; ++qq) - { - oscdata->extraConfig.data[qq] = 1.f / (qq + 1); - } + { + auto action = [this]() { + for (int qq = 0; qq < AliasOscillator::n_additive_partials; ++qq) + { + oscdata->extraConfig.data[qq] = 1.f / (qq + 1); + } - repaint(); - }; + repaint(); + }; - contextMenu.addItem("Sawtooth", action); - } + contextMenu.addItem("Sawtooth", action); + } - { - auto action = [this]() { - for (int qq = 0; qq < AliasOscillator::n_additive_partials; ++qq) - { - oscdata->extraConfig.data[qq] = (qq % 2 == 0) * 1.f / (qq + 1); - } + { + auto action = [this]() { + for (int qq = 0; qq < AliasOscillator::n_additive_partials; ++qq) + { + oscdata->extraConfig.data[qq] = (qq % 2 == 0) * 1.f / (qq + 1); + } - repaint(); - }; + repaint(); + }; - contextMenu.addItem("Square", action); - } + contextMenu.addItem("Square", action); + } - { - auto action = [this]() { - for (int qq = 0; qq < AliasOscillator::n_additive_partials; ++qq) - { - oscdata->extraConfig.data[qq] = storage->rand_pm1(); - } + { + auto action = [this]() { + for (int qq = 0; qq < AliasOscillator::n_additive_partials; ++qq) + { + oscdata->extraConfig.data[qq] = storage->rand_pm1(); + } - repaint(); - }; + repaint(); + }; - contextMenu.addItem("Random", action); - } + contextMenu.addItem("Random", action); + } - contextMenu.addSeparator(); + contextMenu.addSeparator(); - { - auto action = [this]() { - for (int qq = 0; qq < AliasOscillator::n_additive_partials; ++qq) + { + auto action = [this]() { + for (int qq = 0; qq < AliasOscillator::n_additive_partials; ++qq) + { + if (oscdata->extraConfig.data[qq] < 0) { - if (oscdata->extraConfig.data[qq] < 0) - { - oscdata->extraConfig.data[qq] *= -1; - } + oscdata->extraConfig.data[qq] *= -1; } + } - repaint(); - }; + repaint(); + }; - contextMenu.addItem("Absolute", action); - } + contextMenu.addItem("Absolute", action); + } - { - auto action = [this]() { - for (int qq = 0; qq < AliasOscillator::n_additive_partials; ++qq) - { - oscdata->extraConfig.data[qq] = -oscdata->extraConfig.data[qq]; - } + { + auto action = [this]() { + for (int qq = 0; qq < AliasOscillator::n_additive_partials; ++qq) + { + oscdata->extraConfig.data[qq] = -oscdata->extraConfig.data[qq]; + } - repaint(); - }; + repaint(); + }; - contextMenu.addItem("Invert", action); - } + contextMenu.addItem("Invert", action); + } - { - auto action = [this]() { - float pdata[AliasOscillator::n_additive_partials]; + { + auto action = [this]() { + float pdata[AliasOscillator::n_additive_partials]; - for (int qq = 0; qq < AliasOscillator::n_additive_partials; ++qq) - { - pdata[qq] = oscdata->extraConfig.data[qq]; - } + for (int qq = 0; qq < AliasOscillator::n_additive_partials; ++qq) + { + pdata[qq] = oscdata->extraConfig.data[qq]; + } - for (int qq = 0; qq < AliasOscillator::n_additive_partials; ++qq) - { - oscdata->extraConfig.data[15 - qq] = pdata[qq]; - } + for (int qq = 0; qq < AliasOscillator::n_additive_partials; ++qq) + { + oscdata->extraConfig.data[15 - qq] = pdata[qq]; + } + + repaint(); + }; - repaint(); - }; + contextMenu.addItem("Reverse", action); + } - contextMenu.addItem("Reverse", action); - } + contextMenu.showMenuAsync(sge->popupMenuOptions(useComponentBounds ? this : nullptr)); + } - contextMenu.showMenuAsync(sge->popupMenuOptions()); + void mouseDown(const juce::MouseEvent &event) override + { + mouseDownLongHold(event); + if (event.mods.isPopupMenu()) + { + createOptionsMenu(false); return; } @@ -1456,6 +1484,8 @@ struct AliasAdditiveEditor : public juce::Component, Surge::GUI::SkinConsumingCo } } + void mouseUp(const juce::MouseEvent &event) override { mouseUpLongHold(event); } + void mouseDoubleClick(const juce::MouseEvent &event) override { if (event.mods.isMiddleButtonDown()) @@ -1488,6 +1518,8 @@ struct AliasAdditiveEditor : public juce::Component, Surge::GUI::SkinConsumingCo return; } + mouseDragLongHold(event); + int draggedSlider = -1; for (int i = 0; i < AliasOscillator::n_additive_partials; ++i) @@ -1552,6 +1584,8 @@ struct AliasAdditiveEditor : public juce::Component, Surge::GUI::SkinConsumingCo } }; +template <> void LongHoldMixin::onLongHold() { asT()->createOptionsMenu(); } + void OscillatorWaveformDisplay::showCustomEditor() { if (customEditor) diff --git a/src/surge-xt/gui/widgets/OscillatorWaveformDisplay.h b/src/surge-xt/gui/widgets/OscillatorWaveformDisplay.h index 59c5de3ddf3..d9a6dc8a05b 100644 --- a/src/surge-xt/gui/widgets/OscillatorWaveformDisplay.h +++ b/src/surge-xt/gui/widgets/OscillatorWaveformDisplay.h @@ -33,7 +33,12 @@ namespace Surge { namespace Widgets { -struct OscillatorWaveformDisplay : public juce::Component, public Surge::GUI::SkinConsumingComponent +struct OscillatorWaveformDisplay; +template <> void LongHoldMixin::onLongHold(); + +struct OscillatorWaveformDisplay : public juce::Component, + public Surge::GUI::SkinConsumingComponent, + public Surge::Widgets::LongHoldMixin { OscillatorWaveformDisplay(); ~OscillatorWaveformDisplay(); @@ -73,6 +78,7 @@ struct OscillatorWaveformDisplay : public juce::Component, public Surge::GUI::Sk juce::Rectangle leftJog, rightJog, waveTableName; void mouseDown(const juce::MouseEvent &event) override; + void mouseUp(const juce::MouseEvent &event) override; void mouseMove(const juce::MouseEvent &event) override; void mouseExit(const juce::MouseEvent &event) override; @@ -81,6 +87,7 @@ struct OscillatorWaveformDisplay : public juce::Component, public Surge::GUI::Sk void populateMenu(juce::PopupMenu &m, int selectedItem); bool populateMenuForCategory(juce::PopupMenu &parent, int categoryId, int selectedItem); void showWavetableMenu(); + void createWTMenu(const bool useComponentBounds); void createWTMenuItems(juce::PopupMenu &contextMenu, bool centerBold = false, bool add2D3Dswitch = false); @@ -105,6 +112,12 @@ struct OscillatorWaveformDisplay : public juce::Component, public Surge::GUI::Sk JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(OscillatorWaveformDisplay); }; + +template <> inline void LongHoldMixin::onLongHold() +{ + asT()->createWTMenu(true); +} + } // namespace Widgets } // namespace Surge diff --git a/src/surge-xt/gui/widgets/WidgetBaseMixin.h b/src/surge-xt/gui/widgets/WidgetBaseMixin.h index 683e185594d..288ca93a104 100644 --- a/src/surge-xt/gui/widgets/WidgetBaseMixin.h +++ b/src/surge-xt/gui/widgets/WidgetBaseMixin.h @@ -197,18 +197,27 @@ struct WidgetBaseMixin : public Surge::GUI::SkinConsumingComponent, template struct LongHoldMixin { LongHoldMixin() {} + virtual ~LongHoldMixin() { if (timer && timer->isTimerRunning()) + { timer->stopTimer(); + } } + inline T *asT() { return static_cast(this); } static constexpr uint32_t holdDelayTimeInMS = 1000; + static constexpr uint32_t fingerMovementTolerancePx = 8; + void onLongHoldWrapper() { if (timer) + { timer->stopTimer(); + } + onLongHold(); } @@ -225,33 +234,53 @@ template struct LongHoldMixin } juce::Point startingHoldPosition; + virtual void mouseDownLongHold(const juce::MouseEvent &e) { if (!shouldLongHold()) + { return; + } startingHoldPosition = e.position.toFloat(); + if (timer && timer->isTimerRunning()) + { timer->stopTimer(); + } + timer = std::make_unique(this); - timer->startTimer(holdDelayTimeInMS); // ms + timer->startTimer(holdDelayTimeInMS); } + virtual void mouseMoveLongHold(const juce::MouseEvent &e) { - if (e.position.getDistanceFrom(startingHoldPosition) > 8) + if (e.position.getDistanceFrom(startingHoldPosition) > fingerMovementTolerancePx) + { if (timer && timer->isTimerRunning()) + { timer->stopTimer(); + } + } } + virtual void mouseDragLongHold(const juce::MouseEvent &e) { - if (e.position.getDistanceFrom(startingHoldPosition) > 8) + if (e.position.getDistanceFrom(startingHoldPosition) > fingerMovementTolerancePx) + { if (timer && timer->isTimerRunning()) + { timer->stopTimer(); + } + } } + virtual void mouseUpLongHold(const juce::MouseEvent &e) { if (timer && timer->isTimerRunning()) + { timer->stopTimer(); + } } struct LHCB : public juce::Timer @@ -260,6 +289,7 @@ template struct LongHoldMixin LHCB(LongHoldMixin *t) : that(t) {} void timerCallback() override { that->onLongHoldWrapper(); } }; + std::unique_ptr timer; }; } // namespace Widgets