diff --git a/ActionManager.cpp b/ActionManager.cpp index 2a3ac8b..7a63e37 100644 --- a/ActionManager.cpp +++ b/ActionManager.cpp @@ -6,10 +6,12 @@ #include "Mixer.h" #include "MixingGroup.h" #include "tinyxml2Toolkit.h" -#include "SessionSource.h" +#include "ImageProcessingShader.h" #include "SessionVisitor.h" #include "SessionCreator.h" #include "Settings.h" +#include "BaseToolkit.h" +#include "Interpolator.h" #include "ActionManager.h" @@ -17,20 +19,59 @@ #define ACTION_DEBUG #endif +#define HISTORY_NODE(i) std::to_string(i).insert(0,1,'H') +#define SNAPSHOT_NODE(i) std::to_string(i).insert(0,1,'S') + using namespace tinyxml2; -Action::Action(): step_(0), max_step_(0) +void captureMixerSession(tinyxml2::XMLDocument *doc, std::string node, std::string label) { + + // create node + XMLElement *sessionNode = doc->NewElement( node.c_str() ); + doc->InsertEndChild(sessionNode); + // label describes the action + sessionNode->SetAttribute("label", label.c_str() ); + // view indicates the view when this action occured + sessionNode->SetAttribute("view", (int) Mixer::manager().view()->mode()); + + // get session to operate on + Session *se = Mixer::manager().session(); + se->lock(); + + // get the thumbnail (requires one opengl update to render) + FrameBufferImage *thumbnail = se->thumbnail(); + XMLElement *imageelement = SessionVisitor::ImageToXML(thumbnail, doc); + if (imageelement) + sessionNode->InsertEndChild(imageelement); + delete thumbnail; + + // save all sources using source visitor + SessionVisitor sv(doc, sessionNode); + for (auto iter = se->begin(); iter != se->end(); ++iter, sv.setRoot(sessionNode) ) + (*iter)->accept(sv); + + se->unlock(); } -void Action::clear() + +Action::Action(): history_step_(0), history_max_step_(0), locked_(false), + snapshot_id_(0), snapshot_node_(nullptr), interpolator_(nullptr), interpolator_node_(nullptr) +{ + +} + +void Action::init() { // clean the history - xmlDoc_.Clear(); - step_ = 0; - max_step_ = 0; + history_doc_.Clear(); + history_step_ = 0; + history_max_step_ = 0; + + // reset snapshot + snapshot_id_ = 0; + snapshot_node_ = nullptr; - // start fresh store("Session start"); } @@ -41,69 +82,53 @@ void Action::store(const std::string &label) return; // incremental naming of history nodes - step_++; - std::string nodename = "H" + std::to_string(step_); + history_step_++; // erase future - for (uint e = step_; e <= max_step_; e++) { - std::string name = "H" + std::to_string(e); - XMLElement *node = xmlDoc_.FirstChildElement( name.c_str() ); + for (uint e = history_step_; e <= history_max_step_; e++) { + XMLElement *node = history_doc_.FirstChildElement( HISTORY_NODE(e).c_str() ); if ( node ) - xmlDoc_.DeleteChild(node); + history_doc_.DeleteChild(node); } - max_step_ = step_; + history_max_step_ = history_step_; - // create history node - XMLElement *sessionNode = xmlDoc_.NewElement( nodename.c_str() ); - xmlDoc_.InsertEndChild(sessionNode); - // label describes the action - sessionNode->SetAttribute("label", label.c_str()); - // view indicates the view when this action occured - sessionNode->SetAttribute("view", (int) Mixer::manager().view()->mode()); + // threaded capturing state of current session + std::thread(captureMixerSession, &history_doc_, HISTORY_NODE(history_step_), label).detach(); - // get session to operate on - Session *se = Mixer::manager().session(); - - // save all sources using source visitor - SessionVisitor sv(&xmlDoc_, sessionNode); - for (auto iter = se->begin(); iter != se->end(); iter++, sv.setRoot(sessionNode) ) - (*iter)->accept(sv); - - // debug #ifdef ACTION_DEBUG - Log::Info("Action stored %s '%s'", nodename.c_str(), label.c_str()); -// XMLSaveDoc(&xmlDoc_, "/home/bhbn/history.xml"); + Log::Info("Action stored %d '%s'", history_step_, label.c_str()); +// XMLSaveDoc(&history_doc_, "/home/bhbn/history.xml"); #endif } void Action::undo() { // not possible to go to 1 -1 = 0 - if (step_ <= 1) + if (history_step_ <= 1) return; // restore always changes step_ to step_ - 1 - restore( step_ - 1); + restore( history_step_ - 1); } void Action::redo() { // not possible to go to max_step_ + 1 - if (step_ >= max_step_) + if (history_step_ >= history_max_step_) return; // restore always changes step_ to step_ + 1 - restore( step_ + 1); + restore( history_step_ + 1); } void Action::stepTo(uint target) { // get reasonable target - uint t = CLAMP(target, 1, max_step_); + uint t = CLAMP(target, 1, history_max_step_); // ignore t == step_ - if (t != step_) + if (t != history_step_) restore(t); } @@ -111,137 +136,263 @@ std::string Action::label(uint s) const { std::string l = ""; - if (s > 0 && s <= max_step_) { - std::string nodename = "H" + std::to_string(s); - const XMLElement *sessionNode = xmlDoc_.FirstChildElement( nodename.c_str() ); - l = sessionNode->Attribute("label"); + if (s > 0 && s <= history_max_step_) { + const XMLElement *sessionNode = history_doc_.FirstChildElement( HISTORY_NODE(s).c_str()); + if (sessionNode) + l = sessionNode->Attribute("label"); } return l; } +FrameBufferImage *Action::thumbnail(uint s) const +{ + FrameBufferImage *img = nullptr; + + if (s > 0 && s <= history_max_step_) { + const XMLElement *sessionNode = history_doc_.FirstChildElement( HISTORY_NODE(s).c_str()); + if (sessionNode) + img = SessionLoader::XMLToImage(sessionNode); + } + + return img; +} + void Action::restore(uint target) { // lock locked_ = true; // get history node of target step - step_ = CLAMP(target, 1, max_step_); - std::string nodename = "H" + std::to_string(step_); - XMLElement *sessionNode = xmlDoc_.FirstChildElement( nodename.c_str() ); + history_step_ = CLAMP(target, 1, history_max_step_); + XMLElement *sessionNode = history_doc_.FirstChildElement( HISTORY_NODE(history_step_).c_str() ); - // ask view to refresh, and switch to action view if user prefers - int view = Settings::application.current_view ; - if (Settings::application.action_history_follow_view) - sessionNode->QueryIntAttribute("view", &view); - Mixer::manager().setView( (View::Mode) view); + if (sessionNode) { -#ifdef ACTION_DEBUG - Log::Info("Restore %s '%s' ", nodename.c_str(), sessionNode->Attribute("label")); -#endif + // ask view to refresh, and switch to action view if user prefers + int view = Settings::application.current_view ; + if (Settings::application.action_history_follow_view) + sessionNode->QueryIntAttribute("view", &view); + Mixer::manager().setView( (View::Mode) view); - // - // compare source lists - // - - // we operate on the current session - Session *se = Mixer::manager().session(); - if (se == nullptr) - return; - - // sessionsources contains list of ids of all sources currently in the session (before loading) - SourceIdList session_sources = se->getIdList(); -// for( auto it = sessionsources.begin(); it != sessionsources.end(); it++) -// Log::Info("sessionsources id %s", std::to_string(*it).c_str()); - - // load history status: - // - if a source exists, its attributes are updated, and that's all - // - if a source does not exists (in current session), it is created inside the session - SessionLoader loader( se ); - loader.load( sessionNode ); - - // loaded_sources contains map of xml ids of all sources treated by loader - std::map< uint64_t, Source* > loaded_sources = loader.getSources(); - - // remove intersect of both lists (sources were updated by SessionLoader) - for( auto lsit = loaded_sources.begin(); lsit != loaded_sources.end(); ){ - auto ssit = std::find(session_sources.begin(), session_sources.end(), (*lsit).first); - if ( ssit != session_sources.end() ) { - lsit = loaded_sources.erase(lsit); - session_sources.erase(ssit); - } - else - lsit++; - } - - // remaining ids in list sessionsources : to remove - while ( !session_sources.empty() ){ - Source *s = Mixer::manager().findSource( session_sources.front() ); - if (s!=nullptr) { -#ifdef ACTION_DEBUG - Log::Info("Delete id %s\n", std::to_string(session_sources.front() ).c_str()); -#endif - // remove the source from the mixer - Mixer::manager().detach( s ); - // delete source from session - se->deleteSource( s ); - } - session_sources.pop_front(); - } - - // remaining sources in list loaded_sources : to add - for ( auto lsit = loaded_sources.begin(); lsit != loaded_sources.end(); lsit++) - { -#ifdef ACTION_DEBUG - Log::Info("Recreate id %s to %s\n", std::to_string((*lsit).first).c_str(), std::to_string((*lsit).second->id()).c_str()); -#endif - // attach created source - Mixer::manager().attach( (*lsit).second ); - - // change the history to match the new id - replaceSourceId( (*lsit).first, (*lsit).second->id()); - } - - // - // compare mixing groups - // - - // Get the list of mixing groups in the xml loader - std::list< SourceList > loadergroups = loader.getMixingGroups(); - - // clear all session groups - auto group_iter = se->beginMixingGroup(); - while ( group_iter != se->endMixingGroup() ) - group_iter = se->deleteMixingGroup(group_iter); - - // apply all changes creating or modifying groups in the session - // (after this, new groups are created and existing groups are adjusted) - for (auto group_loader_it = loadergroups.begin(); group_loader_it != loadergroups.end(); group_loader_it++) { - se->link( *group_loader_it, Mixer::manager().view(View::MIXING)->scene.fg() ); + // actually restore + Mixer::manager().restore(sessionNode); } // free locked_ = false; - } -void Action::replaceSourceId(uint64_t previousid, uint64_t newid) + +void Action::snapshot(const std::string &label) { - // loop over every session history step - XMLElement* historyNode = xmlDoc_.FirstChildElement("H1"); - for( ; historyNode ; historyNode = historyNode->NextSiblingElement()) + // ignore if locked + if (locked_) + return; + + std::string snap_label = BaseToolkit::uniqueName(label, labels()); + + // create snapshot id + u_int64_t id = BaseToolkit::uniqueId(); + + // get session to operate on + Session *se = Mixer::manager().session(); + se->snapshots()->keys_.push_back(id); + + // threaded capture state of current session + std::thread(captureMixerSession, se->snapshots()->xmlDoc_, SNAPSHOT_NODE(id), snap_label).detach(); + +#ifdef ACTION_DEBUG + Log::Info("Snapshot stored %d '%s'", id, snap_label.c_str()); +#endif +} + +void Action::open(uint64_t snapshotid) +{ + if ( snapshot_id_ != snapshotid ) { - // loop over every source in session history - XMLElement* sourceNode = historyNode->FirstChildElement("Source"); - for( ; sourceNode ; sourceNode = sourceNode->NextSiblingElement()) - { - // check if this source node has this id - uint64_t id_source_ = 0; - sourceNode->QueryUnsigned64Attribute("id", &id_source_); - if ( id_source_ == previousid ) - // change to new id - sourceNode->SetAttribute("id", newid); + // get snapshot node of target in current session + Session *se = Mixer::manager().session(); + snapshot_node_ = se->snapshots()->xmlDoc_->FirstChildElement( SNAPSHOT_NODE(snapshotid).c_str() ); + + if (snapshot_node_) + snapshot_id_ = snapshotid; + else + snapshot_id_ = 0; + + interpolator_node_ = nullptr; + } +} + +void Action::replace(uint64_t snapshotid) +{ + // ignore if locked or if no label is given + if (locked_) + return; + + if (snapshotid > 0) + open(snapshotid); + + if (snapshot_node_) { + // remember label + std::string label = snapshot_node_->Attribute("label"); + + // remove previous node + Session *se = Mixer::manager().session(); + se->snapshots()->xmlDoc_->DeleteChild( snapshot_node_ ); + + // threaded capture state of current session + std::thread(captureMixerSession, se->snapshots()->xmlDoc_, SNAPSHOT_NODE(snapshot_id_), label).detach(); + +#ifdef ACTION_DEBUG + Log::Info("Snapshot replaced %d '%s'", snapshot_id_, label.c_str()); +#endif + } +} + +std::list Action::snapshots() const +{ + return Mixer::manager().session()->snapshots()->keys_; +} + +std::list Action::labels() const +{ + std::list names; + + tinyxml2::XMLDocument *doc = Mixer::manager().session()->snapshots()->xmlDoc_; + for ( XMLElement *snap = doc->FirstChildElement(); snap ; snap = snap->NextSiblingElement() ) + names.push_back( snap->Attribute("label")); + + return names; +} + +std::string Action::label(uint64_t snapshotid) const +{ + std::string label = ""; + + // get snapshot node of target in current session + Session *se = Mixer::manager().session(); + const XMLElement *snap = se->snapshots()->xmlDoc_->FirstChildElement( SNAPSHOT_NODE(snapshotid).c_str() ); + + if (snap) + label = snap->Attribute("label"); + + return label; +} + +void Action::setLabel (uint64_t snapshotid, const std::string &label) +{ + open(snapshotid); + + if (snapshot_node_) + snapshot_node_->SetAttribute("label", label.c_str()); +} + +FrameBufferImage *Action::thumbnail(uint64_t snapshotid) const +{ + FrameBufferImage *img = nullptr; + + // get snapshot node of target in current session + Session *se = Mixer::manager().session(); + const XMLElement *snap = se->snapshots()->xmlDoc_->FirstChildElement( SNAPSHOT_NODE(snapshotid).c_str() ); + + if (snap){ + img = SessionLoader::XMLToImage(snap); + } + + return img; +} + +void Action::remove(uint64_t snapshotid) +{ + if (snapshotid > 0) + open(snapshotid); + + if (snapshot_node_) { + // remove + Session *se = Mixer::manager().session(); + se->snapshots()->xmlDoc_->DeleteChild( snapshot_node_ ); + se->snapshots()->keys_.remove( snapshot_id_ ); + } + + snapshot_node_ = nullptr; + snapshot_id_ = 0; +} + +void Action::restore(uint64_t snapshotid) +{ + // lock + locked_ = true; + + if (snapshotid > 0) + open(snapshotid); + + if (snapshot_node_) + // actually restore + Mixer::manager().restore(snapshot_node_); + + // free + locked_ = false; + + store("Snapshot " + label(snapshot_id_)); +} + +float Action::interpolation() +{ + float ret = 0.f; + if ( interpolator_node_ == snapshot_node_ && interpolator_) + ret = interpolator_->current(); + + return ret; +} + +void Action::interpolate(float val, uint64_t snapshotid) +{ + if (snapshotid > 0) + open(snapshotid); + + if (snapshot_node_) { + + if ( interpolator_node_ != snapshot_node_ ) { + + // change interpolator + if (interpolator_) + delete interpolator_; + + // create new interpolator + interpolator_ = new Interpolator; + + // current session + Session *se = Mixer::manager().session(); + + XMLElement* N = snapshot_node_->FirstChildElement("Source"); + for( ; N ; N = N->NextSiblingElement()) { + + // check if a source with the given id exists in the session + uint64_t id_xml_ = 0; + N->QueryUnsigned64Attribute("id", &id_xml_); + SourceList::iterator sit = se->find(id_xml_); + + // a source with this id exists + if ( sit != se->end() ) { + // read target in the snapshot xml + SourceCore target; + SessionLoader::XMLToSourcecore(N, target); + + // add an interpolator for this source + interpolator_->add(*sit, target); + } + } + + // operate interpolation on opened snapshot + interpolator_node_ = snapshot_node_; + } + + if (interpolator_) { +// Log::Info("Action::interpolate %f", val); + interpolator_->apply( val ); } } } + diff --git a/ActionManager.h b/ActionManager.h index 840d253..b8c65b9 100644 --- a/ActionManager.h +++ b/ActionManager.h @@ -1,49 +1,76 @@ #ifndef ACTIONMANAGER_H #define ACTIONMANAGER_H - +#include #include #include +class Interpolator; +class FrameBufferImage; + class Action { // Private Constructor Action(); - Action(Action const& copy); // Not Implemented - Action& operator=(Action const& copy); // Not Implemented + Action(Action const& copy) = delete; + Action& operator=(Action const& copy) = delete; public: - static Action& manager() + static Action& manager () { // The only instance static Action _instance; return _instance; } + void init (); - void store(const std::string &label); + // Undo History + void store (const std::string &label); + void undo (); + void redo (); + void stepTo (uint target); - void clear(); - void undo(); - void redo(); - void stepTo(uint target); + inline uint current () const { return history_step_; } + inline uint max () const { return history_max_step_; } + std::string label (uint s) const; + FrameBufferImage *thumbnail (uint s) const; - inline uint current() const { return step_; } - inline uint max() const { return max_step_; } + // Snapshots + void snapshot (const std::string &label = ""); - std::string label(uint s) const; + std::list snapshots () const; + uint64_t currentSnapshot () const { return snapshot_id_; } + + void open (uint64_t snapshotid); + void replace (uint64_t snapshotid = 0); + void restore (uint64_t snapshotid = 0); + void remove (uint64_t snapshotid = 0); + + std::string label (uint64_t snapshotid) const; + std::list labels () const; + void setLabel (uint64_t snapshotid, const std::string &label); + FrameBufferImage *thumbnail (uint64_t snapshotid) const; + + float interpolation (); + void interpolate (float val, uint64_t snapshotid = 0); private: - void restore(uint target); - void replaceSourceId(uint64_t previousid, uint64_t newid); -// void replaceSourceId(tinyxml2::XMLElement* parentnode, uint64_t previousid, uint64_t newid); - - tinyxml2::XMLDocument xmlDoc_; - uint step_; - uint max_step_; + tinyxml2::XMLDocument history_doc_; + uint history_step_; + uint history_max_step_; std::atomic locked_; + void restore(uint target); + + uint64_t snapshot_id_; + tinyxml2::XMLElement *snapshot_node_; + + Interpolator *interpolator_; + tinyxml2::XMLElement *interpolator_node_; + }; + #endif // ACTIONMANAGER_H diff --git a/BaseToolkit.cpp b/BaseToolkit.cpp new file mode 100644 index 0000000..8234193 --- /dev/null +++ b/BaseToolkit.cpp @@ -0,0 +1,244 @@ +#include "BaseToolkit.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +uint64_t BaseToolkit::uniqueId() +{ + auto duration = std::chrono::high_resolution_clock::now().time_since_epoch(); + // 64-bit int 18446744073709551615UL + return std::chrono::duration_cast(duration).count() % 1000000000000000000UL; +} + + + +std::string BaseToolkit::uniqueName(const std::string &basename, std::list existingnames) +{ + std::string tentativename = basename; + int count = 1; + int max = 100; + + // while tentativename can be found in the list of existingnames + while ( std::find( existingnames.begin(), existingnames.end(), tentativename ) != existingnames.end() ) + { + for( auto it = existingnames.cbegin(); it != existingnames.cend(); ++it) { + if ( it->find(tentativename) != std::string::npos) + ++count; + } + + if (count > 1) + tentativename = basename + "_" + std::to_string( count ); + else + tentativename += "_"; + + if ( --max < 0 ) // for safety only, should never be needed + break; + } + + return tentativename; +} + +// Using ICU transliteration : +// https://unicode-org.github.io/icu/userguide/transforms/general/#icu-transliterators + +std::string BaseToolkit::transliterate(const std::string &input) +{ + auto ucs = icu::UnicodeString::fromUTF8(input); + + UErrorCode status = U_ZERO_ERROR; + icu::Transliterator *firstTrans = icu::Transliterator::createInstance( + "any-NFKD ; [:Nonspacing Mark:] Remove; NFKC; Latin", UTRANS_FORWARD, status); + firstTrans->transliterate(ucs); + delete firstTrans; + + icu::Transliterator *secondTrans = icu::Transliterator::createInstance( + "any-NFKD ; [:Nonspacing Mark:] Remove; [@!#$*%~] Remove; NFKC", UTRANS_FORWARD, status); + secondTrans->transliterate(ucs); + delete secondTrans; + + std::ostringstream output; + output << ucs; + + return output.str(); +} + + +std::string BaseToolkit::byte_to_string(long b) +{ + double numbytes = static_cast(b); + std::ostringstream oss; + + std::list list = {" Bytes", " KB", " MB", " GB", " TB"}; + std::list::iterator i = list.begin(); + + while(numbytes >= 1024.0 && i != list.end()) + { + ++i; + numbytes /= 1024.0; + } + oss << std::fixed << std::setprecision(2) << numbytes << *i; + return oss.str(); +} + +std::string BaseToolkit::bits_to_string(long b) +{ + double numbytes = static_cast(b); + std::ostringstream oss; + + std::list list = {" bit", " Kbit", " Mbit", " Gbit", " Tbit"}; + std::list::iterator i = list.begin(); + + while(numbytes >= 1000.0 && i != list.end()) + { + ++i; + numbytes /= 1000.0; + } + oss << std::fixed << std::setprecision(2) << numbytes << *i; + return oss.str(); +} + + +std::string BaseToolkit::trunc_string(const std::string& path, int N) +{ + std::string trunc = path; + int l = path.size(); + if ( l > N ) { + trunc = std::string("...") + path.substr( l - N + 3 ); + } + return trunc; +} + + +std::string BaseToolkit::common_prefix( const std::list & allStrings ) +{ + if (allStrings.empty()) + return std::string(); + + const std::string &s0 = allStrings.front(); + auto _end = s0.cend(); + for (auto it=std::next(allStrings.cbegin()); it != allStrings.cend(); ++it) + { + auto _loc = std::mismatch(s0.cbegin(), s0.cend(), it->cbegin(), it->cend()); + if (std::distance(_loc.first, _end) > 0) + _end = _loc.first; + } + + return std::string(s0.cbegin(), _end); +} + + +std::string BaseToolkit::common_suffix(const std::list & allStrings) +{ + if (allStrings.empty()) + return std::string(); + + const std::string &s0 = allStrings.front(); + auto r_end = s0.crend(); + for (auto it=std::next(allStrings.cbegin()); it != allStrings.cend(); ++it) + { + auto r_loc = std::mismatch(s0.crbegin(), s0.crend(), it->crbegin(), it->crend()); + if (std::distance(r_loc.first, r_end) > 0) + r_end = r_loc.first; + } + + std::string suffix = std::string(s0.crbegin(), r_end); + std::reverse(suffix.begin(), suffix.end()); + return suffix; +} + + +std::string BaseToolkit::common_pattern(const std::list &allStrings) +{ + if (allStrings.empty()) + return std::string(); + + // find common prefix and suffix + const std::string &s0 = allStrings.front(); + auto _end = s0.cend(); + auto r_end = s0.crend(); + for (auto it=std::next(allStrings.cbegin()); it != allStrings.cend(); ++it) + { + auto _loc = std::mismatch(s0.cbegin(), s0.cend(), it->cbegin(), it->cend()); + if (std::distance(_loc.first, _end) > 0) + _end = _loc.first; + + auto r_loc = std::mismatch(s0.crbegin(), s0.crend(), it->crbegin(), it->crend()); + if (std::distance(r_loc.first, r_end) > 0) + r_end = r_loc.first; + } + + std::string suffix = std::string(s0.crbegin(), r_end); + std::reverse(suffix.begin(), suffix.end()); + + return std::string(s0.cbegin(), _end) + "*" + suffix; +} + +std::string BaseToolkit::common_numbered_pattern(const std::list &allStrings, int *min, int *max) +{ + if (allStrings.empty()) + return std::string(); + + // find common prefix and suffix + const std::string &s0 = allStrings.front(); + auto _end = s0.cend(); + auto r_end = s0.crend(); + for (auto it=std::next(allStrings.cbegin()); it != allStrings.cend(); ++it) + { + auto _loc = std::mismatch(s0.cbegin(), s0.cend(), it->cbegin(), it->cend()); + if (std::distance(_loc.first, _end) > 0) + _end = _loc.first; + + auto r_loc = std::mismatch(s0.crbegin(), s0.crend(), it->crbegin(), it->crend()); + if (std::distance(r_loc.first, r_end) > 0) + r_end = r_loc.first; + } + + // range of middle string, after prefix and before suffix + size_t pos_prefix = std::distance(s0.cbegin(), _end); + size_t pos_suffix = s0.size() - pos_prefix - std::distance(s0.crbegin(), r_end); + + int n = -1; + *max = 0; + *min = INT_MAX; + // loop over all strings to verify there are numbers between prefix and suffix + for (auto it = allStrings.cbegin(); it != allStrings.cend(); ++it) + { + // get middle string, after prefix and before suffix + std::string s = it->substr(pos_prefix, pos_suffix); + // is this central string ONLY made of digits? + if (s.end() == std::find_if(s.begin(), s.end(), [](unsigned char c)->bool { return !isdigit(c); })) { + // yes, validate + *max = std::max(*max, std::atoi(s.c_str()) ); + *min = std::min(*min, std::atoi(s.c_str()) ); + if (n < 0) + n = s.size(); + else if ( n != s.size() ) { + n = 0; + break; + } + } + else { + n = 0; + break; + } + } + + if ( n < 1 ) + return std::string(); + + std::string suffix = std::string(s0.crbegin(), r_end); + std::reverse(suffix.begin(), suffix.end()); + std::string pattern = std::string(s0.cbegin(), _end); + pattern += "%0" + std::to_string(n) + "d"; + pattern += suffix; + return pattern; +} diff --git a/BaseToolkit.h b/BaseToolkit.h new file mode 100644 index 0000000..9f97435 --- /dev/null +++ b/BaseToolkit.h @@ -0,0 +1,41 @@ +#ifndef BASETOOLKIT_H +#define BASETOOLKIT_H + +#include +#include + +namespace BaseToolkit +{ + +// get integer with unique id +uint64_t uniqueId(); + +// proposes a name that is not already in the list +std::string uniqueName(const std::string &basename, std::list existingnames); + +// get a transliteration to Latin of any string +std::string transliterate(const std::string &input); + +// get a string to display memory size with unit KB, MB, GB, TB +std::string byte_to_string(long b); + +// get a string to display bit size with unit Kbit, MBit, Gbit, Tbit +std::string bits_to_string(long b); + +// Truncate a string to display the right most N characters (e.g. ./home/me/toto.mpg -> ...ome/me/toto.mpg) +std::string trunc_string(const std::string& path, int N); + +// find common parts in a list of strings +std::string common_prefix(const std::list &allStrings); +std::string common_suffix(const std::list &allStrings); + +// form a pattern "prefix*suffix" (e.g. file list) +std::string common_pattern(const std::list &allStrings); + +// form a pattern "prefix%03dsuffix" (e.g. numbered file list) +std::string common_numbered_pattern(const std::list &allStrings, int *min, int *max); + +} + + +#endif // BASETOOLKIT_H diff --git a/BoundingBoxVisitor.cpp b/BoundingBoxVisitor.cpp index cd0baab..7eac957 100644 --- a/BoundingBoxVisitor.cpp +++ b/BoundingBoxVisitor.cpp @@ -72,7 +72,7 @@ GlmToolkit::AxisAlignedBoundingBox BoundingBoxVisitor::AABB(SourceList l, View * { // calculate bbox on selection BoundingBoxVisitor selection_visitor_bbox; - for (auto it = l.begin(); it != l.end(); it++) { + for (auto it = l.begin(); it != l.end(); ++it) { // calculate bounding box of area covered by selection selection_visitor_bbox.setModelview( view->scene.ws()->transform_ ); (*it)->group( view->mode() )->accept(selection_visitor_bbox); @@ -86,7 +86,7 @@ GlmToolkit::OrientedBoundingBox BoundingBoxVisitor::OBB(SourceList l, View *view GlmToolkit::OrientedBoundingBox obb_; // try the orientation of each source in the list - for (auto source_it = l.begin(); source_it != l.end(); source_it++) { + for (auto source_it = l.begin(); source_it != l.end(); ++source_it) { float angle = (*source_it)->group( view->mode() )->rotation_.z; glm::mat4 transform = view->scene.ws()->transform_; @@ -94,7 +94,7 @@ GlmToolkit::OrientedBoundingBox BoundingBoxVisitor::OBB(SourceList l, View *view // calculate bbox of the list in this orientation BoundingBoxVisitor selection_visitor_bbox; - for (auto it = l.begin(); it != l.end(); it++) { + for (auto it = l.begin(); it != l.end(); ++it) { // calculate bounding box of area covered by sources' nodes selection_visitor_bbox.setModelview( transform ); (*it)->group( view->mode() )->accept(selection_visitor_bbox); diff --git a/CMakeLists.txt b/CMakeLists.txt index 2476f85..40e0bf8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,6 +40,7 @@ if(UNIX) set(CMAKE_OSX_ARCHITECTURES x86_64) set(CMAKE_OSX_DEPLOYMENT_TARGET "10.13") # set(CMAKE_OSX_DEPLOYMENT_TARGET "10.13" CACHE STRING "Minimum OS X version to target for deployment") + set (ICU_ROOT /usr/local/Cellar/icu4c/67.1) else() add_definitions(-DLINUX) @@ -267,6 +268,7 @@ set(VMIX_BINARY "vimix") set(VMIX_SRCS main.cpp Log.cpp + BaseToolkit.cpp Shader.cpp ImageShader.cpp ImageProcessingShader.cpp @@ -289,7 +291,7 @@ set(VMIX_SRCS Selection.cpp SessionSource.cpp SessionVisitor.cpp - GarbageVisitor.cpp + Interpolator.cpp SessionCreator.cpp Mixer.cpp FrameGrabber.cpp @@ -299,7 +301,6 @@ set(VMIX_SRCS Settings.cpp Screenshot.cpp Resource.cpp - FileDialog.cpp Timeline.cpp Stream.cpp MediaPlayer.cpp @@ -308,6 +309,7 @@ set(VMIX_SRCS PatternSource.cpp DeviceSource.cpp NetworkSource.cpp + MultiFileSource.cpp FrameBuffer.cpp RenderingManager.cpp UserInterfaceManager.cpp @@ -325,6 +327,7 @@ set(VMIX_SRCS NetworkToolkit.cpp Connection.cpp ActionManager.cpp + Overlay.cpp ) @@ -427,6 +430,7 @@ set(VMIX_RSC_FILES ./rsc/mesh/icon_eye_slash.ply ./rsc/mesh/icon_vector_square_slash.ply ./rsc/mesh/icon_cube.ply + ./rsc/mesh/icon_sequence.ply ./rsc/mesh/h_line.ply ./rsc/mesh/h_mark.ply ) diff --git a/Connection.h b/Connection.h index 4a5b654..660f9d4 100644 --- a/Connection.h +++ b/Connection.h @@ -62,8 +62,8 @@ class Connection // Private Constructor Connection(); - Connection(Connection const& copy); // Not Implemented - Connection& operator=(Connection const& copy); // Not Implemented + Connection(Connection const& copy) = delete; + Connection& operator=(Connection const& copy) = delete; public: static Connection& manager() diff --git a/CopyVisitor.cpp b/CopyVisitor.cpp new file mode 100644 index 0000000..58b77e9 --- /dev/null +++ b/CopyVisitor.cpp @@ -0,0 +1,85 @@ +#include "GlmToolkit.h" +#include "Scene.h" +#include "Source.h" + +#include "CopyVisitor.h" + +Node *CopyVisitor::deepCopy(Node *node) +{ + CopyVisitor cv; + node->accept(cv); + + return cv.current_; +} + +CopyVisitor::CopyVisitor() : current_(nullptr) +{ + +} + +void CopyVisitor::visit(Node &n) +{ +} + +void CopyVisitor::visit(Group &n) +{ + Group *here = new Group; + + // node + current_ = here; + current_->copyTransform(&n); + current_->visible_ = n.visible_; + + // loop + for (NodeSet::iterator node = n.begin(); node != n.end(); ++node) { + (*node)->accept(*this); + + here->attach( current_ ); + + current_ = here; + } + +} + +void CopyVisitor::visit(Switch &n) +{ + Switch *here = new Switch; + + // node + current_ = here; + current_->copyTransform(&n); + current_->visible_ = n.visible_; + + // switch properties + here->setActive( n.active() ); + + // loop + for (uint i = 0; i < n.numChildren(); ++i) { + n.child(i)->accept(*this); + + here->attach( current_ ); + + current_ = here; + } + +} + +void CopyVisitor::visit(Scene &n) +{ + Scene *here = new Scene; + + current_ = here->root(); + n.root()->accept(*this); +} + + +void CopyVisitor::visit(Primitive &n) +{ + Primitive *here = new Primitive; + + // node + current_ = here; + current_->copyTransform(&n); + current_->visible_ = n.visible_; + +} diff --git a/CopyVisitor.h b/CopyVisitor.h new file mode 100644 index 0000000..8f7ac37 --- /dev/null +++ b/CopyVisitor.h @@ -0,0 +1,24 @@ +#ifndef COPYVISITOR_H +#define COPYVISITOR_H + +#include "Visitor.h" + +class SourceCore; + +class CopyVisitor : public Visitor +{ + Node *current_; + CopyVisitor(); + +public: + + static Node *deepCopy(Node *node); + + void visit(Scene& n) override; + void visit(Node& n) override; + void visit(Primitive& n) override; + void visit(Group& n) override; + void visit(Switch& n) override; +}; + +#endif // COPYVISITOR_H diff --git a/Decorations.cpp b/Decorations.cpp index 7fe27cf..1572631 100644 --- a/Decorations.cpp +++ b/Decorations.cpp @@ -404,6 +404,8 @@ Symbol::Symbol(Type t, glm::vec3 pos) : Node(), type_(t) shadows[SQUARE_POINT] = nullptr; icons[IMAGE] = new Mesh("mesh/icon_image.ply"); shadows[IMAGE] = shadow; + icons[SEQUENCE] = new Mesh("mesh/icon_sequence.ply"); + shadows[SEQUENCE]= shadow; icons[VIDEO] = new Mesh("mesh/icon_video.ply"); shadows[VIDEO] = shadow; icons[SESSION] = new Mesh("mesh/icon_vimix.ply"); diff --git a/Decorations.h b/Decorations.h index 8fb9016..ee6d130 100644 --- a/Decorations.h +++ b/Decorations.h @@ -60,9 +60,9 @@ protected: class Symbol : public Node { public: - typedef enum { CIRCLE_POINT = 0, SQUARE_POINT, IMAGE, VIDEO, SESSION, CLONE, RENDER, GROUP, PATTERN, CAMERA, CUBE, SHARE, + typedef enum { CIRCLE_POINT = 0, SQUARE_POINT, IMAGE, SEQUENCE, VIDEO, SESSION, CLONE, RENDER, GROUP, PATTERN, CAMERA, CUBE, SHARE, DOTS, BUSY, LOCK, UNLOCK, EYE, EYESLASH, VECTORSLASH, ARROWS, ROTATION, CROP, CIRCLE, SQUARE, CLOCK, CLOCK_H, GRID, CROSS, EMPTY } Type; - Symbol(Type t = CIRCLE_POINT, glm::vec3 pos = glm::vec3(0.f)); + Symbol(Type t, glm::vec3 pos = glm::vec3(0.f)); ~Symbol(); void draw (glm::mat4 modelview, glm::mat4 projection) override; diff --git a/DeviceSource.cpp b/DeviceSource.cpp index 80e3350..44cbc5c 100644 --- a/DeviceSource.cpp +++ b/DeviceSource.cpp @@ -325,7 +325,7 @@ int Device::index(const std::string &device) const return i; } -DeviceSource::DeviceSource() : StreamSource() +DeviceSource::DeviceSource(uint64_t id) : StreamSource(id) { // create stream stream_ = new Stream; @@ -342,7 +342,7 @@ DeviceSource::~DeviceSource() } void DeviceSource::setDevice(const std::string &devicename) -{ +{ device_ = devicename; Log::Notify("Creating Source with device '%s'", device_.c_str()); @@ -384,8 +384,12 @@ void DeviceSource::setDevice(const std::string &devicename) pipeline << " ! videoconvert"; + // open gstreamer stream_->open( pipeline.str(), best.width, best.height); stream_->play(true); + + // will be ready after init and one frame rendered + ready_ = false; } else Log::Warning("No such device '%s'", device_.c_str()); diff --git a/DeviceSource.h b/DeviceSource.h index c582543..7c55b9e 100644 --- a/DeviceSource.h +++ b/DeviceSource.h @@ -10,7 +10,7 @@ class DeviceSource : public StreamSource { public: - DeviceSource(); + DeviceSource(uint64_t id = 0); ~DeviceSource(); // Source interface @@ -87,8 +87,8 @@ class Device friend class DeviceSource; Device(); - Device(Device const& copy); // Not Implemented - Device& operator=(Device const& copy); // Not Implemented + Device(Device const& copy) = delete; + Device& operator=(Device const& copy) = delete; public: diff --git a/DialogToolkit.cpp b/DialogToolkit.cpp index bd6079e..cd2e88d 100644 --- a/DialogToolkit.cpp +++ b/DialogToolkit.cpp @@ -178,7 +178,7 @@ std::string DialogToolkit::openSessionFileDialog(const std::string &path) } -std::string DialogToolkit::ImportFileDialog(const std::string &path) +std::string DialogToolkit::openMediaFileDialog(const std::string &path) { std::string filename = ""; std::string startpath = SystemToolkit::file_exists(path) ? path : SystemToolkit::home_path(); @@ -190,7 +190,7 @@ std::string DialogToolkit::ImportFileDialog(const std::string &path) "*.gif", "*.tif", "*.svg" }; #if USE_TINYFILEDIALOG char const * open_file_name; - open_file_name = tinyfd_openFileDialog( "Import a file", startpath.c_str(), 18, open_pattern, "All supported formats", 0); + open_file_name = tinyfd_openFileDialog( "Open Media File", startpath.c_str(), 18, open_pattern, "All supported formats", 0); if (open_file_name) filename = std::string(open_file_name); @@ -201,7 +201,7 @@ std::string DialogToolkit::ImportFileDialog(const std::string &path) return filename; } - GtkWidget *dialog = gtk_file_chooser_dialog_new( "Import Media File", NULL, + GtkWidget *dialog = gtk_file_chooser_dialog_new( "Open Media File", NULL, GTK_FILE_CHOOSER_ACTION_OPEN, "_Cancel", GTK_RESPONSE_CANCEL, "_Open", GTK_RESPONSE_ACCEPT, NULL ); @@ -238,7 +238,7 @@ std::string DialogToolkit::ImportFileDialog(const std::string &path) return filename; } -std::string DialogToolkit::FolderDialog(const std::string &path) +std::string DialogToolkit::openFolderDialog(const std::string &path) { std::string foldername = ""; std::string startpath = SystemToolkit::file_exists(path) ? path : SystemToolkit::home_path(); @@ -290,6 +290,91 @@ std::string DialogToolkit::FolderDialog(const std::string &path) } +std::list DialogToolkit::selectImagesFileDialog(const std::string &path) +{ + std::list files; + + std::string startpath = SystemToolkit::file_exists(path) ? path : SystemToolkit::home_path(); + char const * open_pattern[3] = { "*.tif", "*.jpg", "*.png" }; + +#if USE_TINYFILEDIALOG + char const * open_file_names; + open_file_names = tinyfd_openFileDialog( "Select images", startpath.c_str(), 3, open_pattern, "Images", 1); + + if (open_file_names) { + + const std::string& str (open_file_names); + const std::string& delimiters = "|"; + // Skip delimiters at beginning. + std::string::size_type lastPos = str.find_first_not_of(delimiters, 0); + + // Find first non-delimiter. + std::string::size_type pos = str.find_first_of(delimiters, lastPos); + + while (std::string::npos != pos || std::string::npos != lastPos) { + // Found a token, add it to the vector. + files.push_back(str.substr(lastPos, pos - lastPos)); + + // Skip delimiters. + lastPos = str.find_first_not_of(delimiters, pos); + + // Find next non-delimiter. + pos = str.find_first_of(delimiters, lastPos); + } + } +#else + + if (!gtk_init()) { + ErrorDialog("Could not initialize GTK+ for dialog"); + return files; + } + + GtkWidget *dialog = gtk_file_chooser_dialog_new( "Select images", NULL, + GTK_FILE_CHOOSER_ACTION_OPEN, + "_Cancel", GTK_RESPONSE_CANCEL, + "_Open", GTK_RESPONSE_ACCEPT, NULL ); + + // set file filters + add_filter_file_dialog(dialog, 3, open_pattern, "All supported formats"); + add_filter_any_file_dialog(dialog); + + // multiple files + gtk_file_chooser_set_select_multiple( GTK_FILE_CHOOSER(dialog), true ); + + // Set the default path + gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER(dialog), startpath.c_str() ); + + // ensure front and centered + gtk_window_set_keep_above( GTK_WINDOW(dialog), TRUE ); + if (window_x > 0 && window_y > 0) + gtk_window_move( GTK_WINDOW(dialog), window_x, window_y); + + // display and get filename + if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT ) { + + GSList *open_file_names = gtk_file_chooser_get_filenames( GTK_FILE_CHOOSER(dialog) ); + + while (open_file_names) { + files.push_back( (char *) open_file_names->data ); + open_file_names = open_file_names->next; +// g_free( open_file_names->data ); + } + + g_slist_free( open_file_names ); + } + + // remember position + gtk_window_get_position( GTK_WINDOW(dialog), &window_x, &window_y); + + // done + gtk_widget_destroy(dialog); + wait_for_event(); +#endif + + + return files; +} + void DialogToolkit::ErrorDialog(const char* message) { #if USE_TINYFILEDIALOG diff --git a/DialogToolkit.h b/DialogToolkit.h index bb2b6a0..d486d1c 100644 --- a/DialogToolkit.h +++ b/DialogToolkit.h @@ -2,6 +2,7 @@ #define DIALOGTOOLKIT_H #include +#include namespace DialogToolkit @@ -11,9 +12,11 @@ std::string saveSessionFileDialog(const std::string &path); std::string openSessionFileDialog(const std::string &path); -std::string ImportFileDialog(const std::string &path); +std::string openMediaFileDialog(const std::string &path); -std::string FolderDialog(const std::string &path); +std::string openFolderDialog(const std::string &path); + +std::list selectImagesFileDialog(const std::string &path); void ErrorDialog(const char* message); diff --git a/FileDialog.cpp b/FileDialog.cpp index d4563ae..7fe512e 100644 --- a/FileDialog.cpp +++ b/FileDialog.cpp @@ -178,7 +178,7 @@ inline PathStruct ParsePathFileName(const std::string& vPathFileName) return res; } -inline void AppendToBuffer(char* vBuffer, size_t vBufferLen, std::string vStr) +inline void AppendToBuffer(char* vBuffer, size_t vBufferLen, const std::string &vStr) { std::string st = vStr; size_t len = vBufferLen - 1u; @@ -253,8 +253,6 @@ static bool stringComparator(const FileInfoStruct& a, const FileInfoStruct& b) void FileDialog::ScanDir(const std::string& vPath) { struct dirent **files = NULL; - int i = 0; - int n = 0; std::string path = vPath; #if defined(LINUX) or defined(APPLE) @@ -280,12 +278,12 @@ void FileDialog::ScanDir(const std::string& vPath) path += PATH_SEP; } #endif - n = scandir(path.c_str(), &files, NULL, alphaSort); + int n = scandir(path.c_str(), &files, NULL, alphaSort); if (n > 0) { m_FileList.clear(); - for (i = 0; i < n; i++) + for (int i = 0; i < n; ++i) { struct dirent *ent = files[i]; @@ -328,7 +326,7 @@ void FileDialog::ScanDir(const std::string& vPath) } } - for (i = 0; i < n; i++) + for (int i = 0; i < n; ++i) { free(files[i]); } @@ -434,7 +432,7 @@ void FileDialog::ComposeNewPath(std::vector::iterator vIter) break; } - vIter--; + --vIter; } } @@ -703,9 +701,9 @@ bool FileDialog::Render(const std::string& vKey, ImVec2 geometry) SetPath("."); // Go Home } +#ifdef WIN32 bool drivesClick = false; -#ifdef WIN32 ImGui::SameLine(); if (ImGui::Button("Drives")) @@ -865,10 +863,12 @@ bool FileDialog::Render(const std::string& vKey, ImVec2 geometry) SetPath(m_CurrentPath); } +#ifdef WIN32 if (drivesClick == true) { GetDrives(); } +#endif ImGui::EndChild(); ImGui::PopFont(); @@ -1013,12 +1013,12 @@ std::string FileDialog::GetUserString() return dlg_userString; } -void FileDialog::SetFilterColor(std::string vFilter, ImVec4 vColor) +void FileDialog::SetFilterColor(const std::string &vFilter, ImVec4 vColor) { m_FilterColor[vFilter] = vColor; } -bool FileDialog::GetFilterColor(std::string vFilter, ImVec4 *vColor) +bool FileDialog::GetFilterColor(const std::string &vFilter, ImVec4 *vColor) { if (vColor) { @@ -1054,7 +1054,7 @@ inline void InfosPane(std::string vFilter, bool *vCantContinue) *vCantContinue = canValidateDialog; } -inline void TextInfosPane(std::string vFilter, bool *vCantContinue) // if vCantContinue is false, the user cant validate the dialog +inline void TextInfosPane(const std::string &vFilter, bool *vCantContinue) // if vCantContinue is false, the user cant validate the dialog { ImGui::TextColored(ImVec4(0, 1, 1, 1), "Text"); @@ -1091,7 +1091,7 @@ inline void TextInfosPane(std::string vFilter, bool *vCantContinue) // if vCantC *vCantContinue = text.size() > 0; } -inline void ImageInfosPane(std::string vFilter, bool *vCantContinue) // if vCantContinue is false, the user cant validate the dialog +inline void ImageInfosPane(const std::string &vFilter, bool *vCantContinue) // if vCantContinue is false, the user cant validate the dialog { // opengl texture static GLuint tex = 0; diff --git a/FileDialog.h b/FileDialog.h index ff104ab..aeb2276 100644 --- a/FileDialog.h +++ b/FileDialog.h @@ -71,8 +71,8 @@ public: protected: FileDialog(); // Prevent construction - FileDialog(const FileDialog&) {} // Prevent construction by copying - FileDialog& operator =(const FileDialog&) { return *this; } // Prevent assignment + FileDialog(const FileDialog&) = delete; // Prevent construction by copying + FileDialog& operator =(const FileDialog&) = delete; // Prevent assignment ~FileDialog(); // Prevent unwanted destruction public: @@ -95,8 +95,8 @@ public: std::string GetCurrentFilter(); std::string GetUserString(); - void SetFilterColor(std::string vFilter, ImVec4 vColor); - bool GetFilterColor(std::string vFilter, ImVec4 *vColor); + void SetFilterColor(const std::string &vFilter, ImVec4 vColor); + bool GetFilterColor(const std::string &vFilter, ImVec4 *vColor); void ClearFilterColor(); private: diff --git a/FrameBuffer.cpp b/FrameBuffer.cpp index 83ad159..793b7b6 100644 --- a/FrameBuffer.cpp +++ b/FrameBuffer.cpp @@ -227,13 +227,17 @@ void FrameBuffer::readPixels(uint8_t *target_data) bool FrameBuffer::blit(FrameBuffer *destination) { - if (!framebufferid_ || !destination) + if (!framebufferid_ || !destination || (use_alpha_ != destination->use_alpha_) ) return false; if (!destination->framebufferid_) destination->init(); - glBindFramebuffer(GL_READ_FRAMEBUFFER, framebufferid_); + if (use_multi_sampling_) + glBindFramebuffer(GL_READ_FRAMEBUFFER, intermediate_framebufferid_); + else + glBindFramebuffer(GL_READ_FRAMEBUFFER, framebufferid_); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, destination->framebufferid_); // blit to the frame buffer object glBlitFramebuffer(0, 0, attrib_.viewport.x, attrib_.viewport.y, @@ -318,7 +322,7 @@ FrameBufferImage::~FrameBufferImage() { delete rgb; } -FrameBufferImage::jpegBuffer FrameBufferImage::getJpeg() +FrameBufferImage::jpegBuffer FrameBufferImage::getJpeg() const { jpegBuffer jpgimg; @@ -377,7 +381,9 @@ bool FrameBuffer::fill(FrameBufferImage *image) // fill texture with image glBindTexture(GL_TEXTURE_2D, textureid_); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, image->width, image->height, - GL_RGB, GL_UNSIGNED_BYTE, image->rgb); + GL_RGB, GL_UNSIGNED_BYTE, image->rgb); + glBindTexture(GL_TEXTURE_2D, 0); + return true; } diff --git a/FrameBuffer.h b/FrameBuffer.h index 916061a..ff5bdeb 100644 --- a/FrameBuffer.h +++ b/FrameBuffer.h @@ -12,7 +12,7 @@ class FrameBufferImage { public: - uint8_t *rgb = nullptr; + uint8_t *rgb; int width; int height; @@ -20,10 +20,13 @@ public: unsigned char *buffer = nullptr; uint len = 0; }; - jpegBuffer getJpeg(); + jpegBuffer getJpeg() const; FrameBufferImage(int w, int h); FrameBufferImage(jpegBuffer jpgimg); + // non assignable class + FrameBufferImage(FrameBufferImage const&) = delete; + FrameBufferImage& operator=(FrameBufferImage const&) = delete; ~FrameBufferImage(); }; @@ -45,6 +48,7 @@ public: FrameBuffer(glm::vec3 resolution, bool useAlpha = false, bool multiSampling = false); FrameBuffer(uint width, uint height, bool useAlpha = false, bool multiSampling = false); + FrameBuffer(FrameBuffer const&) = delete; ~FrameBuffer(); // Bind & push attribs to prepare draw diff --git a/FrameGrabber.cpp b/FrameGrabber.cpp index 211c7c2..af97caf 100644 --- a/FrameGrabber.cpp +++ b/FrameGrabber.cpp @@ -10,6 +10,7 @@ #include "defines.h" #include "Log.h" #include "GstToolkit.h" +#include "BaseToolkit.h" #include "FrameBuffer.h" #include "FrameGrabber.h" @@ -30,8 +31,8 @@ FrameGrabbing::~FrameGrabbing() // cleanup if (caps_) gst_caps_unref (caps_); - if (pbo_[0]) - glDeleteBuffers(2, pbo_); +// if (pbo_[0] > 0) // automatically deleted at shutdown +// glDeleteBuffers(2, pbo_); } void FrameGrabbing::add(FrameGrabber *rec) @@ -145,6 +146,7 @@ void FrameGrabbing::grabFrame(FrameBuffer *frame_buffer, float dt) #else glBindTexture(GL_TEXTURE_2D, frame_buffer->texture()); glGetTexImage(GL_TEXTURE_2D, 0, GL_RGB, GL_UNSIGNED_BYTE, 0); + glBindTexture(GL_TEXTURE_2D, 0); #endif // update case ; alternating indices @@ -210,7 +212,7 @@ FrameGrabber::FrameGrabber(): finished_(false), active_(false), accept_buffer_(f pipeline_(nullptr), src_(nullptr), caps_(nullptr), timestamp_(0) { // unique id - id_ = GlmToolkit::uniqueId(); + id_ = BaseToolkit::uniqueId(); // configure fix parameter frame_duration_ = gst_util_uint64_scale_int (1, GST_SECOND, 30); // 30 FPS timeframe_ = 2 * frame_duration_; diff --git a/FrameGrabber.h b/FrameGrabber.h index 5144677..d5fa873 100644 --- a/FrameGrabber.h +++ b/FrameGrabber.h @@ -8,7 +8,6 @@ #include #include -#include "GlmToolkit.h" // use glReadPixel or glGetTextImage // read pixels & pbo should be the fastest @@ -84,8 +83,8 @@ class FrameGrabbing // Private Constructor FrameGrabbing(); - FrameGrabbing(FrameGrabbing const& copy); // Not Implemented - FrameGrabbing& operator=(FrameGrabbing const& copy); // Not Implemented + FrameGrabbing(FrameGrabbing const& copy) = delete; + FrameGrabbing& operator=(FrameGrabbing const& copy) = delete; public: diff --git a/GarbageVisitor.cpp b/GarbageVisitor.cpp index 6ffada8..9437777 100644 --- a/GarbageVisitor.cpp +++ b/GarbageVisitor.cpp @@ -82,73 +82,3 @@ void GarbageVisitor::visit(Primitive &n) } -void GarbageVisitor::visit(Surface &n) -{ - -} - -void GarbageVisitor::visit(ImageSurface &n) -{ - -} - -void GarbageVisitor::visit(FrameBufferSurface &n) -{ - -} - -void GarbageVisitor::visit(MediaSurface &n) -{ - -} - -void GarbageVisitor::visit(MediaPlayer &n) -{ - -} - -void GarbageVisitor::visit(Shader &n) -{ - -} - -void GarbageVisitor::visit(ImageShader &n) -{ - -} - -void GarbageVisitor::visit(ImageProcessingShader &n) -{ - -} - -void GarbageVisitor::visit(LineStrip &n) -{ - -} - -void GarbageVisitor::visit(LineSquare &) -{ - -} - -void GarbageVisitor::visit(Mesh &n) -{ - -} - -void GarbageVisitor::visit(Frame &n) -{ - -} - - -void GarbageVisitor::visit (Source& s) -{ - -} - -void GarbageVisitor::visit (MediaSource& s) -{ - -} diff --git a/GarbageVisitor.h b/GarbageVisitor.h index 6d8d0a3..ac33dfb 100644 --- a/GarbageVisitor.h +++ b/GarbageVisitor.h @@ -24,23 +24,6 @@ public: void visit(Switch& n) override; void visit(Primitive& n) override; - void visit(Surface& n) override; - void visit(ImageSurface& n) override; - void visit(MediaSurface& n) override; - void visit(FrameBufferSurface& n) override; - void visit(LineStrip& n) override; - void visit(LineSquare&) override; - void visit(Mesh& n) override; - void visit(Frame& n) override; - - void visit(MediaPlayer& n) override; - void visit(Shader& n) override; - void visit(ImageShader& n) override; - void visit(ImageProcessingShader& n) override; - - void visit (Source& s) override; - void visit (MediaSource& s) override; - }; #endif // GARBAGEVISITOR_H diff --git a/GeometryView.cpp b/GeometryView.cpp index 49bf0af..1cc200e 100644 --- a/GeometryView.cpp +++ b/GeometryView.cpp @@ -15,6 +15,7 @@ #include "Mixer.h" #include "defines.h" +#include "Source.h" #include "Settings.h" #include "PickingVisitor.h" #include "DrawVisitor.h" @@ -196,7 +197,7 @@ void GeometryView::draw() std::vector surfaces; std::vector overlays; for (auto source_iter = Mixer::manager().session()->begin(); - source_iter != Mixer::manager().session()->end(); source_iter++) { + source_iter != Mixer::manager().session()->end(); ++source_iter) { // if it is in the current workspace if ((*source_iter)->workspace() == Settings::application.current_workspace) { // will draw its surface @@ -257,7 +258,7 @@ void GeometryView::draw() static std::vector< std::pair > icons_ws = { {10,16}, {11,16}, {12,16} }; static std::vector< std::string > labels_ws = { "Background", "Workspace", "Foreground" }; if ( ImGuiToolkit::ComboIcon (icons_ws, labels_ws, &Settings::application.current_workspace) ){ - View::need_deep_update_++; + ++View::need_deep_update_; } ImGui::PopStyleColor(8); // 14 colors @@ -319,7 +320,7 @@ void GeometryView::draw() // batch manipulation of sources in Geometry view if (ImGui::Selectable( ICON_FA_EXPAND " Fit all" )){ - for (auto sit = Mixer::selection().begin(); sit != Mixer::selection().end(); sit++){ + for (auto sit = Mixer::selection().begin(); sit != Mixer::selection().end(); ++sit){ (*sit)->group(mode_)->scale_ = glm::vec3(output_surface_->scale_.x/ (*sit)->frame()->aspectRatio(), 1.f, 1.f); (*sit)->group(mode_)->rotation_.z = 0; (*sit)->group(mode_)->translation_ = glm::vec3(0.f); @@ -329,7 +330,7 @@ void GeometryView::draw() } if (ImGui::Selectable( ICON_FA_VECTOR_SQUARE " Reset all" )){ // apply to every sources in selection - for (auto sit = Mixer::selection().begin(); sit != Mixer::selection().end(); sit++){ + for (auto sit = Mixer::selection().begin(); sit != Mixer::selection().end(); ++sit){ (*sit)->group(mode_)->scale_ = glm::vec3(1.f); (*sit)->group(mode_)->rotation_.z = 0; (*sit)->group(mode_)->crop_ = glm::vec3(1.f); @@ -351,7 +352,7 @@ void GeometryView::draw() } if (ImGui::Selectable( ICON_FA_COMPASS " Align" )){ // apply to every sources in selection - for (auto sit = Mixer::selection().begin(); sit != Mixer::selection().end(); sit++){ + for (auto sit = Mixer::selection().begin(); sit != Mixer::selection().end(); ++sit){ (*sit)->group(mode_)->rotation_.z = overlay_selection_->rotation_.z; (*sit)->touch(); } @@ -413,7 +414,7 @@ std::pair GeometryView::pick(glm::vec2 P) // find if the current source was picked auto itp = pv.rbegin(); - for (; itp != pv.rend(); itp++){ + for (; itp != pv.rend(); ++itp){ // test if source contains this node Source::hasNode is_in_source((*itp).first ); if ( is_in_source( current ) ){ @@ -431,41 +432,45 @@ std::pair GeometryView::pick(glm::vec2 P) openContextMenu(MENU_SOURCE); } // pick on the lock icon; unlock source - else if ( pick.first == current->lock_ ) { + else if ( UserInterface::manager().ctrlModifier() && pick.first == current->lock_ ) { lock(current, false); + pick = { nullptr, glm::vec2(0.f) }; } // pick on the open lock icon; lock source and cancel pick - else if ( pick.first == current->unlock_ ) { + else if ( UserInterface::manager().ctrlModifier() && pick.first == current->unlock_ ) { lock(current, true); pick = { nullptr, glm::vec2(0.f) }; } - // pick a locked source without CTRL key; cancel pick - else if ( current->locked() && !UserInterface::manager().ctrlModifier() ) { + // pick a locked source ; cancel pick + else if ( current->locked() ) { pick = { nullptr, glm::vec2(0.f) }; } } // the clicked source changed (not the current source) if (current == nullptr) { - // default to failed pick - pick = { nullptr, glm::vec2(0.f) }; + if (UserInterface::manager().ctrlModifier()) { - // loop over all nodes picked to detect clic on locks - for (auto itp = pv.rbegin(); itp != pv.rend(); itp++){ - // get if a source was picked - Source *s = Mixer::manager().findSource((*itp).first); - // lock icon of a source (not current) is picked : unlock - if ( s!=nullptr && (*itp).first == s->lock_) { - lock(s, false); - pick = { s->locker_, (*itp).second }; - break; + // default to failed pick + pick = { nullptr, glm::vec2(0.f) }; + + // loop over all nodes picked to detect clic on locks + for (auto itp = pv.rbegin(); itp != pv.rend(); ++itp){ + // get if a source was picked + Source *s = Mixer::manager().findSource((*itp).first); + // lock icon of a source (not current) is picked : unlock + if ( s!=nullptr && (*itp).first == s->lock_) { + lock(s, false); + pick = { s->locker_, (*itp).second }; + break; + } } } // no lock icon picked, find what else was picked if ( pick.first == nullptr) { // loop over all nodes picked - for (auto itp = pv.rbegin(); itp != pv.rend(); itp++){ + for (auto itp = pv.rbegin(); itp != pv.rend(); ++itp){ // get if a source was picked Source *s = Mixer::manager().findSource((*itp).first); // accept picked sources in current workspaces @@ -511,13 +516,13 @@ std::pair GeometryView::pick(glm::vec2 P) bool GeometryView::canSelect(Source *s) { - return ( View::canSelect(s) && s->active() && s->workspace() == Settings::application.current_workspace); + return ( View::canSelect(s) && s->ready() && s->active() && s->workspace() == Settings::application.current_workspace); } void GeometryView::applySelectionTransform(glm::mat4 M) { - for (auto sit = Mixer::selection().begin(); sit != Mixer::selection().end(); sit++){ + for (auto sit = Mixer::selection().begin(); sit != Mixer::selection().end(); ++sit){ // recompute all from matrix transform glm::mat4 transform = M * (*sit)->stored_status_->transform_; glm::vec3 tra, rot, sca; @@ -1009,7 +1014,7 @@ void GeometryView::terminate() // restore of all handles overlays glm::vec2 c(0.f, 0.f); for (auto sit = Mixer::manager().session()->begin(); - sit != Mixer::manager().session()->end(); sit++){ + sit != Mixer::manager().session()->end(); ++sit){ (*sit)->handles_[mode_][Handles::RESIZE]->overlayActiveCorner(c); (*sit)->handles_[mode_][Handles::RESIZE_H]->overlayActiveCorner(c); @@ -1037,7 +1042,7 @@ void GeometryView::arrow (glm::vec2 movement) bool first = true; glm::vec3 delta_translation(0.f); - for (auto it = Mixer::selection().begin(); it != Mixer::selection().end(); it++) { + for (auto it = Mixer::selection().begin(); it != Mixer::selection().end(); ++it) { // individual move with SHIFT if ( !Source::isCurrent(*it) && UserInterface::manager().shiftModifier() ) diff --git a/GeometryView.h b/GeometryView.h index 95a0e7f..3d42e9f 100644 --- a/GeometryView.h +++ b/GeometryView.h @@ -7,6 +7,9 @@ class GeometryView : public View { public: GeometryView(); + // non assignable class + GeometryView(GeometryView const&) = delete; + GeometryView& operator=(GeometryView const&) = delete; void draw () override; void update (float dt) override; diff --git a/GlmToolkit.cpp b/GlmToolkit.cpp index 3df04aa..b9b8817 100644 --- a/GlmToolkit.cpp +++ b/GlmToolkit.cpp @@ -1,4 +1,3 @@ -// Freely inspired from https://github.com/alter-rokuz/glm-aabb.git #include "GlmToolkit.h" @@ -10,14 +9,6 @@ #include #include - -uint64_t GlmToolkit::uniqueId() -{ - auto duration = std::chrono::high_resolution_clock::now().time_since_epoch(); - // 64-bit int 18446744073709551615UL - return std::chrono::duration_cast(duration).count() % 1000000000000000000UL; -} - glm::mat4 GlmToolkit::transform(glm::vec3 translation, glm::vec3 rotation, glm::vec3 scale) { glm::mat4 View = glm::translate(glm::identity(), translation); @@ -59,6 +50,8 @@ void GlmToolkit::inverse_transform(glm::mat4 M, glm::vec3 &translation, glm::vec // return angle; //} +// Freely inspired from https://github.com/alter-rokuz/glm-aabb.git + GlmToolkit::AxisAlignedBoundingBox::AxisAlignedBoundingBox() : mMin(glm::vec3(1.f)), mMax(glm::vec3(-1.f)) { @@ -79,7 +72,7 @@ void GlmToolkit::AxisAlignedBoundingBox::extend(const glm::vec3& point) void GlmToolkit::AxisAlignedBoundingBox::extend(std::vector points) { - for (auto p = points.begin(); p != points.end(); p++) + for (auto p = points.begin(); p != points.end(); ++p) extend(*p); } diff --git a/GlmToolkit.h b/GlmToolkit.h index 52234b8..54e3e30 100644 --- a/GlmToolkit.h +++ b/GlmToolkit.h @@ -1,16 +1,12 @@ #ifndef GLMTOOLKIT_H #define GLMTOOLKIT_H - #include #include namespace GlmToolkit { -// get integer with unique id -uint64_t uniqueId(); - // get Matrix for these transformation components glm::mat4 transform(glm::vec3 translation, glm::vec3 rotation, glm::vec3 scale); void inverse_transform(glm::mat4 M, glm::vec3 &translation, glm::vec3 &rotation, glm::vec3 &scale); diff --git a/ImGuiToolkit.cpp b/ImGuiToolkit.cpp index cc4083b..1e0dd7f 100644 --- a/ImGuiToolkit.cpp +++ b/ImGuiToolkit.cpp @@ -1,6 +1,8 @@ #include #include +#include +#include #ifdef _WIN32 # include @@ -20,7 +22,6 @@ #include #include "Resource.h" -#include "FileDialog.h" #include "ImGuiToolkit.h" #include "GstToolkit.h" #include "SystemToolkit.h" @@ -30,14 +31,12 @@ unsigned int textureicons = 0; std::map fontmap; -void ImGuiToolkit::ButtonOpenUrl( const char* url, const ImVec2& size_arg ) +void ImGuiToolkit::ButtonOpenUrl( const char* label, const char* url, const ImVec2& size_arg ) { - char label[512]; + char _label[512]; + sprintf( _label, "%s %s", ICON_FA_EXTERNAL_LINK_ALT, label ); - std::string str = SystemToolkit::transliterate( url ); - sprintf( label, "%s %s", ICON_FA_EXTERNAL_LINK_ALT, str.c_str() ); - - if ( ImGui::Button(label, size_arg) ) + if ( ImGui::Button(_label, size_arg) ) SystemToolkit::open(url); } @@ -58,8 +57,10 @@ bool ImGuiToolkit::ButtonToggle( const char* label, bool* toggle ) } -void ImGuiToolkit::ButtonSwitch(const char* label, bool* toggle, const char* help) +bool ImGuiToolkit::ButtonSwitch(const char* label, bool* toggle, const char* help) { + bool ret = false; + // utility style ImVec4* colors = ImGui::GetStyle().Colors; ImDrawList* draw_list = ImGui::GetWindowDrawList(); @@ -76,8 +77,10 @@ void ImGuiToolkit::ButtonSwitch(const char* label, bool* toggle, const char* hel // toggle action : operate on the whole area ImGui::InvisibleButton(label, ImVec2(frame_width, frame_height)); - if (ImGui::IsItemClicked()) + if (ImGui::IsItemClicked()) { *toggle = !*toggle; + ret = true; + } float t = *toggle ? 1.0f : 0.0f; // animation @@ -113,6 +116,7 @@ void ImGuiToolkit::ButtonSwitch(const char* label, bool* toggle, const char* hel draw_list->AddRectFilled(p, ImVec2(p.x + width, p.y + height), col_bg, height * 0.5f); draw_list->AddCircleFilled(ImVec2(p.x + radius + t * (width - radius * 2.0f), p.y + radius), radius - 1.5f, IM_COL32(255, 255, 255, 250)); + return ret; } @@ -201,6 +205,36 @@ bool ImGuiToolkit::IconButton(int i, int j, const char *tooltip) return ret; } + +bool ImGuiToolkit::IconButton(const char* icon, const char *tooltip) +{ + bool ret = false; + ImGui::PushID( icon ); + + float frame_height = ImGui::GetFrameHeight(); + float frame_width = frame_height; + ImVec2 draw_pos = ImGui::GetCursorScreenPos(); + + // toggle action : operate on the whole area + ImGui::InvisibleButton("##iconbutton", ImVec2(frame_width, frame_height)); + if (ImGui::IsItemClicked()) + ret = true; + + ImGui::SetCursorScreenPos(draw_pos); + ImGui::Text(icon); + + if (tooltip != nullptr && ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::Text("%s", tooltip); + ImGui::EndTooltip(); + } + + ImGui::PopID(); + return ret; +} + + bool ImGuiToolkit::IconToggle(int i, int j, int i_toggle, int j_toggle, bool* toggle, const char *tooltips[]) { bool ret = false; @@ -311,33 +345,34 @@ void ImGuiToolkit::ShowIconsWindow(bool* p_open) const ImGuiIO& io = ImGui::GetIO(); - ImGui::Begin("Icons", p_open); + if ( ImGui::Begin("Icons", p_open) ) { - ImVec2 pos = ImGui::GetCursorScreenPos(); - ImGui::Image((void*)(intptr_t)textureicons, ImVec2(640, 640)); - if (ImGui::IsItemHovered()) - { - float my_tex_w = 640.0; - float my_tex_h = 640.0; - float zoom = 4.0f; - float region_sz = 32.0f; // 64 x 64 icons - float region_x = io.MousePos.x - pos.x - region_sz * 0.5f; - if (region_x < 0.0f) region_x = 0.0f; else if (region_x > my_tex_w - region_sz) region_x = my_tex_w - region_sz; - float region_y = io.MousePos.y - pos.y - region_sz * 0.5f; - if (region_y < 0.0f) region_y = 0.0f; else if (region_y > my_tex_h - region_sz) region_y = my_tex_h - region_sz; - ImGui::BeginTooltip(); - int i = (int) ( (region_x + region_sz * 0.5f) / region_sz); - int j = (int) ( (region_y + region_sz * 0.5f)/ region_sz); - ImGuiToolkit::Icon(i, j); - ImGui::SameLine(); - ImGui::Text(" Icon (%d, %d)", i, j); - ImVec2 uv0 = ImVec2((region_x) / my_tex_w, (region_y) / my_tex_h); - ImVec2 uv1 = ImVec2((region_x + region_sz) / my_tex_w, (region_y + region_sz) / my_tex_h); - ImGui::Image((void*)(intptr_t)textureicons, ImVec2(region_sz * zoom, region_sz * zoom), uv0, uv1, ImVec4(1.0f, 1.0f, 1.0f, 1.0f), ImVec4(1.0f, 1.0f, 1.0f, 0.5f)); - ImGui::EndTooltip(); + ImVec2 pos = ImGui::GetCursorScreenPos(); + ImGui::Image((void*)(intptr_t)textureicons, ImVec2(640, 640)); + if (ImGui::IsItemHovered()) + { + float my_tex_w = 640.0; + float my_tex_h = 640.0; + float zoom = 4.0f; + float region_sz = 32.0f; // 64 x 64 icons + float region_x = io.MousePos.x - pos.x - region_sz * 0.5f; + if (region_x < 0.0f) region_x = 0.0f; else if (region_x > my_tex_w - region_sz) region_x = my_tex_w - region_sz; + float region_y = io.MousePos.y - pos.y - region_sz * 0.5f; + if (region_y < 0.0f) region_y = 0.0f; else if (region_y > my_tex_h - region_sz) region_y = my_tex_h - region_sz; + ImGui::BeginTooltip(); + int i = (int) ( (region_x + region_sz * 0.5f) / region_sz); + int j = (int) ( (region_y + region_sz * 0.5f)/ region_sz); + ImGuiToolkit::Icon(i, j); + ImGui::SameLine(); + ImGui::Text(" Icon (%d, %d)", i, j); + ImVec2 uv0 = ImVec2((region_x) / my_tex_w, (region_y) / my_tex_h); + ImVec2 uv1 = ImVec2((region_x + region_sz) / my_tex_w, (region_y + region_sz) / my_tex_h); + ImGui::Image((void*)(intptr_t)textureicons, ImVec2(region_sz * zoom, region_sz * zoom), uv0, uv1, ImVec4(1.0f, 1.0f, 1.0f, 1.0f), ImVec4(1.0f, 1.0f, 1.0f, 0.5f)); + ImGui::EndTooltip(); + } + + ImGui::End(); } - - ImGui::End(); } void ImGuiToolkit::ToolTip(const char* desc, const char* shortcut) @@ -1113,4 +1148,83 @@ void ImGuiToolkit::SetAccentColor(accent_color color) } +void word_wrap(std::string *str, unsigned per_line) +{ + unsigned line_begin = 0; + while (line_begin < str->size()) + { + const unsigned ideal_end = line_begin + per_line ; + unsigned line_end = ideal_end < str->size() ? ideal_end : str->size()-1; + + if (line_end == str->size() - 1) + ++line_end; + else if (std::isspace(str->at(line_end))) + { + str->replace(line_end, 1, 1, '\n' ); + ++line_end; + } + else // backtrack + { + unsigned end = line_end; + while ( end > line_begin && !std::isspace(str->at(end))) + --end; + + if (end != line_begin) + { + line_end = end; + str->replace(line_end++, 1, 1, '\n' ); + } + else { + str->insert(line_end++, 1, '\n' ); + } + } + + line_begin = line_end; + } +} + + +struct InputTextCallback_UserData +{ + std::string* Str; + int WordWrap; +}; + +static int InputTextCallback(ImGuiInputTextCallbackData* data) +{ + InputTextCallback_UserData* user_data = static_cast(data->UserData); + if (data->EventFlag == ImGuiInputTextFlags_CallbackResize) + { +// if (user_data->WordWrap > 1) +// word_wrap(user_data->Str, user_data->WordWrap ); + + // Resize string callback + std::string* str = user_data->Str; + IM_ASSERT(data->Buf == str->c_str()); + str->resize(data->BufTextLen); + data->Buf = (char*)str->c_str(); + } + + return 0; +} + +bool ImGuiToolkit::InputText(const char* label, std::string* str) +{ + ImGuiInputTextFlags flags = ImGuiInputTextFlags_CallbackResize | ImGuiInputTextFlags_CharsNoBlank; + InputTextCallback_UserData cb_user_data; + cb_user_data.Str = str; + + return ImGui::InputText(label, (char*)str->c_str(), str->capacity() + 1, flags, InputTextCallback, &cb_user_data); +} + +bool ImGuiToolkit::InputTextMultiline(const char* label, std::string* str, const ImVec2& size, int linesize) +{ + ImGuiInputTextFlags flags = ImGuiInputTextFlags_CallbackResize; + InputTextCallback_UserData cb_user_data; + cb_user_data.Str = str; + cb_user_data.WordWrap = linesize; + + return ImGui::InputTextMultiline(label, (char*)str->c_str(), str->capacity() + 1, size, flags, InputTextCallback, &cb_user_data); +} + diff --git a/ImGuiToolkit.h b/ImGuiToolkit.h index 2273eea..43598e4 100644 --- a/ImGuiToolkit.h +++ b/ImGuiToolkit.h @@ -13,6 +13,7 @@ namespace ImGuiToolkit // Icons from resource icon.dds void Icon (int i, int j, bool enabled = true); bool IconButton (int i, int j, const char *tooltips = nullptr); + bool IconButton (const char* icon = ICON_FA_EXCLAMATION_CIRCLE, const char *tooltips = nullptr); bool IconToggle (int i, int j, int i_toggle, int j_toggle, bool* toggle, const char *tooltips[] = nullptr); void ShowIconsWindow(bool* p_open); @@ -24,8 +25,8 @@ namespace ImGuiToolkit // utility buttons bool ButtonToggle (const char* label, bool* toggle); - void ButtonSwitch (const char* label, bool* toggle , const char *help = nullptr); - void ButtonOpenUrl (const char* url, const ImVec2& size_arg = ImVec2(0,0)); + bool ButtonSwitch (const char* label, bool* toggle , const char *help = nullptr); + void ButtonOpenUrl (const char* label, const char* url, const ImVec2& size_arg = ImVec2(0,0)); void ToolTip (const char* desc, const char* shortcut = nullptr); void HelpMarker (const char* desc, const char* icon = ICON_FA_QUESTION_CIRCLE, const char* shortcut = nullptr); @@ -62,6 +63,8 @@ namespace ImGuiToolkit void SetAccentColor (accent_color color); struct ImVec4 HighlightColor (bool active = true); + bool InputText(const char* label, std::string* str); + bool InputTextMultiline(const char* label, std::string* str, const ImVec2& size = ImVec2(0, 0), int linesize = 0); } #endif // __IMGUI_TOOLKIT_H_ diff --git a/ImGuiVisitor.cpp b/ImGuiVisitor.cpp index 3d1fc24..1b51cac 100644 --- a/ImGuiVisitor.cpp +++ b/ImGuiVisitor.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include "tinyxml2Toolkit.h" @@ -24,6 +25,7 @@ #include "PatternSource.h" #include "DeviceSource.h" #include "NetworkSource.h" +#include "MultiFileSource.h" #include "SessionCreator.h" #include "SessionVisitor.h" #include "Settings.h" @@ -32,6 +34,7 @@ #include "imgui.h" #include "ImGuiToolkit.h" +#include "BaseToolkit.h" #include "UserInterfaceManager.h" #include "SystemToolkit.h" @@ -395,7 +398,6 @@ void ImGuiVisitor::visit(ImageProcessingShader &n) ImGui::Spacing(); } - void ImGuiVisitor::visit (Source& s) { ImGui::PushID(std::to_string(s.id()).c_str()); @@ -539,10 +541,6 @@ void ImGuiVisitor::visit (Source& s) s.processingShader()->accept(*this); } - // geometry direct control for DEBUG -// s.groupNode(View::GEOMETRY)->accept(*this); -// s.groupNode((View::Mode) Settings::application.current_view)->accept(*this); - ImGui::PopID(); } @@ -557,7 +555,12 @@ void ImGuiVisitor::visit (MediaSource& s) if ( ImGui::Button(IMGUI_TITLE_MEDIAPLAYER, ImVec2(IMGUI_RIGHT_ALIGN, 0)) ) UserInterface::manager().showMediaPlayer( s.mediaplayer()); - ImGuiToolkit::ButtonOpenUrl( SystemToolkit::path_filename(s.path()).c_str(), ImVec2(IMGUI_RIGHT_ALIGN, 0) ); + + std::string path = SystemToolkit::path_filename(s.path()); + std::string label = BaseToolkit::trunc_string(path, 25); + label = BaseToolkit::transliterate(label); + ImGuiToolkit::ButtonOpenUrl( label.c_str(), path.c_str(), ImVec2(IMGUI_RIGHT_ALIGN, 0) ); + ImGui::SameLine(); ImGui::Text("Folder"); } @@ -588,7 +591,10 @@ void ImGuiVisitor::visit (SessionFileSource& s) ImGui::SameLine(); ImGui::Text("File"); - ImGuiToolkit::ButtonOpenUrl( SystemToolkit::path_filename(s.path()).c_str(), ImVec2(IMGUI_RIGHT_ALIGN, 0) ); + std::string path = SystemToolkit::path_filename(s.path()); + std::string label = BaseToolkit::trunc_string(path, 25); + label = BaseToolkit::transliterate(label); + ImGuiToolkit::ButtonOpenUrl( label.c_str(), path.c_str(), ImVec2(IMGUI_RIGHT_ALIGN, 0) ); ImGui::SameLine(); ImGui::Text("Folder"); @@ -703,6 +709,60 @@ void ImGuiVisitor::visit (NetworkSource& s) s.setConnection(s.connection()); } - } + +void ImGuiVisitor::visit (MultiFileSource& s) +{ + ImGuiToolkit::Icon(s.icon().x, s.icon().y); + ImGui::SameLine(0, 10); + ImGui::Text("Images sequence"); + static int64_t id = s.id(); + + // information text + std::ostringstream msg; + msg << "Sequence of " << s.sequence().max - s.sequence().min + 1 << " "; + msg << s.sequence().codec << " images"; + ImGui::Text("%s", msg.str().c_str()); + + // change range + static int _begin = -1; + if (_begin < 0 || id != s.id()) + _begin = s.begin(); + static int _end = -1; + if (_end < 0 || id != s.id()) + _end = s.end(); + ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); + ImGui::DragIntRange2("Range", &_begin, &_end, 1, s.sequence().min, s.sequence().max); + if (ImGui::IsItemDeactivatedAfterEdit()){ + s.setRange( _begin, _end ); + std::ostringstream oss; + oss << s.name() << ": Range " << _begin << "-" << _end; + Action::manager().store(oss.str()); + _begin = _end = -1; + } + + // change framerate + static int _fps = -1; + if (_fps < 0 || id != s.id()) + _fps = s.framerate(); + ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); + ImGui::SliderInt("Framerate", &_fps, 1, 30, "%d fps"); + if (ImGui::IsItemDeactivatedAfterEdit()){ + s.setFramerate(_fps); + std::ostringstream oss; + oss << s.name() << ": Framerate " << _fps << " fps"; + Action::manager().store(oss.str()); + _fps = -1; + } + + // offer to open file browser at location + std::string path = SystemToolkit::path_filename(s.sequence().location); + std::string label = BaseToolkit::trunc_string(path, 25); + label = BaseToolkit::transliterate(label); + ImGuiToolkit::ButtonOpenUrl( label.c_str(), path.c_str(), ImVec2(IMGUI_RIGHT_ALIGN, 0) ); + ImGui::SameLine(); + ImGui::Text("Folder"); + + id = s.id(); +} diff --git a/ImGuiVisitor.h b/ImGuiVisitor.h index 06f2a76..2fa46d2 100644 --- a/ImGuiVisitor.h +++ b/ImGuiVisitor.h @@ -30,6 +30,7 @@ public: void visit (PatternSource& s) override; void visit (DeviceSource& s) override; void visit (NetworkSource& s) override; + void visit (MultiFileSource& s) override; }; #endif // IMGUIVISITOR_H diff --git a/ImageProcessingShader.cpp b/ImageProcessingShader.cpp index 6375b5c..489ce07 100644 --- a/ImageProcessingShader.cpp +++ b/ImageProcessingShader.cpp @@ -58,7 +58,7 @@ void ImageProcessingShader::reset() } -void ImageProcessingShader::copy(const ImageProcessingShader &S) +void ImageProcessingShader::copy(ImageProcessingShader const& S) { brightness = S.brightness; contrast = S.contrast; @@ -75,11 +75,6 @@ void ImageProcessingShader::copy(const ImageProcessingShader &S) chromadelta = S.chromadelta; } -void ImageProcessingShader::operator = (const ImageProcessingShader &S ) -{ - copy(S); -} - void ImageProcessingShader::accept(Visitor& v) { diff --git a/ImageProcessingShader.h b/ImageProcessingShader.h index 0c185c1..ee43981 100644 --- a/ImageProcessingShader.h +++ b/ImageProcessingShader.h @@ -16,8 +16,7 @@ public: void reset() override; void accept(Visitor& v) override; - void operator = (const ImageProcessingShader &S); - void copy(const ImageProcessingShader &S); + void copy(ImageProcessingShader const& S); // color effects float brightness; // [-1 1] diff --git a/ImageShader.cpp b/ImageShader.cpp index b799b72..9a2e8f8 100644 --- a/ImageShader.cpp +++ b/ImageShader.cpp @@ -58,10 +58,8 @@ void ImageShader::reset() stipple = 0.f; } -void ImageShader::operator = (const ImageShader &S) +void ImageShader::copy(ImageShader const& S) { - Shader::operator =(S); - mask_texture = S.mask_texture; stipple = S.stipple; } @@ -133,10 +131,8 @@ void MaskShader::reset() effect = 0; } -void MaskShader::operator = (const MaskShader &S) +void MaskShader::copy(MaskShader const& S) { - Shader::operator =(S); - mode = S.mode; shape = S.shape; blur = S.blur; diff --git a/ImageShader.h b/ImageShader.h index 8992f85..251d81f 100644 --- a/ImageShader.h +++ b/ImageShader.h @@ -19,7 +19,7 @@ public: void use() override; void reset() override; void accept(Visitor& v) override; - void operator = (const ImageShader &S); + void copy(ImageShader const& S); uint mask_texture; @@ -45,7 +45,7 @@ public: void use() override; void reset() override; void accept(Visitor& v) override; - void operator = (const MaskShader &S); + void copy(MaskShader const& S); enum Modes { NONE = 0, diff --git a/Interpolator.cpp b/Interpolator.cpp new file mode 100644 index 0000000..07314e1 --- /dev/null +++ b/Interpolator.cpp @@ -0,0 +1,166 @@ + +#include +#include + +#include "defines.h" +#include "Log.h" +#include "Source.h" +#include "ImageProcessingShader.h" +#include "UpdateCallback.h" + +#include "Interpolator.h" + + +SourceInterpolator::SourceInterpolator(Source *subject, const SourceCore &target) : + subject_(subject), from_(static_cast(*subject)), to_(target), current_cursor_(0.f) +{ + + +} + + +void SourceInterpolator::interpolateGroup(View::Mode m) +{ + current_state_.group(m)->translation_ = + (1.f - current_cursor_) * from_.group(m)->translation_ + + current_cursor_ * to_.group(m)->translation_; + current_state_.group(m)->scale_ = + (1.f - current_cursor_) * from_.group(m)->scale_ + + current_cursor_ * to_.group(m)->scale_; + current_state_.group(m)->rotation_ = + (1.f - current_cursor_) * from_.group(m)->rotation_ + + current_cursor_ * to_.group(m)->rotation_; + current_state_.group(m)->crop_ = + (1.f - current_cursor_) * from_.group(m)->crop_ + + current_cursor_ * to_.group(m)->crop_; + + CopyCallback *anim = new CopyCallback( current_state_.group(m) ); + subject_->group(m)->update_callbacks_.clear(); + subject_->group(m)->update_callbacks_.push_back(anim); +} + +void SourceInterpolator::interpolateImageProcessing() +{ + current_state_.processingShader()->brightness = + (1.f - current_cursor_) * from_.processingShader()->brightness + + current_cursor_ * to_.processingShader()->brightness; + + current_state_.processingShader()->contrast = + (1.f - current_cursor_) * from_.processingShader()->contrast + + current_cursor_ * to_.processingShader()->contrast; + + current_state_.processingShader()->saturation = + (1.f - current_cursor_) * from_.processingShader()->saturation + + current_cursor_ * to_.processingShader()->saturation; + + current_state_.processingShader()->hueshift = + (1.f - current_cursor_) * from_.processingShader()->hueshift + + current_cursor_ * to_.processingShader()->hueshift; + + current_state_.processingShader()->threshold = + (1.f - current_cursor_) * from_.processingShader()->threshold + + current_cursor_ * to_.processingShader()->threshold; + + current_state_.processingShader()->lumakey = + (1.f - current_cursor_) * from_.processingShader()->lumakey + + current_cursor_ * to_.processingShader()->lumakey; + + current_state_.processingShader()->nbColors = + (1.f - current_cursor_) * from_.processingShader()->nbColors + + current_cursor_ * to_.processingShader()->nbColors; + + current_state_.processingShader()->gamma = + (1.f - current_cursor_) * from_.processingShader()->gamma + + current_cursor_ * to_.processingShader()->gamma; + + current_state_.processingShader()->levels = + (1.f - current_cursor_) * from_.processingShader()->levels + + current_cursor_ * to_.processingShader()->levels; + + current_state_.processingShader()->chromakey = + (1.f - current_cursor_) * from_.processingShader()->chromakey + + current_cursor_ * to_.processingShader()->chromakey; + + current_state_.processingShader()->chromadelta = + (1.f - current_cursor_) * from_.processingShader()->chromadelta + + current_cursor_ * to_.processingShader()->chromadelta; + + subject_->processingShader()->copy( *current_state_.processingShader() ); + +// not interpolated : invert , filterid +} + +float SourceInterpolator::current() const +{ + return current_cursor_; +} + +void SourceInterpolator::apply(float percent) +{ + percent = CLAMP( percent, 0.f, 1.f); + + if ( subject_ && ABS_DIFF(current_cursor_, percent) > EPSILON) + { + current_cursor_ = percent; + + if (current_cursor_ < EPSILON) { + current_cursor_ = 0.f; + current_state_ = from_; + subject_->copy(current_state_); + } + else if (current_cursor_ > 1.f - EPSILON) { + current_cursor_ = 1.f; + current_state_ = to_; + subject_->copy(current_state_); + } + else { + interpolateGroup(View::MIXING); + interpolateGroup(View::GEOMETRY); + interpolateGroup(View::LAYER); + interpolateGroup(View::TEXTURE); + interpolateImageProcessing(); +// Log::Info("SourceInterpolator::update %f", cursor); + } + + subject_->touch(); + } +} + +Interpolator::Interpolator() +{ + +} + +Interpolator::~Interpolator() +{ + for (auto i = interpolators_.begin(); i != interpolators_.end(); ) { + delete *i; + i = interpolators_.erase(i); + } + +} + +void Interpolator::add (Source *s, const SourceCore &target) +{ + SourceInterpolator *i = new SourceInterpolator(s, target); + interpolators_.push_back(i); +} + + +float Interpolator::current() const +{ + float ret = 0.f; + if (interpolators_.size() > 0) + ret = interpolators_.front()->current(); + + return ret; +} + +void Interpolator::apply(float percent) +{ + for (auto i = interpolators_.begin(); i != interpolators_.end(); ++i) + (*i)->apply( percent ); + +} + + diff --git a/Interpolator.h b/Interpolator.h new file mode 100644 index 0000000..6f9a987 --- /dev/null +++ b/Interpolator.h @@ -0,0 +1,43 @@ +#ifndef INTERPOLATOR_H +#define INTERPOLATOR_H + +#include "Source.h" +#include "SourceList.h" + +class SourceInterpolator +{ +public: + SourceInterpolator(Source *subject, const SourceCore &target); + + void apply (float percent); + float current() const; + +protected: + Source *subject_; + + SourceCore from_; + SourceCore to_; + SourceCore current_state_; + float current_cursor_; + + void interpolateGroup (View::Mode m); + void interpolateImageProcessing (); +}; + +class Interpolator +{ +public: + Interpolator(); + ~Interpolator(); + + void add (Source *s, const SourceCore &target ); + + void apply (float percent); + float current() const; + +protected: + std::list interpolators_; + +}; + +#endif // INTERPOLATOR_H diff --git a/LayerView.cpp b/LayerView.cpp index 3e253fc..5d4005a 100644 --- a/LayerView.cpp +++ b/LayerView.cpp @@ -14,6 +14,7 @@ #include "Mixer.h" #include "defines.h" +#include "Source.h" #include "Settings.h" #include "Decorations.h" #include "UserInterfaceManager.h" @@ -123,7 +124,7 @@ void LayerView::draw() (*it)->setDepth(depth); } Action::manager().store(std::string("Selection: Layer Distribute")); - View::need_deep_update_++; + ++View::need_deep_update_; } if (ImGui::Selectable( ICON_FA_RULER_HORIZONTAL " Compress" )){ SourceList dsl = depth_sorted(Mixer::selection().getCopy()); @@ -134,7 +135,7 @@ void LayerView::draw() (*it)->setDepth(depth); } Action::manager().store(std::string("Selection: Layer Compress")); - View::need_deep_update_++; + ++View::need_deep_update_; } if (ImGui::Selectable( ICON_FA_EXCHANGE_ALT " Reverse order" )){ SourceList dsl = depth_sorted(Mixer::selection().getCopy()); @@ -144,7 +145,7 @@ void LayerView::draw() (*it)->setDepth((*rit)->depth()); } Action::manager().store(std::string("Selection: Layer Reverse order")); - View::need_deep_update_++; + ++View::need_deep_update_; } ImGui::PopStyleColor(2); @@ -226,12 +227,13 @@ std::pair LayerView::pick(glm::vec2 P) Source *s = Mixer::manager().findSource(pick.first); if (s != nullptr) { // pick on the lock icon; unlock source - if ( pick.first == s->lock_) { + if ( UserInterface::manager().ctrlModifier() && pick.first == s->lock_) { lock(s, false); - pick = { s->locker_, pick.second }; +// pick = { s->locker_, pick.second }; + pick = { nullptr, glm::vec2(0.f) }; } // pick on the open lock icon; lock source and cancel pick - else if ( pick.first == s->unlock_ ) { + else if ( UserInterface::manager().ctrlModifier() && pick.first == s->unlock_ ) { lock(s, true); pick = { nullptr, glm::vec2(0.f) }; } diff --git a/LayerView.h b/LayerView.h index 967a9c7..bd1d2b6 100644 --- a/LayerView.h +++ b/LayerView.h @@ -7,6 +7,9 @@ class LayerView : public View { public: LayerView(); + // non assignable class + LayerView(LayerView const&) = delete; + LayerView& operator=(LayerView const&) = delete; void draw () override; void update (float dt) override; diff --git a/Log.cpp b/Log.cpp index b46cfbf..d27e6cb 100644 --- a/Log.cpp +++ b/Log.cpp @@ -57,7 +57,7 @@ struct AppLog ImGui::SetNextWindowPos(ImVec2(430, 660), ImGuiCond_FirstUseEver); ImGui::SetNextWindowSize(ImVec2(1150, 220), ImGuiCond_FirstUseEver); ImGui::SetNextWindowSizeConstraints(ImVec2(600, 180), ImVec2(FLT_MAX, FLT_MAX)); - if (!ImGui::Begin(title, p_open)) + if ( !ImGui::Begin(title, p_open)) { ImGui::End(); return; @@ -75,69 +75,71 @@ struct AppLog Filter.Draw("Filter", -60.0f); ImGui::Separator(); - ImGui::BeginChild("scrolling", ImVec2(0,0), false, ImGuiWindowFlags_AlwaysHorizontalScrollbar); - - if (clear) - Clear(); - if (copy) - ImGui::LogToClipboard(); - - ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); - - mtx.lock(); - - const char* buf = Buf.begin(); - const char* buf_end = Buf.end(); - if (Filter.IsActive()) + if ( ImGui::BeginChild("scrolling", ImVec2(0,0), false, ImGuiWindowFlags_AlwaysHorizontalScrollbar) ) { - // In this example we don't use the clipper when Filter is enabled. - // This is because we don't have a random access on the result on our filter. - // A real application processing logs with ten of thousands of entries may want to store the result of search/filter. - // especially if the filtering function is not trivial (e.g. reg-exp). - for (int line_no = 0; line_no < LineOffsets.Size; line_no++) + if (clear) + Clear(); + if (copy) + ImGui::LogToClipboard(); + + ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); + + mtx.lock(); + + const char* buf = Buf.begin(); + const char* buf_end = Buf.end(); + if (Filter.IsActive()) { - const char* line_start = buf + LineOffsets[line_no]; - const char* line_end = (line_no + 1 < LineOffsets.Size) ? (buf + LineOffsets[line_no + 1] - 1) : buf_end; - if (Filter.PassFilter(line_start, line_end)) - ImGui::TextUnformatted(line_start, line_end); - } - } - else - { - // The simplest and easy way to display the entire buffer: - // ImGui::TextUnformatted(buf_begin, buf_end); - // And it'll just work. TextUnformatted() has specialization for large blob of text and will fast-forward to skip non-visible lines. - // Here we instead demonstrate using the clipper to only process lines that are within the visible area. - // If you have tens of thousands of items and their processing cost is non-negligible, coarse clipping them on your side is recommended. - // Using ImGuiListClipper requires A) random access into your data, and B) items all being the same height, - // both of which we can handle since we an array pointing to the beginning of each line of text. - // When using the filter (in the block of code above) we don't have random access into the data to display anymore, which is why we don't use the clipper. - // Storing or skimming through the search result would make it possible (and would be recommended if you want to search through tens of thousands of entries) - ImGuiListClipper clipper; - clipper.Begin(LineOffsets.Size); - while (clipper.Step()) - { - for (int line_no = clipper.DisplayStart; line_no < clipper.DisplayEnd; line_no++) + // In this example we don't use the clipper when Filter is enabled. + // This is because we don't have a random access on the result on our filter. + // A real application processing logs with ten of thousands of entries may want to store the result of search/filter. + // especially if the filtering function is not trivial (e.g. reg-exp). + for (int line_no = 0; line_no < LineOffsets.Size; line_no++) { - const char* line_start = buf + LineOffsets[line_no] + (numbering?0:6); + const char* line_start = buf + LineOffsets[line_no]; const char* line_end = (line_no + 1 < LineOffsets.Size) ? (buf + LineOffsets[line_no + 1] - 1) : buf_end; - ImGui::TextUnformatted(line_start, line_end); + if (Filter.PassFilter(line_start, line_end)) + ImGui::TextUnformatted(line_start, line_end); } } - clipper.End(); + else + { + // The simplest and easy way to display the entire buffer: + // ImGui::TextUnformatted(buf_begin, buf_end); + // And it'll just work. TextUnformatted() has specialization for large blob of text and will fast-forward to skip non-visible lines. + // Here we instead demonstrate using the clipper to only process lines that are within the visible area. + // If you have tens of thousands of items and their processing cost is non-negligible, coarse clipping them on your side is recommended. + // Using ImGuiListClipper requires A) random access into your data, and B) items all being the same height, + // both of which we can handle since we an array pointing to the beginning of each line of text. + // When using the filter (in the block of code above) we don't have random access into the data to display anymore, which is why we don't use the clipper. + // Storing or skimming through the search result would make it possible (and would be recommended if you want to search through tens of thousands of entries) + ImGuiListClipper clipper; + clipper.Begin(LineOffsets.Size); + while (clipper.Step()) + { + for (int line_no = clipper.DisplayStart; line_no < clipper.DisplayEnd; line_no++) + { + const char* line_start = buf + LineOffsets[line_no] + (numbering?0:6); + const char* line_end = (line_no + 1 < LineOffsets.Size) ? (buf + LineOffsets[line_no + 1] - 1) : buf_end; + ImGui::TextUnformatted(line_start, line_end); + } + } + clipper.End(); + } + + mtx.unlock(); + + ImGui::PopStyleVar(); + ImGui::PopFont(); + + // Auto scroll + if (ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) + ImGui::SetScrollHereY(1.0f); + + ImGui::EndChild(); } - mtx.unlock(); - - ImGui::PopStyleVar(); - ImGui::PopFont(); - - // Auto scroll - if (ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) - ImGui::SetScrollHereY(1.0f); - - ImGui::EndChild(); ImGui::End(); } }; diff --git a/MediaPlayer.cpp b/MediaPlayer.cpp index 9b259b1..c82ea38 100644 --- a/MediaPlayer.cpp +++ b/MediaPlayer.cpp @@ -12,7 +12,7 @@ using namespace std; #include "Resource.h" #include "Visitor.h" #include "SystemToolkit.h" -#include "GlmToolkit.h" +#include "BaseToolkit.h" #include "GstToolkit.h" #include "RenderingManager.h" @@ -27,20 +27,20 @@ std::list MediaPlayer::registered_; MediaPlayer::MediaPlayer() { // create unique id - id_ = GlmToolkit::uniqueId(); + id_ = BaseToolkit::uniqueId(); uri_ = "undefined"; pipeline_ = nullptr; + opened_ = false; + enabled_ = true; + desired_state_ = GST_STATE_PAUSED; - ready_ = false; failed_ = false; seeking_ = false; - enabled_ = true; force_software_decoding_ = false; hardware_decoder_ = ""; rate_ = 1.0; position_ = GST_CLOCK_TIME_NONE; - desired_state_ = GST_STATE_PAUSED; loop_ = LoopMode::LOOP_REWIND; // start index in frame_ stack @@ -65,6 +65,9 @@ MediaPlayer::~MediaPlayer() if (textureindex_) glDeleteTextures(1, &textureindex_); + // cleanup picture buffer + if (pbo_[0]) + glDeleteBuffers(2, pbo_); } void MediaPlayer::accept(Visitor& v) { @@ -81,7 +84,7 @@ guint MediaPlayer::texture() const #define LIMIT_DISCOVERER -static MediaInfo UriDiscoverer_(std::string uri) +MediaInfo MediaPlayer::UriDiscoverer(const std::string &uri) { #ifdef MEDIA_PLAYER_DEBUG Log::Info("Checking file '%s'", uri.c_str()); @@ -217,23 +220,27 @@ static MediaInfo UriDiscoverer_(std::string uri) return video_stream_info; } -void MediaPlayer::open(string path) +void MediaPlayer::open (const std::string & filename, const string &uri) { // set path - filename_ = SystemToolkit::transliterate( path ); + filename_ = BaseToolkit::transliterate( filename ); // set uri to open - uri_ = GstToolkit::filename_to_uri(path); + if (uri.empty()) + uri_ = GstToolkit::filename_to_uri( filename ); + else + uri_ = uri; - // reset - ready_ = false; + // close before re-openning + if (isOpen()) + close(); // start URI discovering thread: - discoverer_ = std::async( UriDiscoverer_, uri_); + discoverer_ = std::async( MediaPlayer::UriDiscoverer, uri_); // wait for discoverer to finish in the future (test in update) // // debug without thread -// media_ = UriDiscoverer_(uri_); +// media_ = MediaPlayer::UriDiscoverer(uri_); // if (media_.valid) { // timeline_.setEnd( media_.end ); // timeline_.setStep( media_.dt ); @@ -329,47 +336,48 @@ void MediaPlayer::execute_open() // setup appsink GstElement *sink = gst_bin_get_by_name (GST_BIN (pipeline_), "sink"); - if (sink) { - - // instruct the sink to send samples synched in time - gst_base_sink_set_sync (GST_BASE_SINK(sink), true); - - // instruct sink to use the required caps - gst_app_sink_set_caps (GST_APP_SINK(sink), caps); - - // Instruct appsink to drop old buffers when the maximum amount of queued buffers is reached. - gst_app_sink_set_max_buffers( GST_APP_SINK(sink), 5); - gst_app_sink_set_drop (GST_APP_SINK(sink), true); - -#ifdef USE_GST_APPSINK_CALLBACKS - // set the callbacks - GstAppSinkCallbacks callbacks; - callbacks.new_preroll = callback_new_preroll; - if (media_.isimage) { - callbacks.eos = NULL; - callbacks.new_sample = NULL; - } - else { - callbacks.eos = callback_end_of_stream; - callbacks.new_sample = callback_new_sample; - } - gst_app_sink_set_callbacks (GST_APP_SINK(sink), &callbacks, this, NULL); - gst_app_sink_set_emit_signals (GST_APP_SINK(sink), false); -#else - // connect signals callbacks - g_signal_connect(G_OBJECT(sink), "new-sample", G_CALLBACK (callback_new_sample), this); - g_signal_connect(G_OBJECT(sink), "new-preroll", G_CALLBACK (callback_new_preroll), this); - g_signal_connect(G_OBJECT(sink), "eos", G_CALLBACK (callback_end_of_stream), this); - gst_app_sink_set_emit_signals (GST_APP_SINK(sink), true); -#endif - // done with ref to sink - gst_object_unref (sink); - } - else { + if (!sink) { Log::Warning("MediaPlayer %s Could not configure sink", std::to_string(id_).c_str()); failed_ = true; return; } + + // instruct the sink to send samples synched in time + gst_base_sink_set_sync (GST_BASE_SINK(sink), true); + + // instruct sink to use the required caps + gst_app_sink_set_caps (GST_APP_SINK(sink), caps); + + // Instruct appsink to drop old buffers when the maximum amount of queued buffers is reached. + gst_app_sink_set_max_buffers( GST_APP_SINK(sink), 5); + gst_app_sink_set_drop (GST_APP_SINK(sink), true); + +#ifdef USE_GST_APPSINK_CALLBACKS + // set the callbacks + GstAppSinkCallbacks callbacks; + callbacks.new_preroll = callback_new_preroll; + if (media_.isimage) { + callbacks.eos = NULL; + callbacks.new_sample = NULL; + } + else { + callbacks.eos = callback_end_of_stream; + callbacks.new_sample = callback_new_sample; + } + gst_app_sink_set_callbacks (GST_APP_SINK(sink), &callbacks, this, NULL); + gst_app_sink_set_emit_signals (GST_APP_SINK(sink), false); +#else + // connect signals callbacks + g_signal_connect(G_OBJECT(sink), "new-preroll", G_CALLBACK (callback_new_preroll), this); + if (!media_.isimage) { + g_signal_connect(G_OBJECT(sink), "new-sample", G_CALLBACK (callback_new_sample), this); + g_signal_connect(G_OBJECT(sink), "eos", G_CALLBACK (callback_end_of_stream), this); + } + gst_app_sink_set_emit_signals (GST_APP_SINK(sink), true); +#endif + + // done with ref to sink + gst_object_unref (sink); gst_caps_unref (caps); #ifdef USE_GST_OPENGL_SYNC_HANDLER @@ -399,7 +407,7 @@ void MediaPlayer::execute_open() Log::Info("MediaPlayer %s Timeline [%ld %ld] %ld frames, %d gaps", std::to_string(id_).c_str(), timeline_.begin(), timeline_.end(), timeline_.numFrames(), timeline_.numGaps()); - ready_ = true; + opened_ = true; // register media player MediaPlayer::registered_.push_back(this); @@ -407,7 +415,7 @@ void MediaPlayer::execute_open() bool MediaPlayer::isOpen() const { - return ready_; + return opened_; } bool MediaPlayer::failed() const @@ -417,16 +425,15 @@ bool MediaPlayer::failed() const void MediaPlayer::Frame::unmap() { - if ( full ) { + if ( full ) gst_video_frame_unmap(&vframe); - full = false; - } + full = false; } void MediaPlayer::close() { // not openned? - if (!ready_) { + if (!opened_) { // wait for loading to finish if (discoverer_.valid()) discoverer_.wait(); @@ -435,7 +442,7 @@ void MediaPlayer::close() } // un-ready the media player - ready_ = false; + opened_ = false; // clean up GST if (pipeline_ != nullptr) { @@ -463,12 +470,6 @@ void MediaPlayer::close() write_index_ = 0; last_index_ = 0; - // cleanup picture buffer - if (pbo_[0]) - glDeleteBuffers(2, pbo_); - pbo_size_ = 0; - pbo_index_ = 0; - pbo_next_index_ = 0; #ifdef MEDIA_PLAYER_DEBUG Log::Info("MediaPlayer %s closed", std::to_string(id_).c_str()); @@ -507,7 +508,7 @@ GstClockTime MediaPlayer::position() void MediaPlayer::enable(bool on) { - if ( !ready_ ) + if ( !opened_ || pipeline_ == nullptr) return; if ( enabled_ != on ) { @@ -772,6 +773,7 @@ void MediaPlayer::init_texture(guint index) // for possible hadrware decoding plugins used. Empty string means none. hardware_decoder_ = GstToolkit::used_gpu_decoding_plugins(pipeline_); } + glBindTexture(GL_TEXTURE_2D, 0); } @@ -819,6 +821,7 @@ void MediaPlayer::fill_texture(guint index) glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, media_.width, media_.height, GL_RGBA, GL_UNSIGNED_BYTE, frame_[index].vframe.data[0]); } + glBindTexture(GL_TEXTURE_2D, 0); } } @@ -829,7 +832,7 @@ void MediaPlayer::update() return; // not ready yet - if (!ready_) { + if (!opened_) { if (discoverer_.valid()) { // try to get info from discoverer if (discoverer_.wait_for( std::chrono::milliseconds(4) ) == std::future_status::ready ) @@ -1153,7 +1156,7 @@ bool MediaPlayer::fill_frame(GstBuffer *buf, FrameStatus status) void MediaPlayer::callback_end_of_stream (GstAppSink *, gpointer p) { MediaPlayer *m = static_cast(p); - if (m && m->ready_) { + if (m && m->opened_) { m->fill_frame(NULL, MediaPlayer::EOS); } } @@ -1170,7 +1173,7 @@ GstFlowReturn MediaPlayer::callback_new_preroll (GstAppSink *sink, gpointer p) // send frames to media player only if ready MediaPlayer *m = static_cast(p); - if (m && m->ready_) { + if (m && m->opened_) { // get buffer from sample GstBuffer *buf = gst_sample_get_buffer (sample); @@ -1205,7 +1208,7 @@ GstFlowReturn MediaPlayer::callback_new_sample (GstAppSink *sink, gpointer p) // send frames to media player only if ready MediaPlayer *m = static_cast(p); - if (m && m->ready_) { + if (m && m->opened_) { // get buffer from sample (valid until sample is released) GstBuffer *buf = gst_sample_get_buffer (sample) ; diff --git a/MediaPlayer.h b/MediaPlayer.h index 65f12db..f20c81e 100644 --- a/MediaPlayer.h +++ b/MediaPlayer.h @@ -91,8 +91,8 @@ public: /** * Open a media using gstreamer URI * */ - void open( std::string path); - void reopen(); + void open ( const std::string &filename, const std::string &uri = ""); + void reopen (); /** * Get name of the media * */ @@ -265,6 +265,8 @@ public: static std::list::const_iterator begin() { return registered_.cbegin(); } static std::list::const_iterator end() { return registered_.cend(); } + static MediaInfo UriDiscoverer(const std::string &uri); + private: // video player description @@ -285,7 +287,7 @@ private: GstState desired_state_; GstElement *pipeline_; GstVideoInfo v_frame_video_info_; - std::atomic ready_; + std::atomic opened_; std::atomic failed_; bool seeking_; bool enabled_; diff --git a/MediaSource.cpp b/MediaSource.cpp index 8559663..54fd7c9 100644 --- a/MediaSource.cpp +++ b/MediaSource.cpp @@ -11,7 +11,7 @@ #include "Visitor.h" #include "Log.h" -MediaSource::MediaSource() : Source(), path_("") +MediaSource::MediaSource(uint64_t id) : Source(id), path_("") { // create media player mediaplayer_ = new MediaPlayer; @@ -25,11 +25,15 @@ MediaSource::~MediaSource() void MediaSource::setPath(const std::string &p) { - Log::Notify("Creating Source with media '%s'", p.c_str()); - path_ = p; + Log::Notify("Creating Source with media '%s'", path_.c_str()); + + // open gstreamer mediaplayer_->open(path_); mediaplayer_->play(true); + + // will be ready after init and one frame rendered + ready_ = false; } std::string MediaSource::path() const @@ -91,10 +95,9 @@ void MediaSource::init() active_ = true; // deep update to reorder - View::need_deep_update_++; + ++View::need_deep_update_; // done init - initialized_ = true; Log::Info("Source '%s' linked to Media %s.", name().c_str(), std::to_string(mediaplayer_->id()).c_str()); } } @@ -123,21 +126,16 @@ void MediaSource::update(float dt) void MediaSource::render() { - if (!initialized_) + if ( renderbuffer_ == nullptr ) init(); else { -// blendingshader_->color.r = mediaplayer_->currentTimelineFading(); -// blendingshader_->color.g = mediaplayer_->currentTimelineFading(); -// blendingshader_->color.b = mediaplayer_->currentTimelineFading(); - // render the media player into frame buffer renderbuffer_->begin(); -// texturesurface_->shader()->color.a = mediaplayer_->currentTimelineFading(); - texturesurface_->shader()->color.r = mediaplayer_->currentTimelineFading(); - texturesurface_->shader()->color.g = mediaplayer_->currentTimelineFading(); - texturesurface_->shader()->color.b = mediaplayer_->currentTimelineFading(); + // apply fading + texturesurface_->shader()->color = glm::vec4( glm::vec3(mediaplayer_->currentTimelineFading()), 1.f); texturesurface_->draw(glm::identity(), renderbuffer_->projection()); renderbuffer_->end(); + ready_ = true; } } diff --git a/MediaSource.h b/MediaSource.h index a48cdeb..122d598 100644 --- a/MediaSource.h +++ b/MediaSource.h @@ -8,7 +8,7 @@ class MediaPlayer; class MediaSource : public Source { public: - MediaSource(); + MediaSource(uint64_t id = 0); ~MediaSource(); // implementation of source API diff --git a/Mixer.cpp b/Mixer.cpp index 3f7b7a4..d01b99d 100644 --- a/Mixer.cpp +++ b/Mixer.cpp @@ -18,6 +18,7 @@ #include "Log.h" #include "View.h" #include "ImageShader.h" +#include "BaseToolkit.h" #include "SystemToolkit.h" #include "SessionCreator.h" #include "SessionVisitor.h" @@ -25,6 +26,7 @@ #include "MediaSource.h" #include "PatternSource.h" #include "DeviceSource.h" +#include "MultiFileSource.h" #include "StreamSource.h" #include "NetworkSource.h" #include "ActionManager.h" @@ -138,7 +140,7 @@ void Mixer::update() if ( back_session_ ) { // swap front and back sessions swap(); - View::need_deep_update_++; + ++View::need_deep_update_; // set session filename Rendering::manager().mainWindow().setTitle(session_->filename()); Settings::application.recentSessions.push(session_->filename()); @@ -181,7 +183,7 @@ void Mixer::update() failure = nullptr; // prevent delete (already done in recreateSource) } // delete the source - deleteSource(failure, false); + deleteSource(failure); } // update views @@ -193,7 +195,7 @@ void Mixer::update() // deep update was performed if (View::need_deep_update_ > 0) - View::need_deep_update_--; + --View::need_deep_update_; } void Mixer::draw() @@ -244,6 +246,37 @@ Source * Mixer::createSourceFile(const std::string &path) return s; } +Source * Mixer::createSourceMultifile(const std::list &list_files, uint fps) +{ + // ready to create a source + Source *s = nullptr; + + if ( list_files.size() >0 ) { + + // validate the creation of a sequence from the list + MultiFileSequence sequence(list_files); + + if ( sequence.valid() ) { + + // try to create a sequence + MultiFileSource *mfs = new MultiFileSource; + mfs->setSequence(sequence, fps); + s = mfs; + + // remember in recent media + Settings::application.recentImport.path = SystemToolkit::path_filename(list_files.front()); + + // propose a new name + s->setName( SystemToolkit::base_filename( BaseToolkit::common_prefix(list_files) ) ); + } + else { + Log::Notify("Could not find a sequence of consecutively numbered files."); + } + } + + return s; +} + Source * Mixer::createSourceRender() { // ready to create a source @@ -251,7 +284,10 @@ Source * Mixer::createSourceRender() s->setSession(session_); // propose a new name based on session name - s->setName(SystemToolkit::base_filename(session_->filename())); + if ( !session_->filename().empty() ) + s->setName(SystemToolkit::base_filename(session_->filename())); + else + s->setName("Output"); return s; } @@ -263,8 +299,7 @@ Source * Mixer::createSourceStream(const std::string &gstreamerpipeline) s->setDescription(gstreamerpipeline); // propose a new name based on pattern name - std::string name = gstreamerpipeline.substr(0, gstreamerpipeline.find(" ")); - s->setName(name); + s->setName( gstreamerpipeline.substr(0, gstreamerpipeline.find(" ")) ); return s; } @@ -289,8 +324,7 @@ Source * Mixer::createSourceDevice(const std::string &namedevice) Source *s = Device::manager().createSource(namedevice); // propose a new name based on pattern name - std::string name = namedevice.substr(0, namedevice.find(" ")); - s->setName(name); + s->setName( namedevice.substr(0, namedevice.find(" ")) ); return s; } @@ -333,23 +367,17 @@ Source * Mixer::createSourceClone(const std::string &namesource) origin = current_source_; // have an origin, can clone it - if (origin != session_->end()) { - + if (origin != session_->end()) // create a source s = (*origin)->clone(); - // propose new name (this automatically increments name) - renameSource(s, (*origin)->name()); - } - return s; } void Mixer::addSource(Source *s) { - if (s != nullptr) { + if (s != nullptr) candidate_sources_.push_back(s); - } } void Mixer::insertSource(Source *s, View::Mode m) @@ -357,7 +385,7 @@ void Mixer::insertSource(Source *s, View::Mode m) if ( s != nullptr ) { // avoid duplicate name - renameSource(s, s->name()); + renameSource(s); // Add source to Session (ignored if source already in) SourceList::iterator sit = session_->addSource(s); @@ -455,7 +483,7 @@ bool Mixer::recreateSource(Source *s) return true; } -void Mixer::deleteSource(Source *s, bool withundo) +void Mixer::deleteSource(Source *s) { if ( s != nullptr ) { @@ -468,10 +496,6 @@ void Mixer::deleteSource(Source *s, bool withundo) // delete source session_->deleteSource(s); - // store new state in history manager - if (withundo) - Action::manager().store(name + std::string(" source deleted")); - // log Log::Notify("Source %s deleted.", name.c_str()); } @@ -570,22 +594,36 @@ void Mixer::deselect(Source *s) void Mixer::deleteSelection() { - // get clones first : this way we store the history of deletion in the right order - SourceList selection_clones_; - for ( auto sit = selection().begin(); sit != selection().end(); sit++ ) { - CloneSource *clone = dynamic_cast(*sit); - if (clone) - selection_clones_.push_back(clone); - } - // delete all clones - while ( !selection_clones_.empty() ) { - deleteSource( selection_clones_.front()); - selection_clones_.pop_front(); - } - // empty the selection - while ( !selection().empty() ) - deleteSource( selection().front() ); // this also remove element from selection() + // number of sources in selection + int N = selection().size(); + // ignore if selection empty + if (N > 0) { + // adapt Action::manager undo info depending on case + std::ostringstream info; + if (N > 1) + info << N << " sources deleted"; + else + info << selection().front()->name() << ": deleted"; + + // get clones first : this way we store the history of deletion in the right order + SourceList selection_clones_; + for ( auto sit = selection().begin(); sit != selection().end(); sit++ ) { + CloneSource *clone = dynamic_cast(*sit); + if (clone) + selection_clones_.push_back(clone); + } + // delete all clones + while ( !selection_clones_.empty() ) { + deleteSource( selection_clones_.front());// this also removes element from selection() + selection_clones_.pop_front(); + } + // empty the selection + while ( !selection().empty() ) + deleteSource( selection().front() ); // this also removes element from selection() + + Action::manager().store(info.str()); + } } void Mixer::groupSelection() @@ -637,7 +675,7 @@ void Mixer::groupSelection() // store in action manager std::ostringstream info; - info << sessiongroup->name() << " source inserted, " << sessiongroup->session()->numSource() << " sources flatten."; + info << sessiongroup->name() << " inserted: " << sessiongroup->session()->numSource() << " sources flatten."; Action::manager().store(info.str()); // give the hand to the user @@ -650,19 +688,13 @@ void Mixer::renameSource(Source *s, const std::string &newname) if ( s != nullptr ) { // tentative new name - std::string tentativename = newname; + std::string tentativename = s->name(); - // refuse to rename to an empty name - if ( newname.empty() ) - tentativename = "source"; + // try the given new name if valid + if ( !newname.empty() ) + tentativename = newname; - // search for a source of the name 'tentativename' - std::string basename = tentativename; - int count = 1; - for( auto it = session_->begin(); it != session_->end(); it++){ - if ( s->id() != (*it)->id() && (*it)->name() == tentativename ) - tentativename = basename + std::to_string( ++count ); - } + tentativename = BaseToolkit::uniqueName(tentativename, session_->getNameList(s->id())); // ok to rename s->setName(tentativename); @@ -679,7 +711,7 @@ void Mixer::setCurrentSource(SourceList::iterator it) unsetCurrentSource(); // change current if 'it' is valid - if ( it != session_->end() ) { + if ( it != session_->end() /*&& (*it)->mode() > Source::UNINITIALIZED */) { current_source_ = it; current_source_index_ = session_->index(current_source_); @@ -886,7 +918,7 @@ void Mixer::setView(View::Mode m) } // need to deeply update view to apply eventual changes - View::need_deep_update_++; + ++View::need_deep_update_; } View *Mixer::view(View::Mode m) @@ -942,25 +974,28 @@ void Mixer::load(const std::string& filename) #endif } -void Mixer::open(const std::string& filename) +void Mixer::open(const std::string& filename, bool smooth) { - if (Settings::application.smooth_transition) + if (smooth) { - Log::Info("\nStarting transition to session %s", filename.c_str()); - // create special SessionSource to be used for the smooth transition SessionFileSource *ts = new SessionFileSource; + // open filename if specified if (!filename.empty()) + { + Log::Info("\nStarting transition to session %s", filename.c_str()); ts->load(filename); - // propose a new name based on uri - renameSource(ts, SystemToolkit::base_filename(filename)); + // propose a new name based on uri + ts->setName(SystemToolkit::base_filename(filename)); + } + + // attach the SessionSource to the transition view + transition_.attach(ts); // insert source and switch to transition view insertSource(ts, View::TRANSITION); - // attach the SessionSource to the transition view - transition_.attach(ts); } else load(filename); @@ -993,15 +1028,12 @@ void Mixer::merge(Session *session) return; } - // new state in history manager - std::ostringstream info; - info << session->numSource() << " sources imported from " << session->filename(); - Action::manager().store(info.str()); - // import every sources + std::ostringstream info; + info << session->numSource() << " sources imported from:" << session->filename(); for ( Source *s = session->popSource(); s != nullptr; s = session->popSource()) { // avoid name duplicates - renameSource(s, s->name()); + renameSource(s); // Add source to Session session_->addSource(s); @@ -1018,10 +1050,14 @@ void Mixer::merge(Session *session) } // needs to update ! - View::need_deep_update_++; + ++View::need_deep_update_; // avoid display issues current_view_->update(0.f); + + // new state in history manager + Action::manager().store(info.str()); + } void Mixer::merge(SessionSource *source) @@ -1036,7 +1072,7 @@ void Mixer::merge(SessionSource *source) // prepare Action manager info std::ostringstream info; - info << source->name().c_str() << " source deleted, " << session->numSource() << " sources imported"; + info << source->name().c_str() << " expanded:" << session->numSource() << " sources imported"; // import sources of the session (if not empty) if ( !session->empty() ) { @@ -1067,7 +1103,7 @@ void Mixer::merge(SessionSource *source) for ( Source *s = session->popSource(); s != nullptr; s = session->popSource()) { // avoid name duplicates - renameSource(s, s->name()); + renameSource(s); // scale alpha s->setAlpha( s->alpha() * source->alpha() ); @@ -1101,7 +1137,7 @@ void Mixer::merge(SessionSource *source) } // needs to update ! - View::need_deep_update_++; + ++View::need_deep_update_; } @@ -1109,12 +1145,11 @@ void Mixer::merge(SessionSource *source) detach(source); session_->deleteSource(source); - // new state in history manager - Action::manager().store(info.str()); - // avoid display issues current_view_->update(0.f); + // new state in history manager + Action::manager().store(info.str()); } void Mixer::swap() @@ -1164,15 +1199,15 @@ void Mixer::swap() back_session_ = nullptr; // reset History manager - Action::manager().clear(); + Action::manager().init(); // notification Log::Notify("Session %s loaded. %d source(s) created.", session_->filename().c_str(), session_->numSource()); } -void Mixer::close() +void Mixer::close(bool smooth) { - if (Settings::application.smooth_transition) + if (smooth) { // create empty SessionSource to be used for the smooth transition SessionFileSource *ts = new SessionFileSource; @@ -1200,7 +1235,7 @@ void Mixer::clear() sessionSwapRequested_ = true; // need to deeply update view to apply eventual changes - View::need_deep_update_++; + ++View::need_deep_update_; Log::Info("New session ready."); } @@ -1241,3 +1276,82 @@ void Mixer::paste(const std::string& clipboard) } } } + + +void Mixer::restore(tinyxml2::XMLElement *sessionNode) +{ + // + // source lists + // + + // sessionsources contains list of ids of all sources currently in the session (before loading) + SourceIdList session_sources = session_->getIdList(); +// for( auto it = sessionsources.begin(); it != sessionsources.end(); it++) +// Log::Info("sessionsources id %s", std::to_string(*it).c_str()); + + // load history status: + // - if a source exists, its attributes are updated, and that's all + // - if a source does not exists (in current session), it is created inside the session + SessionLoader loader( session_ ); + loader.load( sessionNode ); + + // loaded_sources contains map of xml ids of all sources treated by loader + std::map< uint64_t, Source* > loaded_sources = loader.getSources(); + + // remove intersect of both lists (sources were updated by SessionLoader) + for( auto lsit = loaded_sources.begin(); lsit != loaded_sources.end(); ){ + auto ssit = std::find(session_sources.begin(), session_sources.end(), (*lsit).first); + if ( ssit != session_sources.end() ) { + lsit = loaded_sources.erase(lsit); + session_sources.erase(ssit); + } + else + lsit++; + } + + // remaining ids in list sessionsources : to remove + while ( !session_sources.empty() ){ + Source *s = Mixer::manager().findSource( session_sources.front() ); + if (s!=nullptr) { +#ifdef ACTION_DEBUG + Log::Info("Delete id %s\n", std::to_string(session_sources.front() ).c_str()); +#endif + // remove the source from the mixer + detach( s ); + // delete source from session + session_->deleteSource( s ); + } + session_sources.pop_front(); + } + + // remaining sources in list loaded_sources : to add + for ( auto lsit = loaded_sources.begin(); lsit != loaded_sources.end(); lsit++) + { +#ifdef ACTION_DEBUG + Log::Info("Recreate id %s to %s\n", std::to_string((*lsit).first).c_str(), std::to_string((*lsit).second->id()).c_str()); +#endif + // attach created source + attach( (*lsit).second ); + } + + // + // mixing groups + // + + // Get the list of mixing groups in the xml loader + std::list< SourceList > loadergroups = loader.getMixingGroups(); + + // clear all session groups + auto group_iter = session_->beginMixingGroup(); + while ( group_iter != session_->endMixingGroup() ) + group_iter = session_->deleteMixingGroup(group_iter); + + // apply all changes creating or modifying groups in the session + // (after this, new groups are created and existing groups are adjusted) + for (auto group_loader_it = loadergroups.begin(); group_loader_it != loadergroups.end(); group_loader_it++) + session_->link( *group_loader_it, view(View::MIXING)->scene.fg() ); + + + ++View::need_deep_update_; +} + diff --git a/Mixer.h b/Mixer.h index 9e85555..302207f 100644 --- a/Mixer.h +++ b/Mixer.h @@ -9,14 +9,18 @@ #include "Session.h" #include "Selection.h" +namespace tinyxml2 { +class XMLElement; +} + class SessionSource; class Mixer { // Private Constructor Mixer(); - Mixer(Mixer const& copy); // Not Implemented - Mixer& operator=(Mixer const& copy); // Not Implemented + Mixer(Mixer const& copy) = delete; + Mixer& operator=(Mixer const& copy) = delete; public: @@ -44,6 +48,7 @@ public: // creation of sources Source * createSourceFile (const std::string &path); + Source * createSourceMultifile(const std::list &list_files, uint fps); Source * createSourceClone (const std::string &namesource = ""); Source * createSourceRender (); Source * createSourceStream (const std::string &gstreamerpipeline); @@ -54,8 +59,8 @@ public: // operations on sources void addSource (Source *s); - void deleteSource (Source *s, bool withundo=true); - void renameSource (Source *s, const std::string &newname); + void deleteSource (Source *s); + void renameSource (Source *s, const std::string &newname = ""); void attach (Source *s); void detach (Source *s); void deselect (Source *s); @@ -103,11 +108,12 @@ public: void set (Session *session); // operations depending on transition mode - void close (); - void open (const std::string& filename); + void close (bool smooth = false); + void open (const std::string& filename, bool smooth = false); // create sources if clipboard contains well-formed xml text void paste (const std::string& clipboard); + void restore(tinyxml2::XMLElement *sessionNode); protected: diff --git a/MixingGroup.cpp b/MixingGroup.cpp index 57d05aa..1dd60fe 100644 --- a/MixingGroup.cpp +++ b/MixingGroup.cpp @@ -7,6 +7,7 @@ #include "Source.h" #include "Decorations.h" #include "Visitor.h" +#include "BaseToolkit.h" #include "Log.h" #include "MixingGroup.h" @@ -16,10 +17,10 @@ MixingGroup::MixingGroup (SourceList sources) : parent_(nullptr), root_(nullptr) center_pos_(glm::vec2(0.f, 0.f)), active_(true), update_action_(ACTION_NONE), updated_source_(nullptr) { // create unique id - id_ = GlmToolkit::uniqueId(); + id_ = BaseToolkit::uniqueId(); // fill the vector of sources with the given list - for (auto it = sources.begin(); it != sources.end(); it++){ + for (auto it = sources.begin(); it != sources.end(); ++it){ // add only if not linked already if ((*it)->mixinggroup_ == nullptr) { (*it)->mixinggroup_ = this; @@ -43,7 +44,7 @@ MixingGroup::MixingGroup (SourceList sources) : parent_(nullptr), root_(nullptr) MixingGroup::~MixingGroup () { - for (auto it = sources_.begin(); it != sources_.end(); it++) + for (auto it = sources_.begin(); it != sources_.end(); ++it) (*it)->clearMixingGroup(); if (parent_) @@ -71,7 +72,7 @@ void MixingGroup::recenter() { // compute barycenter (0) center_pos_ = glm::vec2(0.f, 0.f); - for (auto it = sources_.begin(); it != sources_.end(); it++){ + for (auto it = sources_.begin(); it != sources_.end(); ++it){ // compute barycenter (1) center_pos_ += glm::vec2((*it)->group(View::MIXING)->translation_); } @@ -126,7 +127,7 @@ void MixingGroup::update (float) // compute barycenter (0) center_pos_ = glm::vec2(0.f, 0.f); auto it = sources_.begin(); - for (; it != sources_.end(); it++){ + for (; it != sources_.end(); ++it){ // update point p[ index_points_[*it] ] = glm::vec2((*it)->group(View::MIXING)->translation_); @@ -160,7 +161,7 @@ void MixingGroup::update (float) // compute barycenter (0) center_pos_ = glm::vec2(0.f, 0.f); auto it = sources_.begin(); - for (; it != sources_.end(); it++){ + for (; it != sources_.end(); ++it){ // modify all but the already updated source if ( *it != updated_source_ && !(*it)->locked() ) { @@ -196,7 +197,7 @@ void MixingGroup::update (float) int numactions = 0; auto it = sources_.begin(); - for (; it != sources_.end(); it++){ + for (; it != sources_.end(); ++it){ // modify all but the already updated source if ( *it != updated_source_ && !(*it)->locked() ) { @@ -256,7 +257,7 @@ void MixingGroup::detach (Source *s) void MixingGroup::detach (SourceList l) { - for (auto sit = l.begin(); sit != l.end(); sit++) { + for (auto sit = l.begin(); sit != l.end(); ++sit) { // find the source SourceList::iterator its = std::find(sources_.begin(), sources_.end(), *sit); // ok, its in the list ! @@ -291,7 +292,7 @@ void MixingGroup::attach (Source *s) void MixingGroup::attach (SourceList l) { - for (auto sit = l.begin(); sit != l.end(); sit++) { + for (auto sit = l.begin(); sit != l.end(); ++sit) { if ( (*sit)->mixinggroup_ == nullptr) { // tell the source (*sit)->mixinggroup_ = this; @@ -359,7 +360,7 @@ void MixingGroup::createLineStrip() // path linking all sources std::vector path; - for (auto it = sources_.begin(); it != sources_.end(); it++){ + for (auto it = sources_.begin(); it != sources_.end(); ++it){ index_points_[*it] = path.size(); path.push_back(glm::vec2((*it)->group(View::MIXING)->translation_)); } diff --git a/MixingGroup.h b/MixingGroup.h index de60033..54250ed 100644 --- a/MixingGroup.h +++ b/MixingGroup.h @@ -14,6 +14,9 @@ class MixingGroup public: MixingGroup (SourceList sources); + // non assignable class + MixingGroup(MixingGroup const&) = delete; + MixingGroup& operator=(MixingGroup const&) = delete; ~MixingGroup (); // Get unique id diff --git a/MixingView.cpp b/MixingView.cpp index 44ed867..6a1ff46 100644 --- a/MixingView.cpp +++ b/MixingView.cpp @@ -14,6 +14,7 @@ #include "Mixer.h" #include "defines.h" +#include "Source.h" #include "Settings.h" #include "Decorations.h" #include "UserInterfaceManager.h" @@ -351,12 +352,13 @@ std::pair MixingView::pick(glm::vec2 P) Source *s = Mixer::manager().findSource(pick.first); if (s != nullptr) { // pick on the lock icon; unlock source - if ( pick.first == s->lock_) { + if ( UserInterface::manager().ctrlModifier() && pick.first == s->lock_) { lock(s, false); - pick = { s->locker_, pick.second }; +// pick = { s->locker_, pick.second }; + pick = { nullptr, glm::vec2(0.f) }; } // pick on the open lock icon; lock source and cancel pick - else if ( pick.first == s->unlock_ ) { + else if ( UserInterface::manager().ctrlModifier() && pick.first == s->unlock_ ) { lock(s, true); pick = { nullptr, glm::vec2(0.f) }; } @@ -494,7 +496,7 @@ void MixingView::terminate() // terminate all mixing group actions for (auto g = Mixer::manager().session()->beginMixingGroup(); - g != Mixer::manager().session()->endMixingGroup(); g++) + g != Mixer::manager().session()->endMixingGroup(); ++g) (*g)->setAction( MixingGroup::ACTION_FINISH ); } @@ -527,7 +529,7 @@ void MixingView::arrow (glm::vec2 movement) bool first = true; glm::vec3 delta_translation(0.f); - for (auto it = Mixer::selection().begin(); it != Mixer::selection().end(); it++) { + for (auto it = Mixer::selection().begin(); it != Mixer::selection().end(); ++it) { // individual move with SHIFT if ( !Source::isCurrent(*it) && UserInterface::manager().shiftModifier() ) @@ -678,7 +680,7 @@ uint textureMixingQuadratic() glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); - + glBindTexture(GL_TEXTURE_2D, 0); } return texid; } diff --git a/MixingView.h b/MixingView.h index 97a5a95..74ba8c3 100644 --- a/MixingView.h +++ b/MixingView.h @@ -9,6 +9,9 @@ class MixingView : public View { public: MixingView(); + // non assignable class + MixingView(MixingView const&) = delete; + MixingView& operator=(MixingView const&) = delete; void draw () override; void update (float dt) override; diff --git a/MultiFileSource.cpp b/MultiFileSource.cpp new file mode 100644 index 0000000..1e9850a --- /dev/null +++ b/MultiFileSource.cpp @@ -0,0 +1,202 @@ +#include +#include + +#include "defines.h" +#include "ImageShader.h" +#include "Resource.h" +#include "Decorations.h" +#include "Stream.h" +#include "Visitor.h" +#include "GstToolkit.h" +#include "BaseToolkit.h" +#include "SystemToolkit.h" +#include "MediaPlayer.h" +#include "Log.h" + +#include "MultiFileSource.h" + +// example test gstreamer pipelines +// +// multifile : sequence of numbered images +// gst-launch-1.0 multifilesrc location="/home/bhbn/Images/sequence/frames%03d.png" caps="image/png,framerate=\(fraction\)12/1" loop=1 ! decodebin ! videoconvert ! autovideosink +// +// imagesequencesrc : sequence of numbered images (cannot loop) +// gst-launch-1.0 imagesequencesrc location=frames%03d.png start-index=1 framerate=24/1 ! decodebin ! videoconvert ! autovideosink +// + +MultiFileSequence::MultiFileSequence() : width(0), height(0), min(0), max(0) +{ +} + +MultiFileSequence::MultiFileSequence(const std::list &list_files) +{ + location = BaseToolkit::common_numbered_pattern(list_files, &min, &max); + + // sanity check: the location pattern looks like a filename and seems consecutive numbered + if ( SystemToolkit::extension_filename(location).empty() || + SystemToolkit::path_filename(location) != SystemToolkit::path_filename(list_files.front()) || + list_files.size() != max - min + 1 ) { + location.clear(); + } + + if ( !location.empty() ) { + MediaInfo media = MediaPlayer::UriDiscoverer( GstToolkit::filename_to_uri( list_files.front() ) ); + if (media.valid && media.isimage) { + codec.resize(media.codec_name.size()); + std::transform(media.codec_name.begin(), media.codec_name.end(), codec.begin(), ::tolower); + width = media.width; + height = media.height; + } + } +} + +bool MultiFileSequence::valid() const +{ + return !( location.empty() || codec.empty() || width < 1 || height < 1 || max == min); +} + +inline MultiFileSequence& MultiFileSequence::operator = (const MultiFileSequence& b) +{ + if (this != &b) { + this->width = b.width; + this->height = b.height; + this->min = b.min; + this->max = b.max; + this->location = b.location; + this->codec = b.codec; + } + return *this; +} + + +bool MultiFileSequence::operator != (const MultiFileSequence& b) +{ + return ( location != b.location || codec != b.codec || width != b.width || + height != b.height || min != b.min || max != b.max ); +} + +MultiFile::MultiFile() : Stream(), src_(nullptr) +{ + +} + +void MultiFile::open (const MultiFileSequence &sequence, uint framerate ) +{ + if (sequence.location.empty()) + return; + + std::ostringstream gstreamer_pipeline; + gstreamer_pipeline << "multifilesrc name=src location=\""; + gstreamer_pipeline << sequence.location; + gstreamer_pipeline << "\" caps=\"image/"; + gstreamer_pipeline << sequence.codec; + gstreamer_pipeline << ",framerate=(fraction)"; + gstreamer_pipeline << framerate; + gstreamer_pipeline << "/1\" loop=1"; + gstreamer_pipeline << " start-index="; + gstreamer_pipeline << sequence.min; + gstreamer_pipeline << " stop-index="; + gstreamer_pipeline << sequence.max; + gstreamer_pipeline << " ! decodebin ! videoconvert"; + + // (private) open stream + Stream::open(gstreamer_pipeline.str(), sequence.width, sequence.height); + + // keep multifile source for dynamic properties change + src_ = gst_bin_get_by_name (GST_BIN (pipeline_), "src"); +} + +void MultiFile::close () +{ + if (src_ != nullptr) { + gst_object_unref (src_); + src_ = nullptr; + } + Stream::close(); +} + +void MultiFile::setProperties (int begin, int end, int loop) +{ + if (src_) { + g_object_set (src_, "start-index", MAX(begin, 0), NULL); + g_object_set (src_, "stop-index", MAX(end, 0), NULL); + g_object_set (src_, "loop", MIN(loop, 1), NULL); + } +} + + + +MultiFileSource::MultiFileSource (uint64_t id) : StreamSource(id), framerate_(0), begin_(-1), end_(INT_MAX), loop_(1) +{ + // create stream + stream_ = static_cast( new MultiFile ); + + // set symbol + symbol_ = new Symbol(Symbol::SEQUENCE, glm::vec3(0.75f, 0.75f, 0.01f)); + symbol_->scale_.y = 1.5f; +} + + +void MultiFileSource::setFiles (const std::list &list_files, uint framerate) +{ + setSequence(MultiFileSequence(list_files), framerate); +} + +void MultiFileSource::setSequence (const MultiFileSequence &sequence, uint framerate) +{ + framerate_ = CLAMP( framerate, 1, 30); + sequence_ = sequence; + + if (sequence_.valid()) + { + // open gstreamer + multifile()->open( sequence_, framerate_ ); + stream_->play(true); + + // validate range and apply loop_ + setRange(begin_, end_); + + // will be ready after init and one frame rendered + ready_ = false; + } +} + +void MultiFileSource::setFramerate (uint framerate) +{ + if (multifile()) { + setSequence(sequence_, framerate); + } +} + +void MultiFileSource::setLoop (bool on) +{ + if (multifile()) { + loop_ = on ? 1 : 0; + multifile()->setProperties (begin_, end_, loop_); + } +} + +void MultiFileSource::setRange (int begin, int end) +{ + begin_ = glm::clamp( begin, sequence_.min, sequence_.max ); + end_ = glm::clamp( end , sequence_.min, sequence_.max ); + begin_ = glm::min( begin_, end_ ); + end_ = glm::max( begin_, end_ ); + + if (multifile()) + multifile()->setProperties (begin_, end_, loop_); +} + +void MultiFileSource::accept (Visitor& v) +{ + Source::accept(v); + if (!failed()) + v.visit(*this); +} + +MultiFile *MultiFileSource::multifile () const +{ + return dynamic_cast(stream_); +} + + diff --git a/MultiFileSource.h b/MultiFileSource.h new file mode 100644 index 0000000..5435730 --- /dev/null +++ b/MultiFileSource.h @@ -0,0 +1,76 @@ +#ifndef MULTIFILESOURCE_H +#define MULTIFILESOURCE_H + +#include +#include + +#include "StreamSource.h" + +struct MultiFileSequence { + std::string location; + std::string codec; + uint width; + uint height; + int min; + int max; + + MultiFileSequence (); + MultiFileSequence (const std::list &list_files); + bool valid () const; + MultiFileSequence& operator = (const MultiFileSequence& b); + bool operator != (const MultiFileSequence& b); +}; + +class MultiFile : public Stream +{ +public: + MultiFile (); + void open (const MultiFileSequence &sequence, uint framerate = 30); + void close () override; + + // dynamic change of gstreamer multifile source properties + void setProperties(int begin, int end, int loop); + +protected: + GstElement *src_ ; +}; + +class MultiFileSource : public StreamSource +{ +public: + MultiFileSource (uint64_t id = 0); + + // Source interface + void accept (Visitor& v) override; + + // StreamSource interface + Stream *stream () const override { return stream_; } + glm::ivec2 icon () const override { return glm::ivec2(3, 9); } + + // specific interface + void setFiles (const std::list &list_files, uint framerate); + + void setSequence (const MultiFileSequence &sequence, uint framerate); + inline MultiFileSequence sequence () const { return sequence_; } + + void setFramerate (uint fps); + inline uint framerate () const { return framerate_; } + + void setLoop (bool on); + inline bool loop () const { return loop_ > 0 ? true : false; } + + void setRange (int begin, int end); + inline int begin() const { return begin_; } + inline int end () const { return end_; } + + MultiFile *multifile () const; + +private: + MultiFileSequence sequence_; + uint framerate_; + int begin_, end_, loop_; + +}; + + +#endif // MULTIFILESOURCE_H diff --git a/NetworkSource.cpp b/NetworkSource.cpp index 7728f75..f50c1f6 100644 --- a/NetworkSource.cpp +++ b/NetworkSource.cpp @@ -204,7 +204,7 @@ void NetworkStream::update() { Stream::update(); - if ( !ready_ && !failed_ && received_config_) + if ( !opened_ && !failed_ && received_config_) { // only once received_config_ = false; @@ -268,7 +268,7 @@ void NetworkStream::update() } -NetworkSource::NetworkSource() : StreamSource() +NetworkSource::NetworkSource(uint64_t id) : StreamSource(id) { // create stream stream_ = static_cast( new NetworkStream ); @@ -297,6 +297,9 @@ void NetworkSource::setConnection(const std::string &nameconnection) // open network stream networkStream()->connect( connection_name_ ); stream_->play(true); + + // will be ready after init and one frame rendered + ready_ = false; } diff --git a/NetworkSource.h b/NetworkSource.h index bb4857f..b5214a2 100644 --- a/NetworkSource.h +++ b/NetworkSource.h @@ -20,6 +20,7 @@ protected: const IpEndpointName& remoteEndpoint ); public: inline void setParent(NetworkStream *s) { parent_ = s; } + StreamerResponseListener() : parent_(nullptr) {} }; @@ -59,7 +60,7 @@ class NetworkSource : public StreamSource std::string connection_name_; public: - NetworkSource(); + NetworkSource(uint64_t id = 0); ~NetworkSource(); // Source interface diff --git a/Overlay.cpp b/Overlay.cpp new file mode 100644 index 0000000..ccef13b --- /dev/null +++ b/Overlay.cpp @@ -0,0 +1,23 @@ +#include "Overlay.h" + +Overlay::Overlay() +{ + +} + +SnapshotOverlay::SnapshotOverlay() : Overlay() +{ + +} + +void SnapshotOverlay::draw() +{ + +} + + +void SnapshotOverlay::update (float dt) +{ + + +} diff --git a/Overlay.h b/Overlay.h new file mode 100644 index 0000000..81dc87c --- /dev/null +++ b/Overlay.h @@ -0,0 +1,38 @@ +#ifndef OVERLAY_H +#define OVERLAY_H + +#include "View.h" + +class Overlay +{ +public: + Overlay(); + + virtual void update (float dt) = 0; + virtual void draw () = 0; + + virtual std::pair pick(glm::vec2) { + return { nullptr, glm::vec2(0.f) }; + } + + virtual View::Cursor grab (Source*, glm::vec2, glm::vec2, std::pair) { + return View::Cursor (); + } + + virtual View::Cursor over (glm::vec2) { + return View::Cursor (); + } +}; + + +class SnapshotOverlay: public Overlay +{ +public: + SnapshotOverlay(); + + void draw () override; + void update (float dt) override; +}; + + +#endif // OVERLAY_H diff --git a/PatternSource.cpp b/PatternSource.cpp index 4d83bcb..d7c7ff6 100644 --- a/PatternSource.cpp +++ b/PatternSource.cpp @@ -131,7 +131,7 @@ void Pattern::open( uint pattern, glm::ivec2 res ) Stream::open(gstreamer_pattern, res.x, res.y); } -PatternSource::PatternSource() : StreamSource() +PatternSource::PatternSource(uint64_t id) : StreamSource(id) { // create stream stream_ = static_cast( new Pattern ); @@ -145,8 +145,12 @@ void PatternSource::setPattern(uint type, glm::ivec2 resolution) { Log::Notify("Creating Source with pattern '%s'", Pattern::pattern_types[type].c_str()); + // open gstreamer pattern()->open( (uint) type, resolution ); stream_->play(true); + + // will be ready after init and one frame rendered + ready_ = false; } void PatternSource::accept(Visitor& v) diff --git a/PatternSource.h b/PatternSource.h index 6e9c94f..a8cb71f 100644 --- a/PatternSource.h +++ b/PatternSource.h @@ -23,7 +23,7 @@ private: class PatternSource : public StreamSource { public: - PatternSource(); + PatternSource(uint64_t id = 0); // Source interface void accept (Visitor& v) override; diff --git a/RenderView.cpp b/RenderView.cpp index 19428c1..f5e57e0 100644 --- a/RenderView.cpp +++ b/RenderView.cpp @@ -24,11 +24,6 @@ RenderView::~RenderView() delete fading_overlay_; } -bool RenderView::canSelect(Source *s) { - - return false; -} - void RenderView::setFading(float f) { if (fading_overlay_ == nullptr) @@ -82,3 +77,65 @@ void RenderView::draw() frame_buffer_->end(); } } + +void RenderView::drawThumbnail() +{ + if (frame_buffer_) { + // if a thumbnailer is pending + if (thumbnailer_.size() > 0) { + + try { + // new thumbnailing framebuffer + FrameBuffer *frame_thumbnail = new FrameBuffer( glm::vec3(SESSION_THUMBNAIL_HEIGHT * frame_buffer_->aspectRatio(), SESSION_THUMBNAIL_HEIGHT, 1.f) ); + + // render + if (Settings::application.render.blit) { + if ( !frame_buffer_->blit(frame_thumbnail) ) + throw std::runtime_error("no blit"); + } + else { + FrameBufferSurface *thumb = new FrameBufferSurface(frame_buffer_); + frame_thumbnail->begin(); + thumb->draw(glm::identity(), frame_thumbnail->projection()); + frame_thumbnail->end(); + delete thumb; + } + + // return valid thumbnail promise + thumbnailer_.back().set_value( frame_thumbnail->image() ); + + // done with thumbnailing framebuffer + delete frame_thumbnail; + } + catch(...) { + // return failed thumbnail promise + thumbnailer_.back().set_exception(std::current_exception()); + } + + // done with this promise + thumbnailer_.pop_back(); + } + } +} + +FrameBufferImage *RenderView::thumbnail () +{ + // by default null image + FrameBufferImage *img = nullptr; + + // create and store a promise for a FrameBufferImage + thumbnailer_.emplace_back( std::promise() ); + + // future will return the primised FrameBufferImage + std::future t = thumbnailer_.back().get_future(); + + try { + // wait for valid return value from promise + img = t.get(); + } + // catch any failed promise + catch (std::runtime_error&){ + } + + return img; +} diff --git a/RenderView.h b/RenderView.h index 987a9a7..7a2a7f1 100644 --- a/RenderView.h +++ b/RenderView.h @@ -1,19 +1,27 @@ #ifndef RENDERVIEW_H #define RENDERVIEW_H +#include +#include + #include "View.h" class RenderView : public View { + // rendering FBO FrameBuffer *frame_buffer_; Surface *fading_overlay_; + // promises of returning thumbnails after an update + std::vector< std::promise > thumbnailer_; + public: RenderView (); ~RenderView (); + // render frame (in opengl context) void draw () override; - bool canSelect(Source *) override; + bool canSelect(Source *) override { return false; } void setResolution (glm::vec3 resolution = glm::vec3(0.f), bool useAlpha = false); glm::vec3 resolution() const { return frame_buffer_->resolution(); } @@ -21,7 +29,12 @@ public: void setFading(float f = 0.f); float fading() const; + // current frame inline FrameBuffer *frame () const { return frame_buffer_; } + + // get a thumbnail outside of opengl context; wait for a promise to be fullfiled after draw + void drawThumbnail(); + FrameBufferImage *thumbnail (); }; #endif // RENDERVIEW_H diff --git a/RenderingManager.cpp b/RenderingManager.cpp index c90f6a2..d2d6b57 100644 --- a/RenderingManager.cpp +++ b/RenderingManager.cpp @@ -188,6 +188,7 @@ bool Rendering::init() // additional window callbacks for main window glfwSetWindowRefreshCallback( main_.window(), WindowRefreshCallback ); glfwSetDropCallback( main_.window(), Rendering::FileDropped); + glfwSetWindowSizeLimits( main_.window(), 800, 500, GLFW_DONT_CARE, GLFW_DONT_CARE); // // Gstreamer setup @@ -342,7 +343,7 @@ void Rendering::terminate() // close window glfwDestroyWindow(output_.window()); glfwDestroyWindow(main_.window()); - glfwTerminate(); +// glfwTerminate(); } @@ -423,14 +424,15 @@ glm::vec2 Rendering::project(glm::vec3 scene_coordinate, glm::mat4 modelview, bo void Rendering::FileDropped(GLFWwindow *, int path_count, const char* paths[]) { - for (int i = 0; i < path_count; ++i) { + int i = 0; + for (; i < path_count; ++i) { std::string filename(paths[i]); if (filename.empty()) break; // try to create a source Mixer::manager().addSource ( Mixer::manager().createSourceFile( filename ) ); } - if (path_count>0) { + if (i>0) { UserInterface::manager().showPannel(); Rendering::manager().mainWindow().show(); } @@ -726,6 +728,8 @@ bool RenderingWindow::init(int index, GLFWwindow *share) // DPI scaling (retina) dpi_scale_ = float(window_attributes_.viewport.y) / float(winset.h); + // We decide for byte aligned textures all over + glPixelStorei(GL_UNPACK_ALIGNMENT,1); // This hint can improve the speed of texturing when perspective-correct texture coordinate interpolation isn't needed glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST); // fast mipmaps (we are not really using mipmaps anyway) diff --git a/RenderingManager.h b/RenderingManager.h index 3f36f22..76355dc 100644 --- a/RenderingManager.h +++ b/RenderingManager.h @@ -92,8 +92,8 @@ class Rendering // Private Constructor Rendering(); - Rendering(Rendering const& copy); // Not Implemented - Rendering& operator=(Rendering const& copy); // Not Implemented + Rendering(Rendering const& copy) = delete; + Rendering& operator=(Rendering const& copy) = delete; public: diff --git a/Resource.cpp b/Resource.cpp index 469dfda..64d1829 100644 --- a/Resource.cpp +++ b/Resource.cpp @@ -38,6 +38,7 @@ uint Resource::getTextureBlack() // texture with one black pixel glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 1, 1); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, clearColor); + glBindTexture(GL_TEXTURE_2D, 0); } return tex_index_black; @@ -59,6 +60,7 @@ uint Resource::getTextureWhite() // texture with one black pixel glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 1, 1); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, clearColor); + glBindTexture(GL_TEXTURE_2D, 0); } return tex_index_white; @@ -80,6 +82,7 @@ uint Resource::getTextureTransparent() // texture with one black pixel glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 1, 1); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, clearColor); + glBindTexture(GL_TEXTURE_2D, 0); } return tex_index_transparent; @@ -219,6 +222,7 @@ uint Resource::getTextureDDS(const std::string& path, float *aspect_ratio) if(height < 1) height = 1; } + glBindTexture(GL_TEXTURE_2D, 0); // remember to avoid openning the same resource twice textureIndex[path] = textureID; @@ -263,19 +267,20 @@ uint Resource::getTextureImage(const std::string& path, float *aspect_ratio) } if (h == 0){ Log::Error("Invalid image in ressource %s", std::string(path).c_str()); + stbi_image_free(img); return 0; } ar = static_cast(w) / static_cast(h); glGenTextures(1, &textureID); - glBindTexture( GL_TEXTURE_2D, textureID); - glPixelStorei(GL_UNPACK_ALIGNMENT, 4); + glBindTexture( GL_TEXTURE_2D, textureID); glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, w, h); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, img); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glBindTexture(GL_TEXTURE_2D, 0); // free memory stbi_image_free(img); diff --git a/Scene.cpp b/Scene.cpp index 4eb8367..0fd70fb 100644 --- a/Scene.cpp +++ b/Scene.cpp @@ -13,6 +13,7 @@ #include "Visitor.h" #include "GarbageVisitor.h" #include "Log.h" +#include "BaseToolkit.h" #include "GlmToolkit.h" #include "SessionVisitor.h" @@ -25,7 +26,7 @@ static int num_nodes_ = 0; Node::Node() : initialized_(false), visible_(true), refcount_(0) { // create unique id - id_ = GlmToolkit::uniqueId(); + id_ = BaseToolkit::uniqueId(); transform_ = glm::identity(); scale_ = glm::vec3(1.f); @@ -56,7 +57,7 @@ void Node::clearCallbacks() } } -void Node::copyTransform(Node *other) +void Node::copyTransform(const Node *other) { if (!other) return; diff --git a/Scene.h b/Scene.h index c48fcb8..ee33b15 100644 --- a/Scene.h +++ b/Scene.h @@ -13,6 +13,7 @@ #include #include "UpdateCallback.h" +#include "GlmToolkit.h" // Forward declare classes referenced class Shader; @@ -64,7 +65,7 @@ public: // accept all kind of visitors virtual void accept (Visitor& v); - void copyTransform (Node *other); + void copyTransform (const Node *other); // public members, to manipulate with care bool visible_; @@ -239,6 +240,9 @@ class Scene { public: Scene(); + // non assignable class + Scene(Scene const&) = delete; + Scene& operator=(Scene const&) = delete; ~Scene(); void accept (Visitor& v); diff --git a/Selection.cpp b/Selection.cpp index d5c9a09..a4f03b7 100644 --- a/Selection.cpp +++ b/Selection.cpp @@ -61,7 +61,7 @@ void Selection::set(SourceList l) { clear(); - for(auto it = l.begin(); it != l.end(); it++) + for(auto it = l.begin(); it != l.end(); ++it) (*it)->setMode(Source::SELECTED); l.sort(); @@ -71,7 +71,7 @@ void Selection::set(SourceList l) void Selection::add(SourceList l) { - for(auto it = l.begin(); it != l.end(); it++) + for(auto it = l.begin(); it != l.end(); ++it) (*it)->setMode(Source::SELECTED); // generate new set as union of current selection and give list @@ -86,7 +86,7 @@ void Selection::add(SourceList l) void Selection::remove(SourceList l) { - for(auto it = l.begin(); it != l.end(); it++) + for(auto it = l.begin(); it != l.end(); ++it) (*it)->setMode(Source::VISIBLE); // generate new set as difference of current selection and give list @@ -98,7 +98,7 @@ void Selection::remove(SourceList l) void Selection::clear() { - for(auto it = selection_.begin(); it != selection_.end(); it++) + for(auto it = selection_.begin(); it != selection_.end(); ++it) (*it)->setMode(Source::VISIBLE); selection_.clear(); diff --git a/Session.cpp b/Session.cpp index 04b8790..c7a7606 100644 --- a/Session.cpp +++ b/Session.cpp @@ -1,6 +1,8 @@ #include #include "defines.h" +#include "BaseToolkit.h" +#include "Source.h" #include "Settings.h" #include "FrameBuffer.h" #include "Session.h" @@ -11,7 +13,12 @@ #include "Log.h" -Session::Session() : failedSource_(nullptr), active_(true), fading_target_(0.f), filename_("") +SessionNote::SessionNote(const std::string &t, bool l, int s): label(std::to_string(BaseToolkit::uniqueId())), + text(t), large(l), stick(s), pos(glm::vec2(520.f, 30.f)), size(glm::vec2(220.f, 220.f)) +{ +} + +Session::Session() : active_(true), filename_(""), failedSource_(nullptr), fading_target_(0.f) { config_[View::RENDERING] = new Group; config_[View::RENDERING]->scale_ = glm::vec3(0.f); @@ -31,6 +38,8 @@ Session::Session() : failedSource_(nullptr), active_(true), fading_target_(0.f), config_[View::TEXTURE] = new Group; config_[View::TEXTURE]->scale_ = Settings::application.views[View::TEXTURE].default_scale; config_[View::TEXTURE]->translation_ = Settings::application.views[View::TEXTURE].default_translation; + + snapshots_.xmlDoc_ = new tinyxml2::XMLDocument; } @@ -54,13 +63,16 @@ Session::~Session() delete config_[View::LAYER]; delete config_[View::MIXING]; delete config_[View::TEXTURE]; + + snapshots_.keys_.clear(); + delete snapshots_.xmlDoc_; } void Session::setActive (bool on) { if (active_ != on) { active_ = on; - for(auto it = sources_.begin(); it != sources_.end(); it++) { + for(auto it = sources_.begin(); it != sources_.end(); ++it) { (*it)->setActive(active_); } } @@ -75,6 +87,7 @@ void Session::update(float dt) // pre-render of all sources failedSource_ = nullptr; + bool ready = true; for( SourceList::iterator it = sources_.begin(); it != sources_.end(); ++it){ // ensure the RenderSource is rendering this session @@ -86,6 +99,8 @@ void Session::update(float dt) failedSource_ = (*it); } else { + if ( !(*it)->ready() ) + ready = false; // render the source (*it)->render(); // update the source @@ -118,9 +133,11 @@ void Session::update(float dt) // draw render view in Frame Buffer render_.draw(); + // draw the thumbnail only after all sources are ready + if (ready) + render_.drawThumbnail(); } - SourceList::iterator Session::addSource(Source *s) { // lock before change @@ -172,7 +189,6 @@ SourceList::iterator Session::deleteSource(Source *s) return its; } - void Session::removeSource(Source *s) { // lock before change @@ -195,7 +211,6 @@ void Session::removeSource(Source *s) access_.unlock(); } - Source *Session::popSource() { Source *s = nullptr; @@ -279,6 +294,19 @@ SourceIdList Session::getIdList() const return ids(sources_); } +std::list Session::getNameList(uint64_t exceptid) const +{ + std::list namelist; + + for( SourceList::const_iterator it = sources_.cbegin(); it != sources_.cend(); ++it) { + if ( (*it)->id() != exceptid ) + namelist.push_back( (*it)->name() ); + } + + return namelist; +} + + bool Session::empty() const { return sources_.empty(); @@ -302,7 +330,7 @@ int Session::index(SourceList::iterator it) const { int index = -1; int count = 0; - for(auto i = sources_.begin(); i != sources_.end(); i++, count++) { + for(auto i = sources_.begin(); i != sources_.end(); ++i, ++count) { if ( i == it ) { index = count; break; @@ -335,7 +363,7 @@ bool Session::canlink (SourceList sources) // verify that all sources given are valid in the sesion validate(sources); - for (auto it = sources.begin(); it != sources.end(); it++) { + for (auto it = sources.begin(); it != sources.end(); ++it) { // this source is linked if ( (*it)->mixingGroup() != nullptr ) { // askt its group to detach it @@ -370,7 +398,7 @@ void Session::unlink (SourceList sources) validate(sources); // brute force : detach all given sources - for (auto it = sources.begin(); it != sources.end(); it++) { + for (auto it = sources.begin(); it != sources.end(); ++it) { // this source is linked if ( (*it)->mixingGroup() != nullptr ) { // askt its group to detach it @@ -379,17 +407,39 @@ void Session::unlink (SourceList sources) } } +void Session::addNote(SessionNote note) +{ + notes_.push_back( note ); +} + +std::list::iterator Session::beginNotes () +{ + return notes_.begin(); +} + +std::list::iterator Session::endNotes () +{ + return notes_.end(); +} + +std::list::iterator Session::deleteNote (std::list::iterator n) +{ + if (n != notes_.end()) + return notes_.erase(n); + + return notes_.end(); +} + std::list Session::getMixingGroups () const { std::list lmg; - for (auto group_it = mixing_groups_.begin(); group_it!= mixing_groups_.end(); group_it++) + for (auto group_it = mixing_groups_.begin(); group_it!= mixing_groups_.end(); ++group_it) lmg.push_back( (*group_it)->getCopy() ); return lmg; } - std::list::iterator Session::deleteMixingGroup (std::list::iterator g) { if (g != mixing_groups_.end()) { @@ -419,7 +469,6 @@ void Session::unlock() access_.unlock(); } - void Session::validate (SourceList &sources) { // verify that all sources given are valid in the sesion diff --git a/Session.h b/Session.h index e0dce94..a1d710f 100644 --- a/Session.h +++ b/Session.h @@ -3,12 +3,33 @@ #include +#include "SourceList.h" #include "RenderView.h" -#include "Source.h" +namespace tinyxml2 { +class XMLDocument; +} class FrameGrabber; class MixingGroup; +struct SessionNote +{ + std::string label; + std::string text; + bool large; + int stick; + glm::vec2 pos; + glm::vec2 size; + + SessionNote(const std::string &t = "", bool l = false, int s = 0); +}; + +struct SessionSnapshots { + + tinyxml2::XMLDocument *xmlDoc_; + std::list keys_; +}; + class Session { public: @@ -45,6 +66,8 @@ public: SourceList::iterator find (uint64_t id); SourceIdList getIdList() const; + std::list getNameList(uint64_t exceptid=0) const; + SourceList::iterator at (int index); int index (SourceList::iterator it) const; void move (int current_index, int target_index); @@ -62,6 +85,9 @@ public: // get frame result of render inline FrameBuffer *frame () const { return render_.frame(); } + // get thumbnail image + inline FrameBufferImage *thumbnail () { return render_.thumbnail(); } + // configure rendering resolution void setResolution (glm::vec3 resolution, bool useAlpha = false); @@ -76,6 +102,12 @@ public: void setFilename (const std::string &filename) { filename_ = filename; } std::string filename () const { return filename_; } + // get the list of notes + void addNote(SessionNote note = SessionNote()); + std::list::iterator beginNotes (); + std::list::iterator endNotes (); + std::list::iterator deleteNote (std::list::iterator n); + // get the list of sources in mixing groups std::list getMixingGroups () const; // returns true if something can be done to create a mixing group @@ -91,23 +123,27 @@ public: std::list::iterator endMixingGroup (); std::list::iterator deleteMixingGroup (std::list::iterator g); + // snapshots + SessionSnapshots * const snapshots () { return &snapshots_; } + // lock and unlock access (e.g. while saving) void lock (); void unlock (); protected: + bool active_; RenderView render_; std::string filename_; Source *failedSource_; SourceList sources_; void validate(SourceList &sources); - + std::list notes_; std::list mixing_groups_; std::map config_; - bool active_; - std::list grabbers_; + SessionSnapshots snapshots_; float fading_target_; std::mutex access_; + }; diff --git a/SessionCreator.cpp b/SessionCreator.cpp index f8e45c4..513f1ee 100644 --- a/SessionCreator.cpp +++ b/SessionCreator.cpp @@ -1,4 +1,4 @@ -#include "SessionCreator.h" +#include #include "Log.h" #include "defines.h" @@ -12,43 +12,49 @@ #include "PatternSource.h" #include "DeviceSource.h" #include "NetworkSource.h" +#include "MultiFileSource.h" #include "Session.h" #include "ImageShader.h" #include "ImageProcessingShader.h" #include "MediaPlayer.h" #include "SystemToolkit.h" -#include #include "tinyxml2Toolkit.h" using namespace tinyxml2; +#include "SessionCreator.h" -std::string SessionCreator::info(const std::string& filename) +SessionInformation SessionCreator::info(const std::string& filename) { - std::string ret = ""; + SessionInformation ret; // if the file exists if (SystemToolkit::file_exists(filename)) { + // impose C locale + setlocale(LC_ALL, "C"); // try to load the file XMLDocument doc; XMLError eResult = doc.LoadFile(filename.c_str()); // silently ignore on error if ( !XMLResultError(eResult, false)) { - XMLElement *header = doc.FirstChildElement(APP_NAME); - if (header != nullptr && header->Attribute("date") != 0) { + const XMLElement *header = doc.FirstChildElement(APP_NAME); + if (header != nullptr) { int s = header->IntAttribute("size"); - ret = std::to_string( s ) + " source" + ( s > 1 ? "s\n" : "\n"); + ret.description = std::to_string( s ) + " source" + ( s > 1 ? "s\n" : "\n"); const char *att_string = header->Attribute("resolution"); if (att_string) - ret += std::string( att_string ) + "\n"; + ret.description += std::string( att_string ) + "\n"; att_string = header->Attribute("date"); if (att_string) { std::string date( att_string ); - ret += date.substr(6,2) + "/" + date.substr(4,2) + "/" + date.substr(0,4) + " @ "; - ret += date.substr(8,2) + ":" + date.substr(10,2); + ret.description += date.substr(6,2) + "/" + date.substr(4,2) + "/" + date.substr(0,4) + " @ "; + ret.description += date.substr(8,2) + ":" + date.substr(10,2); } - + } + const XMLElement *session = doc.FirstChildElement("Session"); + if (session != nullptr ) { + ret.thumbnail = XMLToImage(session); } } } @@ -97,9 +103,15 @@ void SessionCreator::load(const std::string& filename) // create groups std::list< SourceList > groups = getMixingGroups(); - for (auto group_it = groups.begin(); group_it != groups.end(); group_it++) + for (auto group_it = groups.begin(); group_it != groups.end(); ++group_it) session_->link( *group_it ); + // load snapshots + loadSnapshots( xmlDoc_.FirstChildElement("Snapshots") ); + + // load notes + loadNotes( xmlDoc_.FirstChildElement("Notes") ); + // all good session_->setFilename(filename); } @@ -107,7 +119,7 @@ void SessionCreator::load(const std::string& filename) void SessionCreator::loadConfig(XMLElement *viewsNode) { - if (viewsNode != nullptr) { + if (viewsNode != nullptr && session_ != nullptr) { // ok, ready to read views SessionLoader::XMLToNode( viewsNode->FirstChildElement("Mixing"), *session_->config(View::MIXING)); SessionLoader::XMLToNode( viewsNode->FirstChildElement("Geometry"), *session_->config(View::GEOMETRY)); @@ -117,6 +129,48 @@ void SessionCreator::loadConfig(XMLElement *viewsNode) } } +void SessionCreator::loadSnapshots(XMLElement *snapshotsNode) +{ + if (snapshotsNode != nullptr && session_ != nullptr) { + + const XMLElement* N = snapshotsNode->FirstChildElement(); + for( ; N ; N = N->NextSiblingElement()) { + + char c; + u_int64_t id = 0; + std::istringstream nodename( N->Name() ); + nodename >> c >> id; + + session_->snapshots()->keys_.push_back(id); + session_->snapshots()->xmlDoc_->InsertEndChild( N->DeepClone(session_->snapshots()->xmlDoc_) ); + } + } +} + +void SessionCreator::loadNotes(XMLElement *notesNode) +{ + if (notesNode != nullptr && session_ != nullptr) { + + XMLElement* note = notesNode->FirstChildElement("Note"); + for( ; note ; note = note->NextSiblingElement()) + { + SessionNote N; + + note->QueryBoolAttribute("large", &N.large); + note->QueryIntAttribute("stick", &N.stick); + XMLElement *posNode = note->FirstChildElement("pos"); + if (posNode) tinyxml2::XMLElementToGLM( posNode->FirstChildElement("vec2"), N.pos); + XMLElement *sizeNode = note->FirstChildElement("size"); + if (sizeNode) tinyxml2::XMLElementToGLM( sizeNode->FirstChildElement("vec2"), N.size); + XMLElement* contentNode = note->FirstChildElement("text"); + if (contentNode) N.text = std::string ( contentNode->GetText() ); + + session_->addNote(N); + } + + } +} + SessionLoader::SessionLoader(): Visitor(), session_(nullptr), xmlCurrent_(nullptr), recursion_(0) { @@ -146,10 +200,10 @@ std::list< SourceList > SessionLoader::getMixingGroups() const std::list< SourceList > groups_new_sources_id; // perform conversion from xml id to new id - for (auto git = groups_sources_id_.begin(); git != groups_sources_id_.end(); git++) + for (auto git = groups_sources_id_.begin(); git != groups_sources_id_.end(); ++git) { SourceList new_sources; - for (auto sit = (*git).begin(); sit != (*git).end(); sit++ ) { + for (auto sit = (*git).begin(); sit != (*git).end(); ++sit ) { if (sources_id_.count(*sit) > 0) new_sources.push_back( sources_id_.at(*sit) ); } @@ -194,25 +248,28 @@ void SessionLoader::load(XMLElement *sessionNode) if (!pType) continue; if ( std::string(pType) == "MediaSource") { - load_source = new MediaSource; + load_source = new MediaSource(id_xml_); } else if ( std::string(pType) == "SessionSource") { - load_source = new SessionFileSource; + load_source = new SessionFileSource(id_xml_); } else if ( std::string(pType) == "GroupSource") { - load_source = new SessionGroupSource; + load_source = new SessionGroupSource(id_xml_); } else if ( std::string(pType) == "RenderSource") { - load_source = new RenderSource; + load_source = new RenderSource(id_xml_); } else if ( std::string(pType) == "PatternSource") { - load_source = new PatternSource; + load_source = new PatternSource(id_xml_); } else if ( std::string(pType) == "DeviceSource") { - load_source = new DeviceSource; + load_source = new DeviceSource(id_xml_); } else if ( std::string(pType) == "NetworkSource") { - load_source = new NetworkSource; + load_source = new NetworkSource(id_xml_); + } + else if ( std::string(pType) == "MultiFileSource") { + load_source = new MultiFileSource(id_xml_); } // skip failed (including clones) @@ -255,12 +312,17 @@ void SessionLoader::load(XMLElement *sessionNode) // clone from given origin XMLElement* originNode = xmlCurrent_->FirstChildElement("origin"); if (originNode) { - std::string sourcename = std::string ( originNode->GetText() ); - SourceList::iterator origin = session_->find(sourcename); + uint64_t id_origin_ = 0; + originNode->QueryUnsigned64Attribute("id", &id_origin_); + SourceList::iterator origin; + if (id_origin_ > 0) + origin = session_->find(id_origin_); + else + origin = session_->find( std::string ( originNode->GetText() ) ); // found the orign source if (origin != session_->end()) { // create a new source of type Clone - Source *clone_source = (*origin)->clone(); + Source *clone_source = (*origin)->clone(id_xml_); // add source to session session_->addSource(clone_source); @@ -291,11 +353,12 @@ Source *SessionLoader::createSource(tinyxml2::XMLElement *sourceNode, Mode mode) Source *load_source = nullptr; bool is_clone = false; - SourceList::iterator sit = session_->end(); + uint64_t id__ = 0; + xmlCurrent_->QueryUnsigned64Attribute("id", &id__); + // check if a source with the given id exists in the session + SourceList::iterator sit = session_->end(); if (mode == CLONE) { - uint64_t id__ = 0; - xmlCurrent_->QueryUnsigned64Attribute("id", &id__); sit = session_->find(id__); } @@ -305,35 +368,43 @@ Source *SessionLoader::createSource(tinyxml2::XMLElement *sourceNode, Mode mode) const char *pType = xmlCurrent_->Attribute("type"); if (pType) { if ( std::string(pType) == "MediaSource") { - load_source = new MediaSource; + load_source = new MediaSource(id__); } else if ( std::string(pType) == "SessionSource") { - load_source = new SessionFileSource; + load_source = new SessionFileSource(id__); } else if ( std::string(pType) == "GroupSource") { - load_source = new SessionGroupSource; + load_source = new SessionGroupSource(id__); } else if ( std::string(pType) == "RenderSource") { - load_source = new RenderSource; + load_source = new RenderSource(id__); } else if ( std::string(pType) == "PatternSource") { - load_source = new PatternSource; + load_source = new PatternSource(id__); } else if ( std::string(pType) == "DeviceSource") { - load_source = new DeviceSource; + load_source = new DeviceSource(id__); } else if ( std::string(pType) == "NetworkSource") { - load_source = new NetworkSource; + load_source = new NetworkSource(id__); + } + else if ( std::string(pType) == "MultiFileSource") { + load_source = new MultiFileSource(id__); } else if ( std::string(pType) == "CloneSource") { // clone from given origin XMLElement* originNode = xmlCurrent_->FirstChildElement("origin"); if (originNode) { - std::string sourcename = std::string ( originNode->GetText() ); - SourceList::iterator origin = session_->find(sourcename); + uint64_t id_origin_ = 0; + originNode->QueryUnsigned64Attribute("id", &id_origin_); + SourceList::iterator origin; + if (id_origin_ > 0) + origin = session_->find(id_origin_); + else + origin = session_->find( std::string ( originNode->GetText() ) ); // found the orign source if (origin != session_->end()) - load_source = (*origin)->clone(); + load_source = (*origin)->clone(id__); } } } @@ -356,7 +427,7 @@ Source *SessionLoader::createSource(tinyxml2::XMLElement *sourceNode, Mode mode) } -bool SessionLoader::isClipboard(std::string clipboard) +bool SessionLoader::isClipboard(const std::string &clipboard) { if (clipboard.size() > 6 && clipboard.substr(0, 6) == "<" APP_NAME ) return true; @@ -364,7 +435,7 @@ bool SessionLoader::isClipboard(std::string clipboard) return false; } -tinyxml2::XMLElement* SessionLoader::firstSourceElement(std::string clipboard, XMLDocument &xmlDoc) +tinyxml2::XMLElement* SessionLoader::firstSourceElement(const std::string &clipboard, XMLDocument &xmlDoc) { tinyxml2::XMLElement* sourceNode = nullptr; @@ -385,7 +456,7 @@ tinyxml2::XMLElement* SessionLoader::firstSourceElement(std::string clipboard, X return sourceNode; } -void SessionLoader::applyImageProcessing(const Source &s, std::string clipboard) +void SessionLoader::applyImageProcessing(const Source &s, const std::string &clipboard) { if ( !isClipboard(clipboard) ) return; @@ -449,28 +520,85 @@ void SessionLoader::applyImageProcessing(const Source &s, std::string clipboard) //// s.processingShader()->accept(loader); //} -void SessionLoader::XMLToNode(tinyxml2::XMLElement *xml, Node &n) +void SessionLoader::XMLToNode(const tinyxml2::XMLElement *xml, Node &n) { if (xml != nullptr){ - XMLElement *node = xml->FirstChildElement("Node"); + const XMLElement *node = xml->FirstChildElement("Node"); if ( !node || std::string(node->Name()).find("Node") == std::string::npos ) return; - XMLElement *scaleNode = node->FirstChildElement("scale"); + const XMLElement *scaleNode = node->FirstChildElement("scale"); if (scaleNode) tinyxml2::XMLElementToGLM( scaleNode->FirstChildElement("vec3"), n.scale_); - XMLElement *translationNode = node->FirstChildElement("translation"); + const XMLElement *translationNode = node->FirstChildElement("translation"); if (translationNode) tinyxml2::XMLElementToGLM( translationNode->FirstChildElement("vec3"), n.translation_); - XMLElement *rotationNode = node->FirstChildElement("rotation"); + const XMLElement *rotationNode = node->FirstChildElement("rotation"); if (rotationNode) tinyxml2::XMLElementToGLM( rotationNode->FirstChildElement("vec3"), n.rotation_); - XMLElement *cropNode = node->FirstChildElement("crop"); + const XMLElement *cropNode = node->FirstChildElement("crop"); if (cropNode) tinyxml2::XMLElementToGLM( cropNode->FirstChildElement("vec3"), n.crop_); } } +void SessionLoader::XMLToSourcecore(tinyxml2::XMLElement *xml, SourceCore &s) +{ + SessionLoader::XMLToNode(xml->FirstChildElement("Mixing"), *s.group(View::MIXING) ); + SessionLoader::XMLToNode(xml->FirstChildElement("Geometry"),*s.group(View::GEOMETRY) ); + SessionLoader::XMLToNode(xml->FirstChildElement("Layer"), *s.group(View::LAYER) ); + SessionLoader::XMLToNode(xml->FirstChildElement("Texture"), *s.group(View::TEXTURE) ); + + SessionLoader v(nullptr); + v.xmlCurrent_ = xml->FirstChildElement("ImageProcessing"); + if (v.xmlCurrent_) + s.processingShader()->accept(v); + +} + +FrameBufferImage *SessionLoader::XMLToImage(const XMLElement *xml) +{ + FrameBufferImage *i = nullptr; + + if (xml != nullptr){ + // if there is an Image mask stored + const XMLElement* imageNode = xml->FirstChildElement("Image"); + if (imageNode) { + // get theoretical image size + int w = 0, h = 0; + imageNode->QueryIntAttribute("width", &w); + imageNode->QueryIntAttribute("height", &h); + // if there is an internal array of data + const XMLElement* array = imageNode->FirstChildElement("array"); + if (array) { + // create a temporary jpeg with size of the array + FrameBufferImage::jpegBuffer jpgimg; + array->QueryUnsignedAttribute("len", &jpgimg.len); + // ok, we got a size of data to load + if (jpgimg.len>0) { + // allocate jpeg buffer + jpgimg.buffer = (unsigned char*) malloc(jpgimg.len); + // actual decoding of array + if (XMLElementDecodeArray(array, jpgimg.buffer, jpgimg.len) ) { + // create and set the image from jpeg + i = new FrameBufferImage(jpgimg); + // failed if wrong size + if ( (w>0 && h>0) && (i->width != w || i->height != h) ) { + delete i; + i = nullptr; + } + } + // free temporary buffer + if (jpgimg.buffer) + free(jpgimg.buffer); + } + } + } + } + + return i; +} + void SessionLoader::visit(Node &n) { XMLToNode(xmlCurrent_, n); @@ -634,35 +762,15 @@ void SessionLoader::visit (Source& s) } xmlCurrent_ = sourceNode->FirstChildElement("Blending"); - if (xmlCurrent_) s.blendingShader()->accept(*this); + if (xmlCurrent_) + s.blendingShader()->accept(*this); xmlCurrent_ = sourceNode->FirstChildElement("Mask"); if (xmlCurrent_) { // read the mask shader attributes s.maskShader()->accept(*this); - // if there is an Image mask stored - XMLElement* imageNode = xmlCurrent_->FirstChildElement("Image"); - if (imageNode) { - // if there is an internal array of data - XMLElement* array = imageNode->FirstChildElement("array"); - if (array) { - // create a temporary jpeg with size of the array - FrameBufferImage::jpegBuffer jpgimg; - array->QueryUnsignedAttribute("len", &jpgimg.len); - // ok, we got a size of data to load - if (jpgimg.len>0) { - // allocate jpeg buffer - jpgimg.buffer = (unsigned char*) malloc(jpgimg.len); - // actual decoding of array - if (XMLElementDecodeArray(array, jpgimg.buffer, jpgimg.len) ) - // create and set the image from jpeg - s.setMask(new FrameBufferImage(jpgimg)); - // free temporary buffer - if (jpgimg.buffer) - free(jpgimg.buffer); - } - } - } + // set the mask from jpeg + s.setMask( SessionLoader::XMLToImage(xmlCurrent_) ); } xmlCurrent_ = sourceNode->FirstChildElement("ImageProcessing"); @@ -778,27 +886,48 @@ void SessionLoader::visit (NetworkSource& s) s.setConnection(connect); } -// dirty hack wich can be useful ? -//class DummySource : public Source -//{ -// friend class SessionLoader; -//public: -// uint texture() const override { return 0; } -// bool failed() const override { return true; } -// void accept (Visitor& v) override { Source::accept(v); } -//protected: -// DummySource() : Source() {} -// void init() override {} -//}; +void SessionLoader::visit (MultiFileSource& s) +{ + XMLElement* seq = xmlCurrent_->FirstChildElement("Sequence"); -//Source *SessionLoader::createDummy(tinyxml2::XMLElement *sourceNode) -//{ -// SessionLoader loader; -// loader.xmlCurrent_ = sourceNode; -// DummySource *dum = new DummySource; -// dum->accept(loader); -// return dum; -//} + if (seq) { + + MultiFileSequence sequence; + sequence.location = std::string ( seq->GetText() ); + seq->QueryIntAttribute("min", &sequence.min); + seq->QueryIntAttribute("max", &sequence.max); + seq->QueryUnsignedAttribute("width", &sequence.width); + seq->QueryUnsignedAttribute("height", &sequence.height); + const char *codec = seq->Attribute("codec"); + if (codec) + sequence.codec = std::string(codec); + + uint fps = 0; + seq->QueryUnsignedAttribute("fps", &fps); + + // different sequence + if ( sequence != s.sequence() ) { + s.setSequence( sequence, fps); + } + // same sequence, different framerate + else if ( fps != s.framerate() ) { + s.setFramerate( fps ); + } + + int begin = -1; + seq->QueryIntAttribute("begin", &begin); + int end = INT_MAX; + seq->QueryIntAttribute("end", &end); + if ( begin != s.begin() || end != s.end() ) + s.setRange(begin, end); + + bool loop = true; + seq->QueryBoolAttribute("loop", &loop); + if ( loop != s.loop() ) + s.setLoop(loop); + } + +} diff --git a/SessionCreator.h b/SessionCreator.h index c9f7f09..321c1ba 100644 --- a/SessionCreator.h +++ b/SessionCreator.h @@ -8,6 +8,7 @@ #include "SourceList.h" class Session; +class FrameBufferImage; class SessionLoader : public Visitor { @@ -29,10 +30,10 @@ public: } Mode; Source *createSource(tinyxml2::XMLElement *sourceNode, Mode mode = CLONE); - static bool isClipboard(std::string clipboard); - static tinyxml2::XMLElement* firstSourceElement(std::string clipboard, tinyxml2::XMLDocument &xmlDoc); - static void applyImageProcessing(const Source &s, std::string clipboard); - //TODO static void applyMask(const Source &s, std::string clipboard); + static bool isClipboard(const std::string &clipboard); + static tinyxml2::XMLElement* firstSourceElement(const std::string &clipboard, tinyxml2::XMLDocument &xmlDoc); + static void applyImageProcessing(const Source &s, const std::string &clipboard); + //TODO static void applyMask(const Source &s, const std::string &clipboard); // Elements of Scene void visit (Node& n) override; @@ -57,6 +58,11 @@ public: void visit (PatternSource& s) override; void visit (DeviceSource& s) override; void visit (NetworkSource& s) override; + void visit (MultiFileSource& s) override; + + static void XMLToNode(const tinyxml2::XMLElement *xml, Node &n); + static void XMLToSourcecore(tinyxml2::XMLElement *xml, SourceCore &s); + static FrameBufferImage *XMLToImage(const tinyxml2::XMLElement *xml); protected: // result created session @@ -70,7 +76,15 @@ protected: // list of groups (lists of xml source id) std::list< SourceIdList > groups_sources_id_; - static void XMLToNode(tinyxml2::XMLElement *xml, Node &n); +}; + +struct SessionInformation { + std::string description; + FrameBufferImage *thumbnail; + SessionInformation() { + description = ""; + thumbnail = nullptr; + } }; class SessionCreator : public SessionLoader { @@ -78,13 +92,15 @@ class SessionCreator : public SessionLoader { tinyxml2::XMLDocument xmlDoc_; void loadConfig(tinyxml2::XMLElement *viewsNode); + void loadNotes(tinyxml2::XMLElement *notesNode); + void loadSnapshots(tinyxml2::XMLElement *snapshotNode); public: SessionCreator(int recursion = 0); void load(const std::string& filename); - static std::string info(const std::string& filename); + static SessionInformation info(const std::string& filename); }; #endif // SESSIONCREATOR_H diff --git a/SessionSource.cpp b/SessionSource.cpp index a9c8ec9..a33755b 100644 --- a/SessionSource.cpp +++ b/SessionSource.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include "SessionSource.h" @@ -17,7 +18,7 @@ #include "Mixer.h" -SessionSource::SessionSource() : Source(), failed_(false) +SessionSource::SessionSource(uint64_t id) : Source(id), failed_(false) { session_ = new Session; } @@ -37,8 +38,8 @@ Session *SessionSource::detach() // work on a new session session_ = new Session; - // make disabled - initialized_ = false; + // un-ready + ready_ = false; // ask to delete me failed_ = true; @@ -90,7 +91,7 @@ void SessionSource::update(float dt) } -SessionFileSource::SessionFileSource() : SessionSource(), path_("") +SessionFileSource::SessionFileSource(uint64_t id) : SessionSource(id), path_(""), initialized_(false), wait_for_sources_(false) { // specific node for transition view groups_[View::TRANSITION]->visible_ = false; @@ -124,7 +125,6 @@ SessionFileSource::SessionFileSource() : SessionSource(), path_("") symbol_ = new Symbol(Symbol::SESSION, glm::vec3(0.75f, 0.75f, 0.01f)); symbol_->scale_.y = 1.5f; - wait_for_sources_ = false; } void SessionFileSource::load(const std::string &p, uint recursion) @@ -148,6 +148,9 @@ void SessionFileSource::load(const std::string &p, uint recursion) sessionLoader_ = std::async(std::launch::async, Session::load, path_, recursion); Log::Notify("Opening %s", p.c_str()); } + + // will be ready after init and one frame rendered + ready_ = false; } void SessionFileSource::init() @@ -163,26 +166,18 @@ void SessionFileSource::init() } else { - session_->update(dt_); - if (wait_for_sources_) { // force update of of all sources active_ = true; touch(); - // check that every source is ready.. - bool ready = true; - for (SourceList::iterator iter = session_->begin(); iter != session_->end(); ++iter) - { - // interrupt if any source is NOT ready - if ( !(*iter)->ready() ){ - ready = false; - break; - } - } + // update to draw framebuffer + session_->update(dt_); + // if all sources are ready, done with initialization! - if (ready) { + auto unintitializedsource = std::find_if_not(session_->begin(), session_->end(), Source::isInitialized); + if (unintitializedsource == session_->end()) { // done init wait_for_sources_ = false; initialized_ = true; @@ -223,7 +218,20 @@ void SessionFileSource::init() overlays_[View::TRANSITION]->detach(loader); delete loader; // deep update to reorder - View::need_deep_update_++; + ++View::need_deep_update_; + } +} + +void SessionFileSource::render() +{ + if ( !initialized_ ) + init(); + else { + // render the media player into frame buffer + renderbuffer_->begin(); + texturesurface_->draw(glm::identity(), renderbuffer_->projection()); + renderbuffer_->end(); + ready_ = true; } } @@ -235,7 +243,7 @@ void SessionFileSource::accept(Visitor& v) } -SessionGroupSource::SessionGroupSource() : SessionSource(), resolution_(glm::vec3(0.f)) +SessionGroupSource::SessionGroupSource(uint64_t id) : SessionSource(id), resolution_(glm::vec3(0.f)) { // // redo frame for layers view // frames_[View::LAYER]->clear(); @@ -287,10 +295,9 @@ void SessionGroupSource::init() attach(renderbuffer); // deep update to reorder - View::need_deep_update_++; + ++View::need_deep_update_; // done init - initialized_ = true; Log::Info("Source Group (%d x %d).", int(renderbuffer->resolution().x), int(renderbuffer->resolution().y) ); } } @@ -316,19 +323,16 @@ void SessionGroupSource::accept(Visitor& v) v.visit(*this); } - - -RenderSource::RenderSource() : Source(), session_(nullptr) +RenderSource::RenderSource(uint64_t id) : Source(id), session_(nullptr) { // set symbol symbol_ = new Symbol(Symbol::RENDER, glm::vec3(0.75f, 0.75f, 0.01f)); symbol_->scale_.y = 1.5f; } - bool RenderSource::failed() const { - if (initialized_ && session_!=nullptr) + if ( mode_ > Source::UNINITIALIZED && session_!=nullptr ) return renderbuffer_->resolution() != session_->frame()->resolution(); return false; @@ -358,10 +362,9 @@ void RenderSource::init() attach(renderbuffer); // deep update to reorder - View::need_deep_update_++; + ++View::need_deep_update_; // done init - initialized_ = true; Log::Info("Source Render linked to session (%d x %d).", int(fb->resolution().x), int(fb->resolution().y) ); } } @@ -369,7 +372,7 @@ void RenderSource::init() glm::vec3 RenderSource::resolution() const { - if (initialized_) + if (mode_ > Source::UNINITIALIZED) return renderbuffer_->resolution(); else if (session_ && session_->frame()) return session_->frame()->resolution(); diff --git a/SessionSource.h b/SessionSource.h index 7863bad..dc7e65f 100644 --- a/SessionSource.h +++ b/SessionSource.h @@ -8,7 +8,7 @@ class SessionSource : public Source { public: - SessionSource(); + SessionSource(uint64_t id = 0); virtual ~SessionSource(); // implementation of source API @@ -29,10 +29,11 @@ protected: class SessionFileSource : public SessionSource { public: - SessionFileSource(); + SessionFileSource(uint64_t id = 0); // implementation of source API void accept (Visitor& v) override; + void render() override; // SessionFile Source specific interface void load(const std::string &p = "", uint recursion = 0); @@ -45,14 +46,15 @@ protected: void init() override; std::string path_; - std::atomic wait_for_sources_; + bool initialized_; + bool wait_for_sources_; std::future sessionLoader_; }; class SessionGroupSource : public SessionSource { public: - SessionGroupSource(); + SessionGroupSource(uint64_t id = 0); // implementation of source API void accept (Visitor& v) override; @@ -75,7 +77,7 @@ protected: class RenderSource : public Source { public: - RenderSource(); + RenderSource(uint64_t id = 0); // implementation of source API bool failed () const override; diff --git a/SessionVisitor.cpp b/SessionVisitor.cpp index 2cd576f..f593c5b 100644 --- a/SessionVisitor.cpp +++ b/SessionVisitor.cpp @@ -11,11 +11,13 @@ #include "PatternSource.h" #include "DeviceSource.h" #include "NetworkSource.h" +#include "MultiFileSource.h" #include "ImageShader.h" #include "ImageProcessingShader.h" #include "MediaPlayer.h" #include "MixingGroup.h" #include "SystemToolkit.h" +#include "ActionManager.h" #include #include @@ -44,10 +46,17 @@ bool SessionVisitor::saveSession(const std::string& filename, Session *session) XMLElement *sessionNode = xmlDoc.NewElement("Session"); xmlDoc.InsertEndChild(sessionNode); SessionVisitor sv(&xmlDoc, sessionNode); - for (auto iter = session->begin(); iter != session->end(); iter++, sv.setRoot(sessionNode) ) + for (auto iter = session->begin(); iter != session->end(); ++iter, sv.setRoot(sessionNode) ) // source visitor (*iter)->accept(sv); + // get the thumbnail + FrameBufferImage *thumbnail = session->thumbnail(); + XMLElement *imageelement = SessionVisitor::ImageToXML(thumbnail, &xmlDoc); + if (imageelement) + sessionNode->InsertEndChild(imageelement); + delete thumbnail; + // 2. config of views XMLElement *views = xmlDoc.NewElement("Views"); xmlDoc.InsertEndChild(views); @@ -73,6 +82,35 @@ bool SessionVisitor::saveSession(const std::string& filename, Session *session) views->InsertEndChild(render); } + // 3. snapshots + XMLElement *snapshots = xmlDoc.NewElement("Snapshots"); + const XMLElement* N = session->snapshots()->xmlDoc_->FirstChildElement(); + for( ; N ; N=N->NextSiblingElement()) + snapshots->InsertEndChild( N->DeepClone( &xmlDoc )); + xmlDoc.InsertEndChild(snapshots); + + // 4. optional notes + XMLElement *notes = xmlDoc.NewElement("Notes"); + xmlDoc.InsertEndChild(notes); + for (auto nit = session->beginNotes(); nit != session->endNotes(); ++nit) { + XMLElement *note = xmlDoc.NewElement( "Note" ); + note->SetAttribute("large", (*nit).large ); + note->SetAttribute("stick", (*nit).stick ); + XMLElement *pos = xmlDoc.NewElement("pos"); + pos->InsertEndChild( XMLElementFromGLM(&xmlDoc, (*nit).pos) ); + note->InsertEndChild(pos); + XMLElement *size = xmlDoc.NewElement("size"); + size->InsertEndChild( XMLElementFromGLM(&xmlDoc, (*nit).size) ); + note->InsertEndChild(size); + XMLElement *content = xmlDoc.NewElement("text"); + XMLText *text = xmlDoc.NewText( (*nit).text.c_str() ); + content->InsertEndChild( text ); + note->InsertEndChild(content); + + notes->InsertEndChild(note); + } + + // save file to disk return ( XMLSaveDoc(&xmlDoc, filename) ); } @@ -90,7 +128,7 @@ SessionVisitor::SessionVisitor(tinyxml2::XMLDocument *doc, xmlDoc_ = doc; } -tinyxml2::XMLElement *SessionVisitor::NodeToXML(Node &n, tinyxml2::XMLDocument *doc) +XMLElement *SessionVisitor::NodeToXML(const Node &n, XMLDocument *doc) { XMLElement *newelement = doc->NewElement("Node"); newelement->SetAttribute("visible", n.visible_); @@ -115,6 +153,31 @@ tinyxml2::XMLElement *SessionVisitor::NodeToXML(Node &n, tinyxml2::XMLDocument * return newelement; } + +XMLElement *SessionVisitor::ImageToXML(const FrameBufferImage *img, XMLDocument *doc) +{ + XMLElement *imageelement = nullptr; + if (img != nullptr) { + // get the jpeg encoded buffer + FrameBufferImage::jpegBuffer jpgimg = img->getJpeg(); + if (jpgimg.buffer != nullptr) { + // fill the xml array with jpeg buffer + XMLElement *array = XMLElementEncodeArray(doc, jpgimg.buffer, jpgimg.len); + // free the buffer + free(jpgimg.buffer); + // if we could create the array + if (array) { + // create an Image node to store the mask image + imageelement = doc->NewElement("Image"); + imageelement->SetAttribute("width", img->width); + imageelement->SetAttribute("height", img->height); + imageelement->InsertEndChild(array); + } + } + } + return imageelement; +} + void SessionVisitor::visit(Node &n) { XMLElement *newelement = NodeToXML(n, xmlDoc_); @@ -151,7 +214,7 @@ void SessionVisitor::visit(Switch &n) if (recursive_) { // loop over members of the group XMLElement *group = xmlCurrent_; - for(uint i = 0; i < n.numChildren(); i++) { + for(uint i = 0; i < n.numChildren(); ++i) { n.child(i)->accept(*this); // revert to group as current xmlCurrent_ = group; @@ -225,7 +288,7 @@ void SessionVisitor::visit(MediaPlayer &n) // gaps in timeline XMLElement *gapselement = xmlDoc_->NewElement("Gaps"); TimeIntervalSet gaps = n.timeline()->gaps(); - for( auto it = gaps.begin(); it!= gaps.end(); it++) { + for( auto it = gaps.begin(); it!= gaps.end(); ++it) { XMLElement *g = xmlDoc_->NewElement("Interval"); g->SetAttribute("begin", (*it).begin); g->SetAttribute("end", (*it).end); @@ -419,24 +482,9 @@ void SessionVisitor::visit (Source& s) // if we are saving a pain mask if (s.maskShader()->mode == MaskShader::PAINT) { // get the mask previously stored - FrameBufferImage *img = s.getMask(); - if (img != nullptr) { - // get the jpeg encoded buffer - FrameBufferImage::jpegBuffer jpgimg = img->getJpeg(); - if (jpgimg.buffer != nullptr) { - // fill the xml array with jpeg buffer - XMLElement *array = XMLElementEncodeArray(xmlDoc_, jpgimg.buffer, jpgimg.len); - // free the buffer - free(jpgimg.buffer); - // if we could create the array - if (array) { - // create an Image node to store the mask image - XMLElement *imageelement = xmlDoc_->NewElement("Image"); - imageelement->InsertEndChild(array); - xmlCurrent_->InsertEndChild(imageelement); - } - } - } + XMLElement *imageelement = SessionVisitor::ImageToXML(s.getMask(), xmlDoc_); + if (imageelement) + xmlCurrent_->InsertEndChild(imageelement); } xmlCurrent_ = xmlDoc_->NewElement( "ImageProcessing" ); @@ -487,7 +535,7 @@ void SessionVisitor::visit (SessionGroupSource& s) XMLElement *sessionNode = xmlDoc_->NewElement("Session"); xmlCurrent_->InsertEndChild(sessionNode); - for (auto iter = se->begin(); iter != se->end(); iter++){ + for (auto iter = se->begin(); iter != se->end(); ++iter){ setRoot(sessionNode); (*iter)->accept(*this); } @@ -504,6 +552,7 @@ void SessionVisitor::visit (CloneSource& s) xmlCurrent_->SetAttribute("type", "CloneSource"); XMLElement *origin = xmlDoc_->NewElement("origin"); + origin->SetAttribute("id", s.origin()->id()); xmlCurrent_->InsertEndChild(origin); XMLText *text = xmlDoc_->NewText( s.origin()->name().c_str() ); origin->InsertEndChild( text ); @@ -512,11 +561,14 @@ void SessionVisitor::visit (CloneSource& s) void SessionVisitor::visit (PatternSource& s) { xmlCurrent_->SetAttribute("type", "PatternSource"); - xmlCurrent_->SetAttribute("pattern", s.pattern()->type() ); - XMLElement *resolution = xmlDoc_->NewElement("resolution"); - resolution->InsertEndChild( XMLElementFromGLM(xmlDoc_, s.pattern()->resolution() ) ); - xmlCurrent_->InsertEndChild(resolution); + if (s.pattern()) { + xmlCurrent_->SetAttribute("pattern", s.pattern()->type() ); + + XMLElement *resolution = xmlDoc_->NewElement("resolution"); + resolution->InsertEndChild( XMLElementFromGLM(xmlDoc_, s.pattern()->resolution() ) ); + xmlCurrent_->InsertEndChild(resolution); + } } void SessionVisitor::visit (DeviceSource& s) @@ -535,14 +587,36 @@ void SessionVisitor::visit (MixingGroup& g) { xmlCurrent_->SetAttribute("size", g.size()); - for (auto it = g.begin(); it != g.end(); it++) { + for (auto it = g.begin(); it != g.end(); ++it) { XMLElement *sour = xmlDoc_->NewElement("source"); sour->SetAttribute("id", (*it)->id()); xmlCurrent_->InsertEndChild(sour); } } -std::string SessionVisitor::getClipboard(SourceList list) +void SessionVisitor::visit (MultiFileSource& s) +{ + xmlCurrent_->SetAttribute("type", "MultiFileSource"); + + XMLElement *sequence = xmlDoc_->NewElement("Sequence"); + // play properties + sequence->SetAttribute("fps", s.framerate()); + sequence->SetAttribute("begin", s.begin()); + sequence->SetAttribute("end", s.end()); + sequence->SetAttribute("loop", s.loop()); + // file sequence description + sequence->SetAttribute("min", s.sequence().min); + sequence->SetAttribute("max", s.sequence().max); + sequence->SetAttribute("width", s.sequence().width); + sequence->SetAttribute("height", s.sequence().height); + sequence->SetAttribute("codec", s.sequence().codec.c_str()); + XMLText *location = xmlDoc_->NewText( s.sequence().location.c_str() ); + sequence->InsertEndChild( location ); + + xmlCurrent_->InsertEndChild(sequence); +} + +std::string SessionVisitor::getClipboard(const SourceList &list) { std::string x = ""; @@ -557,7 +631,7 @@ std::string SessionVisitor::getClipboard(SourceList list) // fill doc by visiting sources SourceList selection_clones_; SessionVisitor sv(&xmlDoc, selectionNode); - for (auto iter = list.begin(); iter != list.end(); iter++, sv.setRoot(selectionNode) ){ + for (auto iter = list.begin(); iter != list.end(); ++iter, sv.setRoot(selectionNode) ){ // start with clones CloneSource *clone = dynamic_cast(*iter); if (clone) @@ -566,7 +640,7 @@ std::string SessionVisitor::getClipboard(SourceList list) selection_clones_.push_back(*iter); } // add others in front - for (auto iter = selection_clones_.begin(); iter != selection_clones_.end(); iter++, sv.setRoot(selectionNode) ){ + for (auto iter = selection_clones_.begin(); iter != selection_clones_.end(); ++iter, sv.setRoot(selectionNode) ){ (*iter)->accept(sv); } @@ -579,7 +653,7 @@ std::string SessionVisitor::getClipboard(SourceList list) return x; } -std::string SessionVisitor::getClipboard(Source *s) +std::string SessionVisitor::getClipboard(Source * const s) { std::string x = ""; @@ -603,7 +677,7 @@ std::string SessionVisitor::getClipboard(Source *s) return x; } -std::string SessionVisitor::getClipboard(ImageProcessingShader *s) +std::string SessionVisitor::getClipboard(ImageProcessingShader * const s) { std::string x = ""; diff --git a/SessionVisitor.h b/SessionVisitor.h index e4653e1..35961e0 100644 --- a/SessionVisitor.h +++ b/SessionVisitor.h @@ -6,6 +6,7 @@ #include "SourceList.h" class Session; +class FrameBufferImage; class SessionVisitor : public Visitor { @@ -22,31 +23,31 @@ public: static bool saveSession(const std::string& filename, Session *session); - static std::string getClipboard(SourceList list); - static std::string getClipboard(Source *s); - static std::string getClipboard(ImageProcessingShader *s); + static std::string getClipboard(const SourceList &list); + static std::string getClipboard(Source * const s); + static std::string getClipboard(ImageProcessingShader * const s); // Elements of Scene - void visit(Scene& n) override; - void visit(Node& n) override; - void visit(Group& n) override; - void visit(Switch& n) override; - void visit(Primitive& n) override; - void visit(Surface&) override; - void visit(ImageSurface& n) override; - void visit(MediaSurface& n) override; - void visit(FrameBufferSurface&) override; - void visit(LineStrip& n) override; - void visit(LineSquare&) override; - void visit(Mesh& n) override; - void visit(Frame& n) override; + void visit (Scene& n) override; + void visit (Node& n) override; + void visit (Group& n) override; + void visit (Switch& n) override; + void visit (Primitive& n) override; + void visit (Surface&) override; + void visit (ImageSurface& n) override; + void visit (MediaSurface& n) override; + void visit (FrameBufferSurface&) override; + void visit (LineStrip& n) override; + void visit (LineSquare&) override; + void visit (Mesh& n) override; + void visit (Frame& n) override; // Elements with attributes - void visit(MediaPlayer& n) override; - void visit(Shader& n) override; - void visit(ImageShader& n) override; - void visit(MaskShader& n) override; - void visit(ImageProcessingShader& n) override; + void visit (MediaPlayer& n) override; + void visit (Shader& n) override; + void visit (ImageShader& n) override; + void visit (MaskShader& n) override; + void visit (ImageProcessingShader& n) override; // Sources void visit (Source& s) override; @@ -59,9 +60,10 @@ public: void visit (DeviceSource& s) override; void visit (NetworkSource& s) override; void visit (MixingGroup& s) override; + void visit (MultiFileSource& s) override; -protected: - static tinyxml2::XMLElement *NodeToXML(Node &n, tinyxml2::XMLDocument *doc); + static tinyxml2::XMLElement *NodeToXML(const Node &n, tinyxml2::XMLDocument *doc); + static tinyxml2::XMLElement *ImageToXML(const FrameBufferImage *img, tinyxml2::XMLDocument *doc); }; #endif // XMLVISITOR_H diff --git a/Settings.cpp b/Settings.cpp index 57ec707..cfc9dd8 100644 --- a/Settings.cpp +++ b/Settings.cpp @@ -63,11 +63,12 @@ void Settings::Save() XMLElement *applicationNode = xmlDoc.NewElement( "Application" ); applicationNode->SetAttribute("scale", application.scale); applicationNode->SetAttribute("accent_color", application.accent_color); - applicationNode->SetAttribute("pannel_stick", application.pannel_stick); applicationNode->SetAttribute("smooth_transition", application.smooth_transition); + applicationNode->SetAttribute("smooth_snapshot", application.smooth_snapshot); applicationNode->SetAttribute("smooth_cursor", application.smooth_cursor); applicationNode->SetAttribute("action_history_follow_view", application.action_history_follow_view); applicationNode->SetAttribute("accept_connections", application.accept_connections); + applicationNode->SetAttribute("pannel_history_mode", application.pannel_history_mode); pRoot->InsertEndChild(applicationNode); // Widgets @@ -175,7 +176,7 @@ void Settings::Save() recentsession->SetAttribute("autosave", application.recentSessions.save_on_exit); recentsession->SetAttribute("valid", application.recentSessions.front_is_valid); for(auto it = application.recentSessions.filenames.begin(); - it != application.recentSessions.filenames.end(); it++) { + it != application.recentSessions.filenames.end(); ++it) { XMLElement *fileNode = xmlDoc.NewElement("path"); XMLText *text = xmlDoc.NewText( (*it).c_str() ); fileNode->InsertEndChild( text ); @@ -185,7 +186,7 @@ void Settings::Save() XMLElement *recentfolder = xmlDoc.NewElement( "Folder" ); for(auto it = application.recentFolders.filenames.begin(); - it != application.recentFolders.filenames.end(); it++) { + it != application.recentFolders.filenames.end(); ++it) { XMLElement *fileNode = xmlDoc.NewElement("path"); XMLText *text = xmlDoc.NewText( (*it).c_str() ); fileNode->InsertEndChild( text ); @@ -196,7 +197,7 @@ void Settings::Save() XMLElement *recentmedia = xmlDoc.NewElement( "Import" ); recentmedia->SetAttribute("path", application.recentImport.path.c_str()); for(auto it = application.recentImport.filenames.begin(); - it != application.recentImport.filenames.end(); it++) { + it != application.recentImport.filenames.end(); ++it) { XMLElement *fileNode = xmlDoc.NewElement("path"); XMLText *text = xmlDoc.NewText( (*it).c_str() ); fileNode->InsertEndChild( text ); @@ -254,11 +255,12 @@ void Settings::Load() if (applicationNode != nullptr) { applicationNode->QueryFloatAttribute("scale", &application.scale); applicationNode->QueryIntAttribute("accent_color", &application.accent_color); - applicationNode->QueryBoolAttribute("pannel_stick", &application.pannel_stick); applicationNode->QueryBoolAttribute("smooth_transition", &application.smooth_transition); + applicationNode->QueryBoolAttribute("smooth_snapshot", &application.smooth_snapshot); applicationNode->QueryBoolAttribute("smooth_cursor", &application.smooth_cursor); applicationNode->QueryBoolAttribute("action_history_follow_view", &application.action_history_follow_view); applicationNode->QueryBoolAttribute("accept_connections", &application.accept_connections); + applicationNode->QueryIntAttribute("pannel_history_mode", &application.pannel_history_mode); } // Widgets @@ -453,6 +455,39 @@ void Settings::Load() } +void Settings::History::push(const string &filename) +{ + if (filename.empty()) { + front_is_valid = false; + return; + } + filenames.remove(filename); + filenames.push_front(filename); + if (filenames.size() > MAX_RECENT_HISTORY) + filenames.pop_back(); + front_is_valid = true; + changed = true; +} + +void Settings::History::remove(const std::string &filename) +{ + if (filename.empty()) + return; + if (filenames.front() == filename) + front_is_valid = false; + filenames.remove(filename); + changed = true; +} + +void Settings::History::validate() +{ + for (auto fit = filenames.begin(); fit != filenames.end();) { + if ( SystemToolkit::file_exists( *fit )) + ++fit; + else + fit = filenames.erase(fit); + } +} void Settings::Lock() { diff --git a/Settings.h b/Settings.h index 253a586..874d256 100644 --- a/Settings.h +++ b/Settings.h @@ -93,26 +93,9 @@ struct History save_on_exit = false; changed = false; } - void push(const std::string &filename) { - if (filename.empty()) { - front_is_valid = false; - return; - } - filenames.remove(filename); - filenames.push_front(filename); - if (filenames.size() > MAX_RECENT_HISTORY) - filenames.pop_back(); - front_is_valid = true; - changed = true; - } - void remove(const std::string &filename) { - if (filename.empty()) - return; - if (filenames.front() == filename) - front_is_valid = false; - filenames.remove(filename); - changed = true; - } + void push(const std::string &filename); + void remove(const std::string &filename); + void validate(); }; struct TransitionConfig @@ -178,14 +161,15 @@ struct Application // Global settings Application interface float scale; int accent_color; - bool pannel_stick; + bool smooth_snapshot; bool smooth_transition; bool smooth_cursor; bool action_history_follow_view; + int pannel_history_mode; + // connection settings bool accept_connections; -// std::map instance_names; // Settings of widgets WidgetsConfig widget; @@ -221,11 +205,12 @@ struct Application Application() : fresh_start(false), instance_id(0), name(APP_NAME), executable(APP_NAME) { scale = 1.f; accent_color = 0; - pannel_stick = false; - smooth_transition = true; + smooth_transition = false; + smooth_snapshot = false; smooth_cursor = false; action_history_follow_view = false; accept_connections = false; + pannel_history_mode = 0; current_view = 1; current_workspace= 1; brush = glm::vec3(0.5f, 0.1f, 0.f); diff --git a/Shader.cpp b/Shader.cpp index 482d74d..18ccb6a 100644 --- a/Shader.cpp +++ b/Shader.cpp @@ -14,7 +14,7 @@ #include #include -#include "GlmToolkit.h" +#include "BaseToolkit.h" #include #define GLM_ENABLE_EXPERIMENTAL #include @@ -203,14 +203,14 @@ bool Shader::force_blending_opacity = false; Shader::Shader() : blending(BLEND_OPACITY) { // create unique id - id_ = GlmToolkit::uniqueId(); + id_ = BaseToolkit::uniqueId(); program_ = &simpleShadingProgram; reset(); } -void Shader::operator = (const Shader &S ) +void Shader::copy(Shader const& S) { color = S.color; blending = S.blending; diff --git a/Shader.h b/Shader.h index 4ae850c..8b18b41 100644 --- a/Shader.h +++ b/Shader.h @@ -50,8 +50,7 @@ public: virtual void use(); virtual void reset(); virtual void accept(Visitor& v); - - void operator = (const Shader &D ); + void copy(Shader const& S); glm::mat4 projection; glm::mat4 modelview; diff --git a/Source.cpp b/Source.cpp index bd2238b..de56205 100644 --- a/Source.cpp +++ b/Source.cpp @@ -11,16 +11,97 @@ #include "SearchVisitor.h" #include "ImageShader.h" #include "ImageProcessingShader.h" +#include "BaseToolkit.h" #include "SystemToolkit.h" #include "Log.h" #include "MixingGroup.h" #include "Source.h" -Source::Source() : initialized_(false), symbol_(nullptr), active_(true), locked_(false), need_update_(true), workspace_(STAGE) +SourceCore::SourceCore() +{ + // default nodes + groups_[View::RENDERING] = new Group; + groups_[View::RENDERING]->visible_ = false; + groups_[View::MIXING] = new Group; + groups_[View::MIXING]->visible_ = false; + groups_[View::GEOMETRY] = new Group; + groups_[View::GEOMETRY]->visible_ = false; + groups_[View::LAYER] = new Group; + groups_[View::LAYER]->visible_ = false; + groups_[View::TEXTURE] = new Group; + groups_[View::TEXTURE]->visible_ = false; + groups_[View::TRANSITION] = new Group; + // temp node + stored_status_ = new Group; + + // filtered image shader (with texturing and processing) for rendering + processingshader_ = new ImageProcessingShader; + // default rendering with image processing disabled + renderingshader_ = static_cast(new ImageShader); +} + +SourceCore::SourceCore(SourceCore const& other) : SourceCore() +{ + copy(other); +} + +SourceCore::~SourceCore() +{ + // all groups and their children are deleted in the scene + // this deletes renderingshader_ (and all source-attached nodes + // e.g. rendersurface_, overlays, blendingshader_, etc.) + delete groups_[View::RENDERING]; + delete groups_[View::MIXING]; + delete groups_[View::GEOMETRY]; + delete groups_[View::LAYER]; + delete groups_[View::TEXTURE]; + delete groups_[View::TRANSITION]; + delete stored_status_; + + // don't forget that the processing shader + // could be created but not used and not deleted above + if ( renderingshader_ != processingshader_ ) + delete processingshader_; + + groups_.clear(); +} + +void SourceCore::copy(SourceCore const& other) +{ + // copy groups properties +// groups_[View::RENDERING]->copyTransform( other.group(View::RENDERING) ); + groups_[View::MIXING]->copyTransform( other.group(View::MIXING) ); + groups_[View::GEOMETRY]->copyTransform( other.group(View::GEOMETRY) ); + groups_[View::LAYER]->copyTransform( other.group(View::LAYER) ); + groups_[View::TEXTURE]->copyTransform( other.group(View::TEXTURE) ); + groups_[View::TRANSITION]->copyTransform( other.group(View::TRANSITION) ); + stored_status_->copyTransform( other.stored_status_ ); + + // copy shader properties + processingshader_->copy(*other.processingshader_); + renderingshader_->copy(*other.renderingshader_); +} + +void SourceCore::store (View::Mode m) +{ + stored_status_->copyTransform(groups_[m]); +} + +SourceCore& SourceCore::operator= (SourceCore const& other) +{ + if (this != &other) // no self assignment + copy(other); + return *this; +} + + +Source::Source(uint64_t id) : SourceCore(), id_(id), ready_(false), symbol_(nullptr), + active_(true), locked_(false), need_update_(true), dt_(0), workspace_(STAGE) { // create unique id - id_ = GlmToolkit::uniqueId(); + if (id_ == 0) + id_ = BaseToolkit::uniqueId(); sprintf(initials_, "__"); name_ = "Source"; @@ -28,13 +109,7 @@ Source::Source() : initialized_(false), symbol_(nullptr), active_(true), locked_ // create groups and overlays for each view - // default rendering node - groups_[View::RENDERING] = new Group; - groups_[View::RENDERING]->visible_ = false; - // default mixing nodes - groups_[View::MIXING] = new Group; - groups_[View::MIXING]->visible_ = false; groups_[View::MIXING]->scale_ = glm::vec3(0.15f, 0.15f, 1.f); groups_[View::MIXING]->translation_ = glm::vec3(DEFAULT_MIXING_TRANSLATION, 0.f); @@ -69,9 +144,6 @@ Source::Source() : initialized_(false), symbol_(nullptr), active_(true), locked_ groups_[View::MIXING]->attach(overlay_mixinggroup_); // default geometry nodes - groups_[View::GEOMETRY] = new Group; - groups_[View::GEOMETRY]->visible_ = false; - frames_[View::GEOMETRY] = new Switch; frame = new Frame(Frame::SHARP, Frame::THIN, Frame::NONE); frame->translation_.z = 0.1; @@ -122,8 +194,6 @@ Source::Source() : initialized_(false), symbol_(nullptr), active_(true), locked_ groups_[View::GEOMETRY]->attach(overlays_[View::GEOMETRY]); // default layer nodes - groups_[View::LAYER] = new Group; - groups_[View::LAYER]->visible_ = false; groups_[View::LAYER]->translation_.z = -1.f; frames_[View::LAYER] = new Switch; @@ -143,9 +213,6 @@ Source::Source() : initialized_(false), symbol_(nullptr), active_(true), locked_ groups_[View::LAYER]->attach(overlays_[View::LAYER]); // default appearance node - groups_[View::TEXTURE] = new Group; - groups_[View::TEXTURE]->visible_ = false; - frames_[View::TEXTURE] = new Switch; frame = new Frame(Frame::SHARP, Frame::THIN, Frame::NONE); frame->translation_.z = 0.1; @@ -186,9 +253,6 @@ Source::Source() : initialized_(false), symbol_(nullptr), active_(true), locked_ overlays_[View::TEXTURE]->attach(handles_[View::TEXTURE][Handles::MENU]); groups_[View::TEXTURE]->attach(overlays_[View::TEXTURE]); - // empty transition node - groups_[View::TRANSITION] = new Group; - // locker switch button : locked / unlocked icons locker_ = new Switch; lock_ = new Handles(Handles::LOCKED); @@ -196,20 +260,12 @@ Source::Source() : initialized_(false), symbol_(nullptr), active_(true), locked_ unlock_ = new Handles(Handles::UNLOCKED); locker_->attach(unlock_); - // create objects - stored_status_ = new Group; - // simple image shader (with texturing) for blending blendingshader_ = new ImageShader; // mask produced by dedicated shader maskshader_ = new MaskShader; masksurface_ = new Surface(maskshader_); - // filtered image shader (with texturing and processing) for rendering - processingshader_ = new ImageProcessingShader; - // default rendering with image processing enabled - renderingshader_ = static_cast(processingshader_); - // for drawing in mixing view mixingshader_ = new ImageShader; mixingshader_->stipple = 1.0; @@ -238,12 +294,11 @@ Source::~Source() links_.front()->disconnect(); // inform clones that they lost their origin - for (auto it = clones_.begin(); it != clones_.end(); it++) + for (auto it = clones_.begin(); it != clones_.end(); ++it) (*it)->detach(); clones_.clear(); // delete objects - delete stored_status_; if (renderbuffer_) delete renderbuffer_; if (maskbuffer_) @@ -253,30 +308,17 @@ Source::~Source() if (masksurface_) delete masksurface_; // deletes maskshader_ - // all groups and their children are deleted in the scene - // this includes rendersurface_, overlays, blendingshader_ and rendershader_ - delete groups_[View::RENDERING]; - delete groups_[View::MIXING]; - delete groups_[View::GEOMETRY]; - delete groups_[View::LAYER]; - delete groups_[View::TEXTURE]; - delete groups_[View::TRANSITION]; - - groups_.clear(); - frames_.clear(); - overlays_.clear(); - - // don't forget that the processing shader - // could be created but not used - if ( renderingshader_ != processingshader_ ) - delete processingshader_; - delete texturesurface_; + + overlays_.clear(); + frames_.clear(); + handles_.clear(); } void Source::setName (const std::string &name) { - name_ = SystemToolkit::transliterate(name); + if (!name.empty()) + name_ = BaseToolkit::transliterate(name); initials_[0] = std::toupper( name_.front(), std::locale("C") ); initials_[1] = std::toupper( name_.back(), std::locale("C") ); @@ -296,18 +338,18 @@ void Source::setMode(Source::Mode m) { // make visible on first time if ( mode_ == Source::UNINITIALIZED ) { - for (auto g = groups_.begin(); g != groups_.end(); g++) + for (auto g = groups_.begin(); g != groups_.end(); ++g) (*g).second->visible_ = true; } // choose frame 0 if visible, 1 if selected uint index_frame = m == Source::VISIBLE ? 0 : 1; - for (auto f = frames_.begin(); f != frames_.end(); f++) + for (auto f = frames_.begin(); f != frames_.end(); ++f) (*f).second->setActive(index_frame); // show overlay if current bool current = m >= Source::CURRENT; - for (auto o = overlays_.begin(); o != overlays_.end(); o++) + for (auto o = overlays_.begin(); o != overlays_.end(); ++o) (*o).second->visible_ = (current && !locked_); // the lock icon @@ -372,13 +414,14 @@ bool Source::imageProcessingEnabled() void Source::render() { - if (!initialized_) + if ( renderbuffer_ == nullptr ) init(); else { // render the view into frame buffer renderbuffer_->begin(); texturesurface_->draw(glm::identity(), renderbuffer_->projection()); renderbuffer_->end(); + ready_ = true; } } @@ -459,7 +502,7 @@ void Source::setActive (bool on) active_ = on; // do not disactivate if a clone depends on it - for(auto clone = clones_.begin(); clone != clones_.end(); clone++) { + for(auto clone = clones_.begin(); clone != clones_.end(); ++clone) { if ( (*clone)->active() ) active_ = true; } @@ -665,7 +708,7 @@ void Source::update(float dt) FrameBuffer *Source::frame() const { - if (initialized_ && renderbuffer_) + if ( mode_ > Source::UNINITIALIZED && renderbuffer_) { return renderbuffer_; } @@ -738,13 +781,13 @@ bool Source::hasNode::operator()(const Source* elem) const // general case: traverse tree of all Groups recursively using a SearchVisitor SearchVisitor sv(_n); // search in groups for all views - for (auto g = elem->groups_.begin(); g != elem->groups_.end(); g++) { + for (auto g = elem->groups_.begin(); g != elem->groups_.end(); ++g) { (*g).second->accept(sv); if (sv.found()) return true; } // search in overlays for all views - for (auto g = elem->overlays_.begin(); g != elem->overlays_.end(); g++) { + for (auto g = elem->overlays_.begin(); g != elem->overlays_.end(); ++g) { (*g).second->accept(sv); if (sv.found()) return true; @@ -762,9 +805,9 @@ void Source::clearMixingGroup() } -CloneSource *Source::clone() +CloneSource *Source::clone(uint64_t id) { - CloneSource *s = new CloneSource(this); + CloneSource *s = new CloneSource(this, id); clones_.push_back(s); @@ -772,8 +815,9 @@ CloneSource *Source::clone() } -CloneSource::CloneSource(Source *origin) : Source(), origin_(origin) +CloneSource::CloneSource(Source *origin, uint64_t id) : Source(id), origin_(origin) { + name_ = origin->name(); // set symbol symbol_ = new Symbol(Symbol::CLONE, glm::vec3(0.75f, 0.75f, 0.01f)); symbol_->scale_.y = 1.5f; @@ -785,18 +829,18 @@ CloneSource::~CloneSource() origin_->clones_.remove(this); } -CloneSource *CloneSource::clone() +CloneSource *CloneSource::clone(uint64_t id) { // do not clone a clone : clone the original instead if (origin_) - return origin_->clone(); + return origin_->clone(id); else return nullptr; } void CloneSource::init() { - if (origin_ && origin_->ready()) { + if (origin_ && origin_->mode_ > Source::UNINITIALIZED) { // get the texture index from framebuffer of view, apply it to the surface texturesurface_->setTextureIndex( origin_->texture() ); @@ -808,10 +852,9 @@ void CloneSource::init() attach(renderbuffer); // deep update to reorder - View::need_deep_update_++; + ++View::need_deep_update_; // done init - initialized_ = true; Log::Info("Source %s cloning source %s.", name().c_str(), origin_->name().c_str() ); } } @@ -824,14 +867,14 @@ void CloneSource::setActive (bool on) groups_[View::GEOMETRY]->visible_ = active_; groups_[View::LAYER]->visible_ = active_; - if (initialized_ && origin_ != nullptr) + if ( mode_ > Source::UNINITIALIZED && origin_ != nullptr) origin_->touch(); } uint CloneSource::texture() const { - if (initialized_ && origin_ != nullptr) + if (origin_ != nullptr) return origin_->texture(); else return Resource::getTextureBlack(); diff --git a/Source.h b/Source.h index 6c033c3..44e9b62 100644 --- a/Source.h +++ b/Source.h @@ -23,7 +23,39 @@ class MixingGroup; typedef std::list CloneList; -class Source +class SourceCore +{ +public: + SourceCore(); + SourceCore(SourceCore const&); + SourceCore& operator= (SourceCore const& other); + virtual ~SourceCore(); + + // get handle on the nodes used to manipulate the source in a view + inline Group *group (View::Mode m) const { return groups_.at(m); } + inline Node *groupNode (View::Mode m) const { return static_cast(groups_.at(m)); } + void store (View::Mode m); + + // a Source has a shader used to render in fbo + inline Shader *renderingShader () const { return renderingshader_; } + // a Source always has an image processing shader + inline ImageProcessingShader *processingShader () const { return processingshader_; } + + void copy(SourceCore const& other); + +protected: + // nodes + std::map groups_; + // temporary copy for interaction + Group *stored_status_; + // image processing shaders + ImageProcessingShader *processingshader_; + // pointer to the currently attached shader + // (will be processingshader_ if image processing is enabled) + Shader *renderingshader_; +}; + +class Source : public SourceCore { friend class SourceLink; friend class CloneSource; @@ -38,7 +70,10 @@ class Source public: // create a source and add it to the list // only subclasses of sources can actually be instanciated - Source (); + Source (uint64_t id = 0); + // non assignable class + Source(Source const&) = delete; + Source& operator=(Source const&) = delete; virtual ~Source (); // Get unique id @@ -50,7 +85,7 @@ public: inline const char *initials () const { return initials_; } // cloning mechanism - virtual CloneSource *clone (); + virtual CloneSource *clone (uint64_t id = 0); // Display mode typedef enum { @@ -62,16 +97,9 @@ public: Mode mode () const; void setMode (Mode m); - // get handle on the nodes used to manipulate the source in a view - inline Group *group (View::Mode m) const { return groups_.at(m); } - inline Node *groupNode (View::Mode m) const { return static_cast(groups_.at(m)); } - // tests if a given node is part of the source bool contains (Node *node) const; - // the rendering shader always have an image processing shader - inline ImageProcessingShader *processingShader () const { return processingshader_; } - // the image processing shader can be enabled or disabled // (NB: when disabled, a simple ImageShader is applied) void setImageProcessingEnabled (bool on); @@ -80,9 +108,6 @@ public: // a Source has a shader to control mixing effects inline ImageShader *blendingShader () const { return blendingshader_; } - // a Source has a shader used to render in fbo - inline Shader *renderingShader () const { return renderingshader_; } - // every Source has a frame buffer from the renderbuffer virtual FrameBuffer *frame () const; @@ -93,7 +118,7 @@ public: inline void touch () { need_update_ = true; } // informs if its ready (i.e. initialized) - inline bool ready () const { return initialized_; } + inline bool ready () const { return ready_; } // a Source shall be updated before displayed (Mixing, Geometry and Layer) virtual void update (float dt); @@ -206,12 +231,9 @@ protected: char initials_[3]; uint64_t id_; - // every Source shall be initialized on first draw - bool initialized_; + // every Source shall be initialized to be ready after first draw virtual void init() = 0; - - // nodes - std::map groups_; + bool ready_; // render() fills in the renderbuffer at every frame // NB: rendershader_ is applied at render() @@ -223,12 +245,6 @@ protected: FrameBufferSurface *rendersurface_; FrameBufferSurface *mixingsurface_; - // image processing shaders - ImageProcessingShader *processingshader_; - // pointer to the currently attached shader - // (will be processingshader_ if image processing is enabled) - Shader *renderingshader_; - // blendingshader provides mixing controls ImageShader *blendingshader_; ImageShader *mixingshader_; @@ -259,7 +275,6 @@ protected: bool locked_; bool need_update_; float dt_; - Group *stored_status_; Workspace workspace_; // clones @@ -288,7 +303,7 @@ public: bool failed() const override { return origin_ == nullptr; } void accept (Visitor& v) override; - CloneSource *clone() override; + CloneSource *clone(uint64_t id = 0) override; inline void detach() { origin_ = nullptr; } inline Source *origin() const { return origin_; } @@ -296,7 +311,7 @@ public: protected: // only Source class can create new CloneSource via clone(); - CloneSource(Source *origin); + CloneSource(Source *origin, uint64_t id = 0); void init() override; Source *origin_; diff --git a/SourceList.cpp b/SourceList.cpp index 978cb66..14130ee 100644 --- a/SourceList.cpp +++ b/SourceList.cpp @@ -49,7 +49,7 @@ SourceIdList ids (const SourceList &list) { SourceIdList idlist; - for( auto sit = list.begin(); sit != list.end(); sit++) + for( auto sit = list.begin(); sit != list.end(); ++sit) idlist.push_back( (*sit)->id() ); // make sure no duplicate @@ -67,7 +67,7 @@ SourceListCompare compare (const SourceList &first, const SourceList &second) // a new test list: start with the second list and remove all commons with first list SourceList test = second; - for (auto it = first.begin(); it != first.end(); it++){ + for (auto it = first.begin(); it != first.end(); ++it){ test.remove(*it); } @@ -100,12 +100,12 @@ SourceList intersect (const SourceList &first, const SourceList &second) // take second list and remove all elements also in first list // -> builds the list of what remains in second list SourceList l1 = second; - for (auto it = first.begin(); it != first.end(); it++) + for (auto it = first.begin(); it != first.end(); ++it) l1.remove(*it); // take second list and remove all elements in the remainer list // -> builds the list of what is in second list and was part of the first list SourceList l2 = second; - for (auto it = l1.begin(); it != l1.end(); it++) + for (auto it = l1.begin(); it != l1.end(); ++it) l2.remove(*it); return l2; } @@ -114,13 +114,15 @@ SourceList intersect (const SourceList &first, const SourceList &second) SourceList join (const SourceList &first, const SourceList &second) { SourceList l = second; - for (auto it = first.begin(); it != first.end(); it++) + for (auto it = first.begin(); it != first.end(); ++it) l.push_back(*it); l.unique(); return l; } + + void SourceLink::connect(uint64_t id, Session *se) { if (connected()) @@ -136,8 +138,9 @@ void SourceLink::connect(Source *s) disconnect(); target_ = s; - id_ = s->id(); target_->links_.push_back(this); + + id_ = s->id(); // TODO veryfy circular dependency recursively ? } @@ -145,9 +148,9 @@ void SourceLink::disconnect() { if (target_) target_->links_.remove(this); + target_ = nullptr; id_ = 0; - target_ = nullptr; host_ = nullptr; } @@ -181,3 +184,21 @@ Source *SourceLink::source() return target_; } + +SourceList validate (const SourceLinkList &list) +{ + SourceList sourcelist; + + for( auto sit = list.begin(); sit != list.end(); ++sit) { + + Source *s = (*sit)->source(); + if (s) + sourcelist.push_back( s ); + + } + + // make sure no duplicate + sourcelist.unique(); + + return sourcelist; +} diff --git a/SourceList.h b/SourceList.h index 1c27b60..160b547 100644 --- a/SourceList.h +++ b/SourceList.h @@ -5,9 +5,11 @@ #include class Source; +class SourceCore; class Session; typedef std::list SourceList; +typedef std::list SourceCoreList; SourceList depth_sorted (const SourceList &list); SourceList mixing_sorted (const SourceList &list, glm::vec2 center = glm::vec2(0.f, 0.f)); @@ -42,9 +44,11 @@ public: protected: Session *host_; - Source *target_; + Source *target_; uint64_t id_; }; + typedef std::list SourceLinkList; +SourceList validate (const SourceLinkList &list); #endif // SOURCELIST_H diff --git a/Stream.cpp b/Stream.cpp index b94c76b..0be660e 100644 --- a/Stream.cpp +++ b/Stream.cpp @@ -12,7 +12,7 @@ using namespace std; #include "Resource.h" #include "Visitor.h" #include "SystemToolkit.h" -#include "GlmToolkit.h" +#include "BaseToolkit.h" #include "Stream.h" @@ -24,19 +24,19 @@ using namespace std; Stream::Stream() { // create unique id - id_ = GlmToolkit::uniqueId(); + id_ = BaseToolkit::uniqueId(); description_ = "undefined"; pipeline_ = nullptr; + opened_ = false; + enabled_ = true; + desired_state_ = GST_STATE_PAUSED; width_ = -1; height_ = -1; single_frame_ = false; live_ = false; - ready_ = false; failed_ = false; - enabled_ = true; - desired_state_ = GST_STATE_PAUSED; // start index in frame_ stack write_index_ = 0; @@ -50,11 +50,20 @@ Stream::Stream() // OpenGL texture textureindex_ = 0; + textureinitialized_ = false; } Stream::~Stream() { close(); + + // cleanup opengl texture + if (textureindex_) + glDeleteTextures(1, &textureindex_); + + // cleanup picture buffer + if (pbo_[0]) + glDeleteBuffers(2, pbo_); } void Stream::accept(Visitor& v) { @@ -70,8 +79,11 @@ guint Stream::texture() const } -void Stream::open(const std::string &gstreamer_description, int w, int h) +void Stream::open(const std::string &gstreamer_description, guint w, guint h) { + if (w != width_ || h != height_ ) + textureinitialized_ = false; + // set gstreamer pipeline source description_ = gstreamer_description; width_ = w; @@ -81,6 +93,7 @@ void Stream::open(const std::string &gstreamer_description, int w, int h) if (isOpen()) close(); + // open the stream execute_open(); } @@ -93,7 +106,7 @@ std::string Stream::description() const void Stream::execute_open() { // reset - ready_ = false; + opened_ = false; // Add custom app sink to the gstreamer pipeline string description = description_; @@ -109,6 +122,7 @@ void Stream::execute_open() return; } g_object_set(G_OBJECT(pipeline_), "name", std::to_string(id_).c_str(), NULL); + gst_pipeline_set_auto_flush_bus( GST_PIPELINE(pipeline_), true); // GstCaps *caps = gst_static_caps_get (&frame_render_caps); string capstring = "video/x-raw,format=RGBA,width="+ std::to_string(width_) + @@ -138,14 +152,13 @@ void Stream::execute_open() #ifdef USE_GST_APPSINK_CALLBACKS // set the callbacks GstAppSinkCallbacks callbacks; + callbacks.new_preroll = callback_new_preroll; if (single_frame_) { - callbacks.new_preroll = callback_new_preroll; callbacks.eos = NULL; callbacks.new_sample = NULL; Log::Info("Stream %s contains a single frame", std::to_string(id_).c_str()); } else { - callbacks.new_preroll = callback_new_preroll; callbacks.eos = callback_end_of_stream; callbacks.new_sample = callback_new_sample; } @@ -176,18 +189,18 @@ void Stream::execute_open() // instruct the sink to send samples synched in time if not live source gst_base_sink_set_sync (GST_BASE_SINK(sink), !live_); - // all good - Log::Info("Stream %s Opened '%s' (%d x %d)", std::to_string(id_).c_str(), description.c_str(), width_, height_); - ready_ = true; - // done with refs gst_object_unref (sink); gst_caps_unref (caps); + + // all good + Log::Info("Stream %s Opened '%s' (%d x %d)", std::to_string(id_).c_str(), description.c_str(), width_, height_); + opened_ = true; } bool Stream::isOpen() const { - return ready_; + return opened_; } bool Stream::failed() const @@ -197,22 +210,23 @@ bool Stream::failed() const void Stream::Frame::unmap() { - if ( full ) { + if ( full ) gst_video_frame_unmap(&vframe); - full = false; - } + full = false; } void Stream::close() { // not openned? - if (!ready_) { + if (!opened_) { // nothing else to change return; } // un-ready - ready_ = false; + opened_ = false; + single_frame_ = false; + live_ = false; // clean up GST if (pipeline_ != nullptr) { @@ -229,10 +243,9 @@ void Stream::close() gst_object_unref (pipeline_); pipeline_ = nullptr; } - desired_state_ = GST_STATE_PAUSED; // cleanup eventual remaining frame memory - for(guint i = 0; i < N_FRAME; i++){ + for(guint i = 0; i < N_FRAME; ++i){ frame_[i].access.lock(); frame_[i].unmap(); frame_[i].access.unlock(); @@ -240,15 +253,6 @@ void Stream::close() write_index_ = 0; last_index_ = 0; - // cleanup opengl texture - if (textureindex_) - glDeleteTextures(1, &textureindex_); - textureindex_ = 0; - - // cleanup picture buffer - if (pbo_[0]) - glDeleteBuffers(2, pbo_); - pbo_size_ = 0; } @@ -269,7 +273,7 @@ float Stream::aspectRatio() const void Stream::enable(bool on) { - if ( !ready_ || pipeline_ == nullptr) + if ( !opened_ || pipeline_ == nullptr) return; if ( enabled_ != on ) { @@ -312,7 +316,7 @@ bool Stream::live() const void Stream::play(bool on) { // ignore if disabled, and cannot play an image - if (!enabled_) + if (!enabled_ || single_frame_) return; // request state @@ -353,6 +357,10 @@ void Stream::play(bool on) bool Stream::isPlaying(bool testpipeline) const { + // image cannot play + if (single_frame_) + return false; + // if not ready yet, answer with requested state if ( !testpipeline || pipeline_ == nullptr || !enabled_) return desired_state_ == GST_STATE_PLAYING; @@ -368,6 +376,8 @@ bool Stream::isPlaying(bool testpipeline) const void Stream::init_texture(guint index) { glActiveTexture(GL_TEXTURE0); + if (textureindex_) + glDeleteTextures(1, &textureindex_); glGenTextures(1, &textureindex_); glBindTexture(GL_TEXTURE_2D, textureindex_); glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, width_, height_); @@ -378,53 +388,58 @@ void Stream::init_texture(guint index) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - // set pbo image size - pbo_size_ = height_ * width_ * 4; + if (!single_frame_) { - // create pixel buffer objects, - if (pbo_[0]) - glDeleteBuffers(2, pbo_); - glGenBuffers(2, pbo_); + // set pbo image size + pbo_size_ = height_ * width_ * 4; - for(int i = 0; i < 2; i++ ) { - // create 2 PBOs - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo_[i]); - // glBufferDataARB with NULL pointer reserves only memory space. - glBufferData(GL_PIXEL_UNPACK_BUFFER, pbo_size_, 0, GL_STREAM_DRAW); - // fill in with reset picture - GLubyte* ptr = (GLubyte*) glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY); - if (ptr) { - // update data directly on the mapped buffer - memmove(ptr, frame_[index].vframe.data[0], pbo_size_); - // release pointer to mapping buffer - glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); - } - else { - // did not work, disable PBO + // create pixel buffer objects, + if (pbo_[0]) glDeleteBuffers(2, pbo_); - pbo_[0] = pbo_[1] = 0; - pbo_size_ = 0; - break; + glGenBuffers(2, pbo_); + + for(int i = 0; i < 2; i++ ) { + // create 2 PBOs + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo_[i]); + // glBufferDataARB with NULL pointer reserves only memory space. + glBufferData(GL_PIXEL_UNPACK_BUFFER, pbo_size_, 0, GL_STREAM_DRAW); + // fill in with reset picture + GLubyte* ptr = (GLubyte*) glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY); + if (ptr) { + // update data directly on the mapped buffer + memmove(ptr, frame_[index].vframe.data[0], pbo_size_); + // release pointer to mapping buffer + glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); + } + else { + // did not work, disable PBO + glDeleteBuffers(2, pbo_); + pbo_[0] = pbo_[1] = 0; + pbo_size_ = 0; + break; + } + } - } - - // should be good to go, wrap it up - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); - pbo_index_ = 0; - pbo_next_index_ = 1; + // should be good to go, wrap it up + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + pbo_index_ = 0; + pbo_next_index_ = 1; #ifdef STREAM_DEBUG Log::Info("Stream %s Use Pixel Buffer Object texturing.", std::to_string(id_).c_str()); #endif + } + glBindTexture(GL_TEXTURE_2D, 0); + textureinitialized_ = true; } void Stream::fill_texture(guint index) { // is this the first frame ? - if (textureindex_ < 1) + if ( !textureinitialized_ || textureindex_ < 1) { // initialize texture init_texture(index); @@ -466,6 +481,7 @@ void Stream::fill_texture(guint index) glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width_, height_, GL_RGBA, GL_UNSIGNED_BYTE, frame_[index].vframe.data[0]); } + glBindTexture(GL_TEXTURE_2D, 0); } } @@ -476,12 +492,14 @@ void Stream::update() return; // not ready yet - if (!ready_) + if (!opened_){ + // wait next frame to display return; + } -// // prevent unnecessary updates: disabled or already filled image -// if (!enabled_) -// return; + // prevent unnecessary updates: already filled image + if (single_frame_ && textureindex_>0) + return; // local variables before trying to update guint read_index = 0; @@ -627,7 +645,7 @@ bool Stream::fill_frame(GstBuffer *buf, FrameStatus status) void Stream::callback_end_of_stream (GstAppSink *, gpointer p) { Stream *m = static_cast(p); - if (m && m->ready_) { + if (m && m->opened_) { m->fill_frame(NULL, Stream::EOS); } } @@ -643,7 +661,7 @@ GstFlowReturn Stream::callback_new_preroll (GstAppSink *sink, gpointer p) if (sample != NULL) { // send frames to media player only if ready Stream *m = static_cast(p); - if (m && m->ready_) { + if (m && m->opened_) { // get buffer from sample GstBuffer *buf = gst_sample_get_buffer (sample); @@ -677,7 +695,7 @@ GstFlowReturn Stream::callback_new_sample (GstAppSink *sink, gpointer p) // send frames to media player only if ready Stream *m = static_cast(p); - if (m && m->ready_) { + if (m && m->opened_) { // get buffer from sample (valid until sample is released) GstBuffer *buf = gst_sample_get_buffer (sample) ; @@ -710,7 +728,7 @@ void Stream::TimeCounter::tic () GstClockTime dt = t - last_time; // one more frame since last time - nbFrames++; + ++nbFrames; // calculate instantaneous framerate // Exponential moving averate with previous framerate to filter jitter (50/50) diff --git a/Stream.h b/Stream.h index aa8f78a..f4c6a4b 100644 --- a/Stream.h +++ b/Stream.h @@ -33,7 +33,7 @@ public: /** * Open a media using gstreamer pipeline keyword * */ - void open(const std::string &gstreamer_description, int w = 1024, int h = 576); + void open(const std::string &gstreamer_description, guint w = 1024, guint h = 576); /** * Get description string * */ @@ -49,7 +49,7 @@ public: /** * Close the Media * */ - void close(); + virtual void close(); /** * Update texture with latest frame * Must be called in rendering update loop @@ -129,7 +129,7 @@ protected: GstState desired_state_; GstElement *pipeline_; GstVideoInfo v_frame_video_info_; - std::atomic ready_; + std::atomic opened_; std::atomic failed_; bool enabled_; @@ -185,6 +185,7 @@ protected: virtual void execute_open(); // gst frame filling + bool textureinitialized_; void init_texture(guint index); void fill_texture(guint index); bool fill_frame(GstBuffer *buf, FrameStatus status); diff --git a/StreamSource.cpp b/StreamSource.cpp index 611fd4b..0a50533 100644 --- a/StreamSource.cpp +++ b/StreamSource.cpp @@ -26,8 +26,12 @@ void GenericStreamSource::setDescription(const std::string &desc) { Log::Notify("Creating Source with Stream description '%s'", desc.c_str()); + // open gstreamer stream_->open(desc); stream_->play(true); + + // will be ready after init and one frame rendered + ready_ = false; } void GenericStreamSource::accept(Visitor& v) @@ -37,7 +41,7 @@ void GenericStreamSource::accept(Visitor& v) v.visit(*this); } -StreamSource::StreamSource() : Source(), stream_(nullptr) +StreamSource::StreamSource(uint64_t id) : Source(id), stream_(nullptr) { } @@ -81,14 +85,13 @@ void StreamSource::init() // set the renderbuffer of the source and attach rendering nodes attach(renderbuffer); - // deep update to reorder - View::need_deep_update_++; - // force update of activation mode active_ = true; + // deep update to reorder + ++View::need_deep_update_; + // done init - initialized_ = true; Log::Info("Source '%s' linked to Stream %s", name().c_str(), std::to_string(stream_->id()).c_str()); } } diff --git a/StreamSource.h b/StreamSource.h index 77417ea..41b944d 100644 --- a/StreamSource.h +++ b/StreamSource.h @@ -24,7 +24,7 @@ class StreamSource: public Source { public: - StreamSource(); + StreamSource(uint64_t id = 0); virtual ~StreamSource(); // implementation of source API diff --git a/Streamer.h b/Streamer.h index 46b3d57..c24a5f2 100644 --- a/Streamer.h +++ b/Streamer.h @@ -29,8 +29,8 @@ class Streaming // Private Constructor Streaming(); - Streaming(Streaming const& copy); // Not Implemented - Streaming& operator=(Streaming const& copy); // Not Implemented + Streaming(Streaming const& copy) = delete; + Streaming& operator=(Streaming const& copy) = delete; public: diff --git a/SystemToolkit.cpp b/SystemToolkit.cpp index 2dd83f2..c021f5c 100644 --- a/SystemToolkit.cpp +++ b/SystemToolkit.cpp @@ -1,15 +1,11 @@ #include #include #include -#include #include +#include #include -#include #include - -#include -#include -#include +#include using namespace std; @@ -88,45 +84,10 @@ long SystemToolkit::memory_max_usage() { struct rusage r_usage; getrusage(RUSAGE_SELF,&r_usage); - return r_usage.ru_maxrss; + return 1024 * r_usage.ru_maxrss; // return r_usage.ru_isrss; } -string SystemToolkit::byte_to_string(long b) -{ - double numbytes = static_cast(b); - ostringstream oss; - - std::list list = {" Bytes", " KB", " MB", " GB", " TB"}; - std::list::iterator i = list.begin(); - - while(numbytes >= 1024.0 && i != list.end()) - { - ++i; - numbytes /= 1024.0; - } - oss << std::fixed << std::setprecision(2) << numbytes << *i; - return oss.str(); -} - -string SystemToolkit::bits_to_string(long b) -{ - double numbytes = static_cast(b); - ostringstream oss; - - std::list list = {" bit", " Kbit", " Mbit", " Gbit", " Tbit"}; - std::list::iterator i = list.begin(); - - while(numbytes >= 1000.0 && i != list.end()) - { - ++i; - numbytes /= 1000.0; - } - oss << std::fixed << std::setprecision(2) << numbytes << *i; - return oss.str(); -} - - string SystemToolkit::date_time_string() { @@ -171,19 +132,12 @@ string SystemToolkit::path_filename(const string& path) return path.substr(0, path.find_last_of(PATH_SEP) + 1); } -string SystemToolkit::trunc_filename(const string& path, int lenght) -{ - string trunc = path; - int l = path.size(); - if ( l > lenght ) { - trunc = string("...") + path.substr( l - lenght + 3 ); - } - return trunc; -} - string SystemToolkit::extension_filename(const string& filename) { - string ext = filename.substr(filename.find_last_of(".") + 1); + string ext; + auto loc = filename.find_last_of("."); + if (loc != string::npos) + ext = filename.substr( loc + 1 ); return ext; } @@ -323,7 +277,7 @@ std::string SystemToolkit::path_directory(const std::string& path) return directorypath; } -list SystemToolkit::list_directory(const string& path, const string& filter) +list SystemToolkit::list_directory(const string& path, const list& extensions) { list ls; @@ -335,13 +289,16 @@ list SystemToolkit::list_directory(const string& path, const string& fil if ( ent->d_type == DT_REG) { string filename = string(ent->d_name); - if ( extension_filename(filename) == filter) + string ext = extension_filename(filename); + if ( extensions.empty() || find(extensions.cbegin(), extensions.cend(), ext) != extensions.cend()) ls.push_back( full_filename(path, filename) ); } } closedir (dir); } + ls.sort(); + return ls; } @@ -376,27 +333,3 @@ void SystemToolkit::execute(const string& command) // std::thread (SystemToolkit::execute, // "gst-launch-1.0 udpsrc port=5000 ! application/x-rtp,encoding-name=JPEG,payload=26 ! rtpjpegdepay ! jpegdec ! autovideosink").detach();; - -// Using ICU transliteration : -// https://unicode-org.github.io/icu/userguide/transforms/general/#icu-transliterators - -std::string SystemToolkit::transliterate(std::string input) -{ - auto ucs = icu::UnicodeString::fromUTF8(input); - - UErrorCode status = U_ZERO_ERROR; - icu::Transliterator *firstTrans = icu::Transliterator::createInstance( - "any-NFKD ; [:Nonspacing Mark:] Remove; NFKC; Latin", UTRANS_FORWARD, status); - firstTrans->transliterate(ucs); - delete firstTrans; - - icu::Transliterator *secondTrans = icu::Transliterator::createInstance( - "any-NFKD ; [:Nonspacing Mark:] Remove; [@!#$*%~] Remove; NFKC", UTRANS_FORWARD, status); - secondTrans->transliterate(ucs); - delete secondTrans; - - std::ostringstream output; - output << ucs; - - return output.str(); -} diff --git a/SystemToolkit.h b/SystemToolkit.h index a9ee644..1b6f08e 100644 --- a/SystemToolkit.h +++ b/SystemToolkit.h @@ -42,9 +42,6 @@ namespace SystemToolkit // extract the path of a filename from a full URI (e.g. file:://home/me/toto.mpg -> file:://home/me/) std::string path_filename(const std::string& path); - // Truncate a full filename to display the right part (e.g. file:://home/me/toto.mpg -> ...ome/me/toto.mpg) - std::string trunc_filename(const std::string& path, int lenght); - // extract the extension of a filename std::string extension_filename(const std::string& filename); @@ -52,7 +49,7 @@ namespace SystemToolkit std::string path_directory(const std::string& path); // list all files of a directory mathing the given filter extension (if any) - std::list list_directory(const std::string& path, const std::string& filter = ""); + std::list list_directory(const std::string& path, const std::list &extensions); // true of file exists bool file_exists(const std::string& path); @@ -69,18 +66,11 @@ namespace SystemToolkit // try to execute a command void execute(const std::string& command); - // return memory resident set size used (in bytes) + // return memory used (in bytes) long memory_usage(); + // return maximum memory resident set size used (in bytes) long memory_max_usage(); - // get a string to display memory size with unit KB, MB, GB, TB - std::string byte_to_string(long b); - - // get a string to display bit size with unit Kbit, MBit, Gbit, Tbit - std::string bits_to_string(long b); - - // get a transliteration to Latin of any string - std::string transliterate(std::string input); } #endif // SYSTEMTOOLKIT_H diff --git a/TextureView.cpp b/TextureView.cpp index 99d8b79..6822555 100644 --- a/TextureView.cpp +++ b/TextureView.cpp @@ -14,6 +14,7 @@ #include "defines.h" #include "Mixer.h" +#include "Source.h" #include "Settings.h" #include "Resource.h" #include "PickingVisitor.h" diff --git a/TextureView.h b/TextureView.h index 9ac7933..5dedc49 100644 --- a/TextureView.h +++ b/TextureView.h @@ -8,6 +8,9 @@ class TextureView : public View { public: TextureView(); + // non assignable class + TextureView(TextureView const&) = delete; + TextureView& operator=(TextureView const&) = delete; void draw () override; void update (float dt) override; diff --git a/TransitionView.h b/TransitionView.h index 34a1596..661c298 100644 --- a/TransitionView.h +++ b/TransitionView.h @@ -7,6 +7,9 @@ class TransitionView : public View { public: TransitionView(); + // non assignable class + TransitionView(TransitionView const&) = delete; + TransitionView& operator=(TransitionView const&) = delete; void draw () override; void update (float dt) override; diff --git a/UpdateCallback.cpp b/UpdateCallback.cpp index c14c188..44e2953 100644 --- a/UpdateCallback.cpp +++ b/UpdateCallback.cpp @@ -11,6 +11,18 @@ UpdateCallback::UpdateCallback() : enabled_(true), finished_(false) } +CopyCallback::CopyCallback(Node *target) : UpdateCallback(), target_(target) +{ + +} + +void CopyCallback::update(Node *n, float dt) +{ + n->copyTransform(target_); + finished_ = true; +} + + MoveToCallback::MoveToCallback(glm::vec3 target, float duration) : UpdateCallback(), duration_(duration), progress_(0.f), initialized_(false), target_(target) { diff --git a/UpdateCallback.h b/UpdateCallback.h index 2c2fb1b..0584ac3 100644 --- a/UpdateCallback.h +++ b/UpdateCallback.h @@ -1,7 +1,7 @@ #ifndef UPDATECALLBACK_H #define UPDATECALLBACK_H -#include "GlmToolkit.h" +#include class Node; @@ -22,6 +22,15 @@ protected: bool finished_; }; +class CopyCallback : public UpdateCallback +{ + Node *target_; + +public: + CopyCallback(Node *target); + void update(Node *n, float dt); +}; + class MoveToCallback : public UpdateCallback { float duration_; diff --git a/UserInterfaceManager.cpp b/UserInterfaceManager.cpp index 4534934..d7c0935 100644 --- a/UserInterfaceManager.cpp +++ b/UserInterfaceManager.cpp @@ -32,12 +32,11 @@ using namespace std; #define STB_IMAGE_WRITE_IMPLEMENTATION #include -#include "UserInterfaceManager.h" - #include "defines.h" #include "Log.h" #include "SystemToolkit.h" #include "DialogToolkit.h" +#include "BaseToolkit.h" #include "GlmToolkit.h" #include "GstToolkit.h" #include "ImGuiToolkit.h" @@ -68,10 +67,16 @@ using namespace std; #include "TextEditor.h" static TextEditor editor; +#include "UserInterfaceManager.h" +#define PLOT_ARRAY_SIZE 180 +#define LABEL_AUTO_MEDIA_PLAYER "Active source" + // utility functions void ShowAboutGStreamer(bool* p_open); void ShowAboutOpengl(bool* p_open); void ShowSandbox(bool* p_open); +void SetMouseCursor(ImVec2 mousepos, View::Cursor c = View::Cursor()); +void SetNextWindowVisible(ImVec2 pos, ImVec2 size, float margin = 180.f); // static objects for multithreaded file dialog const std::chrono::milliseconds timeout = std::chrono::milliseconds(4); @@ -96,7 +101,7 @@ UserInterface::UserInterface() show_opengl_about = false; show_view_navigator = 0; target_view_navigator = 1; - currentTextEdit = ""; + currentTextEdit.clear(); screenshot_step = 0; // keep hold on frame grabbers @@ -253,6 +258,9 @@ void UserInterface::handleKeyboard() FrameGrabbing::manager().add(fg); } } + else if (ImGui::IsKeyPressed( GLFW_KEY_Y )) { + Action::manager().snapshot( "Snap" ); + } else if (ImGui::IsKeyPressed( GLFW_KEY_Z )) { if (shift_modifier_active) Action::manager().redo(); @@ -279,6 +287,10 @@ void UserInterface::handleKeyboard() else if (ImGui::IsKeyPressed( GLFW_KEY_F ) && shift_modifier_active) { Rendering::manager().mainWindow().toggleFullscreen(); } + else if (ImGui::IsKeyPressed( GLFW_KEY_N ) && shift_modifier_active) { + Mixer::manager().session()->addNote(); + } + } // No CTRL modifier else { @@ -375,30 +387,6 @@ void UserInterface::handleKeyboard() } - -void setMouseCursor(ImVec2 mousepos, View::Cursor c = View::Cursor()) -{ - // Hack if GLFW does not have all cursors, ask IMGUI to redraw cursor -#if GLFW_HAS_NEW_CURSORS == 0 - ImGui::GetIO().MouseDrawCursor = (c.type > 0); // only redraw non-arrow cursor -#endif - ImGui::SetMouseCursor(c.type); - - if ( !c.info.empty()) { - float d = 0.5f * ImGui::GetFrameHeight() ; - ImVec2 window_pos = ImVec2( mousepos.x - d, mousepos.y - d ); - ImGui::SetNextWindowPos(window_pos, ImGuiCond_Always); - ImGui::SetNextWindowBgAlpha(0.75f); // Transparent background - if (ImGui::Begin("MouseInfoContext", NULL, ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav)) - { - ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO); - ImGui::Text(" %s", c.info.c_str()); - ImGui::PopFont(); - ImGui::End(); - } - } -} - void UserInterface::handleMouse() { @@ -427,7 +415,7 @@ void UserInterface::handleMouse() { View::Cursor c = Mixer::manager().view()->over(mousepos); if (c.type > 0) - setMouseCursor(io.MousePos, c); + SetMouseCursor(io.MousePos, c); } // if not on any window @@ -449,7 +437,7 @@ void UserInterface::handleMouse() { // right mouse drag => drag current view View::Cursor c = Mixer::manager().view()->drag( mouseclic[ImGuiMouseButton_Right], mousepos); - setMouseCursor(io.MousePos, c); + SetMouseCursor(io.MousePos, c); } else if ( ImGui::IsMouseDown(ImGuiMouseButton_Right)) { @@ -574,20 +562,20 @@ void UserInterface::handleMouse() { if (!shift_modifier_active) { // grab others from selection - for (auto it = Mixer::selection().begin(); it != Mixer::selection().end(); it++) { + for (auto it = Mixer::selection().begin(); it != Mixer::selection().end(); ++it) { if ( *it != current ) Mixer::manager().view()->grab(*it, mouseclic[ImGuiMouseButton_Left], mouse_smooth, picked); } } // grab current sources View::Cursor c = Mixer::manager().view()->grab(current, mouseclic[ImGuiMouseButton_Left], mouse_smooth, picked); - setMouseCursor(io.MousePos, c); + SetMouseCursor(io.MousePos, c); } // action on other (non-source) elements in the view else { View::Cursor c = Mixer::manager().view()->grab(nullptr, mouseclic[ImGuiMouseButton_Left], mouse_smooth, picked); - setMouseCursor(io.MousePos, c); + SetMouseCursor(io.MousePos, c); } } // Selection area @@ -619,7 +607,7 @@ void UserInterface::handleMouse() mousedown = false; picked = { nullptr, glm::vec2(0.f) }; Mixer::manager().view()->terminate(); - setMouseCursor(io.MousePos); + SetMouseCursor(io.MousePos); // special case of one single source in selection : make current after release if (Mixer::selection().size() == 1) @@ -627,7 +615,6 @@ void UserInterface::handleMouse() } } - void UserInterface::selectSaveFilename() { // launch file dialog to select a session filename to save @@ -740,7 +727,8 @@ void UserInterface::Render() if (Settings::application.widget.shader_editor) RenderShaderEditor(); if (Settings::application.widget.logs) - Log::ShowLogWindow(&Settings::application.widget.logs); + Log::ShowLogWindow(&Settings::application.widget.logs); + RenderNotes(); // dialogs if (show_view_navigator > 0) @@ -813,6 +801,13 @@ void UserInterface::showMenuEdit() Mixer::manager().view()->selectAll(); navigator.hidePannel(); } + ImGui::Separator(); + if ( ImGui::MenuItem( ICON_FA_UNDO " Undo", CTRL_MOD "Z") ) + Action::manager().undo(); + if ( ImGui::MenuItem( ICON_FA_REDO " Redo", CTRL_MOD "Shift+Z") ) + Action::manager().redo(); + if ( ImGui::MenuItem( ICON_FA_STAR "+ Snapshot", CTRL_MOD "Y") ) + Action::manager().snapshot( "Snap" ); } void UserInterface::showMenuFile() @@ -862,14 +857,6 @@ void UserInterface::showMenuFile() } - -ToolBox::ToolBox() -{ - show_demo_window = false; - show_icons_window = false; - show_sandbox = false; -} - void UserInterface::StartScreenshot() { screenshot_step = 1; @@ -909,143 +896,6 @@ void UserInterface::handleScreenshot() } } -#define PLOT_ARRAY_SIZE 180 - -void ToolBox::Render() -{ - static bool record_ = false; - static std::ofstream csv_file_; - - // first run - ImGui::SetNextWindowPos(ImVec2(40, 40), ImGuiCond_FirstUseEver); - ImGui::SetNextWindowSize(ImVec2(400, 300), ImGuiCond_FirstUseEver); - ImGui::SetNextWindowSizeConstraints(ImVec2(350, 300), ImVec2(FLT_MAX, FLT_MAX)); - if ( !ImGui::Begin(IMGUI_TITLE_TOOLBOX, &Settings::application.widget.toolbox, ImGuiWindowFlags_MenuBar) ) - { - ImGui::End(); - return; - } - - // Menu Bar - if (ImGui::BeginMenuBar()) - { - if (ImGui::BeginMenu("Render")) - { - if ( ImGui::MenuItem( ICON_FA_CAMERA_RETRO " Screenshot", "F12") ) - UserInterface::manager().StartScreenshot(); - - ImGui::EndMenu(); - } - if (ImGui::BeginMenu("Gui")) - { - ImGui::MenuItem("Sandbox", nullptr, &show_sandbox); - ImGui::MenuItem("Icons", nullptr, &show_icons_window); - ImGui::MenuItem("Demo ImGui", nullptr, &show_demo_window); - - ImGui::EndMenu(); - } - if (ImGui::BeginMenu("Stats")) - { - if (ImGui::MenuItem("Record", nullptr, &record_) ) - { - if ( record_ ) - csv_file_.open( SystemToolkit::home_path() + std::to_string(GlmToolkit::uniqueId()) + ".csv", std::ofstream::out | std::ofstream::app); - else - csv_file_.close(); - } - ImGui::EndMenu(); - } - ImGui::EndMenuBar(); - } - - // - // display histogram of update time and plot framerate - // - // keep array of 180 values, i.e. approx 3 seconds of recording - static float recorded_values[3][PLOT_ARRAY_SIZE] = {{}}; - static float recorded_sum[3] = { 0.f, 0.f, 0.f }; - static float recorded_bounds[3][2] = { {40.f, 65.f}, {0.f, 50.f}, {0.f, 50.f} }; - static float refresh_rate = -1.f; - static int values_index = 0; - float megabyte = static_cast( static_cast(SystemToolkit::memory_usage()) / 1048576.0 ); - - // init - if (refresh_rate < 0.f) { - - const GLFWvidmode* mode = glfwGetVideoMode(Rendering::manager().outputWindow().monitor()); - refresh_rate = float(mode->refreshRate); - if (Settings::application.render.vsync > 0) - refresh_rate /= Settings::application.render.vsync; - else - refresh_rate = 0.f; - recorded_bounds[0][0] = refresh_rate - 15.f; // min fps - recorded_bounds[0][1] = refresh_rate + 10.f; // max - - for(int i = 0; i io.DisplaySize.y - margin ){ - pos_target.y -= margin; - need_update = true; - } - if ( pos_target.y + size.y < margin ){ - pos_target.y += margin; - need_update = true; - } - if ( pos_target.x > io.DisplaySize.x - margin){ - pos_target.x -= margin; - need_update = true; - } - if ( pos_target.x + size.x aspectRatio(); ImGui::SetNextWindowSizeConstraints(ImVec2(300, 200), ImVec2(FLT_MAX, FLT_MAX), CustomConstraints::AspectRatio, &ar); - if ( !ImGui::Begin("Preview", &Settings::application.widget.preview, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse ) ) + if ( !ImGui::Begin("Preview", &Settings::application.widget.preview, + ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_MenuBar | + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse ) ) { ImGui::End(); return; @@ -1280,7 +1100,7 @@ void UserInterface::RenderPreview() ImGui::Combo("Path", &selected_path, name_path, 4); if (selected_path > 2) { if (recordFolderFileDialogs.empty()) { - recordFolderFileDialogs.emplace_back( std::async(std::launch::async, DialogToolkit::FolderDialog, Settings::application.record.path) ); + recordFolderFileDialogs.emplace_back( std::async(std::launch::async, DialogToolkit::openFolderDialog, Settings::application.record.path) ); fileDialogPending_ = true; } } @@ -1333,7 +1153,7 @@ void UserInterface::RenderPreview() if (ls.size()>0) { ImGui::Separator(); ImGui::MenuItem("Active streams", nullptr, false, false); - for (auto it = ls.begin(); it != ls.end(); it++) + for (auto it = ls.begin(); it != ls.end(); ++it) ImGui::Text(" %s", (*it).c_str() ); } @@ -1430,7 +1250,6 @@ void UserInterface::RenderPreview() #endif } - int UserInterface::RenderViewNavigator(int *shift) { // calculate potential target view index : @@ -1521,7 +1340,6 @@ int UserInterface::RenderViewNavigator(int *shift) return target_index; } - void UserInterface::showSourceEditor(Source *s) { MediaSource *ms = dynamic_cast(s); @@ -1548,8 +1366,546 @@ void UserInterface::showMediaPlayer(MediaPlayer *mp) mediacontrol.setMediaPlayer(mp); } -#define LABEL_AUTO_MEDIA_PLAYER "Active source" +void UserInterface::fillShaderEditor(const std::string &text) +{ + static bool initialized = false; + if (!initialized) { + auto lang = TextEditor::LanguageDefinition::GLSL(); + static const char* const keywords[] = { + "discard", "attribute", "varying", "uniform", "in", "out", "inout", "bvec2", "bvec3", "bvec4", "dvec2", + "dvec3", "dvec4", "ivec2", "ivec3", "ivec4", "uvec2", "uvec3", "uvec4", "vec2", "vec3", "vec4", "mat2", + "mat3", "mat4", "dmat2", "dmat3", "dmat4", "sampler1D", "sampler2D", "sampler3D", "samplerCUBE", "samplerbuffer", + "sampler1DArray", "sampler2DArray", "sampler1DShadow", "sampler2DShadow", "vec4", "vec4", "smooth", "flat", + "precise", "coherent", "uint", "struct", "switch", "unsigned", "void", "volatile", "while", "readonly" + }; + for (auto& k : keywords) + lang.mKeywords.insert(k); + + static const char* const identifiers[] = { + "radians", "degrees", "sin", "cos", "tan", "asin", "acos", "atan", "pow", "exp2", "log2", "sqrt", "inversesqrt", + "abs", "sign", "floor", "ceil", "fract", "mod", "min", "max", "clamp", "mix", "step", "smoothstep", "length", "distance", + "dot", "cross", "normalize", "ftransform", "faceforward", "reflect", "matrixcompmult", "lessThan", "lessThanEqual", + "greaterThan", "greaterThanEqual", "equal", "notEqual", "any", "all", "not", "texture1D", "texture1DProj", "texture1DLod", + "texture1DProjLod", "texture", "texture2D", "texture2DProj", "texture2DLod", "texture2DProjLod", "texture3D", + "texture3DProj", "texture3DLod", "texture3DProjLod", "textureCube", "textureCubeLod", "shadow1D", "shadow1DProj", + "shadow1DLod", "shadow1DProjLod", "shadow2D", "shadow2DProj", "shadow2DLod", "shadow2DProjLod", + "dFdx", "dFdy", "fwidth", "noise1", "noise2", "noise3", "noise4", "refract", "exp", "log", "mainImage", + }; + for (auto& k : identifiers) + { + TextEditor::Identifier id; + id.mDeclaration = "Added function"; + lang.mIdentifiers.insert(std::make_pair(std::string(k), id)); + } + // init editor + editor.SetLanguageDefinition(lang); + initialized = true; + } + + // remember text + currentTextEdit = text; + // fill editor + editor.SetText(currentTextEdit); +} + +void UserInterface::RenderShaderEditor() +{ + static bool show_statusbar = true; + + if ( !ImGui::Begin(IMGUI_TITLE_SHADEREDITOR, &Settings::application.widget.shader_editor, ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_MenuBar) ) + { + ImGui::End(); + return; + } + + ImGui::SetWindowSize(ImVec2(800, 600), ImGuiCond_FirstUseEver); + if (ImGui::BeginMenuBar()) + { + if (ImGui::BeginMenu("Edit")) + { + bool ro = editor.IsReadOnly(); + if (ImGui::MenuItem("Read-only mode", nullptr, &ro)) + editor.SetReadOnly(ro); + ImGui::Separator(); + + if (ImGui::MenuItem( ICON_FA_UNDO " Undo", CTRL_MOD "Z", nullptr, !ro && editor.CanUndo())) + editor.Undo(); + if (ImGui::MenuItem( ICON_FA_REDO " Redo", CTRL_MOD "Y", nullptr, !ro && editor.CanRedo())) + editor.Redo(); + + ImGui::Separator(); + + if (ImGui::MenuItem( ICON_FA_COPY " Copy", CTRL_MOD "C", nullptr, editor.HasSelection())) + editor.Copy(); + if (ImGui::MenuItem( ICON_FA_CUT " Cut", CTRL_MOD "X", nullptr, !ro && editor.HasSelection())) + editor.Cut(); + if (ImGui::MenuItem( ICON_FA_ERASER " Delete", "Del", nullptr, !ro && editor.HasSelection())) + editor.Delete(); + if (ImGui::MenuItem( ICON_FA_PASTE " Paste", CTRL_MOD "V", nullptr, !ro && ImGui::GetClipboardText() != nullptr)) + editor.Paste(); + + ImGui::Separator(); + + if (ImGui::MenuItem( "Select all", nullptr, nullptr)) + editor.SetSelection(TextEditor::Coordinates(), TextEditor::Coordinates(editor.GetTotalLines(), 0)); + + ImGui::EndMenu(); + } + + if (ImGui::BeginMenu("View")) + { + bool ws = editor.IsShowingWhitespaces(); + if (ImGui::MenuItem( ICON_FA_LONG_ARROW_ALT_RIGHT " Whitespace", nullptr, &ws)) + editor.SetShowWhitespaces(ws); + ImGui::MenuItem( ICON_FA_WINDOW_MAXIMIZE " Statusbar", nullptr, &show_statusbar); + ImGui::EndMenu(); + } + ImGui::EndMenuBar(); + } + + if (show_statusbar) { + auto cpos = editor.GetCursorPosition(); + ImGui::Text("%6d/%-6d %6d lines | %s | %s | %s ", cpos.mLine + 1, cpos.mColumn + 1, editor.GetTotalLines(), + editor.IsOverwrite() ? "Ovr" : "Ins", + editor.CanUndo() ? "*" : " ", + editor.GetLanguageDefinition().mName.c_str()); + } + + ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO); + editor.Render("ShaderEditor"); + ImGui::PopFont(); + + ImGui::End(); +} + +void UserInterface::RenderMetrics(bool *p_open, int* p_corner, int *p_mode) +{ + static guint64 start_time_1_ = gst_util_get_timestamp (); + static guint64 start_time_2_ = gst_util_get_timestamp (); + + if (!p_corner || !p_open) + return; + + const float DISTANCE = 10.0f; + int corner = *p_corner; + ImGuiIO& io = ImGui::GetIO(); + if (corner != -1) + { + ImVec2 window_pos = ImVec2((corner & 1) ? io.DisplaySize.x - DISTANCE : DISTANCE, (corner & 2) ? io.DisplaySize.y - DISTANCE : DISTANCE); + ImVec2 window_pos_pivot = ImVec2((corner & 1) ? 1.0f : 0.0f, (corner & 2) ? 1.0f : 0.0f); + ImGui::SetNextWindowPos(window_pos, ImGuiCond_Always, window_pos_pivot); + } + + ImGui::SetNextWindowBgAlpha(0.35f); // Transparent background + + if (ImGui::Begin("Metrics", NULL, (corner != -1 ? ImGuiWindowFlags_NoMove : 0) | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav)) + { + ImGui::SetNextItemWidth(200); + ImGui::Combo("##mode", p_mode, + ICON_FA_TACHOMETER_ALT " Performance\0" + ICON_FA_HOURGLASS_HALF " Timers\0" + ICON_FA_VECTOR_SQUARE " Source\0"); + + ImGui::SameLine(); + if (ImGuiToolkit::IconButton(5,8)) + ImGui::OpenPopup("metrics_menu"); + ImGui::Spacing(); + + if (*p_mode > 1) { + ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO); + Source *s = Mixer::manager().currentSource(); + if (s) { + float rightalign = -2.5f * ImGui::GetTextLineHeightWithSpacing(); + std::ostringstream info; + info << s->name() << ": "; + + float v = s->alpha(); + ImGui::SetNextItemWidth(rightalign); + if ( ImGui::DragFloat("Alpha", &v, 0.01f, 0.f, 1.f) ) + s->setAlpha(v); + if ( ImGui::IsItemDeactivatedAfterEdit() ) { + info << "Alpha " << std::fixed << std::setprecision(3) << v; + Action::manager().store(info.str()); + } + + Group *n = s->group(View::GEOMETRY); + float translation[2] = { n->translation_.x, n->translation_.y}; + ImGui::SetNextItemWidth(rightalign); + if ( ImGui::DragFloat2("Pos", translation, 0.01f, -MAX_SCALE, MAX_SCALE, "%.2f") ) { + n->translation_.x = translation[0]; + n->translation_.y = translation[1]; + s->touch(); + } + if ( ImGui::IsItemDeactivatedAfterEdit() ){ + info << "Position " << std::setprecision(3) << n->translation_.x << ", " << n->translation_.y; + Action::manager().store(info.str()); + } + float scale[2] = { n->scale_.x, n->scale_.y} ; + ImGui::SetNextItemWidth(rightalign); + if ( ImGui::DragFloat2("Scale", scale, 0.01f, -MAX_SCALE, MAX_SCALE, "%.2f") ) + { + n->scale_.x = CLAMP_SCALE(scale[0]); + n->scale_.y = CLAMP_SCALE(scale[1]); + s->touch(); + } + if ( ImGui::IsItemDeactivatedAfterEdit() ){ + info << "Scale " << std::setprecision(3) << n->scale_.x << " x " << n->scale_.y; + Action::manager().store(info.str()); + } + + ImGui::SetNextItemWidth(rightalign); + if ( ImGui::SliderAngle("Angle", &(n->rotation_.z), -180.f, 180.f) ) + s->touch(); + if ( ImGui::IsItemDeactivatedAfterEdit() ) { + info << "Angle " << std::setprecision(3) << n->rotation_.z * 180.f / M_PI; + Action::manager().store(info.str()); + } + } + else + ImGui::Text("No source selected"); + ImGui::PopFont(); + } + else if (*p_mode > 0) { + guint64 time_ = gst_util_get_timestamp (); + + ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE); + ImGui::Text("%s", GstToolkit::time_to_string(time_-start_time_1_, GstToolkit::TIME_STRING_FIXED).c_str()); + ImGui::PopFont(); + ImGui::SameLine(0, 10); + if (ImGuiToolkit::IconButton(12, 14)) + start_time_1_ = time_; // reset timer 1 + ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE); + ImGui::Text("%s", GstToolkit::time_to_string(time_-start_time_2_, GstToolkit::TIME_STRING_FIXED).c_str()); + ImGui::PopFont(); + ImGui::SameLine(0, 10); + if (ImGuiToolkit::IconButton(12, 14)) + start_time_2_ = time_; // reset timer 2 + + } + else { + ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO); + ImGui::Text("Window %.0f x %.0f", io.DisplaySize.x, io.DisplaySize.y); + // ImGui::Text("HiDPI (retina) %s", io.DisplayFramebufferScale.x > 1.f ? "on" : "off"); + ImGui::Text("Refresh %.1f FPS", io.Framerate); + ImGui::Text("Memory %s", BaseToolkit::byte_to_string( SystemToolkit::memory_usage()).c_str() ); + ImGui::PopFont(); + + } + + if (ImGui::BeginPopup("metrics_menu")) + { + if (ImGui::MenuItem( ICON_FA_ANGLE_UP " Top", NULL, corner == 1)) *p_corner = 1; + if (ImGui::MenuItem( ICON_FA_ANGLE_DOWN " Bottom", NULL, corner == 3)) *p_corner = 3; + if (ImGui::MenuItem( ICON_FA_EXPAND_ARROWS_ALT " Free position", NULL, corner == -1)) *p_corner = -1; + if (p_open && ImGui::MenuItem( ICON_FA_TIMES " Close")) *p_open = false; + ImGui::EndPopup(); + } + ImGui::End(); + } +} + +void UserInterface::RenderAbout(bool* p_open) +{ + ImGui::SetNextWindowPos(ImVec2(1000, 20), ImGuiCond_FirstUseEver); + if (!ImGui::Begin("About " APP_NAME APP_TITLE, p_open, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_AlwaysAutoResize)) + { + ImGui::End(); + return; + } + +#ifdef VIMIX_VERSION_MAJOR + ImGuiToolkit::PushFont(ImGuiToolkit::FONT_BOLD); + ImGui::Text("%s %d.%d.%d", APP_NAME, VIMIX_VERSION_MAJOR, VIMIX_VERSION_MINOR, VIMIX_VERSION_PATCH); + ImGui::PopFont(); +#else + ImGuiToolkit::PushFont(ImGuiToolkit::FONT_BOLD); + ImGui::Text("%s", APP_NAME); + ImGui::PopFont(); +#endif + + ImGui::Separator(); + ImGui::Text("vimix performs graphical mixing and blending of\nseveral movie clips and computer generated graphics,\nwith image processing effects in real-time."); + ImGui::Text("\nvimix is licensed under the GNU GPL version 3.\nCopyright 2019-2021 Bruno Herbelin."); + + ImGui::Spacing(); + ImGuiToolkit::ButtonOpenUrl("Visit vimix website", "https://brunoherbelin.github.io/vimix/", ImVec2(ImGui::GetContentRegionAvail().x, 0)); + + + ImGui::Spacing(); + ImGui::Text("\nvimix is built using the following libraries:"); + +// tinyfd_inputBox("tinyfd_query", NULL, NULL); +// ImGui::Text("- Tinyfiledialogs v%s mode '%s'", tinyfd_version, tinyfd_response); + + ImGui::Columns(3, "abouts"); + ImGui::Separator(); + + ImGui::Text("- Dear ImGui"); + ImGui::PushID("dearimguiabout"); + if ( ImGui::Button("More info", ImVec2(ImGui::GetContentRegionAvail().x, 0))) + show_imgui_about = true; + ImGui::PopID(); + + ImGui::NextColumn(); + + ImGui::Text("- GStreamer"); + ImGui::PushID("gstreamerabout"); + if ( ImGui::Button("More info", ImVec2(ImGui::GetContentRegionAvail().x, 0))) + show_gst_about = true; + ImGui::PopID(); + + ImGui::NextColumn(); + + ImGui::Text("- OpenGL"); + ImGui::PushID("openglabout"); + if ( ImGui::Button("More info", ImVec2(ImGui::GetContentRegionAvail().x, 0))) + show_opengl_about = true; + ImGui::PopID(); + + ImGui::Columns(1); + + + ImGui::End(); +} + +void UserInterface::showPannel(int id) +{ + if (id == NAV_MENU) + navigator.togglePannelMenu(); + else if (id == NAV_NEW) + navigator.togglePannelNew(); + else + navigator.showPannelSource(id); +} + +void UserInterface::RenderNotes() +{ + Session *se = Mixer::manager().session(); + if (se!=nullptr) { + + ImVec4 color = ImGui::GetStyle().Colors[ImGuiCol_ResizeGripHovered]; + // ImVec4 color = ImGui::GetStyle().Colors[ImGuiCol_CheckMark]; + color.w = 0.35f; + ImGui::PushStyleColor(ImGuiCol_WindowBg, color); + ImGui::PushStyleColor(ImGuiCol_TitleBg, color); + ImGui::PushStyleColor(ImGuiCol_TitleBgActive, color); + ImGui::PushStyleColor(ImGuiCol_TitleBgCollapsed, color); + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4()); + + for (auto note = se->beginNotes(); note != se->endNotes(); ) { + // detect close clic + bool close = false; + + if ( (*note).stick < 1 || (*note).stick == Settings::application.current_view) + { + // window + ImGui::SetNextWindowSizeConstraints(ImVec2(150, 150), ImVec2(500, 500)); + ImGui::SetNextWindowPos(ImVec2( (*note).pos.x, (*note).pos.y ), ImGuiCond_Once); + ImGui::SetNextWindowSize(ImVec2((*note).size.x, (*note).size.y), ImGuiCond_Once); + ImGui::SetNextWindowBgAlpha(color.w); // Transparent background + + // draw + if (ImGui::Begin((*note).label.c_str(), NULL, ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoFocusOnAppearing | + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoSavedSettings)) + { + ImVec2 size = ImGui::GetContentRegionAvail(); + ImVec2 pos = ImGui::GetCursorPos(); + // close & delete + if (ImGuiToolkit::IconButton(4,16)) close = true; + if (ImGui::IsItemHovered()) + ImGuiToolkit::ToolTip("Delete"); + + if (ImGui::IsWindowFocused()) { + // font size + pos.x = size.x - 2.f * ImGui::GetTextLineHeightWithSpacing(); + ImGui::SetCursorPos( pos ); + if (ImGuiToolkit::IconButton(1, 13) ) + (*note).large = !(*note).large ; + // stick to views icon + pos.x = size.x - ImGui::GetTextLineHeightWithSpacing() + 8.f; + ImGui::SetCursorPos( pos ); + bool s = (*note).stick > 0; + if (ImGuiToolkit::IconToggle(5, 2, 4, 2, &s) ) + (*note).stick = s ? Settings::application.current_view : 0; + } + + // Text area + size.y -= ImGui::GetTextLineHeightWithSpacing() + 2.f; + ImGuiToolkit::PushFont( (*note).large ? ImGuiToolkit::FONT_LARGE : ImGuiToolkit::FONT_MONO ); + ImGuiToolkit::InputTextMultiline("##notes", &(*note).text, size); + ImGui::PopFont(); + + // TODO smart detect when window moves + ImVec2 p = ImGui::GetWindowPos(); + (*note).pos = glm::vec2( p.x, p.y); + p = ImGui::GetWindowSize(); + (*note).size = glm::vec2( p.x, p.y); + + ImGui::End(); + } + } + // loop + if (close) + note = se->deleteNote(note); + else + ++note; + } + + + ImGui::PopStyleColor(5); + } + +} + +/// +/// TOOLBOX +/// +ToolBox::ToolBox() +{ + show_demo_window = false; + show_icons_window = false; + show_sandbox = false; +} + +void ToolBox::Render() +{ + static bool record_ = false; + static std::ofstream csv_file_; + + // first run + ImGui::SetNextWindowPos(ImVec2(40, 40), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowSize(ImVec2(400, 300), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowSizeConstraints(ImVec2(350, 300), ImVec2(FLT_MAX, FLT_MAX)); + if ( !ImGui::Begin(IMGUI_TITLE_TOOLBOX, &Settings::application.widget.toolbox, ImGuiWindowFlags_MenuBar) ) + { + ImGui::End(); + return; + } + + // Menu Bar + if (ImGui::BeginMenuBar()) + { + if (ImGui::BeginMenu("Render")) + { + if ( ImGui::MenuItem( ICON_FA_CAMERA_RETRO " Screenshot", "F12") ) + UserInterface::manager().StartScreenshot(); + + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Gui")) + { + ImGui::MenuItem("Sandbox", nullptr, &show_sandbox); + ImGui::MenuItem("Icons", nullptr, &show_icons_window); + ImGui::MenuItem("Demo ImGui", nullptr, &show_demo_window); + + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Stats")) + { + if (ImGui::MenuItem("Record", nullptr, &record_) ) + { + if ( record_ ) + csv_file_.open( SystemToolkit::home_path() + std::to_string(BaseToolkit::uniqueId()) + ".csv", std::ofstream::out | std::ofstream::app); + else + csv_file_.close(); + } + ImGui::EndMenu(); + } + ImGui::EndMenuBar(); + } + + // + // display histogram of update time and plot framerate + // + // keep array of 180 values, i.e. approx 3 seconds of recording + static float recorded_values[3][PLOT_ARRAY_SIZE] = {{}}; + static float recorded_sum[3] = { 0.f, 0.f, 0.f }; + static float recorded_bounds[3][2] = { {40.f, 65.f}, {0.f, 50.f}, {0.f, 50.f} }; + static float refresh_rate = -1.f; + static int values_index = 0; + float megabyte = static_cast( static_cast(SystemToolkit::memory_usage()) / 1048576.0 ); + + // init + if (refresh_rate < 0.f) { + + const GLFWvidmode* mode = glfwGetVideoMode(Rendering::manager().outputWindow().monitor()); + refresh_rate = float(mode->refreshRate); + if (Settings::application.render.vsync > 0) + refresh_rate /= Settings::application.render.vsync; + else + refresh_rate = 0.f; + recorded_bounds[0][0] = refresh_rate - 15.f; // min fps + recorded_bounds[0][1] = refresh_rate + 10.f; // max + + for(int i = 0; i lm = MediaPlayer::registered(); - for( auto m=lm.begin(); m!=lm.end();++m) - (*m)->reopen(); - } +// if ( ImGui::MenuItem( " RESET") ){ +// std::list lm = MediaPlayer::registered(); +// for( auto m=lm.begin(); m!=lm.end();++m) +// (*m)->reopen(); +// } if ( ImGui::MenuItem( ICON_FA_TIMES " Close", CTRL_MOD "P") ) Settings::application.widget.media_player = false; @@ -1688,7 +2042,7 @@ void MediaController::Render() setMediaPlayer(); // display list of available media - for (auto mpit = MediaPlayer::begin(); mpit != MediaPlayer::end(); mpit++ ) + for (auto mpit = MediaPlayer::begin(); mpit != MediaPlayer::end(); ++mpit ) { std::string label = (*mpit)->filename(); if (ImGui::MenuItem( label.c_str() )) @@ -1836,7 +2190,7 @@ void MediaController::Render() if (ImGui::BeginMenu("Smooth curve")) { const char* names[] = { "Just a little", "A bit more", "Quite a lot"}; - for (int i = 0; i < IM_ARRAYSIZE(names); i++) { + for (int i = 0; i < IM_ARRAYSIZE(names); ++i) { if (ImGui::MenuItem(names[i])) { mp_->timeline()->smoothFading( 10 * (int) pow(4, i) ); Action::manager().store("Timeline Smooth curve"); @@ -1847,7 +2201,7 @@ void MediaController::Render() if (ImGui::BeginMenu("Auto fading")) { const char* names[] = { "250 ms", "500 ms", "1 second", "2 seconds"}; - for (int i = 0; i < IM_ARRAYSIZE(names); i++) { + for (int i = 0; i < IM_ARRAYSIZE(names); ++i) { if (ImGui::MenuItem(names[i])) { mp_->timeline()->autoFading( 250 * (int ) pow(2, i) ); mp_->timeline()->smoothFading( 10 * (i + 1) ); @@ -1939,126 +2293,9 @@ void MediaController::Render() ImGui::End(); } -void UserInterface::fillShaderEditor(const std::string &text) -{ - static bool initialized = false; - if (!initialized) { - auto lang = TextEditor::LanguageDefinition::GLSL(); - - static const char* const keywords[] = { - "discard", "attribute", "varying", "uniform", "in", "out", "inout", "bvec2", "bvec3", "bvec4", "dvec2", - "dvec3", "dvec4", "ivec2", "ivec3", "ivec4", "uvec2", "uvec3", "uvec4", "vec2", "vec3", "vec4", "mat2", - "mat3", "mat4", "dmat2", "dmat3", "dmat4", "sampler1D", "sampler2D", "sampler3D", "samplerCUBE", "samplerbuffer", - "sampler1DArray", "sampler2DArray", "sampler1DShadow", "sampler2DShadow", "vec4", "vec4", "smooth", "flat", - "precise", "coherent", "uint", "struct", "switch", "unsigned", "void", "volatile", "while", "readonly" - }; - for (auto& k : keywords) - lang.mKeywords.insert(k); - - static const char* const identifiers[] = { - "radians", "degrees", "sin", "cos", "tan", "asin", "acos", "atan", "pow", "exp2", "log2", "sqrt", "inversesqrt", - "abs", "sign", "floor", "ceil", "fract", "mod", "min", "max", "clamp", "mix", "step", "smoothstep", "length", "distance", - "dot", "cross", "normalize", "ftransform", "faceforward", "reflect", "matrixcompmult", "lessThan", "lessThanEqual", - "greaterThan", "greaterThanEqual", "equal", "notEqual", "any", "all", "not", "texture1D", "texture1DProj", "texture1DLod", - "texture1DProjLod", "texture", "texture2D", "texture2DProj", "texture2DLod", "texture2DProjLod", "texture3D", - "texture3DProj", "texture3DLod", "texture3DProjLod", "textureCube", "textureCubeLod", "shadow1D", "shadow1DProj", - "shadow1DLod", "shadow1DProjLod", "shadow2D", "shadow2DProj", "shadow2DLod", "shadow2DProjLod", - "dFdx", "dFdy", "fwidth", "noise1", "noise2", "noise3", "noise4", "refract", "exp", "log", "mainImage", - }; - for (auto& k : identifiers) - { - TextEditor::Identifier id; - id.mDeclaration = "Added function"; - lang.mIdentifiers.insert(std::make_pair(std::string(k), id)); - } - // init editor - editor.SetLanguageDefinition(lang); - initialized = true; - } - - // remember text - currentTextEdit = text; - // fill editor - editor.SetText(currentTextEdit); -} - -void UserInterface::RenderShaderEditor() -{ - static bool show_statusbar = true; - - ImGui::Begin(IMGUI_TITLE_SHADEREDITOR, &Settings::application.widget.shader_editor, ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_MenuBar); - ImGui::SetWindowSize(ImVec2(800, 600), ImGuiCond_FirstUseEver); - if (ImGui::BeginMenuBar()) - { - if (ImGui::BeginMenu("Edit")) - { - bool ro = editor.IsReadOnly(); - if (ImGui::MenuItem("Read-only mode", nullptr, &ro)) - editor.SetReadOnly(ro); - ImGui::Separator(); - - if (ImGui::MenuItem( ICON_FA_UNDO " Undo", CTRL_MOD "Z", nullptr, !ro && editor.CanUndo())) - editor.Undo(); - if (ImGui::MenuItem( ICON_FA_REDO " Redo", CTRL_MOD "Y", nullptr, !ro && editor.CanRedo())) - editor.Redo(); - - ImGui::Separator(); - - if (ImGui::MenuItem( ICON_FA_COPY " Copy", CTRL_MOD "C", nullptr, editor.HasSelection())) - editor.Copy(); - if (ImGui::MenuItem( ICON_FA_CUT " Cut", CTRL_MOD "X", nullptr, !ro && editor.HasSelection())) - editor.Cut(); - if (ImGui::MenuItem( ICON_FA_ERASER " Delete", "Del", nullptr, !ro && editor.HasSelection())) - editor.Delete(); - if (ImGui::MenuItem( ICON_FA_PASTE " Paste", CTRL_MOD "V", nullptr, !ro && ImGui::GetClipboardText() != nullptr)) - editor.Paste(); - - ImGui::Separator(); - - if (ImGui::MenuItem( "Select all", nullptr, nullptr)) - editor.SetSelection(TextEditor::Coordinates(), TextEditor::Coordinates(editor.GetTotalLines(), 0)); - - ImGui::EndMenu(); - } - - if (ImGui::BeginMenu("View")) - { - bool ws = editor.IsShowingWhitespaces(); - if (ImGui::MenuItem( ICON_FA_LONG_ARROW_ALT_RIGHT " Whitespace", nullptr, &ws)) - editor.SetShowWhitespaces(ws); - ImGui::MenuItem( ICON_FA_WINDOW_MAXIMIZE " Statusbar", nullptr, &show_statusbar); - ImGui::EndMenu(); - } - ImGui::EndMenuBar(); - } - - if (show_statusbar) { - auto cpos = editor.GetCursorPosition(); - ImGui::Text("%6d/%-6d %6d lines | %s | %s | %s ", cpos.mLine + 1, cpos.mColumn + 1, editor.GetTotalLines(), - editor.IsOverwrite() ? "Ovr" : "Ins", - editor.CanUndo() ? "*" : " ", - editor.GetLanguageDefinition().mName.c_str()); - } - - ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO); - editor.Render("ShaderEditor"); - ImGui::PopFont(); - - ImGui::End(); - -} - - -void UserInterface::showPannel(int id) -{ - if (id == NAV_MENU) - navigator.togglePannelMenu(); - else if (id == NAV_NEW) - navigator.togglePannelNew(); - else - navigator.showPannelSource(id); -} - +/// +/// NAVIGATOR +/// Navigator::Navigator() { // default geometry @@ -2068,6 +2305,7 @@ Navigator::Navigator() padding_width_ = 100; // clean start + show_config_ = false; pannel_visible_ = false; view_pannel_visible = false; clearButtonSelection(); @@ -2082,6 +2320,8 @@ void Navigator::applyButtonSelection(int index) // set visible if button is active pannel_visible_ = status; + + show_config_ = false; } void Navigator::clearButtonSelection() @@ -2093,6 +2333,7 @@ void Navigator::clearButtonSelection() // clear new source pannel new_source_preview_.setSource(); pattern_type = -1; + _selectedFiles.clear(); } void Navigator::showPannelSource(int index) @@ -2122,13 +2363,14 @@ void Navigator::hidePannel() { clearButtonSelection(); pannel_visible_ = false; - view_pannel_visible = false; + view_pannel_visible = false; + show_config_ = false; } void Navigator::Render() { std::string tooltip_ = ""; - static uint timer_tooltip_ = 0; + static uint _timeout_tooltip = 0; const ImGuiIO& io = ImGui::GetIO(); const ImGuiStyle& style = ImGui::GetStyle(); @@ -2141,104 +2383,97 @@ void Navigator::Render() ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, ImVec2(0.50f, 0.50f)); // calculate size of items based on text size and display dimensions - width_ = 2.f * ImGui::GetTextLineHeightWithSpacing(); // dimension of left bar depends on FONT_LARGE + width_ = 2.f * ImGui::GetTextLineHeightWithSpacing(); // dimension of left bar depends on FONT_LARGE pannel_width_ = 5.f * width_; // pannel is 5x the bar - padding_width_ = 2.f * style.WindowPadding.x; // panning for alighment - height_ = io.DisplaySize.y; // cover vertically - sourcelist_height_ = height_ - 8.f * ImGui::GetTextLineHeight(); // space for 4 icons of view - float icon_width = width_ - 2.f * style.WindowPadding.x; // icons keep padding + padding_width_ = 2.f * style.WindowPadding.x; // panning for alighment + height_ = io.DisplaySize.y; // cover vertically + float sourcelist_height_ = height_ - 8.f * ImGui::GetTextLineHeight(); // space for 4 icons of view + float icon_width = width_ - 2.f * style.WindowPadding.x; // icons keep padding ImVec2 iconsize(icon_width, icon_width); - // Left bar top - ImGui::SetNextWindowPos( ImVec2(0, 0), ImGuiCond_Always ); - ImGui::SetNextWindowSize( ImVec2(width_, sourcelist_height_), ImGuiCond_Always ); - ImGui::SetNextWindowBgAlpha(0.95f); // Transparent background - if (ImGui::Begin( ICON_FA_BARS " Navigator", NULL, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing)) - { - if (Settings::application.current_view < View::TRANSITION) { + // Left bar top + ImGui::SetNextWindowPos( ImVec2(0, 0), ImGuiCond_Always ); + ImGui::SetNextWindowSize( ImVec2(width_, sourcelist_height_), ImGuiCond_Always ); + ImGui::SetNextWindowBgAlpha(0.95f); // Transparent background + if (ImGui::Begin( ICON_FA_BARS " Navigator", NULL, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing)) + { + if (Settings::application.current_view < View::TRANSITION) { - // the "=" icon for menu - if (ImGui::Selectable( ICON_FA_BARS, &selected_button[NAV_MENU], 0, iconsize)) - { - // Mixer::manager().unsetCurrentSource(); - applyButtonSelection(NAV_MENU); + // the "=" icon for menu + if (ImGui::Selectable( ICON_FA_BARS, &selected_button[NAV_MENU], 0, iconsize)) + applyButtonSelection(NAV_MENU); + if (ImGui::IsItemHovered()) + tooltip_ = "Main menu HOME"; + + // the list of INITIALS for sources + int index = 0; + SourceList::iterator iter; + for (iter = Mixer::manager().session()->begin(); iter != Mixer::manager().session()->end(); ++iter, ++index) + { + Source *s = (*iter); + // draw an indicator for current source + if ( s->mode() >= Source::SELECTED ){ + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + ImVec2 p1 = ImGui::GetCursorScreenPos() + ImVec2(icon_width, 0.5f * icon_width); + ImVec2 p2 = ImVec2(p1.x + 2.f, p1.y + 2.f); + const ImU32 color = ImGui::GetColorU32( style.Colors[ImGuiCol_Text] ); + if ((*iter)->mode() == Source::CURRENT) { + p1 = ImGui::GetCursorScreenPos() + ImVec2(icon_width, 0); + p2 = ImVec2(p1.x + 2.f, p1.y + icon_width); + } + draw_list->AddRect(p1, p2, color, 0.0f, 0, 3.f); } - if (ImGui::IsItemHovered()) - tooltip_ = "Main menu HOME"; - - // the list of INITIALS for sources - int index = 0; - SourceList::iterator iter; - for (iter = Mixer::manager().session()->begin(); iter != Mixer::manager().session()->end(); ++iter, ++index) + // draw select box + ImGui::PushID(std::to_string(s->group(View::RENDERING)->id()).c_str()); + if (ImGui::Selectable(s->initials(), &selected_button[index], 0, iconsize)) { - Source *s = (*iter); - // draw an indicator for current source - if ( s->mode() >= Source::SELECTED ){ - ImDrawList* draw_list = ImGui::GetWindowDrawList(); - ImVec2 p1 = ImGui::GetCursorScreenPos() + ImVec2(icon_width, 0.5f * icon_width); - ImVec2 p2 = ImVec2(p1.x + 2.f, p1.y + 2.f); - const ImU32 color = ImGui::GetColorU32( style.Colors[ImGuiCol_Text] ); - if ((*iter)->mode() == Source::CURRENT) { - p1 = ImGui::GetCursorScreenPos() + ImVec2(icon_width, 0); - p2 = ImVec2(p1.x + 2.f, p1.y + icon_width); + applyButtonSelection(index); + if (selected_button[index]) + Mixer::manager().setCurrentIndex(index); + } + + if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) + { + ImGui::SetDragDropPayload("DND_SOURCE", &index, sizeof(int)); + ImGui::Text( ICON_FA_SORT " %s ", s->initials()); + ImGui::EndDragDropSource(); + } + if (ImGui::BeginDragDropTarget()) + { + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("DND_SOURCE")) + { + if ( payload->DataSize == sizeof(int) ) { + bool status_current_index = selected_button[Mixer::manager().indexCurrentSource()]; + // drop means move index and reorder + int payload_index = *(const int*)payload->Data; + Mixer::manager().moveIndex(payload_index, index); + // index of current source changed + selected_button[Mixer::manager().indexCurrentSource()] = status_current_index; + applyButtonSelection(Mixer::manager().indexCurrentSource()); } - draw_list->AddRect(p1, p2, color, 0.0f, 0, 3.f); } - // draw select box - ImGui::PushID(std::to_string(s->group(View::RENDERING)->id()).c_str()); - if (ImGui::Selectable(s->initials(), &selected_button[index], 0, iconsize)) - { - applyButtonSelection(index); - if (selected_button[index]) - Mixer::manager().setCurrentIndex(index); - } - - if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) - { - ImGui::SetDragDropPayload("DND_SOURCE", &index, sizeof(int)); - ImGui::Text( ICON_FA_SORT " %s ", s->initials()); - ImGui::EndDragDropSource(); - } - if (ImGui::BeginDragDropTarget()) - { - if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("DND_SOURCE")) - { - if ( payload->DataSize == sizeof(int) ) { - bool status_current_index = selected_button[Mixer::manager().indexCurrentSource()]; - // drop means move index and reorder - int payload_index = *(const int*)payload->Data; - Mixer::manager().moveIndex(payload_index, index); - // index of current source changed - selected_button[Mixer::manager().indexCurrentSource()] = status_current_index; - applyButtonSelection(Mixer::manager().indexCurrentSource()); - } - } - ImGui::EndDragDropTarget(); - } - - ImGui::PopID(); + ImGui::EndDragDropTarget(); } - // the "+" icon for action of creating new source - if (ImGui::Selectable( ICON_FA_PLUS, &selected_button[NAV_NEW], 0, iconsize)) - { -// Mixer::manager().unsetCurrentSource(); - applyButtonSelection(NAV_NEW); - } - if (ImGui::IsItemHovered()) - tooltip_ = "New Source INS"; - + ImGui::PopID(); } - else { - // the ">" icon for transition menu - if (ImGui::Selectable( ICON_FA_ARROW_CIRCLE_RIGHT, &selected_button[NAV_TRANS], 0, iconsize)) - { - // Mixer::manager().unsetCurrentSource(); - applyButtonSelection(NAV_TRANS); - } + + // the "+" icon for action of creating new source + if (ImGui::Selectable( ICON_FA_PLUS, &selected_button[NAV_NEW], 0, iconsize)) + applyButtonSelection(NAV_NEW); + if (ImGui::IsItemHovered()) + tooltip_ = "New Source INS"; + } + else { + // the ">" icon for transition menu + if (ImGui::Selectable( ICON_FA_ARROW_CIRCLE_RIGHT, &selected_button[NAV_TRANS], 0, iconsize)) + { + Mixer::manager().unsetCurrentSource(); + applyButtonSelection(NAV_TRANS); } } ImGui::End(); + } // Left bar bottom ImGui::SetNextWindowPos( ImVec2(0, sourcelist_height_), ImGuiCond_Always ); @@ -2278,19 +2513,19 @@ void Navigator::Render() if (ImGui::IsItemHovered()) tooltip_ = "Texturing F4"; - ImGui::End(); } // show tooltip if (!tooltip_.empty()) { - timer_tooltip_++; // pseudo timeout for showing tooltip - if (timer_tooltip_ > 80) + if (_timeout_tooltip > IMGUI_TOOLTIP_TIMEOUT) ImGuiToolkit::ToolTip(tooltip_.substr(0, tooltip_.size()-6).c_str(), tooltip_.substr(tooltip_.size()-6, 6).c_str()); + else + ++_timeout_tooltip; } else - timer_tooltip_ = 0; + _timeout_tooltip = 0; if ( view_pannel_visible && !pannel_visible_ ) RenderViewPannel( ImVec2(width_, sourcelist_height_), ImVec2(width_*0.8f, height_ - sourcelist_height_) ); @@ -2298,7 +2533,7 @@ void Navigator::Render() ImGui::PopStyleVar(); ImGui::PopFont(); - if ( Settings::application.pannel_stick || pannel_visible_){ + if ( pannel_visible_){ // pannel menu if (selected_button[NAV_MENU]) { @@ -2326,7 +2561,6 @@ void Navigator::Render() } - void Navigator::RenderViewPannel(ImVec2 draw_pos , ImVec2 draw_size) { ImGui::SetNextWindowPos( draw_pos, ImGuiCond_Always ); @@ -2374,21 +2608,20 @@ void Navigator::RenderSourcePannel(Source *s) if (ImGui::Begin("##navigatorSource", NULL, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav)) { // TITLE - ImGui::SetCursorPosY(10); + ImGui::SetCursorPosY(IMGUI_TOP_ALIGN); ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE); ImGui::Text("Source"); ImGui::PopFont(); - ImGui::SetCursorPos(ImVec2(pannel_width_ - 35.f, 15.f)); - const char *tooltip[2] = {"Pin pannel\nCurrent: double-clic on source", "Un-pin Pannel\nCurrent: single-clic on source"}; - ImGuiToolkit::IconToggle(5,2,4,2, &Settings::application.pannel_stick, tooltip ); +// ImGui::SetCursorPos(ImVec2(pannel_width_ - 35.f, 15.f)); +// const char *tooltip[2] = {"Pin pannel\nCurrent: double-clic on source", "Un-pin Pannel\nCurrent: single-clic on source"}; +// ImGuiToolkit::IconToggle(5,2,4,2, &Settings::application.pannel_stick, tooltip ); - static char buf5[128]; - sprintf ( buf5, "%s", s->name().c_str() ); + std::string sname = s->name(); ImGui::SetCursorPosY(width_); ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); - if (ImGui::InputText("Name", buf5, 64, ImGuiInputTextFlags_CharsNoBlank)){ - Mixer::manager().renameSource(s, buf5); + if (ImGuiToolkit::InputText("Name", &sname) ){ + Mixer::manager().renameSource(s, sname); } // Source pannel static ImGuiVisitor v; @@ -2398,79 +2631,14 @@ void Navigator::RenderSourcePannel(Source *s) // Action on source if ( ImGui::Button( ICON_FA_SHARE_SQUARE " Clone", ImVec2(ImGui::GetContentRegionAvail().x, 0)) ) Mixer::manager().addSource ( Mixer::manager().createSourceClone() ); - if ( ImGui::Button( ICON_FA_BACKSPACE " Delete", ImVec2(ImGui::GetContentRegionAvail().x, 0)) ) + if ( ImGui::Button( ICON_FA_BACKSPACE " Delete", ImVec2(ImGui::GetContentRegionAvail().x, 0)) ) { Mixer::manager().deleteSource(s); - } - ImGui::End(); -} - - -SourcePreview::SourcePreview() : source_(nullptr), label_("") -{ - -} - -void SourcePreview::setSource(Source *s, const string &label) -{ - if(source_) - delete source_; - - source_ = s; - label_ = label; -} - -Source * SourcePreview::getSource() -{ - Source *s = source_; - source_ = nullptr; - return s; -} - -void SourcePreview::Render(float width, bool controlbutton) -{ - if(source_) { - // cancel if failed - if (source_->failed()) { - // remove from list of recent import files if relevant - MediaSource *failedFile = dynamic_cast(source_); - if (failedFile != nullptr) { - Settings::application.recentImport.remove( failedFile->path() ); - } - setSource(); - } - else - { - bool active = source_->active(); - // update source - source_->update( Mixer::manager().dt()); - // render framebuffer - source_->render(); - - // draw preview - FrameBuffer *frame = source_->frame(); - ImVec2 preview_size(width, width / frame->aspectRatio()); - ImGui::Image((void*)(uintptr_t) frame->texture(), preview_size); - - if (controlbutton && source_->ready()) { - ImVec2 pos = ImGui::GetCursorPos(); - ImGui::SameLine(); - if (ImGuiToolkit::IconToggle(12,7,1,8, &active)) - source_->setActive(active); - ImGui::SetCursorPos(pos); - } - ImGuiToolkit::Icon(source_->icon().x, source_->icon().y); - ImGui::SameLine(0, 10); - ImGui::Text("%s ", label_.c_str()); - ImGui::Text("%d x %d %s", frame->width(), frame->height(), frame->use_alpha() ? "RGBA" : "RGB"); + Action::manager().store(sname + std::string(": deleted")); } + ImGui::End(); } } -bool SourcePreview::ready() const -{ - return source_ != nullptr && source_->ready(); -} - void Navigator::RenderNewPannel() { // Next window is a side pannel @@ -2488,36 +2656,30 @@ void Navigator::RenderNewPannel() // Edit menu ImGui::SetCursorPosY(width_); ImGui::Text("Source"); - ImGui::SameLine(); - ImGui::SetCursorPosX(pannel_width_ IMGUI_RIGHT_ALIGN); - if (ImGui::BeginMenu("Edit")) - { - UserInterface::manager().showMenuEdit(); - ImGui::EndMenu(); - } // // News Source selection pannel // - static const char* origin_names[4] = { ICON_FA_PHOTO_VIDEO " File", - ICON_FA_SYNC " Internal", + static const char* origin_names[5] = { ICON_FA_PHOTO_VIDEO " File", + ICON_FA_SORT_NUMERIC_DOWN " Sequence", + ICON_FA_PLUG " Connected", ICON_FA_COG " Generated", - ICON_FA_PLUG " Connected" + ICON_FA_SYNC " Internal" }; ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); if (ImGui::Combo("##Origin", &Settings::application.source.new_type, origin_names, IM_ARRAYSIZE(origin_names)) ) new_source_preview_.setSource(); + ImGui::SetCursorPosY(2.f * width_); + // File Source creation if (Settings::application.source.new_type == 0) { - ImGui::SetCursorPosY(2.f * width_); - // clic button to load file - if ( ImGui::Button( ICON_FA_FILE_EXPORT " Open file", ImVec2(ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN, 0)) ) { + if ( ImGui::Button( ICON_FA_FILE_EXPORT " Open media", ImVec2(ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN, 0)) ) { // launch async call to file dialog and get its future. if (fileImportFileDialogs.empty()) { - fileImportFileDialogs.emplace_back( std::async(std::launch::async, DialogToolkit::ImportFileDialog, Settings::application.recentImport.path) ); + fileImportFileDialogs.emplace_back( std::async(std::launch::async, DialogToolkit::openMediaFileDialog, Settings::application.recentImport.path) ); fileDialogPending_ = true; } } @@ -2539,8 +2701,8 @@ void Navigator::RenderNewPannel() if (open_filename.empty()) { Log::Notify("No file selected."); } else { - std::string label = SystemToolkit::transliterate( open_filename ); - label = label.substr( label.size() - MIN( 35, label.size()) ); + std::string label = BaseToolkit::transliterate( open_filename ); + label = BaseToolkit::trunc_string(label, 35); new_source_preview_.setSource( Mixer::manager().createSourceFile(open_filename), label); } } @@ -2555,8 +2717,8 @@ void Navigator::RenderNewPannel() { std::string recentpath(*path); if ( SystemToolkit::file_exists(recentpath)) { - std::string label = SystemToolkit::transliterate( recentpath ); - label = SystemToolkit::trunc_filename(label, 35); + std::string label = BaseToolkit::transliterate( recentpath ); + label = BaseToolkit::trunc_string(label, 35); if (ImGui::Selectable( label.c_str() )) { new_source_preview_.setSource( Mixer::manager().createSourceFile(recentpath.c_str()), label); } @@ -2565,10 +2727,80 @@ void Navigator::RenderNewPannel() ImGui::EndCombo(); } } - // Internal Source creator + // Folder Source creator else if (Settings::application.source.new_type == 1){ - ImGui::SetCursorPosY(2.f * width_); + bool update_new_source = false; + static std::vector< std::future< std::list > > _selectedImagesFileDialogs; + + // clic button to load file + if ( ImGui::Button( ICON_FA_IMAGES " Open images", ImVec2(ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN, 0)) ) { + _selectedFiles.clear(); + if (_selectedImagesFileDialogs.empty()) { + _selectedImagesFileDialogs.emplace_back( std::async(std::launch::async, DialogToolkit::selectImagesFileDialog, Settings::application.recentImport.path) ); + fileDialogPending_ = true; + } + } + + // Indication + ImGui::SameLine(); + ImGuiToolkit::HelpMarker("Create a source from a sequence of numbered images."); + + // return from thread for folder openning + if ( !_selectedImagesFileDialogs.empty() ) { + // check that file dialog thread finished + if (_selectedImagesFileDialogs.back().wait_for(timeout) == std::future_status::ready ) { + // get the filenames from this file dialog + _selectedFiles = _selectedImagesFileDialogs.back().get(); + if (_selectedFiles.empty()) { + Log::Notify("No file selected."); + } + // done with this file dialog + _selectedImagesFileDialogs.pop_back(); + fileDialogPending_ = false; + // ask to reload the preview + update_new_source = true; + } + } + + // multiple files selected + if (_selectedFiles.size() > 1) { + + // set framerate + static int _fps = 30; + static bool _fps_changed = false; + ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); + if ( ImGui::SliderInt("Framerate", &_fps, 1, 30, "%d fps") ) { + _fps_changed = true; + } + // only call for new source after mouse release to avoid repeating call to re-open the stream + else if (_fps_changed && ImGui::IsMouseReleased(ImGuiMouseButton_Left)){ + update_new_source = true; + _fps_changed = false; + } + + if (update_new_source) { + std::string label = BaseToolkit::transliterate( BaseToolkit::common_pattern(_selectedFiles) ); + label = BaseToolkit::trunc_string(label, 35); + new_source_preview_.setSource( Mixer::manager().createSourceMultifile(_selectedFiles, _fps), label); + } + } + // single file selected + else if (_selectedFiles.size() > 0) { + + ImGui::Text("Single file selected"); + + if (update_new_source) { + std::string label = BaseToolkit::transliterate( _selectedFiles.front() ); + label = BaseToolkit::trunc_string(label, 35); + new_source_preview_.setSource( Mixer::manager().createSourceFile(_selectedFiles.front()), label); + } + + } + + } + // Internal Source creator + else if (Settings::application.source.new_type == 4){ // fill new_source_preview with a new source ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); @@ -2595,9 +2827,7 @@ void Navigator::RenderNewPannel() ImGuiToolkit::HelpMarker("Create a source replicating internal vimix objects."); } // Generated Source creator - else if (Settings::application.source.new_type == 2){ - - ImGui::SetCursorPosY(2.f * width_); + else if (Settings::application.source.new_type == 3){ bool update_new_source = false; @@ -2639,9 +2869,7 @@ void Navigator::RenderNewPannel() } } // External source creator - else if (Settings::application.source.new_type == 3){ - - ImGui::SetCursorPosY(2.f * width_); + else if (Settings::application.source.new_type == 2){ ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); if (ImGui::BeginCombo("##External", "Select device")) @@ -2673,7 +2901,7 @@ void Navigator::RenderNewPannel() // if a new source was added if (new_source_preview_.filled()) { // show preview - new_source_preview_.Render(ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN, Settings::application.source.new_type != 1); + new_source_preview_.Render(ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN, Settings::application.source.new_type != 2); // ask to import the source in the mixer ImGui::NewLine(); if (new_source_preview_.ready() && ImGui::Button( ICON_FA_CHECK " Create", ImVec2(pannel_width_ - padding_width_, 0)) ) { @@ -2682,10 +2910,615 @@ void Navigator::RenderNewPannel() } } + ImGui::End(); } - ImGui::End(); } +void Navigator::RenderMainPannelVimix() +{ + // TITLE + ImGui::SetCursorPosY(IMGUI_TOP_ALIGN); + ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE); + ImGui::Text(APP_NAME); + ImGui::PopFont(); + + // MENU + ImGui::SameLine(); + ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, IMGUI_TOP_ALIGN) ); + if (ImGui::BeginMenu("File")) + { + UserInterface::manager().showMenuFile(); + ImGui::EndMenu(); + } + ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, IMGUI_TOP_ALIGN + ImGui::GetTextLineHeightWithSpacing()) ); + if (ImGui::BeginMenu("Edit")) + { + UserInterface::manager().showMenuEdit(); + ImGui::EndMenu(); + } + + ImGui::SetCursorPosY(width_); + + // + // SESSION panel + // + ImGui::Spacing(); + ImGui::Text("Sessions"); + static bool selection_session_mode_changed = true; + static int selection_session_mode = 0; + + // Show combo box of quick selection modes + ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); + if (ImGui::BeginCombo("##SelectionSession", BaseToolkit::trunc_string(Settings::application.recentFolders.path, 25).c_str() )) { + + // Option 0 : recent files + if (ImGui::Selectable( ICON_FA_CLOCK IMGUI_LABEL_RECENT_FILES) ) { + Settings::application.recentFolders.path = IMGUI_LABEL_RECENT_FILES; + selection_session_mode = 0; + selection_session_mode_changed = true; + } + // Options 1 : known folders + for(auto foldername = Settings::application.recentFolders.filenames.begin(); + foldername != Settings::application.recentFolders.filenames.end(); foldername++) { + std::string f = std::string(ICON_FA_FOLDER) + " " + BaseToolkit::trunc_string( *foldername, 40); + if (ImGui::Selectable( f.c_str() )) { + // remember which path was selected + Settings::application.recentFolders.path.assign(*foldername); + // set mode + selection_session_mode = 1; + selection_session_mode_changed = true; + } + } + // Option 2 : add a folder + if (ImGui::Selectable( ICON_FA_FOLDER_PLUS " Add Folder") ){ + if (recentFolderFileDialogs.empty()) { + recentFolderFileDialogs.emplace_back( std::async(std::launch::async, DialogToolkit::openFolderDialog, Settings::application.recentFolders.path) ); + fileDialogPending_ = true; + } + } + ImGui::EndCombo(); + } + + // return from thread for folder openning + if ( !recentFolderFileDialogs.empty() ) { + // check that file dialog thread finished + if (recentFolderFileDialogs.back().wait_for(timeout) == std::future_status::ready ) { + // get the filename from this file dialog + std::string foldername = recentFolderFileDialogs.back().get(); + if (!foldername.empty()) { + Settings::application.recentFolders.push(foldername); + Settings::application.recentFolders.path.assign(foldername); + selection_session_mode = 1; + selection_session_mode_changed = true; + } + // done with this file dialog + recentFolderFileDialogs.pop_back(); + fileDialogPending_ = false; + + } + } + + // icon to clear list + ImVec2 pos_top = ImGui::GetCursorPos(); + ImGui::SameLine(); + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.7); + if ( selection_session_mode == 1) { + if (ImGuiToolkit::IconButton( ICON_FA_FOLDER_MINUS, "Discard folder")) { + Settings::application.recentFolders.filenames.remove(Settings::application.recentFolders.path); + if (Settings::application.recentFolders.filenames.empty()) { + Settings::application.recentFolders.path.assign(IMGUI_LABEL_RECENT_FILES); + selection_session_mode = 0; + } + else + Settings::application.recentFolders.path = Settings::application.recentFolders.filenames.front(); + // reload the list next time + selection_session_mode_changed = true; + } + } + else { + if (ImGuiToolkit::IconButton( ICON_FA_BACKSPACE, "Clear history")) { + Settings::application.recentSessions.filenames.clear(); + Settings::application.recentSessions.front_is_valid = false; + // reload the list next time + selection_session_mode_changed = true; + } + } + ImGui::PopStyleVar(); + ImGui::SetCursorPos(pos_top); + + // fill the session list depending on the mode + static std::list sessions_list; + // change session list if changed + if (selection_session_mode_changed || Settings::application.recentSessions.changed) { + + // selection MODE 0 ; RECENT sessions + if ( selection_session_mode == 0) { + // show list of recent sessions + Settings::application.recentSessions.validate(); + sessions_list = Settings::application.recentSessions.filenames; + Settings::application.recentSessions.changed = false; + } + // selection MODE 1 : LIST FOLDER + else if ( selection_session_mode == 1) { + // show list of vimix files in folder + sessions_list = SystemToolkit::list_directory( Settings::application.recentFolders.path, {"mix", "MIX"}); + } + // indicate the list changed (do not change at every frame) + selection_session_mode_changed = false; + } + + { + static std::list::iterator _file_over = sessions_list.end(); + static std::list::iterator _displayed_over = sessions_list.end(); + static bool _tooltip = 0; + + // display the sessions list and detect if one was selected (double clic) + ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); + if (ImGui::ListBoxHeader("##Sessions", sessions_list.size(), CLAMP(sessions_list.size(), 4, 8)) ) { + + bool done = false; + int count_over = 0; + ImVec2 size = ImVec2( ImGui::GetContentRegionAvailWidth(), ImGui::GetTextLineHeight() ); + + for(auto it = sessions_list.begin(); it != sessions_list.end(); ++it) { + + if (it->empty()) + continue; + + std::string shortname = SystemToolkit::filename(*it); + if (ImGui::Selectable( shortname.c_str(), false, ImGuiSelectableFlags_AllowDoubleClick, size )) { + // open on double clic + if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left) /*|| file_selected == it*/) { + Mixer::manager().open( *it, Settings::application.smooth_transition ); + done = true; + } + else + // show tooltip on clic + _tooltip = true; + + } + if (ImGui::IsItemHovered()) + _file_over = it; + + if (_tooltip && _file_over != sessions_list.end() && count_over < 1) { + + static std::string _file_info = ""; + static Thumbnail _file_thumbnail; + + // load info only if changed from the one already displayed + if (_displayed_over != _file_over) { + _displayed_over = _file_over; + SessionInformation info = SessionCreator::info(*_displayed_over); + _file_info = info.description; + if (info.thumbnail) { + // set image content to thumbnail display + _file_thumbnail.set( info.thumbnail ); + delete info.thumbnail; + } else + _file_thumbnail.reset(); + } + + if ( !_file_info.empty()) { + + ImGui::BeginTooltip(); + _file_thumbnail.Render(size.x); + ImGui::Text("%s", _file_info.c_str()); + ImGui::EndTooltip(); + + } + else + selection_session_mode_changed = true; + + ++count_over; // prevents display twice on item overlap + } + } + ImGui::ListBoxFooter(); + + // done the selection ! + if (done) { + hidePannel(); + _tooltip = false; + _displayed_over = _file_over = sessions_list.end(); + // reload the list next time + selection_session_mode_changed = true; + } + } + // cancel tooltip and mouse over on mouse exit + if ( !ImGui::IsItemHovered()) { + _tooltip = false; + _displayed_over = _file_over = sessions_list.end(); + } + } + + ImVec2 pos_bot = ImGui::GetCursorPos(); + + + // Right side of the list: helper and options + ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_top.y)); + if ( ImGuiToolkit::IconButton( ICON_FA_FILE )) { + Mixer::manager().close(Settings::application.smooth_transition ); + hidePannel(); + } + if (ImGui::IsItemHovered()) + ImGuiToolkit::ToolTip("New session", CTRL_MOD "W"); + + ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_bot.y - 2.f * ImGui::GetFrameHeightWithSpacing())); + ImGuiToolkit::HelpMarker("Select the history of recently\n" + "opened files or a folder.\n" + "Double-clic a filename to open.\n\n" + ICON_FA_ARROW_CIRCLE_RIGHT " Enable smooth transition to\n" + "perform smooth cross fading."); + // toggle button for smooth transition + ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_bot.y - ImGui::GetFrameHeightWithSpacing()) ); + ImGuiToolkit::ButtonToggle(ICON_FA_ARROW_CIRCLE_RIGHT, &Settings::application.smooth_transition); + if (ImGui::IsItemHovered()) + ImGuiToolkit::ToolTip("Smooth transition"); + // come back... + ImGui::SetCursorPos(pos_bot); + + + // + // Status + // + ImGui::Spacing(); + ImGui::Text("Status"); + ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); + ImGui::Combo("##SelectHistory", &Settings::application.pannel_history_mode, ICON_FA_STAR " Snapshots\0" ICON_FA_HISTORY " Undo history\0"); + + // + // UNDO History + if (Settings::application.pannel_history_mode > 0) { + + static uint _over = 0; + static uint64_t _displayed_over = 0; + static bool _tooltip = 0; + + pos_top = ImGui::GetCursorPos(); + ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); + if ( ImGui::ListBoxHeader("##UndoHistory", Action::manager().max(), CLAMP(Action::manager().max(), 4, 8)) ) { + + int count_over = 0; + ImVec2 size = ImVec2( ImGui::GetContentRegionAvailWidth(), ImGui::GetTextLineHeight() ); + + for (uint i = Action::manager().max(); i > 0; --i) { + + if (ImGui::Selectable( Action::manager().label(i).c_str(), i == Action::manager().current(), ImGuiSelectableFlags_AllowDoubleClick, size )) { + // go to on double clic + if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) + Action::manager().stepTo(i); + else + // show tooltip on clic + _tooltip = true; + } + // mouse over + if (ImGui::IsItemHovered()) + _over = i; + + // if mouse over (only once) + if (_tooltip && _over > 0 && count_over < 1) { + static std::string text = ""; + static Thumbnail _undo_thumbnail; + // load label and thumbnail only if current changed + if (_displayed_over != _over) { + _displayed_over = _over; + text = Action::manager().label(_over); + if (text.find_first_of(':') < text.size()) + text = text.insert( text.find_first_of(':') + 1, 1, '\n'); + FrameBufferImage *im = Action::manager().thumbnail(_over); + if (im) { + // set image content to thumbnail display + _undo_thumbnail.set( im ); + delete im; + } + else + _undo_thumbnail.reset(); + } + // draw thumbnail in tooltip + ImGui::BeginTooltip(); + _undo_thumbnail.Render(size.x); + ImGui::Text("%s", text.c_str()); + ImGui::EndTooltip(); + ++count_over; // prevents display twice on item overlap + } + + } + ImGui::ListBoxFooter(); + } + // cancel tooltip and mouse over on mouse exit + if ( !ImGui::IsItemHovered()) { + _tooltip = false; + _displayed_over = _over = 0; + } + + pos_bot = ImGui::GetCursorPos(); + + // right buttons + ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_top.y )); + if ( Action::manager().current() > 1 ) { + if ( ImGuiToolkit::IconButton( ICON_FA_UNDO ) ) + Action::manager().undo(); + } else + ImGui::TextDisabled( ICON_FA_UNDO ); + + ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_top.y + ImGui::GetTextLineHeightWithSpacing() + 4)); + if ( Action::manager().current() < Action::manager().max() ) { + if ( ImGuiToolkit::IconButton( ICON_FA_REDO )) + Action::manager().redo(); + } else + ImGui::TextDisabled( ICON_FA_REDO ); + + ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_bot.y - ImGui::GetFrameHeightWithSpacing()) ); + ImGuiToolkit::ButtonToggle(ICON_FA_LOCATION_ARROW, &Settings::application.action_history_follow_view); + if (ImGui::IsItemHovered()) + ImGuiToolkit::ToolTip("Show in view"); + } + // + // SNAPSHOTS + else { + static uint64_t _over = 0; + static bool _tooltip = 0; + + // list snapshots + std::list snapshots = Action::manager().snapshots(); + pos_top = ImGui::GetCursorPos(); + ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); + if ( ImGui::ListBoxHeader("##Snapshots", snapshots.size(), CLAMP(snapshots.size(), 4, 8)) ) { + + static uint64_t _selected = 0; + static Thumbnail _snap_thumbnail; + static std::string _snap_label = ""; + + int count_over = 0; + ImVec2 size = ImVec2( ImGui::GetContentRegionAvailWidth(), ImGui::GetTextLineHeight() ); + for (auto snapit = snapshots.begin(); snapit != snapshots.end(); ++snapit) + { + // entry + ImVec2 pos = ImGui::GetCursorPos(); + + // context menu icon on currently hovered item + if ( _over == *snapit ) { + // open context menu + ImGui::SetCursorPos(ImVec2(size.x-ImGui::GetTextLineHeight()/2.f, pos.y)); + if ( ImGuiToolkit::IconButton( ICON_FA_CHEVRON_DOWN ) ) { + // current list item + Action::manager().open(*snapit); + // open menu + ImGui::OpenPopup( "MenuSnapshot" ); + } + // show tooltip and select on mouse over menu icon + if (ImGui::IsItemHovered()) { + _selected = *snapit; + _tooltip = true; + } + ImGui::SetCursorPos(pos); + } + + // snapshot item + if (ImGui::Selectable( Action::manager().label(*snapit).c_str(), (*snapit == _selected), ImGuiSelectableFlags_AllowDoubleClick, size )) { + // shot tooltip on clic + _tooltip = true; + // trigger snapshot on double clic + if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) + Action::manager().restore(*snapit); + } + // mouse over + if (ImGui::IsItemHovered()) { + _over = *snapit; + _selected = 0; + } + + // if mouse over (only once) + if (_tooltip && _over > 0 && count_over < 1) { + static uint64_t current_over = 0; + // load label and thumbnail only if current changed + if (current_over != _over) { + _snap_label = Action::manager().label(_over); + FrameBufferImage *im = Action::manager().thumbnail(_over); + if (im) { + // set image content to thumbnail display + _snap_thumbnail.set( im ); + delete im; + } + else + _snap_thumbnail.reset(); + current_over = _over; + } + // draw thumbnail in tooltip + ImGui::BeginTooltip(); + _snap_thumbnail.Render(size.x); + ImGui::EndTooltip(); + ++count_over; // prevents display twice on item overlap + } + } + + // context menu on currently open snapshot + uint64_t current = Action::manager().currentSnapshot(); + if (ImGui::BeginPopup( "MenuSnapshot" ) && current > 0 ) + { + _selected = current; + ImGui::TextDisabled("Edit snapshot"); + // snapshot editable label + ImGui::SetNextItemWidth(size.x); + if ( ImGuiToolkit::InputText("##Rename", &_snap_label ) ) + Action::manager().setLabel( current, _snap_label); + // snapshot thumbnail + _snap_thumbnail.Render(size.x); + // snapshot actions + if (ImGui::Selectable( " " ICON_FA_ANGLE_DOUBLE_RIGHT " Apply", false, 0, size )) + Action::manager().restore(); + if (ImGui::Selectable( ICON_FA_STAR "_ Remove", false, 0, size )) + Action::manager().remove(); + if (ImGui::Selectable( ICON_FA_STAR "= Replace", false, 0, size )) + Action::manager().replace(); + ImGui::EndPopup(); + } + else + _selected = 0; + + // end list snapshots + ImGui::ListBoxFooter(); + } + // cancel tooltip and mouse over on mouse exit + if ( !ImGui::IsItemHovered()) { + _tooltip = false; + _over = 0; + } + + // Right panel buton + pos_bot = ImGui::GetCursorPos(); + + // right buttons + ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_top.y )); + if ( ImGuiToolkit::IconButton( ICON_FA_STAR "+")) + Action::manager().snapshot( "Snap" ); + if (ImGui::IsItemHovered()) + ImGuiToolkit::ToolTip("Take Snapshot ", CTRL_MOD "Y"); + + ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_bot.y - ImGui::GetFrameHeightWithSpacing())); + ImGuiToolkit::HelpMarker("Snapshots keeps a list of favorite\n" + "status of the current session.\n" + "Clic an item to preview or edit.\n" + "Double-clic to restore immediately.\n"); + +// ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_bot.y - 2.f * ImGui::GetFrameHeightWithSpacing())); +// ImGuiToolkit::HelpMarker("Snapshots capture the state of the session.\n" +// "Double-clic on a snapshot to restore it.\n\n" +// ICON_FA_ROUTE " Enable interpolation to animate\n" +// "from current state to snapshot's state."); +// // toggle button for smooth interpolation +// ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_bot.y - ImGui::GetFrameHeightWithSpacing()) ); +// ImGuiToolkit::ButtonToggle(ICON_FA_ROUTE, &Settings::application.smooth_snapshot); +// if (ImGui::IsItemHovered()) +// ImGuiToolkit::ToolTip("Snapshot interpolation"); + +// if (Action::manager().currentSnapshot() > 0) { +// ImGui::SetCursorPos( pos_bot ); +// int interpolation = static_cast (Action::manager().interpolation() * 100.f); +// ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); +// if ( ImGui::SliderInt("Animate", &interpolation, 0, 100, "%d %%") ) +// Action::manager().interpolate( static_cast ( interpolation ) * 0.01f ); + +// } + + ImGui::SetCursorPos( pos_bot ); + } + + // + // Buttons to show WINDOWS + // + ImGui::Spacing(); + ImGui::Text("Windows"); + ImGui::Spacing(); + + ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE); + std::string tooltip_ = ""; + + if ( ImGuiToolkit::IconButton( Rendering::manager().mainWindow().isFullscreen() ? ICON_FA_COMPRESS_ALT : ICON_FA_EXPAND_ALT ) ) + Rendering::manager().mainWindow().toggleFullscreen(); + if (ImGui::IsItemHovered()) + tooltip_ = "Fullscreen " CTRL_MOD "Shift+F"; + + ImGui::SameLine(0, 40); + if ( ImGuiToolkit::IconButton( ICON_FA_STICKY_NOTE ) ) + Mixer::manager().session()->addNote(); + if (ImGui::IsItemHovered()) + tooltip_ = "New note " CTRL_MOD "Shift+N"; + + ImGui::SameLine(0, 40); + if ( ImGuiToolkit::IconButton( ICON_FA_FILM ) ) + Settings::application.widget.media_player = true; + if (ImGui::IsItemHovered()) + tooltip_ = "Player " CTRL_MOD "P"; + + ImGui::SameLine(0, 40); + if ( ImGuiToolkit::IconButton( ICON_FA_DESKTOP ) ) + Settings::application.widget.preview = true; + if (ImGui::IsItemHovered()) + tooltip_ = "Output " CTRL_MOD "D"; + + ImGui::PopFont(); + if (!tooltip_.empty()) { + ImGuiToolkit::ToolTip(tooltip_.substr(0, tooltip_.size()-12).c_str(), tooltip_.substr(tooltip_.size()-12, 12).c_str()); + } + +} + +void Navigator::RenderMainPannelSettings() +{ + // TITLE + ImGui::SetCursorPosY(IMGUI_TOP_ALIGN); + ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE); + ImGui::Text("Settings"); + ImGui::PopFont(); + ImGui::SetCursorPosY(width_); + + // Appearance +// ImGuiToolkit::Icon(3, 2); +// ImGui::SameLine(0, 10); + ImGui::Text("Appearance"); + ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); + if ( ImGui::DragFloat("Scale", &Settings::application.scale, 0.01, 0.5f, 2.0f, "%.1f")) + ImGui::GetIO().FontGlobalScale = Settings::application.scale; + bool b = ImGui::RadioButton("Blue", &Settings::application.accent_color, 0); ImGui::SameLine(); + bool o = ImGui::RadioButton("Orange", &Settings::application.accent_color, 1); ImGui::SameLine(); + bool g = ImGui::RadioButton("Grey", &Settings::application.accent_color, 2); + if (b || o || g) + ImGuiToolkit::SetAccentColor(static_cast(Settings::application.accent_color)); + + // Options + ImGui::Spacing(); +// ImGuiToolkit::Icon(2, 2); +// ImGui::SameLine(0, 10); + ImGui::Text("Options"); + ImGuiToolkit::ButtonSwitch( ICON_FA_MOUSE_POINTER " Smooth cursor", &Settings::application.smooth_cursor); + ImGuiToolkit::ButtonSwitch( ICON_FA_TACHOMETER_ALT " Metrics", &Settings::application.widget.stats); + +#ifndef NDEBUG + ImGui::Text("Expert"); +// ImGuiToolkit::ButtonSwitch( IMGUI_TITLE_HISTORY, &Settings::application.widget.history); + ImGuiToolkit::ButtonSwitch( IMGUI_TITLE_SHADEREDITOR, &Settings::application.widget.shader_editor, CTRL_MOD "E"); + ImGuiToolkit::ButtonSwitch( IMGUI_TITLE_TOOLBOX, &Settings::application.widget.toolbox, CTRL_MOD "T"); + ImGuiToolkit::ButtonSwitch( IMGUI_TITLE_LOGS, &Settings::application.widget.logs, CTRL_MOD "L"); +#endif + + // system preferences + ImGui::Spacing(); +//#ifdef LINUX +// ImGuiToolkit::Icon(12, 6); +//#else +// ImGuiToolkit::Icon(6, 0); +//#endif +// ImGui::SameLine(0, 10); + ImGui::Text("System"); + static bool need_restart = false; + static bool vsync = (Settings::application.render.vsync > 0); + static bool blit = Settings::application.render.blit; + static bool multi = (Settings::application.render.multisampling > 0); + static bool gpu = Settings::application.render.gpu_decoding; + bool change = false; + change |= ImGuiToolkit::ButtonSwitch( "Vertical synchronization", &vsync); + change |= ImGuiToolkit::ButtonSwitch( "Blit framebuffer", &blit); + change |= ImGuiToolkit::ButtonSwitch( "Antialiasing framebuffer", &multi); + change |= ImGuiToolkit::ButtonSwitch( "Hardware video decoding", &gpu); + + if (change) { + need_restart = ( vsync != (Settings::application.render.vsync > 0) || + blit != Settings::application.render.blit || + multi != (Settings::application.render.multisampling > 0) || + gpu != Settings::application.render.gpu_decoding ); + } + if (need_restart) { + ImGui::Spacing(); + if (ImGui::Button( ICON_FA_POWER_OFF " Restart to apply", ImVec2(ImGui::GetContentRegionAvail().x - 50, 0))) { + Settings::application.render.vsync = vsync ? 1 : 0; + Settings::application.render.blit = blit; + Settings::application.render.multisampling = multi ? 3 : 0; + Settings::application.render.gpu_decoding = gpu; + Rendering::manager().close(); + } + } + +} void Navigator::RenderTransitionPannel() { @@ -2701,7 +3534,7 @@ void Navigator::RenderTransitionPannel() if (ImGui::Begin("##navigatorTrans", NULL, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav)) { // TITLE - ImGui::SetCursorPosY(10); + ImGui::SetCursorPosY(IMGUI_TOP_ALIGN); ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE); ImGui::Text("Transition"); ImGui::PopFont(); @@ -2748,8 +3581,8 @@ void Navigator::RenderTransitionPannel() if ( ImGui::Button( ICON_FA_DOOR_OPEN " Exit", ImVec2(ImGui::GetContentRegionAvail().x, 0)) ) Mixer::manager().setView(View::MIXING); + ImGui::End(); } - ImGui::End(); } void Navigator::RenderMainPannel() @@ -2760,257 +3593,20 @@ void Navigator::RenderMainPannel() ImGui::SetNextWindowBgAlpha(0.85f); // Transparent background if (ImGui::Begin("##navigatorMain", NULL, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav)) { - // TITLE - ImGui::SetCursorPosY(10); - ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE); - ImGui::Text(APP_NAME); - ImGui::PopFont(); - - // Icon to switch fullscreen - ImGui::SetCursorPos(ImVec2(pannel_width_ - 40.f, 13.f)); - const char *tooltip[2] = {"Enter Fullscreen (" CTRL_MOD "Shift+F)", "Exit Fullscreen (" CTRL_MOD "Shift+F)"}; - bool fs = Rendering::manager().mainWindow().isFullscreen(); - if ( ImGuiToolkit::IconToggle(4,15,3,15, &fs, tooltip ) ) { - Rendering::manager().mainWindow().toggleFullscreen(); - } - // Session menu - ImGui::SetCursorPosY(width_); - ImGui::Text("Session"); - ImGui::SameLine(); - ImGui::SetCursorPosX(pannel_width_ IMGUI_RIGHT_ALIGN); - if (ImGui::BeginMenu("File")) - { - UserInterface::manager().showMenuFile(); - ImGui::EndMenu(); - } - - static bool selection_session_mode_changed = true; - static int selection_session_mode = 0; - // - // Session quick selection pannel + // Panel content depends on show_config_ // - - // Show combo box of quick selection modes - ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); - if (ImGui::BeginCombo("##SelectionSession", SystemToolkit::trunc_filename(Settings::application.recentFolders.path, 25).c_str() )) { - - // Option 0 : recent files - if (ImGui::Selectable( ICON_FA_HISTORY IMGUI_LABEL_RECENT_FILES) ) { - Settings::application.recentFolders.path = IMGUI_LABEL_RECENT_FILES; - selection_session_mode = 0; - selection_session_mode_changed = true; - } - // Options 1 : known folders - for(auto foldername = Settings::application.recentFolders.filenames.begin(); - foldername != Settings::application.recentFolders.filenames.end(); foldername++) { - std::string f = std::string(ICON_FA_FOLDER) + " " + SystemToolkit::trunc_filename( *foldername, 40); - if (ImGui::Selectable( f.c_str() )) { - // remember which path was selected - Settings::application.recentFolders.path.assign(*foldername); - // set mode - selection_session_mode = 1; - selection_session_mode_changed = true; - } - } - // Option 2 : add a folder - if (ImGui::Selectable( ICON_FA_FOLDER_PLUS " Add Folder") ){ - if (recentFolderFileDialogs.empty()) { - recentFolderFileDialogs.emplace_back( std::async(std::launch::async, DialogToolkit::FolderDialog, Settings::application.recentFolders.path) ); - fileDialogPending_ = true; - } - } - ImGui::EndCombo(); - } - - // return from thread for folder openning - if ( !recentFolderFileDialogs.empty() ) { - // check that file dialog thread finished - if (recentFolderFileDialogs.back().wait_for(timeout) == std::future_status::ready ) { - // get the filename from this file dialog - std::string foldername = recentFolderFileDialogs.back().get(); - if (!foldername.empty()) { - Settings::application.recentFolders.push(foldername); - Settings::application.recentFolders.path.assign(foldername); - selection_session_mode = 1; - selection_session_mode_changed = true; - } - // done with this file dialog - recentFolderFileDialogs.pop_back(); - fileDialogPending_ = false; - - } - } - - // icon to clear list - ImVec2 pos = ImGui::GetCursorPos(); - ImGui::SameLine(); - ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.7); - bool reset = false; - if ( selection_session_mode == 1) { - const char *tooltip_[2] = {"Discard folder", "Discard folder"}; - if (ImGuiToolkit::IconToggle(12,14,11,14, &reset, tooltip_)) { - Settings::application.recentFolders.filenames.remove(Settings::application.recentFolders.path); - if (Settings::application.recentFolders.filenames.empty()) { - Settings::application.recentFolders.path.assign(IMGUI_LABEL_RECENT_FILES); - selection_session_mode = 0; - } - else - Settings::application.recentFolders.path = Settings::application.recentFolders.filenames.front(); - // reload the list next time - selection_session_mode_changed = true; - } - } - else { - const char *tooltip__[2] = {"Clear history", "Clear history"}; - if (ImGuiToolkit::IconToggle(12,14,11,14, &reset, tooltip__)) { - Settings::application.recentSessions.filenames.clear(); - Settings::application.recentSessions.front_is_valid = false; - // reload the list next time - selection_session_mode_changed = true; - } - } - ImGui::PopStyleVar(); - ImGui::SetCursorPos(pos); - - // fill the session list depending on the mode - static std::list sessions_list; - // change session list if changed - if (selection_session_mode_changed || Settings::application.recentSessions.changed) { - - // selection MODE 0 ; RECENT sessions - if ( selection_session_mode == 0) { - // show list of recent sessions - sessions_list = Settings::application.recentSessions.filenames; - Settings::application.recentSessions.changed = false; - } - // selection MODE 1 : LIST FOLDER - else if ( selection_session_mode == 1) { - // show list of vimix files in folder - sessions_list = SystemToolkit::list_directory( Settings::application.recentFolders.path, "mix"); - } - // indicate the list changed (do not change at every frame) - selection_session_mode_changed = false; - } - - // display the sessions list and detect if one was selected (double clic) - bool session_selected = false; - ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); - ImGui::ListBoxHeader("##Sessions", CLAMP(sessions_list.size(), 4, 8)); - static std::string file_info = ""; - static std::list::iterator file_selected = sessions_list.end(); - for(auto it = sessions_list.begin(); it != sessions_list.end(); it++) { - std::string sessionfilename(*it); - if (sessionfilename.empty()) - continue; - std::string shortname = SystemToolkit::filename(*it); - if (ImGui::Selectable( shortname.c_str(), false, ImGuiSelectableFlags_AllowDoubleClick )) { - if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left) || file_selected == it) { - Mixer::manager().open( sessionfilename ); - session_selected = true; - file_info.clear(); - } - else { - file_info = SessionCreator::info(sessionfilename); - if (file_info.empty()) { - // failed : remove from recent - if ( selection_session_mode == 0) { - Settings::application.recentSessions.filenames.remove(sessionfilename); - selection_session_mode_changed = true; - } - } - else - file_selected = it; - } - } - if (ImGui::IsItemHovered()) { - if (file_selected != it) { - file_info.clear(); - file_selected = sessions_list.end(); - } - else if (!file_info.empty()) { - ImGui::BeginTooltip(); - ImGui::Text("%s", file_info.c_str()); - ImGui::EndTooltip(); - } - } - } - ImGui::ListBoxFooter(); - - pos = ImGui::GetCursorPos(); - ImGui::SameLine(); - ImGuiToolkit::HelpMarker("Quick access to Session files;\nSelect the history of recently\nopened files or a folder, and\ndouble-clic a filename to open."); - ImGui::SetCursorPos(pos); - - // done the selection ! - if (session_selected) { - // close pannel - file_info.clear(); - hidePannel(); - // reload the list next time - selection_session_mode_changed = true; - } - - // options session - ImGui::Spacing(); - ImGui::Spacing(); - ImGui::Text("Options"); - ImGuiToolkit::ButtonSwitch( ICON_FA_ARROW_CIRCLE_RIGHT " Smooth transition", &Settings::application.smooth_transition); - ImGuiToolkit::ButtonSwitch( ICON_FA_MOUSE_POINTER " Smooth cursor", &Settings::application.smooth_cursor); - - - // Continue Main pannel - // WINDOWS - ImGui::Spacing(); - ImGui::Text("Windows"); - ImGuiToolkit::ButtonSwitch( IMGUI_TITLE_PREVIEW, &Settings::application.widget.preview, CTRL_MOD "D"); - ImGuiToolkit::ButtonSwitch( IMGUI_TITLE_MEDIAPLAYER, &Settings::application.widget.media_player, CTRL_MOD "P"); -#ifndef NDEBUG - ImGuiToolkit::ButtonSwitch( IMGUI_TITLE_SHADEREDITOR, &Settings::application.widget.shader_editor, CTRL_MOD "E"); - ImGuiToolkit::ButtonSwitch( IMGUI_TITLE_TOOLBOX, &Settings::application.widget.toolbox, CTRL_MOD "T"); - ImGuiToolkit::ButtonSwitch( ICON_FA_LIST " Logs", &Settings::application.widget.logs, CTRL_MOD "L"); -#endif - ImGuiToolkit::ButtonSwitch( ICON_FA_HISTORY " History", &Settings::application.widget.history); - ImGuiToolkit::ButtonSwitch( ICON_FA_TACHOMETER_ALT " Metrics", &Settings::application.widget.stats); - - // Settings & application appearance - static bool show_config = false; - - ImGui::Spacing(); - if (show_config) - { - ImGui::Text("System"); - bool vsync = (Settings::application.render.vsync > 0); - ImGuiToolkit::ButtonSwitch( "Vertical synchronization", &vsync); - Settings::application.render.vsync = vsync ? 1 : 0; - ImGuiToolkit::ButtonSwitch( "Blit framebuffer", &Settings::application.render.blit); - bool multi = (Settings::application.render.multisampling > 0); - ImGuiToolkit::ButtonSwitch( "Antialiasing framebuffer", &multi); - Settings::application.render.multisampling = multi ? 3 : 0; - ImGuiToolkit::ButtonSwitch( "Hardware video decoding", &Settings::application.render.gpu_decoding); - ImGui::Spacing(); - if (ImGui::Button( ICON_FA_POWER_OFF " Restart to apply", ImVec2(ImGui::GetContentRegionAvail().x - 50, 0))) - Rendering::manager().close(); - } - else { - ImGui::Text("Appearance"); - ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); - if ( ImGui::DragFloat("Scale", &Settings::application.scale, 0.01, 0.5f, 2.0f, "%.1f")) - ImGui::GetIO().FontGlobalScale = Settings::application.scale; - bool b = ImGui::RadioButton("Blue", &Settings::application.accent_color, 0); ImGui::SameLine(); - bool o = ImGui::RadioButton("Orange", &Settings::application.accent_color, 1); ImGui::SameLine(); - bool g = ImGui::RadioButton("Grey", &Settings::application.accent_color, 2); - if (b || o || g) - ImGuiToolkit::SetAccentColor(static_cast(Settings::application.accent_color)); - - } + if (show_config_) + RenderMainPannelSettings(); + else + RenderMainPannelVimix(); // Bottom aligned Logo (if enougth room) static unsigned int vimixicon = Resource::getTextureImage("images/vimix_256x256.png"); static float height_about = 1.6f * ImGui::GetTextLineHeightWithSpacing(); bool show_icon = ImGui::GetCursorPosY() + height_about + 128.f < height_ ; if ( show_icon ) { - ImGui::SetCursorPos(ImVec2(pannel_width_ / 2.f - 64.f, height_ -height_about - 128.f)); + ImGui::SetCursorPos(ImVec2((pannel_width_ -1.5f * ImGui::GetTextLineHeightWithSpacing()) / 2.f - 64.f, height_ -height_about - 128.f)); ImGui::Image((void*)(intptr_t)vimixicon, ImVec2(128, 128)); } else { @@ -3018,181 +3614,147 @@ void Navigator::RenderMainPannel() } // About & System config toggle - if ( ImGui::Button( ICON_FA_CROW " About vimix", ImVec2(ImGui::GetContentRegionAvail().x - 50, 0)) ) + if ( ImGui::Button( ICON_FA_CROW " About vimix", ImVec2(pannel_width_ IMGUI_RIGHT_ALIGN, 0)) ) UserInterface::manager().show_vimix_about = true; - ImGui::SameLine(); - ImGuiToolkit::IconToggle(13,5,12,5,&show_config); + ImGui::SameLine(0, ImGui::GetTextLineHeightWithSpacing()); + ImGuiToolkit::IconToggle(13,5,12,5,&show_config_); - } - ImGui::End(); -} -void UserInterface::RenderMetrics(bool *p_open, int* p_corner, int *p_mode) -{ - static guint64 start_time_1_ = gst_util_get_timestamp (); - static guint64 start_time_2_ = gst_util_get_timestamp (); - - if (!p_corner || !p_open) - return; - - const float DISTANCE = 10.0f; - int corner = *p_corner; - ImGuiIO& io = ImGui::GetIO(); - if (corner != -1) - { - ImVec2 window_pos = ImVec2((corner & 1) ? io.DisplaySize.x - DISTANCE : DISTANCE, (corner & 2) ? io.DisplaySize.y - DISTANCE : DISTANCE); - ImVec2 window_pos_pivot = ImVec2((corner & 1) ? 1.0f : 0.0f, (corner & 2) ? 1.0f : 0.0f); - ImGui::SetNextWindowPos(window_pos, ImGuiCond_Always, window_pos_pivot); - } - - ImGui::SetNextWindowBgAlpha(0.35f); // Transparent background - - if (ImGui::Begin("Metrics", NULL, (corner != -1 ? ImGuiWindowFlags_NoMove : 0) | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav)) - { - ImGui::SetNextItemWidth(200); - ImGui::Combo("##mode", p_mode, - ICON_FA_TACHOMETER_ALT " Performance\0" - ICON_FA_HOURGLASS_HALF " Timers\0" - ICON_FA_VECTOR_SQUARE " Source\0"); - - ImGui::SameLine(); - if (ImGuiToolkit::IconButton(5,8)) - ImGui::OpenPopup("metrics_menu"); - ImGui::Spacing(); - - if (*p_mode > 1) { - ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO); - Source *s = Mixer::manager().currentSource(); - if (s) { - float rightalign = -2.5f * ImGui::GetTextLineHeightWithSpacing(); - std::ostringstream info; - info << s->name() << ": "; - - float v = s->alpha(); - ImGui::SetNextItemWidth(rightalign); - if ( ImGui::DragFloat("Alpha", &v, 0.01f, 0.f, 1.f) ) - s->setAlpha(v); - if ( ImGui::IsItemDeactivatedAfterEdit() ) { - info << "Alpha " << std::fixed << std::setprecision(3) << v; - Action::manager().store(info.str()); - } - - Group *n = s->group(View::GEOMETRY); - float translation[2] = { n->translation_.x, n->translation_.y}; - ImGui::SetNextItemWidth(rightalign); - if ( ImGui::DragFloat2("Pos", translation, 0.01f, -MAX_SCALE, MAX_SCALE, "%.2f") ) { - n->translation_.x = translation[0]; - n->translation_.y = translation[1]; - s->touch(); - } - if ( ImGui::IsItemDeactivatedAfterEdit() ){ - info << "Position " << std::setprecision(3) << n->translation_.x << ", " << n->translation_.y; - Action::manager().store(info.str()); - } - float scale[2] = { n->scale_.x, n->scale_.y} ; - ImGui::SetNextItemWidth(rightalign); - if ( ImGui::DragFloat2("Scale", scale, 0.01f, -MAX_SCALE, MAX_SCALE, "%.2f") ) - { - n->scale_.x = CLAMP_SCALE(scale[0]); - n->scale_.y = CLAMP_SCALE(scale[1]); - s->touch(); - } - if ( ImGui::IsItemDeactivatedAfterEdit() ){ - info << "Scale " << std::setprecision(3) << n->scale_.x << " x " << n->scale_.y; - Action::manager().store(info.str()); - } - - ImGui::SetNextItemWidth(rightalign); - if ( ImGui::SliderAngle("Angle", &(n->rotation_.z), -180.f, 180.f) ) - s->touch(); - if ( ImGui::IsItemDeactivatedAfterEdit() ) { - info << "Angle " << std::setprecision(3) << n->rotation_.z * 180.f / M_PI; - Action::manager().store(info.str()); - } - } - else - ImGui::Text("No source selected"); - ImGui::PopFont(); - } - else if (*p_mode > 0) { - guint64 time_ = gst_util_get_timestamp (); - - ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE); - ImGui::Text("%s", GstToolkit::time_to_string(time_-start_time_1_, GstToolkit::TIME_STRING_FIXED).c_str()); - ImGui::PopFont(); - ImGui::SameLine(0, 10); - if (ImGuiToolkit::IconButton(12, 14)) - start_time_1_ = time_; // reset timer 1 - ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE); - ImGui::Text("%s", GstToolkit::time_to_string(time_-start_time_2_, GstToolkit::TIME_STRING_FIXED).c_str()); - ImGui::PopFont(); - ImGui::SameLine(0, 10); - if (ImGuiToolkit::IconButton(12, 14)) - start_time_2_ = time_; // reset timer 2 - - } - else { - ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO); - ImGui::Text("Window %.0f x %.0f", io.DisplaySize.x, io.DisplaySize.y); - // ImGui::Text("HiDPI (retina) %s", io.DisplayFramebufferScale.x > 1.f ? "on" : "off"); - ImGui::Text("Refresh %.1f FPS", io.Framerate); - ImGui::Text("Memory %s", SystemToolkit::byte_to_string( SystemToolkit::memory_usage()).c_str() ); - ImGui::PopFont(); - - } - - if (ImGui::BeginPopup("metrics_menu")) - { - if (ImGui::MenuItem( ICON_FA_ANGLE_UP " Top", NULL, corner == 1)) *p_corner = 1; - if (ImGui::MenuItem( ICON_FA_ANGLE_DOWN " Bottom", NULL, corner == 3)) *p_corner = 3; - if (ImGui::MenuItem( ICON_FA_EXPAND_ARROWS_ALT " Free position", NULL, corner == -1)) *p_corner = -1; - if (p_open && ImGui::MenuItem( ICON_FA_TIMES " Close")) *p_open = false; - ImGui::EndPopup(); - } ImGui::End(); } } +/// +/// SOURCE PREVIEW +/// -//namespace ImGui -//{ +SourcePreview::SourcePreview() : source_(nullptr), label_(""), reset_(0) +{ -//int hover(const char *label) -//{ -// const ImGuiStyle& Style = GetStyle(); -// ImGuiWindow* Window = GetCurrentWindow(); -// if (Window->SkipItems) -// return 0; +} -// int hovered = IsItemActive() || IsItemHovered(); -// Dummy(ImVec2(0,3)); +void SourcePreview::setSource(Source *s, const string &label) +{ + if(source_) + delete source_; -// // prepare canvas -// const float avail = GetContentRegionAvailWidth(); -// const float dim = ImMin(avail, 128.f); -// ImVec2 Canvas(dim, dim); + source_ = s; + label_ = label; + reset_ = true; +} -// ImRect bb(Window->DC.CursorPos, Window->DC.CursorPos + Canvas); -// const ImGuiID id = Window->GetID(label); -// ItemSize(bb); -// if (!ItemAdd(bb, id)) -// return 0; +Source * SourcePreview::getSource() +{ + Source *s = source_; + source_ = nullptr; + return s; +} -// hovered |= 0 != IsItemClicked(); +void SourcePreview::Render(float width, bool controlbutton) +{ + if(source_) { + // cancel if failed + if (source_->failed()) { + // remove from list of recent import files if relevant + MediaSource *failedFile = dynamic_cast(source_); + if (failedFile != nullptr) { + Settings::application.recentImport.remove( failedFile->path() ); + } + setSource(); + } + else + { + // render framebuffer + if ( reset_ && source_->ready() ) { + // trick to ensure a minimum of 2 frames are rendered actively + source_->setActive(true); + source_->update( Mixer::manager().dt() ); + source_->render(); + source_->setActive(false); + reset_ = false; + } + else { + // update source + source_->update( Mixer::manager().dt() ); + source_->render(); + } -// RenderFrame(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg, 1), true, Style.FrameRounding); + // draw preview + FrameBuffer *frame = source_->frame(); + ImVec2 preview_size(width, width / frame->aspectRatio()); + ImGui::Image((void*)(uintptr_t) frame->texture(), preview_size); + if (controlbutton && source_->ready()) { + ImVec2 pos = ImGui::GetCursorPos(); + ImGui::SameLine(); + bool active = source_->active(); + if (ImGuiToolkit::IconToggle(12,7,1,8, &active)) + source_->setActive(active); + ImGui::SetCursorPos(pos); + } + ImGuiToolkit::Icon(source_->icon().x, source_->icon().y); + ImGui::SameLine(0, 10); + ImGui::Text("%s", label_.c_str()); + if (source_->ready()) + ImGui::Text("%d x %d %s", frame->width(), frame->height(), frame->use_alpha() ? "RGBA" : "RGB"); + else + ImGui::Text("loading..."); + } + } +} -// return 1; -//} +bool SourcePreview::ready() const +{ + return source_ != nullptr && source_->ready(); +} -//} +/// +/// THUMBNAIL +/// + +Thumbnail::Thumbnail() : aspect_ratio_(-1.f), texture_(0) +{ +} + +Thumbnail::~Thumbnail() +{ + if (texture_) + glDeleteTextures(1, &texture_); +} + +void Thumbnail::reset() +{ + aspect_ratio_ = -1.f; +} + +void Thumbnail::set(const FrameBufferImage *image) +{ + if (!texture_) { + glGenTextures(1, &texture_); + glBindTexture( GL_TEXTURE_2D, texture_); + glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGB8, SESSION_THUMBNAIL_HEIGHT * 2, SESSION_THUMBNAIL_HEIGHT); + } + + aspect_ratio_ = static_cast(image->width) / static_cast(image->height); + glBindTexture( GL_TEXTURE_2D, texture_); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, image->width, image->height, GL_RGB, GL_UNSIGNED_BYTE, image->rgb); + + glBindTexture(GL_TEXTURE_2D, 0); +} + +void Thumbnail::Render(float width) +{ + if (aspect_ratio_>0.f) + ImGui::Image((void*)(intptr_t)texture_, ImVec2(width, width/aspect_ratio_), ImVec2(0,0), ImVec2(0.5f*aspect_ratio_, 1.f)); +} + +/// +/// UTILITY +/// #define SEGMENT_ARRAY_MAX 1000 - #define MAXSIZE 65535 - void ShowSandbox(bool* p_open) { ImGui::SetNextWindowPos(ImVec2(100, 100), ImGuiCond_FirstUseEver); @@ -3204,7 +3766,16 @@ void ShowSandbox(bool* p_open) } ImGui::Text("Testing sandox"); + ImGui::Separator(); + ImGui::Text("Source list"); + Session *se = Mixer::manager().session(); + for (auto sit = se->begin(); sit != se->end(); ++sit) { + + ImGui::Text("[%s] %s ", std::to_string((*sit)->id()).c_str(), (*sit)->name().c_str()); + } + + ImGui::Separator(); static char buf1[1280] = "videotestsrc pattern=smpte"; ImGui::InputText("gstreamer pipeline", buf1, 1280); @@ -3218,178 +3789,14 @@ void ShowSandbox(bool* p_open) if ( ImGui::Button("Execute") ) SystemToolkit::execute(str); - if (ImGui::Button("Message test")) { - - for(int i=0; i<10; i++) { - Log::Notify("Testing notification %d", i); - } - - for(int i=0; i<10; i++) { - Log::Warning("Testing Warning %d", i); - } - - } - - - -// const guint64 duration = GST_SECOND * 6; -// const guint64 step = GST_MSECOND * 20; -// static guint64 t = 0; - -// static float *arr_lines = nullptr; -// static float *arr_histo = nullptr; -// static uint array_size = 200; - -// if (arr_lines == nullptr) { - -// arr_lines = (float *) malloc(array_size * sizeof(float)); -// arr_histo = (float *) malloc(array_size * sizeof(float)); - -// for (int i = 0; i < array_size; ++i) { -// arr_lines[i] = 1.f; -// arr_histo[i] = 0.f; -// } -// } - -// // scrolling sub-window -// ImGui::BeginChild("##scrolling", -// ImVec2(ImGui::GetContentRegionAvail().x, 250), -// false, ImGuiWindowFlags_HorizontalScrollbar); - - -// if (arr_lines != nullptr) -// { - -// ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(1, 1)); -// ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 1.f); - -// ImVec2 size = ImGui::CalcItemSize(ImVec2(-FLT_MIN, 0.0f), ImGui::CalcItemWidth(), 40); -// size.x *= 1.f; - -//// // draw position when entering -//// ImVec2 draw_pos = ImGui::GetCursorPos(); - -////// // capture user input -////// uint press_index = array_size-1; -////// float val = 0.f; -////// bool pressed = false; - -////// uint starting_index = press_index; -////// pressed = ImGuiToolkit::InvisibleDoubleSliderFloat("test", &press_index, &val, 0, array_size-1, size); -////// if (pressed) -////// { -////// for (int i = MIN(starting_index, press_index); i < MAX(starting_index, press_index); ++i) -////// arr[i] = val; - -//////// starting_index = press_index; -////// } - -//// float x = -1.f; -//// float y = -1.f; -//// bool clicked = ImGuiToolkit::InvisibleCoordinatesFloat("test", &x, &y, size); -//// if (clicked) { -//// Log::Info("clic %f %f in [%f %f]", x, y, size.x, size.y); -//// } - - -//// // back to -//// ImGui::SetCursorPos(draw_pos); -//// // plot lines -//// ImGui::PlotLines("Lines", arr, array_size-1, 0, NULL, 0.0f, 1.0f, size); - -////// size.y = 20; -////// ImGui::PlotHistogram("Hisfd", arr, array_size-1, 0, NULL, 0.0f, 1.0f, size); -// bool r = false; -// ImGuiToolkit::EditPlotHistoLines("Alpha", arr_histo, arr_lines, array_size, 0.f, 1.f, &r, size); - -// bool slider_pressed = ImGuiToolkit::TimelineSlider("timeline", &t, 0, duration, step, size.x); - -// ImGui::PopStyleVar(2); - -// ImGui::Text("Timeline t %" GST_STIME_FORMAT "\n", GST_STIME_ARGS(t)); -// ImGui::Text("Timeline Pressed %s", slider_pressed ? "on" : "off"); - -// static int w = 0; -// ImGui::SetNextItemWidth(size.x); -// ImGui::SliderInt("##int", &w, 0, array_size-1); - -// } - -// ImGui::EndChild(); - - static char str0[128] = "àöäüèáû вторая строчка"; ImGui::InputText("##inputtext", str0, IM_ARRAYSIZE(str0)); - std::string tra = SystemToolkit::transliterate(std::string(str0)); + std::string tra = BaseToolkit::transliterate(std::string(str0)); ImGui::Text("Transliteration: '%s'", tra.c_str()); ImGui::End(); } -void UserInterface::RenderAbout(bool* p_open) -{ - ImGui::SetNextWindowPos(ImVec2(1000, 20), ImGuiCond_FirstUseEver); - if (!ImGui::Begin("About " APP_NAME APP_TITLE, p_open, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_AlwaysAutoResize)) - { - ImGui::End(); - return; - } - -#ifdef VIMIX_VERSION_MAJOR - ImGuiToolkit::PushFont(ImGuiToolkit::FONT_BOLD); - ImGui::Text("%s %d.%d.%d", APP_NAME, VIMIX_VERSION_MAJOR, VIMIX_VERSION_MINOR, VIMIX_VERSION_PATCH); - ImGui::PopFont(); -#else - ImGuiToolkit::PushFont(ImGuiToolkit::FONT_BOLD); - ImGui::Text("%s", APP_NAME); - ImGui::PopFont(); -#endif - - ImGui::Separator(); - ImGui::Text("vimix performs graphical mixing and blending of\nseveral movie clips and computer generated graphics,\nwith image processing effects in real-time."); - ImGui::Text("\nvimix is licensed under the GNU GPL version 3.\nCopyright 2019-2021 Bruno Herbelin."); - - ImGui::Spacing(); - ImGuiToolkit::ButtonOpenUrl("https://brunoherbelin.github.io/vimix/", ImVec2(ImGui::GetContentRegionAvail().x, 0)); - - - ImGui::Spacing(); - ImGui::Text("\nvimix is built using the following libraries:"); - -// tinyfd_inputBox("tinyfd_query", NULL, NULL); -// ImGui::Text("- Tinyfiledialogs v%s mode '%s'", tinyfd_version, tinyfd_response); - - ImGui::Columns(3, "abouts"); - ImGui::Separator(); - - ImGui::Text("- Dear ImGui"); - ImGui::PushID("dearimguiabout"); - if ( ImGui::Button("More info", ImVec2(ImGui::GetContentRegionAvail().x, 0))) - show_imgui_about = true; - ImGui::PopID(); - - ImGui::NextColumn(); - - ImGui::Text("- GStreamer"); - ImGui::PushID("gstreamerabout"); - if ( ImGui::Button("More info", ImVec2(ImGui::GetContentRegionAvail().x, 0))) - show_gst_about = true; - ImGui::PopID(); - - ImGui::NextColumn(); - - ImGui::Text("- OpenGL"); - ImGui::PushID("openglabout"); - if ( ImGui::Button("More info", ImVec2(ImGui::GetContentRegionAvail().x, 0))) - show_opengl_about = true; - ImGui::PopID(); - - ImGui::Columns(1); - - - ImGui::End(); -} - void ShowAboutOpengl(bool* p_open) { ImGui::SetNextWindowPos(ImVec2(430, 640), ImGuiCond_FirstUseEver); @@ -3404,7 +3811,7 @@ void ShowAboutOpengl(bool* p_open) ImGui::PopFont(); ImGui::Separator(); ImGui::Text("OpenGL is the premier environment for developing portable, \ninteractive 2D and 3D graphics applications."); - ImGuiToolkit::ButtonOpenUrl("https://www.opengl.org"); + ImGuiToolkit::ButtonOpenUrl("Visit website", "https://www.opengl.org"); ImGui::SameLine(); static bool show_opengl_info = false; @@ -3456,98 +3863,145 @@ void ShowAboutOpengl(bool* p_open) ImGui::End(); } - - void ShowAboutGStreamer(bool* p_open) { ImGui::SetNextWindowPos(ImVec2(430, 20), ImGuiCond_Appearing); ImGui::SetNextWindowSize(ImVec2(600, 200), ImGuiCond_Appearing); - ImGui::Begin("About Gstreamer", p_open, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings); - - ImGuiToolkit::PushFont(ImGuiToolkit::FONT_BOLD); - ImGui::Text("GStreamer %s", GstToolkit::gst_version().c_str()); - ImGui::PopFont(); - ImGui::Separator(); - ImGui::Text("A flexible, fast and multiplatform multimedia framework."); - ImGui::Text("GStreamer is licensed under the LGPL License."); - ImGuiToolkit::ButtonOpenUrl("https://gstreamer.freedesktop.org/"); - ImGui::SameLine(); - - static bool show_config_info = false; - ImGui::SetNextItemWidth(-100.f); - ImGui::Text(" Details"); - ImGui::SameLine(); - ImGuiToolkit::IconToggle(10,0,11,0,&show_config_info); - if (show_config_info) + if (ImGui::Begin("About Gstreamer", p_open, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings)) { - ImGui::Separator(); - bool copy_to_clipboard = ImGui::Button( ICON_FA_COPY " Copy"); - ImGui::SameLine(0.f, 60.f); - static char _filter[64] = ""; ImGui::InputText("Filter", _filter, 64); - ImGui::SameLine(); - if ( ImGuiToolkit::ButtonIcon( 12, 14 ) ) - _filter[0] = '\0'; - std::string filter(_filter); - - ImGui::BeginChildFrame(ImGui::GetID("gstinfos"), ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * 18), ImGuiWindowFlags_NoMove); - if (copy_to_clipboard) - { - ImGui::LogToClipboard(); - ImGui::LogText("```\n"); - } - + ImGuiToolkit::PushFont(ImGuiToolkit::FONT_BOLD); ImGui::Text("GStreamer %s", GstToolkit::gst_version().c_str()); - ImGui::Text("Plugins & features (runtime) :"); + ImGui::PopFont(); + ImGui::Separator(); + ImGui::Text("A flexible, fast and multiplatform multimedia framework."); + ImGui::Text("GStreamer is licensed under the LGPL License."); + ImGuiToolkit::ButtonOpenUrl("Visit website", "https://gstreamer.freedesktop.org/"); + ImGui::SameLine(); - std::list filteredlist; - static std::list pluginslist; - static std::map > featureslist; - if (pluginslist.empty()) { - pluginslist = GstToolkit::all_plugins(); - for (auto const& i: pluginslist) { - // list features - featureslist[i] = GstToolkit::all_plugin_features(i); - } - } - - // filter list - if ( filter.empty() ) - filteredlist = pluginslist; - else { - for (auto const& i: pluginslist) { - // add plugin if plugin name matches - if ( i.find(filter) != std::string::npos ) - filteredlist.push_back( i ); - // check in features - for (auto const& j: featureslist[i]) { - // add plugin if feature name matches - if ( j.find(filter) != std::string::npos ) - filteredlist.push_back( i ); - } - } - filteredlist.unique(); - } - - // display list - for (auto const& t: filteredlist) { - ImGui::Text("> %s", t.c_str()); - for (auto const& j: featureslist[t]) { - if ( j.find(filter) != std::string::npos ) - { - ImGui::Text(" - %s", j.c_str()); - } - } - } - - if (copy_to_clipboard) + static bool show_config_info = false; + ImGui::SetNextItemWidth(-100.f); + ImGui::Text(" Details"); + ImGui::SameLine(); + ImGuiToolkit::IconToggle(10,0,11,0,&show_config_info); + if (show_config_info) { - ImGui::LogText("\n```\n"); - ImGui::LogFinish(); + ImGui::Separator(); + bool copy_to_clipboard = ImGui::Button( ICON_FA_COPY " Copy"); + ImGui::SameLine(0.f, 60.f); + static char _filter[64] = ""; ImGui::InputText("Filter", _filter, 64); + ImGui::SameLine(); + if ( ImGuiToolkit::ButtonIcon( 12, 14 ) ) + _filter[0] = '\0'; + std::string filter(_filter); + + ImGui::BeginChildFrame(ImGui::GetID("gstinfos"), ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * 18), ImGuiWindowFlags_NoMove); + if (copy_to_clipboard) + { + ImGui::LogToClipboard(); + ImGui::LogText("```\n"); + } + + ImGui::Text("GStreamer %s", GstToolkit::gst_version().c_str()); + ImGui::Text("Plugins & features (runtime) :"); + + std::list filteredlist; + static std::list pluginslist; + static std::map > featureslist; + if (pluginslist.empty()) { + pluginslist = GstToolkit::all_plugins(); + for (auto const& i: pluginslist) { + // list features + featureslist[i] = GstToolkit::all_plugin_features(i); + } + } + + // filter list + if ( filter.empty() ) + filteredlist = pluginslist; + else { + for (auto const& i: pluginslist) { + // add plugin if plugin name matches + if ( i.find(filter) != std::string::npos ) + filteredlist.push_back( i ); + // check in features + for (auto const& j: featureslist[i]) { + // add plugin if feature name matches + if ( j.find(filter) != std::string::npos ) + filteredlist.push_back( i ); + } + } + filteredlist.unique(); + } + + // display list + for (auto const& t: filteredlist) { + ImGui::Text("> %s", t.c_str()); + for (auto const& j: featureslist[t]) { + if ( j.find(filter) != std::string::npos ) + { + ImGui::Text(" - %s", j.c_str()); + } + } + } + + if (copy_to_clipboard) + { + ImGui::LogText("\n```\n"); + ImGui::LogFinish(); + } + + ImGui::EndChildFrame(); } - - ImGui::EndChildFrame(); + ImGui::End(); } - - ImGui::End(); +} + +void SetMouseCursor(ImVec2 mousepos, View::Cursor c) +{ + // Hack if GLFW does not have all cursors, ask IMGUI to redraw cursor +#if GLFW_HAS_NEW_CURSORS == 0 + ImGui::GetIO().MouseDrawCursor = (c.type > 0); // only redraw non-arrow cursor +#endif + ImGui::SetMouseCursor(c.type); + + if ( !c.info.empty()) { + float d = 0.5f * ImGui::GetFrameHeight() ; + ImVec2 window_pos = ImVec2( mousepos.x - d, mousepos.y - d ); + ImGui::SetNextWindowPos(window_pos, ImGuiCond_Always); + ImGui::SetNextWindowBgAlpha(0.75f); // Transparent background + if (ImGui::Begin("MouseInfoContext", NULL, ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav)) + { + ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO); + ImGui::Text(" %s", c.info.c_str()); + ImGui::PopFont(); + ImGui::End(); + } + } +} + +void SetNextWindowVisible(ImVec2 pos, ImVec2 size, float margin) +{ + bool need_update = false; + ImVec2 pos_target = pos; + const ImGuiIO& io = ImGui::GetIO(); + + if ( pos_target.y > io.DisplaySize.y - margin ){ + pos_target.y = io.DisplaySize.y - margin; + need_update = true; + } + if ( pos_target.y + size.y < margin ){ + pos_target.y = margin - size.y; + need_update = true; + } + if ( pos_target.x > io.DisplaySize.x - margin){ + pos_target.x = io.DisplaySize.x - margin; + need_update = true; + } + if ( pos_target.x + size.x < margin ){ + pos_target.x = margin - size.x; + need_update = true; + } + if (need_update) + ImGui::SetNextWindowPos(pos_target, ImGuiCond_Always); } diff --git a/UserInterfaceManager.h b/UserInterfaceManager.h index c6b753e..ba10fd2 100644 --- a/UserInterfaceManager.h +++ b/UserInterfaceManager.h @@ -13,11 +13,13 @@ struct ImVec2; class Source; class MediaPlayer; +class FrameBufferImage; class SourcePreview { Source *source_; std::string label_; + bool reset_; public: SourcePreview(); @@ -30,26 +32,43 @@ public: inline bool filled() const { return source_ != nullptr; } }; +class Thumbnail +{ + float aspect_ratio_; + uint texture_; + +public: + Thumbnail(); + ~Thumbnail(); + + void reset(); + void set (const FrameBufferImage *image); + void Render(float width); +}; + class Navigator { // geometry left bar & pannel float width_; float height_; float pannel_width_; - float sourcelist_height_; float padding_width_; // behavior pannel + bool show_config_; bool pannel_visible_; bool view_pannel_visible; bool selected_button[NAV_COUNT]; int pattern_type; + std::list _selectedFiles; void clearButtonSelection(); void applyButtonSelection(int index); // side pannels void RenderSourcePannel(Source *s); void RenderMainPannel(); + void RenderMainPannelVimix(); + void RenderMainPannelSettings(); void RenderTransitionPannel(); void RenderNewPannel(); void RenderViewPannel(ImVec2 draw_pos, ImVec2 draw_size); @@ -99,6 +118,7 @@ public: void Render(); }; + class UserInterface { friend class Navigator; @@ -123,8 +143,8 @@ class UserInterface // Private Constructor UserInterface(); - UserInterface(UserInterface const& copy); // Not Implemented - UserInterface& operator=(UserInterface const& copy); // Not Implemented + UserInterface(UserInterface const& copy) = delete; + UserInterface& operator=(UserInterface const& copy) = delete; public: @@ -149,9 +169,7 @@ public: inline bool altModifier() const { return alt_modifier_active; } inline bool shiftModifier() const { return shift_modifier_active; } - void StartScreenshot(); void showPannel(int id = 0); - void showSourceEditor(Source *s); void showMediaPlayer(MediaPlayer *mp); @@ -159,6 +177,8 @@ public: std::string currentTextEdit; void fillShaderEditor(const std::string &text); + void StartScreenshot(); + protected: void showMenuFile(); @@ -171,10 +191,12 @@ protected: void RenderHistory(); void RenderShaderEditor(); int RenderViewNavigator(int* shift); + void RenderAbout(bool* p_open); + void RenderNotes(); + void handleKeyboard(); void handleMouse(); void handleScreenshot(); - void RenderAbout(bool* p_open); }; #endif /* #define __UI_MANAGER_H_ */ diff --git a/View.cpp b/View.cpp index b01695c..1693277 100644 --- a/View.cpp +++ b/View.cpp @@ -114,11 +114,8 @@ void View::initiate() { current_action_ = ""; for (auto sit = Mixer::manager().session()->begin(); - sit != Mixer::manager().session()->end(); sit++){ - - (*sit)->stored_status_->copyTransform((*sit)->group(mode_)); - } - + sit != Mixer::manager().session()->end(); ++sit) + (*sit)->store(mode_); } void View::terminate() @@ -190,7 +187,7 @@ void View::selectAll() { Mixer::selection().clear(); for(auto sit = Mixer::manager().session()->begin(); - sit != Mixer::manager().session()->end(); sit++) { + sit != Mixer::manager().session()->end(); ++sit) { if (canSelect(*sit)) Mixer::selection().add(*sit); } diff --git a/View.h b/View.h index b62c5dd..c8da86e 100644 --- a/View.h +++ b/View.h @@ -80,7 +80,7 @@ public: return Cursor (); } - //TODO: test mouse over provided a point in screen coordinates + // test mouse over provided a point in screen coordinates virtual Cursor over (glm::vec2) { return Cursor (); } diff --git a/Visitor.h b/Visitor.h index 2842cdd..b6e0262 100644 --- a/Visitor.h +++ b/Visitor.h @@ -38,6 +38,7 @@ class RenderSource; class CloneSource; class NetworkSource; class MixingGroup; +class MultiFileSource; // Declares the interface for the visitors class Visitor { @@ -81,6 +82,7 @@ public: virtual void visit (SessionGroupSource&) {} virtual void visit (RenderSource&) {} virtual void visit (CloneSource&) {} + virtual void visit (MultiFileSource&) {} }; diff --git a/defines.h b/defines.h index eba1221..250882d 100644 --- a/defines.h +++ b/defines.h @@ -59,20 +59,24 @@ #define TRANSITION_MIN_DURATION 0.2f #define TRANSITION_MAX_DURATION 10.f #define ARROWS_MOVEMENT_FACTOR 4.f +#define SESSION_THUMBNAIL_HEIGHT 120.f #define IMGUI_TITLE_MAINWINDOW ICON_FA_CIRCLE_NOTCH " vimix" #define IMGUI_TITLE_MEDIAPLAYER ICON_FA_FILM " Player" #define IMGUI_TITLE_HISTORY ICON_FA_HISTORY " History" -#define IMGUI_TITLE_TOOLBOX ICON_FA_WRENCH " Development Tools" -#define IMGUI_TITLE_SHADEREDITOR ICON_FA_CODE " Code" +#define IMGUI_TITLE_LOGS ICON_FA_LIST " Logs" +#define IMGUI_TITLE_TOOLBOX ICON_FA_WRENCH " Development Toolbox" +#define IMGUI_TITLE_SHADEREDITOR ICON_FA_CODE " Code Editor" #define IMGUI_TITLE_PREVIEW ICON_FA_DESKTOP " Ouput" #define IMGUI_TITLE_DELETE ICON_FA_BROOM " Delete?" -#define IMGUI_LABEL_RECENT_FILES " Select recent" +#define IMGUI_LABEL_RECENT_FILES " Recent files" #define IMGUI_RIGHT_ALIGN -3.5f * ImGui::GetTextLineHeightWithSpacing() +#define IMGUI_TOP_ALIGN 10 #define IMGUI_COLOR_OVERLAY IM_COL32(5, 5, 5, 150) #define IMGUI_COLOR_RECORD 1.0, 0.05, 0.05 #define IMGUI_COLOR_STREAM 0.05, 0.8, 1.0 #define IMGUI_NOTIFICATION_DURATION 2.5f +#define IMGUI_TOOLTIP_TIMEOUT 80 #ifdef APPLE #define CTRL_MOD "Cmd+" #else diff --git a/rsc/mesh/icon_sequence.ply b/rsc/mesh/icon_sequence.ply new file mode 100644 index 0000000..5396220 --- /dev/null +++ b/rsc/mesh/icon_sequence.ply @@ -0,0 +1,144 @@ +ply +format ascii 1.0 +comment Created by Blender 2.92.0 - www.blender.org +element vertex 73 +property float x +property float y +property float z +element face 61 +property list uchar uint vertex_indices +end_header +-0.073338 -0.096730 0.001250 +0.088477 -0.052608 0.001250 +-0.088049 -0.052608 0.001250 +0.073767 -0.096730 0.001250 +-0.080068 -0.037900 0.001250 +-0.071205 0.006222 0.001250 +-0.088049 0.020929 0.001250 +0.088477 0.020929 0.001250 +0.071568 0.006222 0.001250 +0.080254 -0.037900 0.001250 +-0.065218 -0.037900 0.001250 +0.065397 -0.037900 0.001250 +-0.038191 -0.001199 0.001250 +-0.035936 -0.001199 0.001250 +-0.037069 -0.001132 0.001250 +-0.034843 -0.001395 0.001250 +-0.039256 -0.001396 0.001250 +-0.033799 -0.001711 0.001250 +-0.040255 -0.001712 0.001250 +-0.032816 -0.002139 0.001250 +-0.041178 -0.002141 0.001250 +-0.031903 -0.002669 0.001250 +-0.042017 -0.002672 0.001250 +-0.031070 -0.003293 0.001250 +-0.042761 -0.003296 0.001250 +-0.030329 -0.004002 0.001250 +-0.043403 -0.004006 0.001250 +-0.029690 -0.004787 0.001250 +-0.043932 -0.004792 0.001250 +-0.029162 -0.005639 0.001250 +-0.044340 -0.005646 0.001250 +-0.028756 -0.006551 0.001250 +-0.044617 -0.006558 0.001250 +-0.028484 -0.007512 0.001250 +-0.044753 -0.007519 0.001250 +-0.002441 -0.026583 0.001250 +0.036991 -0.037900 0.001250 +0.010975 -0.006875 0.001250 +-0.028353 -0.008515 0.001250 +-0.044741 -0.008522 0.001250 +-0.028373 -0.009518 0.001250 +-0.044580 -0.009525 0.001250 +-0.028536 -0.010479 0.001250 +-0.044282 -0.010486 0.001250 +-0.028833 -0.011391 0.001250 +-0.043857 -0.011398 0.001250 +-0.029256 -0.012245 0.001250 +-0.043315 -0.012250 0.001250 +-0.029794 -0.013031 0.001250 +-0.042667 -0.013035 0.001250 +-0.030439 -0.013740 0.001250 +-0.041923 -0.013744 0.001250 +-0.031181 -0.014365 0.001250 +-0.041094 -0.014368 0.001250 +-0.032011 -0.014896 0.001250 +-0.040189 -0.014898 0.001250 +-0.032921 -0.015324 0.001250 +-0.039221 -0.015326 0.001250 +-0.033901 -0.015641 0.001250 +-0.038198 -0.015642 0.001250 +-0.034941 -0.015838 0.001250 +-0.037132 -0.015838 0.001250 +-0.036032 -0.015905 0.001250 +-0.036562 -0.037900 0.001250 +-0.015776 -0.016935 0.001250 +-0.074375 0.035637 0.001250 +0.078371 0.050344 0.001250 +-0.077972 0.050344 0.001250 +0.074760 0.035637 0.001250 +-0.062747 0.065052 0.001250 +0.066375 0.079759 0.001250 +-0.065976 0.079759 0.001250 +0.063146 0.065052 0.001250 +3 0 1 2 +3 0 3 1 +3 4 5 6 +3 5 7 6 +3 5 8 7 +3 8 9 7 +3 4 10 5 +3 11 9 8 +3 12 13 14 +3 12 15 13 +3 16 15 12 +3 16 17 15 +3 18 17 16 +3 18 19 17 +3 20 19 18 +3 20 21 19 +3 22 21 20 +3 22 23 21 +3 24 23 22 +3 24 25 23 +3 26 25 24 +3 26 27 25 +3 28 27 26 +3 28 29 27 +3 30 29 28 +3 30 31 29 +3 32 31 30 +3 32 33 31 +3 34 33 32 +3 35 36 37 +3 34 38 33 +3 39 38 34 +3 39 40 38 +3 41 40 39 +3 41 42 40 +3 43 42 41 +3 43 44 42 +3 45 44 43 +3 45 46 44 +3 47 46 45 +3 47 48 46 +3 49 48 47 +3 49 50 48 +3 51 50 49 +3 51 52 50 +3 53 52 51 +3 53 54 52 +3 55 54 53 +3 55 56 54 +3 57 56 55 +3 57 58 56 +3 59 58 57 +3 59 60 58 +3 61 60 59 +3 61 62 60 +3 63 35 64 +3 63 36 35 +3 65 66 67 +3 65 68 66 +3 69 70 71 +3 69 72 70 diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 032028c..980ac06 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,6 +1,6 @@ name: vimix base: core18 -version: '0.5' +version: '0.6' summary: Live video mixing title: vimix description: | diff --git a/tinyxml2Toolkit.cpp b/tinyxml2Toolkit.cpp index 9c92026..147836a 100644 --- a/tinyxml2Toolkit.cpp +++ b/tinyxml2Toolkit.cpp @@ -62,7 +62,7 @@ XMLElement *tinyxml2::XMLElementFromGLM(XMLDocument *doc, glm::mat4 matrix) return newelement; } -void tinyxml2::XMLElementToGLM(XMLElement *elem, glm::ivec2 &vector) +void tinyxml2::XMLElementToGLM(const XMLElement *elem, glm::ivec2 &vector) { if ( !elem || std::string(elem->Name()).find("ivec2") == std::string::npos ) return; @@ -70,7 +70,7 @@ void tinyxml2::XMLElementToGLM(XMLElement *elem, glm::ivec2 &vector) elem->QueryIntAttribute("y", &vector.y); } -void tinyxml2::XMLElementToGLM(XMLElement *elem, glm::vec2 &vector) +void tinyxml2::XMLElementToGLM(const XMLElement *elem, glm::vec2 &vector) { if ( !elem || std::string(elem->Name()).find("vec2") == std::string::npos ) return; @@ -78,7 +78,7 @@ void tinyxml2::XMLElementToGLM(XMLElement *elem, glm::vec2 &vector) elem->QueryFloatAttribute("y", &vector.y); } -void tinyxml2::XMLElementToGLM(XMLElement *elem, glm::vec3 &vector) +void tinyxml2::XMLElementToGLM(const XMLElement *elem, glm::vec3 &vector) { if ( !elem || std::string(elem->Name()).find("vec3") == std::string::npos ) return; @@ -87,7 +87,7 @@ void tinyxml2::XMLElementToGLM(XMLElement *elem, glm::vec3 &vector) elem->QueryFloatAttribute("z", &vector.z); } -void tinyxml2::XMLElementToGLM(XMLElement *elem, glm::vec4 &vector) +void tinyxml2::XMLElementToGLM(const XMLElement *elem, glm::vec4 &vector) { if ( !elem || std::string(elem->Name()).find("vec4") == std::string::npos ) return; @@ -97,13 +97,13 @@ void tinyxml2::XMLElementToGLM(XMLElement *elem, glm::vec4 &vector) elem->QueryFloatAttribute("w", &vector.w); } -void tinyxml2::XMLElementToGLM(XMLElement *elem, glm::mat4 &matrix) +void tinyxml2::XMLElementToGLM(const XMLElement *elem, glm::mat4 &matrix) { if ( !elem || std::string(elem->Name()).find("mat4") == std::string::npos ) return; // loop over rows of vec4 - XMLElement* row = elem->FirstChildElement("vec4"); + const XMLElement* row = elem->FirstChildElement("vec4"); for( ; row ; row = row->NextSiblingElement()) { int r = 0; @@ -129,7 +129,7 @@ XMLElement *tinyxml2::XMLElementEncodeArray(XMLDocument *doc, const void *array, gchar *compressed_array = g_new(gchar, compressed_size); // encoded string will hold the base64 encoding of the array - gchar *encoded_array = nullptr; + const gchar *encoded_array = nullptr; // zlib compress ((Bytef *dest, uLongf *destLen, const Bytef *source, uLong sourceLen)); if ( Z_OK == compress((Bytef *)compressed_array, &compressed_size, @@ -153,12 +153,11 @@ XMLElement *tinyxml2::XMLElementEncodeArray(XMLDocument *doc, const void *array, // free temporary array g_free(compressed_array); - g_free(encoded_array); return newelement; } -bool tinyxml2::XMLElementDecodeArray(XMLElement *elem, void *array, uint arraysize) +bool tinyxml2::XMLElementDecodeArray(const XMLElement *elem, void *array, uint arraysize) { bool ret = false; diff --git a/tinyxml2Toolkit.h b/tinyxml2Toolkit.h index d276073..67c1766 100644 --- a/tinyxml2Toolkit.h +++ b/tinyxml2Toolkit.h @@ -19,14 +19,14 @@ XMLElement *XMLElementFromGLM(XMLDocument *doc, glm::vec3 vector); XMLElement *XMLElementFromGLM(XMLDocument *doc, glm::vec4 vector); XMLElement *XMLElementFromGLM(XMLDocument *doc, glm::mat4 matrix); -void XMLElementToGLM(XMLElement *elem, glm::ivec2 &vector); -void XMLElementToGLM(XMLElement *elem, glm::vec2 &vector); -void XMLElementToGLM(XMLElement *elem, glm::vec3 &vector); -void XMLElementToGLM(XMLElement *elem, glm::vec4 &vector); -void XMLElementToGLM(XMLElement *elem, glm::mat4 &matrix); +void XMLElementToGLM(const XMLElement *elem, glm::ivec2 &vector); +void XMLElementToGLM(const XMLElement *elem, glm::vec2 &vector); +void XMLElementToGLM(const XMLElement *elem, glm::vec3 &vector); +void XMLElementToGLM(const XMLElement *elem, glm::vec4 &vector); +void XMLElementToGLM(const XMLElement *elem, glm::mat4 &matrix); XMLElement *XMLElementEncodeArray(XMLDocument *doc, const void *array, uint arraysize); -bool XMLElementDecodeArray(XMLElement *elem, void *array, uint arraysize); +bool XMLElementDecodeArray(const tinyxml2::XMLElement *elem, void *array, uint arraysize); bool XMLSaveDoc(tinyxml2::XMLDocument * const doc, std::string filename); bool XMLResultError(int result, bool verbose = true);