diff --git a/src/ActionManager.cpp b/src/ActionManager.cpp index 23f5f8d..84c7a3e 100644 --- a/src/ActionManager.cpp +++ b/src/ActionManager.cpp @@ -65,6 +65,8 @@ void Action::init() store("Session start"); } +// must be called in a thread running in parrallel of the rendering +// (needs opengl update to get thumbnail) void captureMixerSession(Session *se, tinyxml2::XMLDocument *doc, std::string node, std::string label) { if (se != nullptr) { @@ -101,6 +103,8 @@ void captureMixerSession(Session *se, tinyxml2::XMLDocument *doc, std::string no void Action::store(const std::string &label) { + // TODO: set a maximum amount of stored steps? (even very big, just to ensure memory limit) + // ignore if locked or if no label is given if (locked_ || label.empty()) return; diff --git a/src/CloneSource.cpp b/src/CloneSource.cpp index 0f33854..5e46416 100644 --- a/src/CloneSource.cpp +++ b/src/CloneSource.cpp @@ -228,8 +228,7 @@ uint CloneSource::texture() const void CloneSource::accept(Visitor& v) { Source::accept(v); - if (!failed()) - v.visit(*this); + v.visit(*this); } glm::ivec2 CloneSource::icon() const diff --git a/src/DeviceSource.cpp b/src/DeviceSource.cpp index 0497687..f460b56 100644 --- a/src/DeviceSource.cpp +++ b/src/DeviceSource.cpp @@ -522,8 +522,7 @@ void DeviceSource::setActive (bool on) void DeviceSource::accept(Visitor& v) { Source::accept(v); - if (!failed()) - v.visit(*this); + v.visit(*this); } bool DeviceSource::failed() const diff --git a/src/ImGuiVisitor.cpp b/src/ImGuiVisitor.cpp index ec024a8..e1af4b9 100644 --- a/src/ImGuiVisitor.cpp +++ b/src/ImGuiVisitor.cpp @@ -376,166 +376,200 @@ void ImGuiVisitor::visit(ImageProcessingShader &n) void ImGuiVisitor::visit (Source& s) { - ImGui::PushID(std::to_string(s.id()).c_str()); - // blending - s.blendingShader()->accept(*this); + const float preview_width = ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN; + const float preview_height = 4.5f * ImGui::GetFrameHeightWithSpacing(); + const float space = ImGui::GetStyle().ItemSpacing.y; - // preview - float preview_width = ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN; - float preview_height = 4.5f * ImGui::GetFrameHeightWithSpacing(); - ImVec2 pos = ImGui::GetCursorPos(); // remember where we were... + if ( !s.failed() ) { - float space = ImGui::GetStyle().ItemSpacing.y; - float width = preview_width; - float height = s.frame()->projectionArea().y * width / ( s.frame()->projectionArea().x * s.frame()->aspectRatio()); - if (height > preview_height - space) { - height = preview_height - space; - width = height * s.frame()->aspectRatio() * ( s.frame()->projectionArea().x / s.frame()->projectionArea().y); - } - // centered image - ImGui::SetCursorPos( ImVec2(pos.x + 0.5f * (preview_width-width), pos.y + 0.5f * (preview_height-height-space)) ); - ImGui::Image((void*)(uintptr_t) s.frame()->texture(), ImVec2(width, height)); + ImGui::PushID(std::to_string(s.id()).c_str()); - // inform on visibility status - ImGui::SetCursorPos( ImVec2(preview_width + 20, pos.y ) ); - if (s.active()) { - if (s.blendingShader()->color.a > 0.f) - ImGuiToolkit::Indication("Visible", ICON_FA_EYE); + // blending selection + s.blendingShader()->accept(*this); + + // remember where we were... + ImVec2 pos = ImGui::GetCursorPos(); + + // preview + float width = preview_width; + float height = s.frame()->projectionArea().y * width / ( s.frame()->projectionArea().x * s.frame()->aspectRatio()); + if (height > preview_height - space) { + height = preview_height - space; + width = height * s.frame()->aspectRatio() * ( s.frame()->projectionArea().x / s.frame()->projectionArea().y); + } + // centered image + ImGui::SetCursorPos( ImVec2(pos.x + 0.5f * (preview_width-width), pos.y + 0.5f * (preview_height-height-space)) ); + ImGui::Image((void*)(uintptr_t) s.frame()->texture(), ImVec2(width, height)); + + // inform on visibility status + ImGui::SetCursorPos( ImVec2(preview_width + 20, pos.y ) ); + if (s.active()) { + if (s.blendingShader()->color.a > 0.f) + ImGuiToolkit::Indication("Visible", ICON_FA_EYE); + else + ImGuiToolkit::Indication("Not visible", ICON_FA_EYE_SLASH); + } else - ImGuiToolkit::Indication("Not visible", ICON_FA_EYE_SLASH); - } - else - ImGuiToolkit::Indication("Inactive", ICON_FA_SNOWFLAKE); + ImGuiToolkit::Indication("Inactive", ICON_FA_SNOWFLAKE); - // Inform on workspace - ImGui::SetCursorPos( ImVec2(preview_width + 20, pos.y + ImGui::GetFrameHeightWithSpacing()) ); - if (s.workspace() == Source::BACKGROUND) - ImGuiToolkit::Indication("in Background",10, 16); - else if (s.workspace() == Source::FOREGROUND) - ImGuiToolkit::Indication("in Foreground",12, 16); - else - ImGuiToolkit::Indication("in Workspace",11, 16); + // Inform on workspace + ImGui::SetCursorPos( ImVec2(preview_width + 20, pos.y + ImGui::GetFrameHeightWithSpacing()) ); + if (s.workspace() == Source::BACKGROUND) + ImGuiToolkit::Indication("in Background",10, 16); + else if (s.workspace() == Source::FOREGROUND) + ImGuiToolkit::Indication("in Foreground",12, 16); + else + ImGuiToolkit::Indication("in Workspace",11, 16); - // Inform on link - ImGui::SetCursorPos( ImVec2(preview_width + 20, pos.y + 2.1f * ImGui::GetFrameHeightWithSpacing()) ); - if (s.mixingGroup() != nullptr) { - if (ImGuiToolkit::IconButton(ICON_FA_LINK, "Linked")){ - Mixer::selection().clear(); - Mixer::selection().add( s.mixingGroup()->getCopy() ); - } - } - else - ImGuiToolkit::Indication("not Linked", ICON_FA_UNLINK); - - // locking - ImGui::SetCursorPos( ImVec2(preview_width + 20, pos.y + 3.f * ImGui::GetFrameHeightWithSpacing()) ); - const char *tooltip[2] = {"Unlocked", "Locked"}; - bool l = s.locked(); - if (ImGuiToolkit::IconToggle(15,6,17,6, &l, tooltip ) ) { - s.setLocked(l); - if (l) { - Mixer::selection().clear(); - Action::manager().store(s.name() + std::string(": lock.")); - } - else { - Mixer::selection().set(&s); - Action::manager().store(s.name() + std::string(": unlock.")); - } - } - - // Filter - bool on = s.imageProcessingEnabled(); - ImGui::SetCursorPos( ImVec2( pos.x, pos.y + preview_height)); - if (on) - ImGui::Text(ICON_FA_PALETTE " Color correction"); - else - ImGuiToolkit::Indication("Color correction filter is disabled", ICON_FA_PALETTE " Color correction"); - pos = ImGui::GetCursorPos(); - - // menu icon for image processing - ImGui::SameLine(preview_width, 2 * IMGUI_SAME_LINE); - if (ImGuiToolkit::IconButton(5, 8)) - ImGui::OpenPopup( "MenuImageProcessing" ); - - if (ImGui::BeginPopup( "MenuImageProcessing" )) - { - if (ImGui::MenuItem("Enable", NULL, &on)) { - std::ostringstream oss; - oss << s.name() << ": " << ( on ? "Enable Color correction" : "Disable Color correction"); - Action::manager().store(oss.str()); - s.setImageProcessingEnabled(on); - } - - if (s.processingshader_link_.connected()) { - if (ImGui::MenuItem( "Unfollow", NULL, false, on)){ - s.processingshader_link_.disconnect(); + // Inform on link + ImGui::SetCursorPos( ImVec2(preview_width + 20, pos.y + 2.1f * ImGui::GetFrameHeightWithSpacing()) ); + if (s.mixingGroup() != nullptr) { + if (ImGuiToolkit::IconButton(ICON_FA_LINK, "Linked")){ + Mixer::selection().clear(); + Mixer::selection().add( s.mixingGroup()->getCopy() ); } } - else { - if (ImGui::MenuItem("Reset", NULL, false, on )){ - ImageProcessingShader defaultvalues; - s.processingShader()->copy(defaultvalues); - s.processingshader_link_.disconnect(); + else + ImGuiToolkit::Indication("not Linked", ICON_FA_UNLINK); + + // locking + ImGui::SetCursorPos( ImVec2(preview_width + 20, pos.y + 3.f * ImGui::GetFrameHeightWithSpacing()) ); + const char *tooltip[2] = {"Unlocked", "Locked"}; + bool l = s.locked(); + if (ImGuiToolkit::IconToggle(15,6,17,6, &l, tooltip ) ) { + s.setLocked(l); + if (l) { + Mixer::selection().clear(); + Action::manager().store(s.name() + std::string(": lock.")); + } + else { + Mixer::selection().set(&s); + Action::manager().store(s.name() + std::string(": unlock.")); + } + } + + // Filter + bool on = s.imageProcessingEnabled(); + ImGui::SetCursorPos( ImVec2( pos.x, pos.y + preview_height)); + if (on) + ImGui::Text(ICON_FA_PALETTE " Color correction"); + else + ImGuiToolkit::Indication("Color correction filter is disabled", ICON_FA_PALETTE " Color correction"); + pos = ImGui::GetCursorPos(); + + // menu icon for image processing + ImGui::SameLine(preview_width, 2 * IMGUI_SAME_LINE); + if (ImGuiToolkit::IconButton(5, 8)) + ImGui::OpenPopup( "MenuImageProcessing" ); + + if (ImGui::BeginPopup( "MenuImageProcessing" )) + { + if (ImGui::MenuItem("Enable", NULL, &on)) { std::ostringstream oss; - oss << s.name() << ": " << "Reset Filter"; + oss << s.name() << ": " << ( on ? "Enable Color correction" : "Disable Color correction"); Action::manager().store(oss.str()); + s.setImageProcessingEnabled(on); } - if (ImGui::MenuItem("Copy", NULL, false, on )){ - std::string clipboard = SessionVisitor::getClipboard(s.processingShader()); - if (!clipboard.empty()) - ImGui::SetClipboardText(clipboard.c_str()); + + if (s.processingshader_link_.connected()) { + if (ImGui::MenuItem( "Unfollow", NULL, false, on)){ + s.processingshader_link_.disconnect(); + } } - const char *clipboard = ImGui::GetClipboardText(); - const bool can_paste = (clipboard != nullptr && SessionLoader::isClipboard(clipboard)); - if (ImGui::MenuItem("Paste", NULL, false, can_paste)) { - SessionLoader::applyImageProcessing(s, clipboard); - std::ostringstream oss; - oss << s.name() << ": " << "Change Filter"; - Action::manager().store(oss.str()); + else { + if (ImGui::MenuItem("Reset", NULL, false, on )){ + ImageProcessingShader defaultvalues; + s.processingShader()->copy(defaultvalues); + s.processingshader_link_.disconnect(); + std::ostringstream oss; + oss << s.name() << ": " << "Reset Filter"; + Action::manager().store(oss.str()); + } + if (ImGui::MenuItem("Copy", NULL, false, on )){ + std::string clipboard = SessionVisitor::getClipboard(s.processingShader()); + if (!clipboard.empty()) + ImGui::SetClipboardText(clipboard.c_str()); + } + const char *clipboard = ImGui::GetClipboardText(); + const bool can_paste = (clipboard != nullptr && SessionLoader::isClipboard(clipboard)); + if (ImGui::MenuItem("Paste", NULL, false, can_paste)) { + SessionLoader::applyImageProcessing(s, clipboard); + std::ostringstream oss; + oss << s.name() << ": " << "Change Filter"; + Action::manager().store(oss.str()); + } + // // NON-stable style follow mechanism + // ImGui::Separator(); + // if (ImGui::BeginMenu("Follow", on)) + // { + // for (auto mpit = Mixer::manager().session()->begin(); + // mpit != Mixer::manager().session()->end(); mpit++ ) + // { + // std::string label = (*mpit)->name(); + // if ( (*mpit)->id() != s.id() && + // (*mpit)->imageProcessingEnabled() && + // !(*mpit)->processingshader_link_.connected()) { + // if (ImGui::MenuItem( label.c_str() )){ + // s.processingshader_link_.connect(*mpit); + // s.touch(); + // } + // } + // } + // ImGui::EndMenu(); + // } } -// // NON-stable style follow mechanism -// ImGui::Separator(); -// if (ImGui::BeginMenu("Follow", on)) -// { -// for (auto mpit = Mixer::manager().session()->begin(); -// mpit != Mixer::manager().session()->end(); mpit++ ) -// { -// std::string label = (*mpit)->name(); -// if ( (*mpit)->id() != s.id() && -// (*mpit)->imageProcessingEnabled() && -// !(*mpit)->processingshader_link_.connected()) { -// if (ImGui::MenuItem( label.c_str() )){ -// s.processingshader_link_.connect(*mpit); -// s.touch(); -// } -// } -// } -// ImGui::EndMenu(); -// } + + ImGui::EndPopup(); } - ImGui::EndPopup(); - } + if (s.imageProcessingEnabled()) { - if (s.imageProcessingEnabled()) { + // full panel for image processing + ImGui::SetCursorPos( pos ); + ImGui::Spacing(); + + if (s.processingshader_link_.connected()) { + Source *target = s.processingshader_link_.source(); + ImGui::Text("Following"); + if ( target != nullptr && ImGui::Button(target->name().c_str(), ImVec2(IMGUI_RIGHT_ALIGN, 0)) ) + Mixer::manager().setCurrentSource(target); + } + else + s.processingShader()->accept(*this); + } - // full panel for image processing - ImGui::SetCursorPos( pos ); ImGui::Spacing(); - if (s.processingshader_link_.connected()) { - Source *target = s.processingshader_link_.source(); - ImGui::Text("Following"); - if ( target != nullptr && ImGui::Button(target->name().c_str(), ImVec2(IMGUI_RIGHT_ALIGN, 0)) ) - Mixer::manager().setCurrentSource(target); - } - else - s.processingShader()->accept(*this); + ImGui::PopID(); + } + else { - ImGui::Spacing(); + // remember where we were... + ImVec2 pos = ImGui::GetCursorPos(); - ImGui::PopID(); + // preview (black texture) + float width = preview_width; + float height = s.frame()->projectionArea().y * width / ( s.frame()->projectionArea().x * s.frame()->aspectRatio()); + if (height > preview_height - space) { + height = preview_height - space; + width = height * s.frame()->aspectRatio() * ( s.frame()->projectionArea().x / s.frame()->projectionArea().y); + } + // centered image + ImGui::SetCursorPos( ImVec2(pos.x + 0.5f * (preview_width-width), pos.y + 0.5f * (preview_height-height-space)) ); + ImGui::Image((void*)(uintptr_t) s.frame()->texture(), ImVec2(width, height)); + + // centered icon of failed (skull) + ImGui::SetCursorPos( ImVec2(pos.x + (width -ImGui::GetFrameHeightWithSpacing())* 0.5f , + pos.y + (height -ImGui::GetFrameHeightWithSpacing()) * 0.5f) ); + ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE); + ImGui::Text(ICON_FA_SKULL); + ImGui::PopFont(); + + // back + ImGui::SetCursorPos( ImVec2( pos.x, pos.y + preview_height)); + ImGui::Spacing(); + } ImGuiToolkit::Icon(s.icon().x, s.icon().y); ImGui::SameLine(0, IMGUI_SAME_LINE); @@ -568,6 +602,14 @@ void ImGuiVisitor::visit (MediaSource& s) ImGui::SameLine(0, IMGUI_SAME_LINE); ImGui::Text("Folder"); + +// ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); +// if ( ImGui::Button("Replace") ) { + +// s.setPath("/home/bh/Videos/iss.mov"); +// Mixer::manager().recreateSource( Mixer::manager().findSource( s.id() ) ); + +// } } void ImGuiVisitor::visit (SessionFileSource& s) diff --git a/src/Mixer.cpp b/src/Mixer.cpp index 5533dde..41c5d73 100644 --- a/src/Mixer.cpp +++ b/src/Mixer.cpp @@ -204,22 +204,28 @@ void Mixer::update() // grab frames to recorders & streamers FrameGrabbing::manager().grabFrame(session_->frame()); - // delete sources which failed update (one by one) - Source *failure = session()->failedSource(); - if (failure != nullptr) { - // failed media: remove it from the list of imports - MediaSource *failedMedia = dynamic_cast(failure); - if (failedMedia != nullptr) { - Settings::application.recentImport.remove( failedMedia->path() ); + // manage sources which failed update + SourceListUnique failures = session()->failedSources(); + for(auto it = failures.begin(); it != failures.end(); ++it) { + // if the failed source is still attached to the mixer + if ( attached( *it ) ) { + // special case of failed Render loopback : + // it fails when resolution change, and we can fix it by + // recreating it in the current session + RenderSource *failedRender = dynamic_cast(*it); + if (failedRender != nullptr) { + g_printerr("%s failed render \n", failedRender->initials()); + // try to recreate the failed render source + if ( !recreateSource(failedRender) ) + // delete the source if could not + deleteSource(failedRender); + } + // general case: + // detatch failed sources from the mixer + // (not deleted in the session; user can replace it) + else + detach( *it ); } - // failed Render loopback: replace it with one matching the current session - RenderSource *failedRender = dynamic_cast(failure); - if (failedRender != nullptr) { - if ( recreateSource(failedRender) ) - failure = nullptr; // prevent delete (already done in recreateSource) - } - // delete the source - deleteSource(failure); } // update views @@ -477,36 +483,36 @@ void Mixer::insertSource(Source *s, View::Mode m) } -bool Mixer::replaceSource(Source *from, Source *to) +void Mixer::replaceSource(Source *previous, Source *s) { - if ( from == nullptr || to == nullptr) - return false; + if ( previous == nullptr || s == nullptr) + return ; - // rename - renameSource(to, from->name()); + // remember name + std::string previous_name = previous->name(); - // remove source Nodes from all views - detach(from); + // remove source Nodes of previous from all views + detach(previous); // copy all transforms - to->group(View::MIXING)->copyTransform( from->group(View::MIXING) ); - to->group(View::GEOMETRY)->copyTransform( from->group(View::GEOMETRY) ); - to->group(View::LAYER)->copyTransform( from->group(View::LAYER) ); - to->group(View::TEXTURE)->copyTransform( from->group(View::TEXTURE) ); + s->group(View::MIXING)->copyTransform( previous->group(View::MIXING) ); + s->group(View::GEOMETRY)->copyTransform( previous->group(View::GEOMETRY) ); + s->group(View::LAYER)->copyTransform( previous->group(View::LAYER) ); + s->group(View::TEXTURE)->copyTransform( previous->group(View::TEXTURE) ); - // TODO copy all filters + // apply image processing + SessionLoader loader( session_ ); + loader.applyImageProcessing(*s, SessionVisitor::getClipboard(previous)); + s->setImageProcessingEnabled( previous->imageProcessingEnabled() ); + // delete previous source + session_->deleteSource(previous); - // add source Nodes to all views - attach(to); + // rename s + renameSource(s, previous_name); - // add source - session_->addSource(to); - - // delete source - session_->deleteSource(from); - - return true; + // add source s + candidate_sources_.push_back(s); } @@ -517,15 +523,20 @@ bool Mixer::recreateSource(Source *s) // get the xml description from this source, and exit if not wellformed tinyxml2::XMLDocument xmlDoc; - tinyxml2::XMLElement* sourceNode = SessionLoader::firstSourceElement(SessionVisitor::getClipboard(s), xmlDoc); + std::string clip = SessionVisitor::getClipboard(s); + g_printerr("clibboard\n%s", clip.c_str()); + + tinyxml2::XMLElement* sourceNode = SessionLoader::firstSourceElement(clip, xmlDoc); if ( sourceNode == nullptr ) return false; // actually create the source with SessionLoader using xml description SessionLoader loader( session_ ); Source *replacement = loader.createSource(sourceNode, SessionLoader::DUPLICATE); // not clone - if (replacement == nullptr) + if (replacement == nullptr) { + g_printerr("replacement failed \n"); return false; + } // remove source Nodes from all views detach(s); @@ -588,7 +599,8 @@ void Mixer::detach(Source *s) if ( s != nullptr ) { // in case it was the current source... - unsetCurrentSource(); + if ( s == currentSource() ) + unsetCurrentSource(); // in case it was selected.. selection().remove(s); // detach from views @@ -600,7 +612,10 @@ void Mixer::detach(Source *s) } } - +bool Mixer::attached (Source *s) const +{ + return s->group(View::MIXING)->refcount_ > 0; +} bool Mixer::concealed(Source *s) { @@ -623,9 +638,8 @@ void Mixer::conceal(Source *s) // remove from session session_->removeSource(s); - // detach from scene workspace, and put only in mixing background + // detach from scene workspace detach(s); - mixing_.scene.bg()->attach( s->group(View::MIXING) ); } } @@ -635,7 +649,6 @@ void Mixer::uncover(Source *s) if ( it != stash_.end() ) { stash_.erase(it); - mixing_.scene.bg()->detach( s->group(View::MIXING) ); attach(s); session_->addSource(s); } diff --git a/src/Mixer.h b/src/Mixer.h index 867316d..e924063 100644 --- a/src/Mixer.h +++ b/src/Mixer.h @@ -62,6 +62,7 @@ public: // operations on sources void addSource (Source *s); void deleteSource (Source *s); + void replaceSource(Source *previous, Source *s); void renameSource (Source *s, const std::string &newname = ""); int numSource () const; @@ -139,10 +140,10 @@ protected: SourceList candidate_sources_; SourceList stash_; void insertSource (Source *s, View::Mode m = View::INVALID); - bool replaceSource (Source *from, Source *to); bool recreateSource(Source *s); void attach (Source *s); void detach (Source *s); + bool attached (Source *s) const; void setCurrentSource(SourceList::iterator it); SourceList::iterator current_source_; diff --git a/src/MultiFileSource.cpp b/src/MultiFileSource.cpp index 2416676..f7c0583 100644 --- a/src/MultiFileSource.cpp +++ b/src/MultiFileSource.cpp @@ -255,8 +255,7 @@ guint64 MultiFileSource::playtime () const void MultiFileSource::accept (Visitor& v) { Source::accept(v); - if (!failed()) - v.visit(*this); + v.visit(*this); } MultiFile *MultiFileSource::multifile () const diff --git a/src/NetworkSource.cpp b/src/NetworkSource.cpp index 1e2d046..e0300ab 100644 --- a/src/NetworkSource.cpp +++ b/src/NetworkSource.cpp @@ -346,8 +346,7 @@ std::string NetworkSource::connection() const void NetworkSource::accept(Visitor& v) { Source::accept(v); - if (!failed()) - v.visit(*this); + v.visit(*this); } glm::ivec2 NetworkSource::icon() const diff --git a/src/PatternSource.cpp b/src/PatternSource.cpp index fbd09df..43b9d2e 100644 --- a/src/PatternSource.cpp +++ b/src/PatternSource.cpp @@ -78,6 +78,8 @@ Pattern::Pattern() : Stream(), type_(UINT_MAX) // invalid pattern pattern_descriptor Pattern::get(uint type) { + type = CLAMP(type, 0, patterns_.size()-1); + // check availability of feature to use this pattern if (!patterns_[type].available) patterns_[type].available = GstToolkit::has_feature(patterns_[type].feature); @@ -164,8 +166,7 @@ void PatternSource::setPattern(uint type, glm::ivec2 resolution) void PatternSource::accept(Visitor& v) { Source::accept(v); - if (!failed()) - v.visit(*this); + v.visit(*this); } Pattern *PatternSource::pattern() const diff --git a/src/RenderSource.cpp b/src/RenderSource.cpp index d6b8e25..72e2479 100644 --- a/src/RenderSource.cpp +++ b/src/RenderSource.cpp @@ -155,8 +155,7 @@ glm::vec3 RenderSource::resolution() const void RenderSource::accept(Visitor& v) { Source::accept(v); - if (!failed()) - v.visit(*this); + v.visit(*this); } glm::ivec2 RenderSource::icon() const diff --git a/src/Session.cpp b/src/Session.cpp index ae48607..a8c58e3 100644 --- a/src/Session.cpp +++ b/src/Session.cpp @@ -46,7 +46,7 @@ SessionNote::SessionNote(const std::string &t, bool l, int s): label(std::to_str } Session::Session(uint64_t id) : id_(id), active_(true), activation_threshold_(MIXING_MIN_THRESHOLD), - filename_(""), failedSource_(nullptr), thumbnail_(nullptr) + filename_(""), thumbnail_(nullptr) { // create unique id if (id_ == 0) @@ -179,7 +179,6 @@ void Session::update(float dt) } // pre-render all sources - failedSource_ = nullptr; bool ready = true; for( SourceList::iterator it = sources_.begin(); it != sources_.end(); ++it){ @@ -190,7 +189,9 @@ void Session::update(float dt) // discard failed source if ( (*it)->failed() ) { - failedSource_ = (*it); + // insert source in list of failed + // (NB: insert in set fails if source is already listed) + failed_.insert( *it ); } // render normally else { @@ -292,6 +293,8 @@ SourceList::iterator Session::deleteSource(Source *s) // inform group if (s->mixingGroup() != nullptr) s->mixingGroup()->detach(s); + // erase the source from the failed list + failed_.erase(s); // erase the source from the update list & get next element its = sources_.erase(its); // delete the source : safe now @@ -321,6 +324,8 @@ SourceList::iterator Session::removeSource(Source *s) // inform group if (s->mixingGroup() != nullptr) s->mixingGroup()->detach(s); + // erase the source from the failed list + failed_.erase(s); // erase the source from the update list & get next element ret = sources_.erase(its); } diff --git a/src/Session.h b/src/Session.h index d748a9c..1870b8e 100644 --- a/src/Session.h +++ b/src/Session.h @@ -94,8 +94,8 @@ public: void setActive (bool on); inline bool active () { return active_; } - // return the last source which failed - Source *failedSource () { return failedSource_; } + // return the list of sources which failed + SourceListUnique failedSources () const { return failed_; } // get frame result of render inline FrameBuffer *frame () const { return render_.frame(); } @@ -183,7 +183,7 @@ protected: float activation_threshold_; RenderView render_; std::string filename_; - Source *failedSource_; + SourceListUnique failed_; SourceList sources_; void validate(SourceList &sources); std::list notes_; diff --git a/src/SessionSource.cpp b/src/SessionSource.cpp index ef890fc..2425164 100644 --- a/src/SessionSource.cpp +++ b/src/SessionSource.cpp @@ -153,9 +153,10 @@ void SessionSource::update(float dt) timer_ += guint64(dt * 1000.f) * GST_USECOND; } - // delete a source which failed - if (session_->failedSource() != nullptr) { - session_->deleteSource(session_->failedSource()); + // delete source which failed + SourceListUnique::iterator failure = session_->failedSources().cbegin(); + if ( failure != session_->failedSources().cend() ) { + session_->deleteSource( *failure ); // fail session if all sources failed if ( session_->size() < 1) failed_ = true; @@ -349,8 +350,7 @@ void SessionFileSource::render() void SessionFileSource::accept(Visitor& v) { Source::accept(v); - if (!failed()) - v.visit(*this); + v.visit(*this); } glm::ivec2 SessionFileSource::icon() const @@ -433,8 +433,7 @@ bool SessionGroupSource::import(Source *source) void SessionGroupSource::accept(Visitor& v) { Source::accept(v); - if (!failed()) - v.visit(*this); + v.visit(*this); } glm::ivec2 SessionGroupSource::icon() const diff --git a/src/SourceList.h b/src/SourceList.h index 9ed3fc7..2ca28b1 100644 --- a/src/SourceList.h +++ b/src/SourceList.h @@ -2,6 +2,7 @@ #define SOURCELIST_H #include +#include #include class Source; @@ -10,6 +11,7 @@ class Session; typedef std::list SourceList; typedef std::list SourceCoreList; +typedef std::set SourceListUnique; SourceList playable_only (const SourceList &list); SourceList valid_only (const SourceList &list); diff --git a/src/SrtReceiverSource.cpp b/src/SrtReceiverSource.cpp index 385a84a..e25264d 100644 --- a/src/SrtReceiverSource.cpp +++ b/src/SrtReceiverSource.cpp @@ -50,8 +50,7 @@ std::string SrtReceiverSource::uri() const void SrtReceiverSource::accept(Visitor& v) { Source::accept(v); - if (!failed()) - v.visit(*this); + v.visit(*this); } glm::ivec2 SrtReceiverSource::icon() const diff --git a/src/StreamSource.cpp b/src/StreamSource.cpp index 93361f7..c48c03f 100644 --- a/src/StreamSource.cpp +++ b/src/StreamSource.cpp @@ -71,8 +71,7 @@ std::list GenericStreamSource::gstElements() const void GenericStreamSource::accept(Visitor& v) { Source::accept(v); - if (!failed()) - v.visit(*this); + v.visit(*this); } glm::ivec2 GenericStreamSource::icon() const diff --git a/src/UserInterfaceManager.cpp b/src/UserInterfaceManager.cpp index 79257bd..12382f4 100644 --- a/src/UserInterfaceManager.cpp +++ b/src/UserInterfaceManager.cpp @@ -6179,6 +6179,8 @@ Navigator::Navigator() else new_media_mode = MEDIA_FOLDER; new_media_mode_changed = true; + + source_to_replace = nullptr; } void Navigator::applyButtonSelection(int index) @@ -6213,6 +6215,7 @@ void Navigator::clearButtonSelection() // clear new source pannel clearNewPannel(); + source_to_replace = nullptr; } void Navigator::showPannelSource(int index) @@ -6297,12 +6300,23 @@ void Navigator::Render() if (ImGui::IsItemHovered()) tooltip = {TOOLTIP_MAIN, SHORTCUT_MAIN}; + // // 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); + + // ignore source if failed (managed in stash below) + if (s->failed()){ + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.f, 0.3f, 0.2f, 1.f)); + ImGui::PushStyleColor(ImGuiCol_Header, ImGui::GetColorU32(ImGuiCol_Button)); + ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImGui::GetColorU32(ImGuiCol_ButtonActive)); + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImGui::GetColorU32(ImGuiCol_ButtonHovered)); + } + // draw an indicator for current source if ( s->mode() >= Source::SELECTED ){ ImVec2 p1 = ImGui::GetCursorScreenPos() + ImVec2(icon_width, 0.5f * sourceiconsize.y); @@ -6346,12 +6360,19 @@ void Navigator::Render() ImGui::EndDragDropTarget(); } + if (s->failed()){ + ImGui::PopStyleColor(4); + } + ImGui::PopID(); } // the "+" icon for action of creating new source - if (ImGui::Selectable( ICON_FA_PLUS, &selected_button[NAV_NEW], 0, iconsize)) + if (ImGui::Selectable( source_to_replace != nullptr ? ICON_FA_PLUS_SQUARE : ICON_FA_PLUS, + &selected_button[NAV_NEW], 0, iconsize)) { + Mixer::manager().unsetCurrentSource(); applyButtonSelection(NAV_NEW); + } if (ImGui::IsItemHovered()) tooltip = {TOOLTIP_NEW_SOURCE, SHORTCUT_NEW_SOURCE}; } @@ -6539,14 +6560,48 @@ void Navigator::RenderSourcePannel(Source *s) if (ImGuiToolkit::InputText("Name", &sname) ){ Mixer::manager().renameSource(s, sname); } + // Source pannel static ImGuiVisitor v; s->accept(v); - // clone & delete buttons ImGui::Text(" "); - // Action on source - if ( ImGui::Button( ICON_FA_SHARE_SQUARE " Clone & Filter", ImVec2(ImGui::GetContentRegionAvail().x, 0)) ) + + // clone button + if ( s->failed() ) { + ImGuiToolkit::ButtonDisabled( ICON_FA_SHARE_SQUARE " Clone & Filter", ImVec2(ImGui::GetContentRegionAvail().x, 0)); + } + else if ( ImGui::Button( ICON_FA_SHARE_SQUARE " Clone & Filter", ImVec2(ImGui::GetContentRegionAvail().x, 0)) ) Mixer::manager().addSource ( Mixer::manager().createSourceClone() ); + + // replace button + if ( s->cloned() ) { + ImGuiToolkit::ButtonDisabled( ICON_FA_PLUS_SQUARE " Replace", ImVec2(ImGui::GetContentRegionAvail().x, 0)); + if (ImGui::IsItemHovered()) + ImGuiToolkit::ToolTip("Cannot replace if source is cloned"); + } + else if ( ImGui::Button( ICON_FA_PLUS_SQUARE " Replace", ImVec2(ImGui::GetContentRegionAvail().x, 0)) ) { + // prepare panel for new source of same type + MediaSource *file = dynamic_cast(s); + MultiFileSource *sequence = dynamic_cast(s); + CloneSource *internal = dynamic_cast(s); + PatternSource *generated = dynamic_cast(s); + if (file != nullptr) + Settings::application.source.new_type = SOURCE_FILE; + else if (sequence != nullptr) + Settings::application.source.new_type = SOURCE_SEQUENCE; + else if (internal != nullptr) + Settings::application.source.new_type = SOURCE_INTERNAL; + else if (generated != nullptr) + Settings::application.source.new_type = SOURCE_GENERATED; + else + Settings::application.source.new_type = SOURCE_CONNECTED; + + // switch to panel new source + showPannelSource(NAV_NEW); + // set source to be replaced + source_to_replace = s; + } + // delete button if ( ImGui::Button( ICON_FA_BACKSPACE " Delete", ImVec2(ImGui::GetContentRegionAvail().x, 0)) ) { Mixer::manager().deleteSource(s); Action::manager().store(sname + std::string(": deleted")); @@ -6603,7 +6658,10 @@ void Navigator::RenderNewPannel() // TITLE ImGui::SetCursorPosY(10); ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE); - ImGui::Text("Insert"); + if (source_to_replace != nullptr) + ImGui::Text("Replace"); + else + ImGui::Text("Insert"); ImGui::PopFont(); // Edit menu @@ -7200,11 +7258,14 @@ void Navigator::RenderNewPannel() new_source_preview_.Render(ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN); // 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)) ) { + if (new_source_preview_.ready() && ImGui::Button( ICON_FA_CHECK " Ok", ImVec2(pannel_width_ - padding_width_, 0)) ) { // take out the source from the preview Source *s = new_source_preview_.getSource(); // restart and add the source. - Mixer::manager().addSource(s); + if (source_to_replace != nullptr) + Mixer::manager().replaceSource(source_to_replace, s); + else + Mixer::manager().addSource(s); s->replay(); // close NEW pannel togglePannelNew(); diff --git a/src/UserInterfaceManager.h b/src/UserInterfaceManager.h index b0e9297..40bf03e 100644 --- a/src/UserInterfaceManager.h +++ b/src/UserInterfaceManager.h @@ -209,6 +209,7 @@ private: std::string sourceMediaFileCurrent; MediaCreateMode new_media_mode; bool new_media_mode_changed; + Source *source_to_replace; }; class ToolBox