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).
This commit is contained in:
Bruno Herbelin
2023-02-05 17:05:29 +01:00
parent 1e9f8d707e
commit c5cb635b4e
9 changed files with 297 additions and 166 deletions

View File

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

View File

@@ -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();
// Add callback to the target(s)
// 1. Case of variant as Source pointer
if (Source * const* v = std::get_if<Source *>(&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 the source (force override)
k->second.source_->call( C, true );
// add callback to source
(*v)->call( C, true );
// get the reverse if the callback, and remember it (can be null)
k->second.reverse_ = C->reverse(k->second.source_);
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<size_t>(&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<MixingGroup *>::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<uint, InputSourceCallback>::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<uint, InputSourceCallback> 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<Source *, SourceCallback *> > Session::getSourceCallbacks(uint input)
std::list<std::pair<Target, SourceCallback *> > Session::getSourceCallbacks(uint input)
{
std::list< std::pair<Source *, SourceCallback*> > 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<Source *, SourceCallback*>(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);
}

View File

@@ -2,6 +2,7 @@
#define SESSION_H
#include <mutex>
#include <variant>
#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<SourceIdList> 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<SourceIdList> getAllBatch() { return batch_; }
// callbacks associated to inputs
void assignSourceCallback(uint input, Source *source, SourceCallback *callback);
std::list< std::pair<Source *, SourceCallback*> > 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<Target, SourceCallback*> > getSourceCallbacks(uint input);
void deleteInputCallback (SourceCallback *callback);
void deleteInputCallbacks(Target target);
void deleteInputCallbacks(uint input);
void clearInputCallbacks ();
std::list<uint> 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<Metronome::Synchronicity> getInputSynchrony();
@@ -190,7 +191,7 @@ protected:
std::list<MixingGroup *> mixing_groups_;
std::map<View::Mode, Group*> config_;
SessionSnapshots snapshots_;
std::vector<SourceIdList> play_groups_;
std::vector<SourceIdList> 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<uint64_t, SourceCallback *> reverse_;
Target target_;
InputSourceCallback() {
active_ = false;
model_ = nullptr;
reverse_ = nullptr;
source_ = nullptr;
target_ = nullptr;
}
void clearReverse();
};
std::multimap<uint, InputSourceCallback> input_callbacks_;
std::vector<Metronome::Synchronicity> input_sync_;

View File

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

View File

@@ -197,7 +197,7 @@ void SessionVisitor::savePlayGroups(tinyxml2::XMLDocument *doc, Session *session
if (doc != nullptr && session != nullptr)
{
XMLElement *playlistNode = doc->NewElement("PlayGroups");
std::vector<SourceIdList> pl = session->getPlayGroups();
std::vector<SourceIdList> 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<Source *>(&kit->first)) {
cbNode->SetAttribute("id", (*v)->id());
}
// 2. Case of variant as index of batch
else if ( const size_t* v = std::get_if<size_t>(&kit->first)) {
cbNode->SetAttribute("batch", *v);
}
inputsNode->InsertEndChild(cbNode);
// launch visitor to complete attributes
SessionVisitor sv(doc, cbNode);

View File

@@ -117,21 +117,26 @@ bool SourceCallback::overlap( SourceCallback *a, SourceCallback *b)
{
const Grab *_a = static_cast<Grab*>(a);
const Grab *_b = static_cast<Grab*>(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<Resize*>(a);
const Resize *_b = static_cast<Resize*>(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:

View File

@@ -3,6 +3,7 @@
#include <list>
#include <set>
#include <variant>
#include <glm/glm.hpp>
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<std::monostate, Source *, size_t> Target;
#endif // SOURCELIST_H

View File

@@ -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 &current)
{
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<Source *>(&current)) {
label = std::string((*v)->initials()) + " - " + (*v)->name();
}
else if ( const size_t* v = std::get_if<size_t>(&current)) {
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]);
if (target.index() > 0) {
// 1. Case of variant as Source pointer
if (Source * const* v = std::get_if<Source *>(&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( source->group(View::GEOMETRY) );
edited->setTarget( (*v)->group(View::GEOMETRY) );
}
}
// 2. Case of variant as index of batch
else if ( const size_t* v = std::get_if<size_t>(&target)) {
std::vector<SourceIdList> _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();
}
// 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();
}
ImGui::SameLine(0, IMGUI_SAME_LINE / 2);
ImGuiToolkit::Indication("Target geometry places the source by setting its position, scale and rotation", 1, 16);
}
else {
ImGui::SameLine(0, IMGUI_SAME_LINE / 2);
ImGui::TextDisabled("Invalid");
}
}
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<Source *>(&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<Source *>(&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;
}

View File

@@ -1,10 +1,12 @@
#ifndef __UI_MANAGER_H_
#define __UI_MANAGER_H_
#include "Session.h"
#include <string>
#include <array>
#include <list>
#include <future>
#include <variant>
#include <gst/gstutils.h>
@@ -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 &current);
uint ComboSelectCallback(uint current, bool imageprocessing);
void SliderParametersCallback(SourceCallback *callback, Source *source);
void SliderParametersCallback(SourceCallback *callback, const Target &target);
public:
InputMappingInterface();