From 3953a62413b41d735a40508b1d16720a2a3ae499 Mon Sep 17 00:00:00 2001 From: Paul Walker Date: Wed, 13 Jan 2021 19:14:22 -0500 Subject: [PATCH] A structured multi-overlay API editorOverlay is now a list so you can have multiple, and the tags which identify it are an enum so you know the overlay classes and can write less buggy code, and generallly everything is better. Although there is inevitably more work we want to do on overlays and positioning, this commit makes the API a good enough starting point to add other overlays, so it Closes #3223 --- CMakeLists.txt | 2 +- src/common/gui/CLFOGui.cpp | 8 +- src/common/gui/MSEGEditor.h | 3 +- src/common/gui/RefreshableOverlay.h | 24 ++++ src/common/gui/SurgeGUIEditor.cpp | 173 +++++++++++++++------------- src/common/gui/SurgeGUIEditor.h | 45 ++++++-- 6 files changed, 160 insertions(+), 95 deletions(-) create mode 100644 src/common/gui/RefreshableOverlay.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 6bab6c8d7c9..b58f9b72af8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -297,7 +297,7 @@ set(SURGE_GUI_SOURCES src/common/gui/SkinImageMaps.h src/common/gui/CSurgeHyperlink.cpp src/common/ModulatorPresetManager.cpp - src/common/gui/CTextButtonWithHover.cpp src/common/gui/CTextButtonWithHover.h) + src/common/gui/CTextButtonWithHover.cpp src/common/gui/CTextButtonWithHover.h src/common/gui/RefreshableOverlay.h) set(SURGE_VST3_SOURCES src/vst3/SurgeVst3Processor.cpp diff --git a/src/common/gui/CLFOGui.cpp b/src/common/gui/CLFOGui.cpp index 7575f868cd1..f2668657732 100644 --- a/src/common/gui/CLFOGui.cpp +++ b/src/common/gui/CLFOGui.cpp @@ -1178,7 +1178,7 @@ void CLFOGui::openPopup(CPoint &where) auto sge = dynamic_cast(listener); - std::string openname = (sge->editorOverlayTag != "msegEditor") ? "Open MSEG Editor" : "Close MSEG Editor"; + std::string openname = (sge && sge->isAnyOverlayPresent(SurgeGUIEditor::MSEG_EDITOR)) ? "Open MSEG Editor" : "Close MSEG Editor"; addCb(contextMenu, Surge::UI::toOSCaseForMenu(openname), [this, sge]() { if (sge) @@ -1190,7 +1190,7 @@ void CLFOGui::openPopup(CPoint &where) auto lpoff = addCb(contextMenu, Surge::UI::toOSCaseForMenu("No Looping"), [this, sge]() { ms->loopMode = MSEGStorage::LoopMode::ONESHOT; - if (sge->editorOverlayTag == "msegEditor") + if (sge && sge->isAnyOverlayPresent(SurgeGUIEditor::MSEG_EDITOR)) { sge->closeMSEGEditor(); sge->showMSEGEditor(); @@ -1202,7 +1202,7 @@ void CLFOGui::openPopup(CPoint &where) auto lpon = addCb(contextMenu, Surge::UI::toOSCaseForMenu("Loop Always"), [this, sge]() { ms->loopMode = MSEGStorage::LoopMode::LOOP; - if (sge->editorOverlayTag == "msegEditor") + if (sge && sge->isAnyOverlayPresent(SurgeGUIEditor::MSEG_EDITOR)) { sge->closeMSEGEditor(); sge->showMSEGEditor(); @@ -1214,7 +1214,7 @@ void CLFOGui::openPopup(CPoint &where) auto lpgate = addCb(contextMenu, Surge::UI::toOSCaseForMenu("Loop Until Release"), [this, sge]() { ms->loopMode = MSEGStorage::LoopMode::GATED_LOOP; - if (sge->editorOverlayTag == "msegEditor") + if (sge && sge->isAnyOverlayPresent(SurgeGUIEditor::MSEG_EDITOR)) { sge->closeMSEGEditor(); sge->showMSEGEditor(); diff --git a/src/common/gui/MSEGEditor.h b/src/common/gui/MSEGEditor.h index 18a376ed1f5..40e4b4f0a13 100644 --- a/src/common/gui/MSEGEditor.h +++ b/src/common/gui/MSEGEditor.h @@ -17,8 +17,9 @@ #include "vstcontrols.h" #include "SurgeStorage.h" #include "SkinSupport.h" +#include "RefreshableOverlay.h" -struct MSEGEditor : public VSTGUI::CViewContainer, public Surge::UI::SkinConsumingComponent { +struct MSEGEditor : public VSTGUI::CViewContainer, public Surge::UI::SkinConsumingComponent, public RefreshableOverlay { /* * Because this is 'late' in the build (in the gui) there is a copy of this structure in * the DawExtraState. If you add something to this state, add it there too. Revisit that diff --git a/src/common/gui/RefreshableOverlay.h b/src/common/gui/RefreshableOverlay.h new file mode 100644 index 00000000000..5e296b77f62 --- /dev/null +++ b/src/common/gui/RefreshableOverlay.h @@ -0,0 +1,24 @@ +/* +** Surge Synthesizer is Free and Open Source Software +** +** Surge is made available under the Gnu General Public License, v3.0 +** https://www.gnu.org/licenses/gpl-3.0.en.html +** +** Copyright 2004-2021 by various individuals as described by the Git transaction log +** +** All source at: https://github.com/surge-synthesizer/surge.git +** +** Surge was a commercial product from 2004-2018, with Copyright and ownership +** in that period held by Claes Johanson at Vember Audio. Claes made Surge +** open source in September 2018. +*/ + +#ifndef SURGE_REFRESHABLEOVERLAY_H +#define SURGE_REFRESHABLEOVERLAY_H + +class RefreshableOverlay { +public: + virtual void forceRefresh() = 0; +}; + +#endif // SURGE_REFRESHABLEOVERLAY_H diff --git a/src/common/gui/SurgeGUIEditor.cpp b/src/common/gui/SurgeGUIEditor.cpp index d2a4ec5b69c..ef1d9cff064 100644 --- a/src/common/gui/SurgeGUIEditor.cpp +++ b/src/common/gui/SurgeGUIEditor.cpp @@ -1064,7 +1064,7 @@ int32_t SurgeGUIEditor::onKeyDown(const VstKeyCode& code, CFrame* frame) return 1; break; case VKEY_TAB: - if ( (editorOverlay && editorOverlayTag == "storePatch" ) || (typeinDialog && typeinDialog->isVisible() ) ) + if ( (topmostEditorTag() == STORE_PATCH) || (typeinDialog && typeinDialog->isVisible() ) ) { /* ** SaveDialog gets access to the tab key to switch between fields if it is open @@ -1227,12 +1227,12 @@ void SurgeGUIEditor::openOrRecreateEditor() editorOverlayTagAtClose = ""; if (editor_open) { - if( editorOverlay != nullptr ) + editorOverlayTagAtClose = topmostEditorTag(); + for(auto el : editorOverlay) { - editorOverlay->remember(); - frame->removeView( editorOverlay ); - editorOverlayTagAtClose = editorOverlayTag; - } + el.second->remember(); + frame->removeView( el.second ); + } close_editor(); } @@ -1456,7 +1456,7 @@ void SurgeGUIEditor::openOrRecreateEditor() case Surge::Skin::Connector::NonParameterConnection::MSEG_EDITOR_OPEN: { msegEditSwitch = layoutComponentForSkin( skinCtrl, tag_mseg_edit ); msegEditSwitch->setVisible( false ); - msegEditSwitch->setValue( editorOverlay != nullptr && editorOverlayTag == "msegEditor" ); + msegEditSwitch->setValue( isAnyOverlayPresent(MSEG_EDITOR) ); auto q = modsource_editor[current_scene]; if( ( q >= ms_lfo1 && q <= ms_lfo6 ) || ( q >= ms_slfo1 && q <= ms_slfo6 ) ) { @@ -1749,13 +1749,15 @@ void SurgeGUIEditor::openOrRecreateEditor() frame->addView(lb); debugLabel = lb; #endif - if( editorOverlay ) + for( auto el : editorOverlay ) { - frame->addView( editorOverlay ); + frame->addView( el.second ); + auto contents = editorOverlayContentsWeakReference[el.second]; + /* * This is a hack for 1.8 which we have to clean up in 1.9 when we do #3223 */ - auto mse = dynamic_cast(editorOverlayContentsWeakReference); + auto mse = dynamic_cast(contents); if (mse) { mse->forceRefresh(); @@ -1950,14 +1952,12 @@ bool PLUGIN_API SurgeGUIEditor::open(void* parent, const PlatformType& platformT void SurgeGUIEditor::close() { populateDawExtraState(synth); - if( editorOverlay ) + for (auto el: editorOverlay) { - frame->removeView( editorOverlay ); - editorOverlayOnClose(); - editorOverlayTagAtClose = editorOverlayTag; - editorOverlayTag = ""; - editorOverlay = nullptr; - editorOverlayContentsWeakReference = nullptr; + frame->removeView( el.second ); + auto f = editorOverlayOnClose[el.second]; + f(); + editorOverlayTagAtClose = el.first; } #if TARGET_VST2 // && WINDOWS // We may need this in other hosts also; but for now @@ -3722,7 +3722,7 @@ void SurgeGUIEditor::valueChanged(CControl* control) } } - if(editorOverlay && editorOverlayTag == "msegEditor" ) + if(isAnyOverlayPresent(MSEG_EDITOR)) { auto ld = &(synth->storage.getPatch().scene[current_scene].lfo[newsource-ms_lfo1]); if( ld->shape.val.i == lt_mseg ) @@ -3782,7 +3782,7 @@ void SurgeGUIEditor::valueChanged(CControl* control) synth->storage.getPatch().scene_active.val.i = current_scene; // synth->storage.getPatch().param_ptr[scene_select_pid]->set_value_f01(control->getValue()); - if (editorOverlay && editorOverlayTag == "msegEditor") + if (isAnyOverlayPresent(MSEG_EDITOR)) { auto ld = &(synth->storage.getPatch().scene[current_scene].lfo[modsource_editor[current_scene] - ms_lfo1]); if (ld->shape.val.i == lt_mseg) @@ -3811,7 +3811,7 @@ void SurgeGUIEditor::valueChanged(CControl* control) break; case tag_mp_category: { - if( editorOverlay && editorOverlayTag == "storePatch" ) + if( isAnyOverlayPresent(STORE_PATCH)) { closeStorePatchDialog(); } @@ -3825,7 +3825,7 @@ void SurgeGUIEditor::valueChanged(CControl* control) break; case tag_mp_patch: { - if( editorOverlay && editorOverlayTag == "storePatch" ) + if( isAnyOverlayPresent(STORE_PATCH) ) { closeStorePatchDialog(); } @@ -4017,7 +4017,7 @@ void SurgeGUIEditor::valueChanged(CControl* control) { // prevent duplicate execution of savePatch() by detecting if the Store Patch dialog is displayed or not // FIXME: baconpaul will know a better and more correct way to fix this - if (editorOverlay && editorOverlayTag == "storePatch") + if (isAnyOverlayPresent(STORE_PATCH)) { /* ** Don't allow a blank patch @@ -4081,15 +4081,25 @@ void SurgeGUIEditor::valueChanged(CControl* control) break; case tag_editor_overlay_close: { - if( editorOverlay != nullptr ) + // So can I find an editor overlay parent + VSTGUI::CView *p = control; + auto tagToNuke = NO_EDITOR; + while( p ) { - editorOverlay->setVisible(false); - removeFromFrame.push_back(editorOverlay); - editorOverlayOnClose(); - editorOverlay = nullptr; - editorOverlayTag = ""; - editorOverlayTagAtClose = ""; - editorOverlayContentsWeakReference = nullptr; + p = p->getParentView(); + for( auto el : editorOverlay ) + { + if( el.second == p ) + { + tagToNuke = el.first; + p = nullptr; + break; + } + } + } + if( tagToNuke != NO_EDITOR ) + { + dismissEditorOfType(tagToNuke); } } break; @@ -4361,9 +4371,9 @@ void SurgeGUIEditor::valueChanged(CControl* control) lfodisplay->setDirty(); lfodisplay->invalid(); } - if( editorOverlay ) + for(auto el: editorOverlay) { - editorOverlay->invalid(); + el.second->invalid(); } } if( p->ctrltype == ct_filtertype ) @@ -5090,7 +5100,7 @@ VSTGUI::COptionMenu *SurgeGUIEditor::makeLfoMenu(VSTGUI::CRect &menuRect) Surge::ModulatorPreset::loadPresetFrom(p.path, &(this->synth->storage), current_scene, currentLfoId ); auto newshape = this->synth->storage.getPatch().scene[current_scene].lfo[currentLfoId].shape.val.i; - if( editorOverlay && editorOverlayTag == "msegEditor" ) + if( isAnyOverlayPresent(MSEG_EDITOR) ) { closeMSEGEditor(); if( newshape == lt_mseg ) @@ -6102,13 +6112,13 @@ void SurgeGUIEditor::reloadFromSkin() clearOffscreenCachesAtZero = 1; // update MSEG editor if opened - if (editorOverlay && editorOverlayTag == "msegEditor") + if (isAnyOverlayPresent(MSEG_EDITOR)) { showMSEGEditor(); } // update Store Patch dialog if opened - if (editorOverlay && editorOverlayTag == "storePatch") + if (isAnyOverlayPresent(STORE_PATCH)) { auto pname = patchName->getText(); auto pcat = patchCategory->getText(); @@ -6792,24 +6802,35 @@ void SurgeGUIEditor::sliderHoverEnd( int tag ) } -void SurgeGUIEditor::dismissEditorOverlay() +void SurgeGUIEditor::dismissEditorOfType(OverlayTags ofType) { - if( editorOverlay != nullptr ) + if( editorOverlay.size() == 0 ) return; + + auto newO = editorOverlay; + newO.clear(); + for( auto el : editorOverlay ) { - editorOverlay->setVisible(false); - editorOverlayOnClose(); - removeFromFrame.push_back( editorOverlay ); - editorOverlay = nullptr; - editorOverlayTag = ""; - editorOverlayTagAtClose = ""; - editorOverlayContentsWeakReference = nullptr; + if( el.first == ofType ) + { + el.second->setVisible(false); + auto f = editorOverlayOnClose[el.second]; + f(); + + editorOverlayOnClose.erase(el.second); + editorOverlayContentsWeakReference.erase(el.second); + } + else + { + newO.push_back(el); + } } + editorOverlay = newO; } -void SurgeGUIEditor::setEditorOverlay(VSTGUI::CView *c, std::string editorTitle, std::string editorTag, +void SurgeGUIEditor::addEditorOverlay(VSTGUI::CView *c, std::string editorTitle, OverlayTags editorTag, const VSTGUI::CPoint &topLeft, bool modalOverlay, bool hasCloseButton, std::function onClose) { - dismissEditorOverlay(); + dismissEditorOfType(editorTag); const int header = 18; const int buttonwidth = 18; @@ -6832,10 +6853,10 @@ void SurgeGUIEditor::setEditorOverlay(VSTGUI::CView *c, std::string editorTitle, fs = containerSize; // add a screen size transparent thing into the editorOverlay - editorOverlay = new CViewContainer(fs); - editorOverlay->setBackgroundColor(currentSkin->getColor(Colors::Overlay::Background)); - editorOverlay->setVisible(true); - frame->addView(editorOverlay); + auto editorOverlayC = new CViewContainer(fs); + editorOverlayC->setBackgroundColor(currentSkin->getColor(Colors::Overlay::Background)); + editorOverlayC->setVisible(true); + frame->addView(editorOverlayC); if (modalOverlay) containerSize = containerSize.centerInside(fs); @@ -6847,13 +6868,13 @@ void SurgeGUIEditor::setEditorOverlay(VSTGUI::CView *c, std::string editorTitle, auto outerc = new CViewContainer(containerSize); outerc->setBackgroundColor(currentSkin->getColor(Colors::Dialog::Border)); - editorOverlay->addView(outerc); + editorOverlayC->addView(outerc); auto csz = containerSize; csz.bottom = csz.top + header; auto innerc = new CViewContainer(csz); innerc->setBackgroundColor(currentSkin->getColor(Colors::Dialog::Titlebar::Background)); - editorOverlay->addView(innerc); + editorOverlayC->addView(innerc); auto tl = new CTextLabel(csz, editorTitle.c_str()); tl->setBackColor(currentSkin->getColor(Colors::Dialog::Titlebar::Background)); @@ -6931,13 +6952,12 @@ void SurgeGUIEditor::setEditorOverlay(VSTGUI::CView *c, std::string editorTitle, c->setViewSize(containerSize); c->setMouseableArea(containerSize); // sigh - editorOverlay->addView(c); + editorOverlayC->addView(c); // save the onClose function - editorOverlayOnClose = onClose; - editorOverlayTag = editorTag; - editorOverlayTagAtClose = editorTag; - editorOverlayContentsWeakReference = c; + editorOverlay.push_back(std::make_pair(editorTag, editorOverlayC)); + editorOverlayOnClose[editorOverlayC] = onClose; + editorOverlayContentsWeakReference[editorOverlayC] = c; } std::string SurgeGUIEditor::getDisplayForTag( long tag ) @@ -7297,7 +7317,8 @@ void SurgeGUIEditor::makeStorePatchDialog() saveDialog->addView(cb); saveDialog->addView(kb); - setEditorOverlay(saveDialog, "Store Patch", "storePatch", CPoint(157, 57), false, false, [this]() {}); + addEditorOverlay(saveDialog, "Store Patch", STORE_PATCH, CPoint(157, 57), false, false, + [this]() {}); } VSTGUI::CControl *SurgeGUIEditor::layoutComponentForSkin( std::shared_ptr skinCtrl, @@ -7733,12 +7754,12 @@ void SurgeGUIEditor::lfoShapeChanged(int prior, int curr) } } - if( curr == lt_mseg && editorOverlay && editorOverlayTag == "msegEditor" ) + if( curr == lt_mseg && isAnyOverlayPresent(MSEG_EDITOR) ) { // We have the MSEGEditor open and have swapped to the MSEG here showMSEGEditor(); } - else if( prior == lt_mseg && curr != lt_mseg && editorOverlay && editorOverlayTag == "msegEditor" ) + else if( prior == lt_mseg && curr != lt_mseg && isAnyOverlayPresent(MSEG_EDITOR) ) { // We can choose to not do this too; if we do we are editing an MSEG which isn't used though closeMSEGEditor(); @@ -7752,10 +7773,8 @@ void SurgeGUIEditor::lfoShapeChanged(int prior, int curr) void SurgeGUIEditor::closeStorePatchDialog() { - if (editorOverlayTag == "storePatch") - { - dismissEditorOverlay(); - } + dismissEditorOfType(STORE_PATCH); + // Have to update all that state too for the newly orphaned items patchName = nullptr; patchCategory = nullptr; @@ -7766,24 +7785,19 @@ void SurgeGUIEditor::closeStorePatchDialog() void SurgeGUIEditor::showStorePatchDialog() { - if (editorOverlayTag == "msegEditor") - { - dismissEditorOverlay(); - } - makeStorePatchDialog(); } void SurgeGUIEditor::closeMSEGEditor() { - if( editorOverlayTag == "msegEditor" ) { + if( isAnyOverlayPresent(MSEG_EDITOR) ) { broadcastMSEGState(); - dismissEditorOverlay( ); + dismissEditorOfType(MSEG_EDITOR); } } void SurgeGUIEditor::toggleMSEGEditor() { - if( editorOverlayTag == "msegEditor" ) + if( isAnyOverlayPresent(MSEG_EDITOR) ) { closeMSEGEditor(); } @@ -7834,13 +7848,14 @@ void SurgeGUIEditor::showMSEGEditor() auto conn = Surge::Skin::Connector::connectorByNonParameterConnection(npc); auto skinCtrl = currentSkin->getOrCreateControlForConnector(conn); - setEditorOverlay(mse, title, "msegEditor", CPoint(skinCtrl->x, skinCtrl->y), false, true, [this]() { - if (msegEditSwitch) - { - msegEditSwitch->setValue(0.0); - msegEditSwitch->invalid(); - } - }); + addEditorOverlay(mse, title, MSEG_EDITOR, CPoint(skinCtrl->x, skinCtrl->y), false, true, + [this]() { + if (msegEditSwitch) + { + msegEditSwitch->setValue(0.0); + msegEditSwitch->invalid(); + } + }); if( msegEditSwitch ) { diff --git a/src/common/gui/SurgeGUIEditor.h b/src/common/gui/SurgeGUIEditor.h index 5db68f92d88..bfeb8e09b89 100644 --- a/src/common/gui/SurgeGUIEditor.h +++ b/src/common/gui/SurgeGUIEditor.h @@ -332,14 +332,39 @@ class SurgeGUIEditor : public EditorType, int getWindowSizeX() const { return wsx; } int getWindowSizeY() const { return wsy; } - void setEditorOverlay( VSTGUI::CView *c, + /* + * We have an enumerated set of overlay tags which we can push + * to the UI. You *have* to give a new overlay type a tag in + * order for it to work. + */ + enum OverlayTags + { + NO_EDITOR, + MSEG_EDITOR, + STORE_PATCH + }; + + void addEditorOverlay( VSTGUI::CView *c, std::string editorTitle, // A window display title - whatever you want - std::string editorTag, // A tag by editor class. Please unique, no spaces. - const VSTGUI::CPoint &topleft = VSTGUI::CPoint( 0, 0 ), + OverlayTags editorTag, // A tag by editor class. Please unique, no spaces. + const VSTGUI::CPoint &topleft = VSTGUI::CPoint(0, 0), bool modalOverlay = true, bool hasCloseButton = true, - std::function onClose = [](){} ); - void dismissEditorOverlay(); + std::function onClose = []() {}); + void dismissEditorOfType(OverlayTags ofType); + OverlayTags topmostEditorTag() { + if( ! editorOverlay.size() ) return NO_EDITOR; + return editorOverlay.back().first; + } + bool isAnyOverlayPresent( OverlayTags tag ) + { + for( auto el : editorOverlay ) + { + if( el.first == tag ) return true; + } + return false; + } + std::string getDisplayForTag( long tag ); @@ -457,16 +482,16 @@ class SurgeGUIEditor : public EditorType, int typeinResetCounter = -1; std::string typeinResetLabel = ""; - VSTGUI::CViewContainer *editorOverlay = nullptr; - VSTGUI::CView* editorOverlayContentsWeakReference = - nullptr; // Use this very very carefully. It may hold a dangling ref until #3223 - std::function editorOverlayOnClose = [](){}; + // Data structures for a list of overlays and associated data witht hem + std::vector> editorOverlay; + std::unordered_map editorOverlayContentsWeakReference; + std::unordered_map> editorOverlayOnClose; VSTGUI::CViewContainer *minieditOverlay = nullptr; VSTGUI::CTextEdit *minieditTypein = nullptr; std::function minieditOverlayDone = [](const char *){}; public: - std::string editorOverlayTag, editorOverlayTagAtClose; + std::string editorOverlayTagAtClose; // FIXME what is this? void promptForMiniEdit(const std::string& value, const std::string& prompt, const std::string& title,