From c5cb635b4e430c283f6fe4f2cfa7bce1de4ed26b Mon Sep 17 00:00:00 2001 From: Bruno Herbelin Date: Sun, 5 Feb 2023 17:05:29 +0100 Subject: [PATCH] Input Mapping for Batch of Sources Session contains a set of 'Batch' that are created in the Player (renamed from PlayGroups). Session InputCallback can now target either a Source or a Batch, using std::variant (new type Target). Input Mapping reacts to input to create callbacks to a target, either a single source (as before) or to a Batch (multiple sources). --- src/ControlManager.cpp | 2 +- src/Session.cpp | 165 +++++++++++++++++++------------ src/Session.h | 41 ++++---- src/SessionCreator.cpp | 32 +++--- src/SessionVisitor.cpp | 11 ++- src/SourceCallback.cpp | 19 ++-- src/SourceList.h | 4 + src/UserInterfaceManager.cpp | 183 ++++++++++++++++++++++++----------- src/UserInterfaceManager.h | 6 +- 9 files changed, 297 insertions(+), 166 deletions(-) diff --git a/src/ControlManager.cpp b/src/ControlManager.cpp index 81cb8e4..81fbe28 100644 --- a/src/ControlManager.cpp +++ b/src/ControlManager.cpp @@ -142,7 +142,7 @@ void Control::RequestListener::ProcessMessage( const osc::ReceivedMessage& m, if ( BaseToolkit::is_a_number(num, &i)){ // confirmed : the target is a Player selection (e.g. 'selection#2') // loop over this selection of sources - SourceList _selection = Mixer::manager().session()->playGroup(i); + SourceList _selection = Mixer::manager().session()->getBatch(i); for (SourceList::iterator it = _selection.begin(); it != _selection.end(); ++it) { // apply attributes if ( Control::manager().receiveSourceAttribute( *it, attribute, m.ArgumentStream()) && Mixer::manager().currentSource() == *it) diff --git a/src/Session.cpp b/src/Session.cpp index a8c58e3..528741e 100644 --- a/src/Session.cpp +++ b/src/Session.cpp @@ -26,11 +26,9 @@ #include "Source.h" #include "Settings.h" #include "FrameBuffer.h" -#include "FrameGrabber.h" #include "SessionCreator.h" #include "SessionVisitor.h" #include "ActionManager.h" -#include "SessionSource.h" #include "RenderSource.h" #include "MixingGroup.h" #include "ControlManager.h" @@ -78,6 +76,15 @@ Session::Session(uint64_t id) : id_(id), active_(true), activation_threshold_(MI } +void Session::InputSourceCallback::clearReverse() +{ + for (auto rev = reverse_.begin(); rev != reverse_.end(); ++rev) { + if (rev->second != nullptr) + delete rev->second; + } + reverse_.clear(); +} + Session::~Session() { // TODO delete all mixing groups? @@ -98,8 +105,7 @@ Session::~Session() iter = input_callbacks_.erase(iter)) { if ( iter->second.model_ != nullptr) delete iter->second.model_; - if ( iter->second.reverse_ != nullptr) - delete iter->second.reverse_; + iter->second.clearReverse(); } delete config_[View::RENDERING]; @@ -141,35 +147,70 @@ void Session::update(float dt) bool input_active = Control::manager().inputActive(k->first); // get the callback model for each input - if ( k->second.model_ != nullptr) { + // if the model is valid and the target variant was filled + if ( k->second.model_ != nullptr && k->second.target_.index() > 0) { // if the value referenced as pressed changed state // or repeat key if there is no reverse callback - if ( input_active != k->second.active_ || k->second.reverse_ == nullptr) { + if ( input_active != k->second.active_ || k->second.reverse_.empty()) { // ON PRESS if (input_active) { // delete the reverse if was not released - if (k->second.reverse_ != nullptr) - delete k->second.reverse_; + k->second.clearReverse(); - // generate a new callback from the model - SourceCallback *C = k->second.model_->clone(); - // apply value multiplyer from input - C->multiply( Control::manager().inputValue(k->first) ); - // add delay - C->delay( Metronome::manager().timeToSync( (Metronome::Synchronicity) input_sync_[k->first] ) ); - // add callback to the source (force override) - k->second.source_->call( C, true ); - // get the reverse if the callback, and remember it (can be null) - k->second.reverse_ = C->reverse(k->second.source_); + // Add callback to the target(s) + // 1. Case of variant as Source pointer + if (Source * const* v = std::get_if(&k->second.target_)) { + // verify variant value + if ( *v != nullptr ) { + // generate a new callback from the model + SourceCallback *C = k->second.model_->clone(); + // apply value multiplyer from input + C->multiply( Control::manager().inputValue(k->first) ); + // add delay + C->delay( Metronome::manager().timeToSync( (Metronome::Synchronicity) input_sync_[k->first] ) ); + // add callback to source + (*v)->call( C, true ); + // get the reverse if the callback, and remember it (can be null) + k->second.reverse_[(*v)->id()] = C->reverse(*v); + } + } + // 2. Case of variant as index of batch + else if ( const size_t* v = std::get_if(&k->second.target_)) { + // verify variant value + if ( *v < batch_.size() ) + { + // loop over all sources in Batch + for (auto sid = batch_[*v].begin(); sid != batch_[*v].end(); ++sid){ + SourceList::const_iterator sit = std::find_if(sources_.begin(), sources_.end(), Source::hasId(*sid));; + if ( sit != sources_.end()) { + // generate a new callback from the model + SourceCallback *C = k->second.model_->clone(); + // apply value multiplyer from input + C->multiply( Control::manager().inputValue(k->first) ); + // add delay + C->delay( Metronome::manager().timeToSync( (Metronome::Synchronicity) input_sync_[k->first] ) ); + // add callback to source + (*sit)->call( C, true ); + // get the reverse if the callback, and remember it (can be null) + k->second.reverse_[*sid] = C->reverse(*sit); + } + } + } + } } // ON RELEASE else { - // call the reverse (NB: invalid value tested in call) - k->second.source_->call( k->second.reverse_, true ); + // call all the reverse of sources (NB: invalid value tested in call) + for (auto rev = k->second.reverse_.begin(); rev != k->second.reverse_.end(); ++rev) { + SourceList::const_iterator sit = std::find_if(sources_.begin(), sources_.end(), Source::hasId(rev->first));; + if ( sit != sources_.end()) { + (*sit)->call( rev->second, true ); + } + } // do not keep reference to reverse: will be deleted when terminated - k->second.reverse_ = nullptr; + k->second.reverse_.clear(); } // remember state of callback @@ -629,47 +670,47 @@ std::list::iterator Session::endMixingGroup() } -size_t Session::numPlayGroups() const +size_t Session::numBatch() const { - return play_groups_.size(); + return batch_.size(); } -void Session::addPlayGroup(const SourceIdList &ids) +void Session::addBatch(const SourceIdList &ids) { - play_groups_.push_back( ids ); + batch_.push_back( ids ); } -void Session::addToPlayGroup(size_t i, Source *s) +void Session::addSourceToBatch(size_t i, Source *s) { - if (i < play_groups_.size() ) + if (i < batch_.size() ) { - if ( std::find(play_groups_[i].begin(), play_groups_[i].end(), s->id()) == play_groups_[i].end() ) - play_groups_[i].push_back(s->id()); + if ( std::find(batch_[i].begin(), batch_[i].end(), s->id()) == batch_[i].end() ) + batch_[i].push_back(s->id()); } } -void Session::removeFromPlayGroup(size_t i, Source *s) +void Session::removeSourceFromBatch(size_t i, Source *s) { - if (i < play_groups_.size() ) + if (i < batch_.size() ) { - if ( std::find(play_groups_[i].begin(), play_groups_[i].end(), s->id()) != play_groups_[i].end() ) - play_groups_[i].remove( s->id() ); + if ( std::find(batch_[i].begin(), batch_[i].end(), s->id()) != batch_[i].end() ) + batch_[i].remove( s->id() ); } } -void Session::deletePlayGroup(size_t i) +void Session::deleteBatch(size_t i) { - if (i < play_groups_.size() ) - play_groups_.erase( play_groups_.begin() + i); + if (i < batch_.size() ) + batch_.erase( batch_.begin() + i); } -SourceList Session::playGroup(size_t i) const +SourceList Session::getBatch(size_t i) const { SourceList list; - if (i < play_groups_.size() ) + if (i < batch_.size() ) { - for (auto sid = play_groups_[i].begin(); sid != play_groups_[i].end(); ++sid){ + for (auto sid = batch_[i].begin(); sid != batch_[i].end(); ++sid){ SourceList::const_iterator it = std::find_if(sources_.begin(), sources_.end(), Source::hasId( *sid));; if ( it != sources_.end()) @@ -728,17 +769,17 @@ std::string Session::save(const std::string& filename, Session *session, const s return ret; } -void Session::assignSourceCallback(uint input, Source *source, SourceCallback *callback) +void Session::assignInputCallback(uint input, Target target, SourceCallback *callback) { // find if this callback is already assigned auto k = input_callbacks_.begin(); for (; k != input_callbacks_.end(); ++k) { - // yes, then just change the source pointer + // yes, then just change the target if ( k->second.model_ == callback) { - if (k->second.reverse_) - delete k->second.reverse_; - k->second.source_ = source; + k->second.target_ = target; + // reverse became invalid + k->second.clearReverse(); break; } } @@ -748,11 +789,11 @@ void Session::assignSourceCallback(uint input, Source *source, SourceCallback *c // create new entry std::multimap::iterator added = input_callbacks_.emplace(input, InputSourceCallback() ); added->second.model_ = callback; - added->second.source_ = source; + added->second.target_ = target; } } -void Session::swapSourceCallback(uint from, uint to) +void Session::swapInputCallback(uint from, uint to) { std::multimap swapped_callbacks_; @@ -767,52 +808,50 @@ void Session::swapSourceCallback(uint from, uint to) input_callbacks_.swap(swapped_callbacks_); } -void Session::copySourceCallback(uint from, uint to) +void Session::copyInputCallback(uint from, uint to) { if ( input_callbacks_.count(from) > 0 ) { auto from_callbacks = getSourceCallbacks(from); for (auto it = from_callbacks.cbegin(); it != from_callbacks.cend(); ++it){ - assignSourceCallback(to, it->first, it->second->clone() ); + assignInputCallback(to, it->first, it->second->clone() ); } } } -std::list< std::pair > Session::getSourceCallbacks(uint input) +std::list > Session::getSourceCallbacks(uint input) { - std::list< std::pair > ret; + std::list< std::pair< Target, SourceCallback*> > ret; if ( input_callbacks_.count(input) > 0 ) { auto result = input_callbacks_.equal_range(input); for (auto it = result.first; it != result.second; ++it) - ret.push_back( std::pair(it->second.source_, it->second.model_) ); + ret.push_back( std::pair< Target, SourceCallback*>(it->second.target_, it->second.model_) ); } return ret; } -void Session::deleteSourceCallback(SourceCallback *callback) +void Session::deleteInputCallback(SourceCallback *callback) { for (auto k = input_callbacks_.begin(); k != input_callbacks_.end(); ++k) { if ( k->second.model_ == callback) { delete callback; - if (k->second.reverse_) - delete k->second.reverse_; + k->second.clearReverse(); input_callbacks_.erase(k); break; } } } -void Session::deleteSourceCallbacks(uint input) +void Session::deleteInputCallbacks(uint input) { for (auto k = input_callbacks_.begin(); k != input_callbacks_.end();) { if ( k->first == input) { if (k->second.model_) delete k->second.model_; - if (k->second.reverse_) - delete k->second.reverse_; + k->second.clearReverse(); k = input_callbacks_.erase(k); } else @@ -820,15 +859,14 @@ void Session::deleteSourceCallbacks(uint input) } } -void Session::deleteSourceCallbacks(Source *source) +void Session::deleteInputCallbacks(Target target) { for (auto k = input_callbacks_.begin(); k != input_callbacks_.end();) { - if ( k->second.source_ == source) { + if ( k->second.target_ == target) { if (k->second.model_) delete k->second.model_; - if (k->second.reverse_) - delete k->second.reverse_; + k->second.clearReverse(); k = input_callbacks_.erase(k); } else @@ -837,14 +875,13 @@ void Session::deleteSourceCallbacks(Source *source) } -void Session::clearSourceCallbacks() +void Session::clearInputCallbacks() { for (auto k = input_callbacks_.begin(); k != input_callbacks_.end(); ) { if (k->second.model_) delete k->second.model_; - if (k->second.reverse_) - delete k->second.reverse_; + k->second.clearReverse(); k = input_callbacks_.erase(k); } diff --git a/src/Session.h b/src/Session.h index 1870b8e..f4c20d2 100644 --- a/src/Session.h +++ b/src/Session.h @@ -2,6 +2,7 @@ #define SESSION_H #include +#include #include "SourceList.h" #include "RenderView.h" @@ -153,25 +154,25 @@ public: void applySnapshot(uint64_t key); // playlists - void addPlayGroup(const SourceIdList &ids); - void deletePlayGroup(size_t i); - size_t numPlayGroups() const; - SourceList playGroup(size_t i) const; - void addToPlayGroup(size_t i, Source *s); - void removeFromPlayGroup(size_t i, Source *s); - std::vector getPlayGroups() { return play_groups_; } + void addBatch(const SourceIdList &ids); + void deleteBatch(size_t i); + size_t numBatch() const; + SourceList getBatch(size_t i) const; + void addSourceToBatch(size_t i, Source *s); + void removeSourceFromBatch(size_t i, Source *s); + std::vector getAllBatch() { return batch_; } // callbacks associated to inputs - void assignSourceCallback(uint input, Source *source, SourceCallback *callback); - std::list< std::pair > getSourceCallbacks(uint input); - void deleteSourceCallback (SourceCallback *callback); - void deleteSourceCallbacks(Source *source); - void deleteSourceCallbacks(uint input); - void clearSourceCallbacks (); + void assignInputCallback(uint input, Target target, SourceCallback *callback); + std::list< std::pair > getSourceCallbacks(uint input); + void deleteInputCallback (SourceCallback *callback); + void deleteInputCallbacks(Target target); + void deleteInputCallbacks(uint input); + void clearInputCallbacks (); std::list assignedInputs(); bool inputAssigned(uint input); - void swapSourceCallback(uint from, uint to); - void copySourceCallback(uint from, uint to); + void swapInputCallback(uint from, uint to); + void copyInputCallback(uint from, uint to); void setInputSynchrony(uint input, Metronome::Synchronicity sync); std::vector getInputSynchrony(); @@ -190,7 +191,7 @@ protected: std::list mixing_groups_; std::map config_; SessionSnapshots snapshots_; - std::vector play_groups_; + std::vector batch_; std::mutex access_; FrameBufferImage *thumbnail_; uint64_t start_time_; @@ -216,14 +217,14 @@ protected: struct InputSourceCallback { bool active_; SourceCallback *model_; - SourceCallback *reverse_; - Source *source_; + std::map reverse_; + Target target_; InputSourceCallback() { active_ = false; model_ = nullptr; - reverse_ = nullptr; - source_ = nullptr; + target_ = nullptr; } + void clearReverse(); }; std::multimap input_callbacks_; std::vector input_sync_; diff --git a/src/SessionCreator.cpp b/src/SessionCreator.cpp index ace3125..1141124 100644 --- a/src/SessionCreator.cpp +++ b/src/SessionCreator.cpp @@ -234,10 +234,24 @@ void SessionCreator::loadInputCallbacks(tinyxml2::XMLElement *inputsNode) uint input = 0; xmlCurrent_->QueryUnsignedAttribute("input", &input); if (input > 0) { - // what id is the source ? - uint64_t id = 0; - xmlCurrent_->QueryUnsigned64Attribute("id", &id); - if (id > 0) { + Target target; + uint64_t sid = 0; + size_t bid = 0; + if ( xmlCurrent_->QueryUnsigned64Attribute("id", &sid) != XML_NO_ATTRIBUTE ) { + // find the source with the given id + SourceList::iterator sit = session_->find(sid); + if (sit != session_->end()) { + // assign variant + target = *sit; + } + } + else if ( xmlCurrent_->QueryUnsigned64Attribute("batch", &bid) != XML_NO_ATTRIBUTE ) { + // assign variant + target = bid; + } + + // if could identify the target + if (target.index() > 0) { // what type is the callback ? uint type = 0; xmlCurrent_->QueryUnsignedAttribute("type", &type); @@ -247,12 +261,8 @@ void SessionCreator::loadInputCallbacks(tinyxml2::XMLElement *inputsNode) if (loadedcallback) { // apply specific parameters loadedcallback->accept(*this); - // find the source with the given id - SourceList::iterator sit = session_->find(id); - if (sit != session_->end()) { - // assign to source in session - session_->assignSourceCallback(input, *sit, loadedcallback); - } + // assign to target + session_->assignInputCallback(input, target, loadedcallback); } } } @@ -321,7 +331,7 @@ void SessionCreator::loadPlayGroups(tinyxml2::XMLElement *playgroupNode) playgroup_sources.push_back( id__ ); } - session_->addPlayGroup( playgroup_sources ); + session_->addBatch( playgroup_sources ); } } } diff --git a/src/SessionVisitor.cpp b/src/SessionVisitor.cpp index 2d58f03..9d81cf3 100644 --- a/src/SessionVisitor.cpp +++ b/src/SessionVisitor.cpp @@ -197,7 +197,7 @@ void SessionVisitor::savePlayGroups(tinyxml2::XMLDocument *doc, Session *session if (doc != nullptr && session != nullptr) { XMLElement *playlistNode = doc->NewElement("PlayGroups"); - std::vector pl = session->getPlayGroups(); + std::vector pl = session->getAllBatch(); for (auto plit = pl.begin(); plit != pl.end(); ++plit) { XMLElement *list = doc->NewElement("PlayGroup"); playlistNode->InsertEndChild(list); @@ -231,7 +231,14 @@ void SessionVisitor::saveInputCallbacks(tinyxml2::XMLDocument *doc, Session *ses // create node for this callback XMLElement *cbNode = doc->NewElement("Callback"); cbNode->SetAttribute("input", *i); - cbNode->SetAttribute("id", kit->first->id()); + // 1. Case of variant as Source pointer + if (Source * const* v = std::get_if(&kit->first)) { + cbNode->SetAttribute("id", (*v)->id()); + } + // 2. Case of variant as index of batch + else if ( const size_t* v = std::get_if(&kit->first)) { + cbNode->SetAttribute("batch", *v); + } inputsNode->InsertEndChild(cbNode); // launch visitor to complete attributes SessionVisitor sv(doc, cbNode); diff --git a/src/SourceCallback.cpp b/src/SourceCallback.cpp index a0c2646..ba1ce0b 100644 --- a/src/SourceCallback.cpp +++ b/src/SourceCallback.cpp @@ -117,21 +117,26 @@ bool SourceCallback::overlap( SourceCallback *a, SourceCallback *b) { const Grab *_a = static_cast(a); const Grab *_b = static_cast(b); - // there is no overlap if the X or Y of a vector is zero - if ( ABS(_a->value().x) < EPSILON || ABS(_b->value().x) < EPSILON ) - ret = false; - else if ( ABS(_a->value().y) < EPSILON || ABS(_b->value().y) < EPSILON ) + if ( ABS_DIFF(_a->value().x, _b->value().x) > EPSILON || ABS_DIFF(_a->value().y, _b->value().y) > EPSILON ) ret = false; + +// // there is no overlap if the X or Y of a vector is zero +// if ( ABS(_a->value().x) < EPSILON || ABS(_b->value().x) < EPSILON ) +// ret = false; +// else if ( ABS(_a->value().y) < EPSILON || ABS(_b->value().y) < EPSILON ) +// ret = false; } break; case SourceCallback::CALLBACK_RESIZE: { const Resize *_a = static_cast(a); const Resize *_b = static_cast(b); - if ( ABS(_a->value().x) < EPSILON || ABS(_b->value().x) < EPSILON ) - ret = false; - else if ( ABS(_a->value().y) < EPSILON || ABS(_b->value().y) < EPSILON ) + if ( ABS_DIFF(_a->value().x, _b->value().x) > EPSILON || ABS_DIFF(_a->value().y, _b->value().y) > EPSILON ) ret = false; +// if ( ABS(_a->value().x) < EPSILON || ABS(_b->value().x) < EPSILON ) +// ret = false; +// else if ( ABS(_a->value().y) < EPSILON || ABS(_b->value().y) < EPSILON ) +// ret = false; } break; default: diff --git a/src/SourceList.h b/src/SourceList.h index 2ca28b1..897f8b3 100644 --- a/src/SourceList.h +++ b/src/SourceList.h @@ -3,6 +3,7 @@ #include #include +#include #include class Source; @@ -61,4 +62,7 @@ SourceLinkList getLinkList (const SourceList &list); void clearLinkList (SourceLinkList list); SourceList validateLinkList (const SourceLinkList &list); +// utility type to target either a source or a batch +typedef std::variant Target; + #endif // SOURCELIST_H diff --git a/src/UserInterfaceManager.cpp b/src/UserInterfaceManager.cpp index d72afbf..12a3c1a 100644 --- a/src/UserInterfaceManager.cpp +++ b/src/UserInterfaceManager.cpp @@ -105,7 +105,7 @@ TextEditor _editor; #define PLOT_CIRCLE_SEGMENTS 64 #define PLOT_ARRAY_SIZE 180 #define LABEL_AUTO_MEDIA_PLAYER ICON_FA_USER_CIRCLE " User selection" -#define LABEL_STORE_SELECTION " Store selection" +#define LABEL_STORE_SELECTION " Create Batch" #define LABEL_EDIT_FADING ICON_FA_RANDOM " Fade in & out" #define LABEL_VIDEO_SEQUENCE " Encode an image sequence" @@ -2386,7 +2386,7 @@ void SourceController::Render() if (ImGui::BeginMenu(active_label_.c_str())) { // info on selection status - size_t N = Mixer::manager().session()->numPlayGroups(); + size_t N = Mixer::manager().session()->numBatch(); bool enabled = !selection_.empty() && active_selection_ < 0; // Menu : Dynamic selection @@ -2396,8 +2396,8 @@ void SourceController::Render() if (ImGui::MenuItem(ICON_FA_PLUS_CIRCLE LABEL_STORE_SELECTION, NULL, false, enabled)) { active_selection_ = N; - active_label_ = std::string(ICON_FA_CHECK_CIRCLE " Selection #") + std::to_string(active_selection_); - Mixer::manager().session()->addPlayGroup( ids(selection_) ); + active_label_ = std::string(ICON_FA_CHECK_CIRCLE " Batch #") + std::to_string(active_selection_); + Mixer::manager().session()->addBatch( ids(selection_) ); info_.reset(); } // Menu : list of selections @@ -2405,7 +2405,7 @@ void SourceController::Render() ImGui::Separator(); for (size_t i = 0 ; i < N; ++i) { - std::string label = std::string(ICON_FA_CHECK_CIRCLE " Selection #") + std::to_string(i); + std::string label = std::string(ICON_FA_CHECK_CIRCLE " Batch #") + std::to_string(i); if (ImGui::MenuItem( label.c_str() )) { active_selection_ = i; @@ -2713,7 +2713,7 @@ void SourceController::RenderSelection(size_t i) ImVec2 rendersize = ImGui::GetContentRegionAvail() - ImVec2(0, buttons_height_ + scrollbar_ + v_space_); ImVec2 bottom = ImVec2(top.x, top.y + rendersize.y + v_space_); - selection_ = Mixer::manager().session()->playGroup(i); + selection_ = Mixer::manager().session()->getBatch(i); int numsources = selection_.size(); // no source selected @@ -3009,11 +3009,11 @@ void SourceController::RenderSelection(size_t i) std::string label = std::string((*s)->initials()) + " - " + (*s)->name(); if (std::find(selection_.begin(),selection_.end(),*s) == selection_.end()) { if (ImGui::MenuItem( label.c_str() , 0, false )) - Mixer::manager().session()->addToPlayGroup(i, *s); + Mixer::manager().session()->addSourceToBatch(i, *s); } else { if (ImGui::MenuItem( label.c_str(), 0, true )) - Mixer::manager().session()->removeFromPlayGroup(i, *s); + Mixer::manager().session()->removeSourceFromBatch(i, *s); } } } @@ -3025,7 +3025,7 @@ void SourceController::RenderSelection(size_t i) ImGui::SetCursorPosX(rendersize.x - buttons_height_ / 1.3f); if (ImGui::Button(ICON_FA_MINUS_CIRCLE)) { resetActiveSelection(); - Mixer::manager().session()->deletePlayGroup(i); + Mixer::manager().session()->deleteBatch(i); } if (ImGui::IsItemHovered()) ImGuiToolkit::ToolTip("Delete"); @@ -3390,9 +3390,9 @@ void SourceController::RenderSelectedSources() ImGui::SameLine(0, space -width); ImGui::SetNextItemWidth(width); if (ImGui::Button( label.c_str() )) { - active_selection_ = Mixer::manager().session()->numPlayGroups(); - active_label_ = std::string("Selection #") + std::to_string(active_selection_); - Mixer::manager().session()->addPlayGroup( ids(selection_) ); + active_selection_ = Mixer::manager().session()->numBatch(); + active_label_ = std::string("Batch #") + std::to_string(active_selection_); + Mixer::manager().session()->addBatch( ids(selection_) ); } if (space < buttons_width_ && ImGui::IsItemHovered()) ImGuiToolkit::ToolTip(LABEL_STORE_SELECTION); @@ -4816,14 +4816,25 @@ bool InputMappingInterface::Visible() const ); } - -Source *InputMappingInterface::ComboSelectSource(Source *current) +/// +/// Draw a combo box listing all sources and all batch of the current session +/// Returns a Target variant : non-assigned by default (std::monostate), or a Source, or a Batch +/// If current element is indicated, it is displayed first +/// +Target InputMappingInterface::ComboSelectTarget(const Target ¤t) { - Source *selected = nullptr; + Target selected; std::string label = "Select"; - if (current) - label = std::string(current->initials()) + " - " + current->name(); + // depending on variant, fill the label of combo + if (current.index() > 0) { + if (Source * const* v = std::get_if(¤t)) { + label = std::string((*v)->initials()) + " - " + (*v)->name(); + } + else if ( const size_t* v = std::get_if(¤t)) { + label = std::string("Batch #") + std::to_string(*v); + } + } if (ImGui::BeginCombo("##ComboSelectSource", label.c_str()) ) { @@ -4834,6 +4845,13 @@ Source *InputMappingInterface::ComboSelectSource(Source *current) selected = *sit; } } + for (size_t b = 0; b < Mixer::manager().session()->numBatch(); ++b){ + label = std::string("Batch #") + std::to_string(b); + if (ImGui::Selectable( label.c_str() )) { + selected = b; + } + } + ImGui::EndCombo(); } @@ -4893,7 +4911,7 @@ struct ClosestIndex void operator()(float v) { if (v < val) ++index; } }; -void InputMappingInterface::SliderParametersCallback(SourceCallback *callback, Source *source) +void InputMappingInterface::SliderParametersCallback(SourceCallback *callback, const Target &target) { const float right_align = -1.05f * ImGui::GetTextLineHeightWithSpacing(); static const char *press_tooltip[3] = {"Key Press\nApply value on key press", @@ -4956,22 +4974,52 @@ void InputMappingInterface::SliderParametersCallback(SourceCallback *callback, S if (ImGuiToolkit::IconMultistate(speed_icon, &speed_index, speed_tooltip )) edited->setDuration(speed_values[speed_index]); - ImGui::SameLine(0, IMGUI_SAME_LINE / 2); - if (ImGui::Button("Capture", ImVec2(right_align, 0))) { - edited->setTarget( source->group(View::GEOMETRY) ); + if (target.index() > 0) { + // 1. Case of variant as Source pointer + if (Source * const* v = std::get_if(&target)) { + // Button to capture the source current geometry + ImGui::SameLine(0, IMGUI_SAME_LINE / 2); + if (ImGui::Button("Capture", ImVec2(right_align, 0))) { + edited->setTarget( (*v)->group(View::GEOMETRY) ); + } + } + // 2. Case of variant as index of batch + else if ( const size_t* v = std::get_if(&target)) { + + std::vector _batch = Mixer::manager().session()->getAllBatch(); + + std::string label = "Capture"; + // Combo box to set which source to capture + if (ImGui::BeginCombo("##ComboSelectGeometryCapture", label.c_str()) ) + { + if ( *v < _batch.size() ) + { + for (auto sid = _batch[*v].begin(); sid != _batch[*v].end(); ++sid){ + SourceList::iterator sit = Mixer::manager().session()->find(*sid); + if ( sit != Mixer::manager().session()->end() ) { + + label = std::string((*sit)->initials()) + " - " + (*sit)->name(); + // C to capture the source current geometry + if (ImGui::Selectable( label.c_str() )) { + edited->setTarget( (*sit)->group(View::GEOMETRY) ); + } + } + } + } + + ImGui::EndCombo(); + } + } + + ImGui::SameLine(0, IMGUI_SAME_LINE / 2); + ImGuiToolkit::Indication("Target geometry places the source by setting its position, scale and rotation", 1, 16); } - // show current geometry on over - if (ImGui::IsItemHovered()) { - InfoVisitor info; - Group tmp; edited->getTarget(&tmp); - tmp.accept(info); - ImGui::BeginTooltip(); - ImGui::Text("%s", info.str().c_str()); - ImGui::EndTooltip(); + else { + + ImGui::SameLine(0, IMGUI_SAME_LINE / 2); + ImGui::TextDisabled("Invalid"); } - ImGui::SameLine(0, IMGUI_SAME_LINE / 2); - ImGuiToolkit::Indication("Target geometry places the source by setting its position, scale and rotation", 1, 16); } break; @@ -5251,7 +5299,7 @@ void InputMappingInterface::Render() // Clear all if ( ImGui::MenuItem( ICON_FA_BACKSPACE " Clear all" ) ) - S->clearSourceCallbacks(); + S->clearInputCallbacks(); // output manager menu ImGui::Separator(); @@ -5289,7 +5337,7 @@ void InputMappingInterface::Render() { if ( ImGui::MenuItem( ICON_FA_WINDOW_CLOSE " Reset mapping", NULL, false, S->inputAssigned(current_input_) ) ) // remove all source callback of this input - S->deleteSourceCallbacks(current_input_); + S->deleteInputCallbacks(current_input_); if (ImGui::BeginMenu(ICON_FA_CLOCK " Metronome", S->inputAssigned(current_input_))) { @@ -5315,7 +5363,7 @@ void InputMappingInterface::Render() for (auto m = models.cbegin(); m != models.cend(); ++m) { if ( *m != current_input_ ) { if ( ImGui::MenuItem( Control::inputLabel( *m ).c_str() ) ){ - S->copySourceCallback( *m, current_input_); + S->copyInputCallback( *m, current_input_); } } } @@ -5378,7 +5426,7 @@ void InputMappingInterface::Render() // drop means change key of input callbacks uint previous_input_key = *(const int*)payload->Data; // swap - S->swapSourceCallback(previous_input_key, ik); + S->swapInputCallback(previous_input_key, ik); // switch to this key current_input_ = ik; } @@ -5455,7 +5503,7 @@ void InputMappingInterface::Render() // drop means change key of input callbacks uint previous_input_key = *(const int*)payload->Data; // swap - S->swapSourceCallback(previous_input_key, ik); + S->swapInputCallback(previous_input_key, ik); // switch to this key current_input_ = ik; } @@ -5529,7 +5577,7 @@ void InputMappingInterface::Render() // drop means change key of input callbacks uint previous_input_key = *(const int*)payload->Data; // swap - S->swapSourceCallback(previous_input_key, it); + S->swapInputCallback(previous_input_key, it); // switch to this key current_input_ = it; } @@ -5623,7 +5671,7 @@ void InputMappingInterface::Render() // drop means change key of input callbacks uint previous_input_key = *(const int*)payload->Data; // swap - S->swapSourceCallback(previous_input_key, ig); + S->swapInputCallback(previous_input_key, ig); // switch to this key current_input_ = ig; } @@ -5741,7 +5789,7 @@ void InputMappingInterface::Render() auto result = S->getSourceCallbacks(current_input_); for (auto kit = result.cbegin(); kit != result.cend(); ++kit) { - Source *source = kit->first; + Target target = kit->first; SourceCallback *callback = kit->second; // push ID because we repeat the same UI @@ -5749,33 +5797,42 @@ void InputMappingInterface::Render() // Delete interface if (ImGuiToolkit::IconButton(ICON_FA_MINUS, "Remove") ){ - S->deleteSourceCallback(callback); + S->deleteInputCallback(callback); // reload ImGui::PopID(); break; } - // Select Source + // Select Target ImGui::SameLine(0, IMGUI_SAME_LINE); ImGui::SetNextItemWidth(w); - Source *selected_source = ComboSelectSource(source); - if (selected_source != nullptr) { + Target selected_target = ComboSelectTarget(target); + // if the selected target variant was filled with a value + if (selected_target.index() > 0) { // reassign the callback to newly selected source - S->assignSourceCallback(current_input_, selected_source, callback); + S->assignInputCallback(current_input_, selected_target, callback); // reload ImGui::PopID(); break; } + // check if target is a Source with image processing enabled + bool withimageprocessing = false; + if ( selected_target.index() == 1 ) { + if (Source * const* v = std::get_if(&selected_target)) { + withimageprocessing = (*v)->imageProcessingEnabled(); + } + } + // Select Reaction ImGui::SameLine(0, IMGUI_SAME_LINE); ImGui::SetNextItemWidth(w); - uint type = ComboSelectCallback( callback->type(), source->imageProcessingEnabled() ); + uint type = ComboSelectCallback( callback->type(), withimageprocessing ); if (type > 0) { // remove previous callback - S->deleteSourceCallback(callback); + S->deleteInputCallback(callback); // assign callback - S->assignSourceCallback(current_input_, source, SourceCallback::create((SourceCallback::CallbackType)type) ); + S->assignInputCallback(current_input_, target, SourceCallback::create((SourceCallback::CallbackType)type) ); // reload ImGui::PopID(); break; @@ -5784,7 +5841,7 @@ void InputMappingInterface::Render() // Adjust parameters ImGui::SameLine(0, IMGUI_SAME_LINE); if (callback) - SliderParametersCallback( callback, source ); + SliderParametersCallback( callback, target ); ImGui::PopID(); @@ -5799,13 +5856,13 @@ void InputMappingInterface::Render() /// Add a new interface /// static bool temp_new_input = false; - static Source *temp_new_source = nullptr; + static Target temp_new_target; static uint temp_new_callback = 0; // step 1 : press '+' if (temp_new_input) { if (ImGuiToolkit::IconButton(ICON_FA_TIMES, "Cancel") ){ - temp_new_source = nullptr; + temp_new_target = std::monostate(); temp_new_callback = 0; temp_new_input = false; } @@ -5817,24 +5874,32 @@ void InputMappingInterface::Render() // step 2 : Get input for source ImGui::SameLine(0, IMGUI_SAME_LINE); ImGui::SetNextItemWidth(w); - Source *s = ComboSelectSource(temp_new_source); - // user selected a source (or changed selection) - if (s != nullptr) { - temp_new_source = s; + + Target selected_target = ComboSelectTarget(temp_new_target); + // if the selected target variant was filled with a value + if (selected_target.index() > 0) { + temp_new_target = selected_target; temp_new_callback = 0; } - // possible new source - if (temp_new_source != nullptr) { + // possible new target + if (temp_new_target.index() > 0) { + // check if target is a Source with image processing enabled + bool withimageprocessing = false; + if ( temp_new_target.index() == 1 ) { + if (Source * const* v = std::get_if(&temp_new_target)) { + withimageprocessing = (*v)->imageProcessingEnabled(); + } + } // step 3: Get input for callback type ImGui::SameLine(0, IMGUI_SAME_LINE); ImGui::SetNextItemWidth(w); - temp_new_callback = ComboSelectCallback( temp_new_callback, temp_new_source->imageProcessingEnabled() ); + temp_new_callback = ComboSelectCallback( temp_new_callback, withimageprocessing ); // user selected a callback type if (temp_new_callback > 0) { // step 4 : create new callback and add it to source - S->assignSourceCallback(current_input_, temp_new_source, SourceCallback::create((SourceCallback::CallbackType)temp_new_callback) ); + S->assignInputCallback(current_input_, temp_new_target, SourceCallback::create((SourceCallback::CallbackType)temp_new_callback) ); // done - temp_new_source = nullptr; + temp_new_target = std::monostate(); temp_new_callback = 0; temp_new_input = false; } diff --git a/src/UserInterfaceManager.h b/src/UserInterfaceManager.h index ad09803..245fbd6 100644 --- a/src/UserInterfaceManager.h +++ b/src/UserInterfaceManager.h @@ -1,10 +1,12 @@ #ifndef __UI_MANAGER_H_ #define __UI_MANAGER_H_ +#include "Session.h" #include #include #include #include +#include #include @@ -392,9 +394,9 @@ class InputMappingInterface : public WorkspaceWindow std::array< uint, 4 > current_input_for_mode; uint current_input_; - Source *ComboSelectSource(Source *current = nullptr); + Target ComboSelectTarget(const Target ¤t); uint ComboSelectCallback(uint current, bool imageprocessing); - void SliderParametersCallback(SourceCallback *callback, Source *source); + void SliderParametersCallback(SourceCallback *callback, const Target &target); public: InputMappingInterface();