diff --git a/ImGuiToolkit.cpp b/ImGuiToolkit.cpp index 2349f17..e914d59 100644 --- a/ImGuiToolkit.cpp +++ b/ImGuiToolkit.cpp @@ -738,7 +738,7 @@ bool ImGuiToolkit::EditPlotLines(const char* label, float *array, int values_cou } bool ImGuiToolkit::EditPlotHistoLines(const char* label, float *histogram_array, float *lines_array, - int values_count, float values_min, float values_max, bool *released, const ImVec2 size) + int values_count, float values_min, float values_max, bool cut, bool *released, const ImVec2 size) { bool array_changed = false; @@ -764,9 +764,7 @@ bool ImGuiToolkit::EditPlotHistoLines(const char* label, float *histogram_array, *released = false; // read user input and activate widget - const bool left_mouse_press = ImGui::IsMouseDown(ImGuiMouseButton_Left); - const bool right_mouse_press = ImGui::IsMouseDown(ImGuiMouseButton_Right) | (ImGui::GetIO().KeyAlt & left_mouse_press) ; - const bool mouse_press = left_mouse_press | right_mouse_press; + const bool mouse_press = ImGui::IsMouseDown(ImGuiMouseButton_Left); const bool hovered = ImGui::ItemHoverable(bbox, id); bool temp_input_is_active = ImGui::TempInputIsActive(id); if (!temp_input_is_active) @@ -805,7 +803,7 @@ bool ImGuiToolkit::EditPlotHistoLines(const char* label, float *histogram_array, if (previous_index == UINT32_MAX) previous_index = index; - if (right_mouse_press){ + if (cut){ static float target_value = values_min; // toggle value histo diff --git a/ImGuiToolkit.h b/ImGuiToolkit.h index 43598e4..ef76225 100644 --- a/ImGuiToolkit.h +++ b/ImGuiToolkit.h @@ -36,7 +36,7 @@ namespace ImGuiToolkit bool TimelineSlider (const char* label, guint64 *time, guint64 start, guint64 end, guint64 step, const float width); bool InvisibleSliderInt(const char* label, uint *index, uint min, uint max, const ImVec2 size); bool EditPlotLines(const char* label, float *array, int values_count, float values_min, float values_max, const ImVec2 size); - bool EditPlotHistoLines(const char* label, float *histogram_array, float *lines_array, int values_count, float values_min, float values_max, bool *released, const ImVec2 size); + bool EditPlotHistoLines(const char* label, float *histogram_array, float *lines_array, int values_count, float values_min, float values_max, bool cut, bool *released, const ImVec2 size); // fonts from ressources 'fonts/' typedef enum { diff --git a/MediaSource.cpp b/MediaSource.cpp index 54fd7c9..c443490 100644 --- a/MediaSource.cpp +++ b/MediaSource.cpp @@ -108,12 +108,38 @@ void MediaSource::setActive (bool on) { bool was_active = active_; + // try to activate (may fail if source is cloned) Source::setActive(on); // change status of media player (only if status changed) - if ( active_ != was_active ) { + if ( active_ != was_active ) mediaplayer_->enable(active_); - } +} + + +bool MediaSource::playing () const +{ + return mediaplayer_->isPlaying(); +} + +void MediaSource::play (bool on) +{ + mediaplayer_->play(on); +} + +bool MediaSource::playable () const +{ + return !mediaplayer_->isImage(); +} + +void MediaSource::replay () +{ + mediaplayer_->rewind(); +} + +guint64 MediaSource::playtime () const +{ + return mediaplayer_->position(); } void MediaSource::update(float dt) diff --git a/MediaSource.h b/MediaSource.h index 122d598..4a31229 100644 --- a/MediaSource.h +++ b/MediaSource.h @@ -14,6 +14,11 @@ public: // implementation of source API void update (float dt) override; void setActive (bool on) override; + bool playing () const override; + void play (bool) override; + bool playable () const override; + void replay () override; + guint64 playtime () const override; void render() override; bool failed() const override; uint texture() const override; diff --git a/MultiFileSource.cpp b/MultiFileSource.cpp index 1e9850a..d1fa92c 100644 --- a/MultiFileSource.cpp +++ b/MultiFileSource.cpp @@ -115,6 +115,22 @@ void MultiFile::close () Stream::close(); } +void MultiFile::setIndex(int val) +{ + if (src_) { + g_object_set (src_, "index", val, NULL); + } +} + +int MultiFile::index() +{ + int val = 0; + if (src_) { + g_object_get (src_, "index", &val, NULL); + } + return val; +} + void MultiFile::setProperties (int begin, int end, int loop) { if (src_) { @@ -187,6 +203,27 @@ void MultiFileSource::setRange (int begin, int end) multifile()->setProperties (begin_, end_, loop_); } +void MultiFileSource::replay () +{ + if (multifile()) { + multifile()->setIndex (begin_); + stream_->rewind(); + } +} + +guint64 MultiFileSource::playtime () const +{ + guint64 time = 0; + + if (multifile()) + time += multifile()->index(); + + time *= GST_SECOND; + time /= framerate_; + + return time; +} + void MultiFileSource::accept (Visitor& v) { Source::accept(v); diff --git a/MultiFileSource.h b/MultiFileSource.h index 5435730..98249c8 100644 --- a/MultiFileSource.h +++ b/MultiFileSource.h @@ -31,6 +31,10 @@ public: // dynamic change of gstreamer multifile source properties void setProperties(int begin, int end, int loop); + // image index + int index(); + void setIndex(int val); + protected: GstElement *src_ ; }; @@ -42,6 +46,8 @@ public: // Source interface void accept (Visitor& v) override; + void replay () override; + guint64 playtime () const override; // StreamSource interface Stream *stream () const override { return stream_; } diff --git a/Session.cpp b/Session.cpp index c7a7606..c29b5b8 100644 --- a/Session.cpp +++ b/Session.cpp @@ -459,6 +459,59 @@ std::list::iterator Session::endMixingGroup() return mixing_groups_.end(); } + +size_t Session::numPlayGroups() const +{ + return play_groups_.size(); +} + +void Session::addPlayGroup(const SourceIdList &ids) +{ + play_groups_.push_back( ids ); +} + +void Session::addToPlayGroup(size_t i, Source *s) +{ + if (i < play_groups_.size() ) + { + if ( std::find(play_groups_[i].begin(), play_groups_[i].end(), s->id()) == play_groups_[i].end() ) + play_groups_[i].push_back(s->id()); + } +} + +void Session::removeFromPlayGroup(size_t i, Source *s) +{ + if (i < play_groups_.size() ) + { + if ( std::find(play_groups_[i].begin(), play_groups_[i].end(), s->id()) != play_groups_[i].end() ) + play_groups_[i].remove( s->id() ); + } +} + +void Session::deletePlayGroup(size_t i) +{ + if (i < play_groups_.size() ) + play_groups_.erase( play_groups_.begin() + i); +} + +SourceList Session::playGroup(size_t i) const +{ + SourceList list; + + if (i < play_groups_.size() ) + { + for (auto sid = play_groups_[i].begin(); sid != play_groups_[i].end(); ++sid){ + + SourceList::const_iterator it = std::find_if(sources_.begin(), sources_.end(), Source::hasId( *sid));; + if ( it != sources_.end()) + list.push_back( *it); + + } + } + + return list; +} + void Session::lock() { access_.lock(); diff --git a/Session.h b/Session.h index a1d710f..c0fc263 100644 --- a/Session.h +++ b/Session.h @@ -126,6 +126,15 @@ public: // snapshots SessionSnapshots * const snapshots () { return &snapshots_; } + // playlists + void addPlayGroup(const SourceIdList &ids); + void deletePlayGroup(size_t i); + size_t numPlayGroups() const; + SourceList playGroup(size_t i) const; + void addToPlayGroup(size_t i, Source *s); + void removeFromPlayGroup(size_t i, Source *s); + std::vector getPlayGroups() { return play_groups_; } + // lock and unlock access (e.g. while saving) void lock (); void unlock (); @@ -141,6 +150,7 @@ protected: std::list mixing_groups_; std::map config_; SessionSnapshots snapshots_; + std::vector play_groups_; float fading_target_; std::mutex access_; diff --git a/SessionCreator.cpp b/SessionCreator.cpp index 513f1ee..daeadb0 100644 --- a/SessionCreator.cpp +++ b/SessionCreator.cpp @@ -112,6 +112,9 @@ void SessionCreator::load(const std::string& filename) // load notes loadNotes( xmlDoc_.FirstChildElement("Notes") ); + // load playlists + loadPlayGroups( xmlDoc_.FirstChildElement("PlayGroups") ); + // all good session_->setFilename(filename); } @@ -163,7 +166,8 @@ void SessionCreator::loadNotes(XMLElement *notesNode) 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() ); + if (contentNode && contentNode->GetText()) + N.text = std::string ( contentNode->GetText() ); session_->addNote(N); } @@ -171,6 +175,29 @@ void SessionCreator::loadNotes(XMLElement *notesNode) } } +void SessionCreator::loadPlayGroups(tinyxml2::XMLElement *playgroupNode) +{ + if (playgroupNode != nullptr && session_ != nullptr) { + + XMLElement* playgroup = playgroupNode->FirstChildElement("PlayGroup"); + for( ; playgroup ; playgroup = playgroup->NextSiblingElement()) + { + SourceIdList playgroup_sources; + + XMLElement* playgroupSourceNode = playgroup->FirstChildElement("source"); + for ( ; playgroupSourceNode ; playgroupSourceNode = playgroupSourceNode->NextSiblingElement()) { + uint64_t id__ = 0; + playgroupSourceNode->QueryUnsigned64Attribute("id", &id__); + + if (sources_id_.count(id__) > 0) + playgroup_sources.push_back( id__ ); + + } + session_->addPlayGroup( playgroup_sources ); + } + } +} + SessionLoader::SessionLoader(): Visitor(), session_(nullptr), xmlCurrent_(nullptr), recursion_(0) { diff --git a/SessionCreator.h b/SessionCreator.h index 321c1ba..a2a769d 100644 --- a/SessionCreator.h +++ b/SessionCreator.h @@ -93,6 +93,7 @@ class SessionCreator : public SessionLoader { void loadConfig(tinyxml2::XMLElement *viewsNode); void loadNotes(tinyxml2::XMLElement *notesNode); + void loadPlayGroups(tinyxml2::XMLElement *playlistsNode); void loadSnapshots(tinyxml2::XMLElement *snapshotNode); public: diff --git a/SessionSource.cpp b/SessionSource.cpp index a33755b..103ceaf 100644 --- a/SessionSource.cpp +++ b/SessionSource.cpp @@ -18,7 +18,7 @@ #include "Mixer.h" -SessionSource::SessionSource(uint64_t id) : Source(id), failed_(false) +SessionSource::SessionSource(uint64_t id) : Source(id), failed_(false), timer_(0), paused_(false) { session_ = new Session; } @@ -62,11 +62,11 @@ uint SessionSource::texture() const } void SessionSource::setActive (bool on) -{ +{ Source::setActive(on); // change status of session (recursive change of internal sources) - if (session_ != nullptr) + if (session_) session_->setActive(active_); } @@ -76,8 +76,10 @@ void SessionSource::update(float dt) return; // update content - if (active_) + if (active_ && !paused_) { session_->update(dt); + timer_ += guint64(dt * 1000.f) * GST_USECOND; + } // delete a source which failed if (session_->failedSource() != nullptr) { @@ -90,6 +92,15 @@ void SessionSource::update(float dt) Source::update(dt); } +void SessionSource::replay () +{ + if (session_) { + for( SourceList::iterator it = session_->begin(); it != session_->end(); ++it) + (*it)->replay(); + timer_ = 0; + } +} + SessionFileSource::SessionFileSource(uint64_t id) : SessionSource(id), path_(""), initialized_(false), wait_for_sources_(false) { @@ -150,6 +161,7 @@ void SessionFileSource::load(const std::string &p, uint recursion) } // will be ready after init and one frame rendered + initialized_ = false; ready_ = false; } @@ -332,7 +344,7 @@ RenderSource::RenderSource(uint64_t id) : Source(id), session_(nullptr) bool RenderSource::failed() const { - if ( mode_ > Source::UNINITIALIZED && session_!=nullptr ) + if ( renderbuffer_ != nullptr && session_ != nullptr ) return renderbuffer_->resolution() != session_->frame()->resolution(); return false; @@ -372,7 +384,7 @@ void RenderSource::init() glm::vec3 RenderSource::resolution() const { - if (mode_ > Source::UNINITIALIZED) + if (renderbuffer_ != nullptr) return renderbuffer_->resolution(); else if (session_ && session_->frame()) return session_->frame()->resolution(); diff --git a/SessionSource.h b/SessionSource.h index dc7e65f..c37728d 100644 --- a/SessionSource.h +++ b/SessionSource.h @@ -14,6 +14,11 @@ public: // implementation of source API void update (float dt) override; void setActive (bool on) override; + bool playing () const override { return !paused_; } + void play (bool on) override { paused_ = !on; } + bool playable () const override { return true; } + guint64 playtime () const override { return timer_; } + void replay () override; bool failed () const override; uint texture () const override; @@ -24,6 +29,8 @@ protected: Session *session_; std::atomic failed_; + guint64 timer_; + bool paused_; }; class SessionFileSource : public SessionSource @@ -80,6 +87,9 @@ public: RenderSource(uint64_t id = 0); // implementation of source API + bool playing () const override { return true; } + void play (bool) override {} + bool playable () const override { return false; } bool failed () const override; uint texture() const override; void accept (Visitor& v) override; diff --git a/SessionVisitor.cpp b/SessionVisitor.cpp index f593c5b..f343434 100644 --- a/SessionVisitor.cpp +++ b/SessionVisitor.cpp @@ -58,63 +58,109 @@ bool SessionVisitor::saveSession(const std::string& filename, Session *session) delete thumbnail; // 2. config of views - XMLElement *views = xmlDoc.NewElement("Views"); - xmlDoc.InsertEndChild(views); - { - XMLElement *mixing = xmlDoc.NewElement( "Mixing" ); - mixing->InsertEndChild( SessionVisitor::NodeToXML(*session->config(View::MIXING), &xmlDoc)); - views->InsertEndChild(mixing); - - XMLElement *geometry = xmlDoc.NewElement( "Geometry" ); - geometry->InsertEndChild( SessionVisitor::NodeToXML(*session->config(View::GEOMETRY), &xmlDoc)); - views->InsertEndChild(geometry); - - XMLElement *layer = xmlDoc.NewElement( "Layer" ); - layer->InsertEndChild( SessionVisitor::NodeToXML(*session->config(View::LAYER), &xmlDoc)); - views->InsertEndChild(layer); - - XMLElement *appearance = xmlDoc.NewElement( "Texture" ); - appearance->InsertEndChild( SessionVisitor::NodeToXML(*session->config(View::TEXTURE), &xmlDoc)); - views->InsertEndChild(appearance); - - XMLElement *render = xmlDoc.NewElement( "Rendering" ); - render->InsertEndChild( SessionVisitor::NodeToXML(*session->config(View::RENDERING), &xmlDoc)); - views->InsertEndChild(render); - } + saveConfig( &xmlDoc, session ); // 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); + saveSnapshots( &xmlDoc, session ); // 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); - } + saveNotes( &xmlDoc, session ); + // 5. optional playlists + savePlayGroups( &xmlDoc, session ); // save file to disk return ( XMLSaveDoc(&xmlDoc, filename) ); } +void SessionVisitor::saveConfig(tinyxml2::XMLDocument *doc, Session *session) +{ + if (doc != nullptr && session != nullptr) + { + XMLElement *views = doc->NewElement("Views"); + + XMLElement *mixing = doc->NewElement( "Mixing" ); + mixing->InsertEndChild( SessionVisitor::NodeToXML(*session->config(View::MIXING), doc)); + views->InsertEndChild(mixing); + + XMLElement *geometry = doc->NewElement( "Geometry" ); + geometry->InsertEndChild( SessionVisitor::NodeToXML(*session->config(View::GEOMETRY), doc)); + views->InsertEndChild(geometry); + + XMLElement *layer = doc->NewElement( "Layer" ); + layer->InsertEndChild( SessionVisitor::NodeToXML(*session->config(View::LAYER), doc)); + views->InsertEndChild(layer); + + XMLElement *appearance = doc->NewElement( "Texture" ); + appearance->InsertEndChild( SessionVisitor::NodeToXML(*session->config(View::TEXTURE), doc)); + views->InsertEndChild(appearance); + + XMLElement *render = doc->NewElement( "Rendering" ); + render->InsertEndChild( SessionVisitor::NodeToXML(*session->config(View::RENDERING), doc)); + views->InsertEndChild(render); + + doc->InsertEndChild(views); + } +} + + +void SessionVisitor::saveSnapshots(tinyxml2::XMLDocument *doc, Session *session) +{ + if (doc != nullptr && session != nullptr) + { + XMLElement *snapshots = doc->NewElement("Snapshots"); + const XMLElement* N = session->snapshots()->xmlDoc_->FirstChildElement(); + for( ; N ; N=N->NextSiblingElement()) + snapshots->InsertEndChild( N->DeepClone( doc )); + doc->InsertEndChild(snapshots); + } +} + +void SessionVisitor::saveNotes(tinyxml2::XMLDocument *doc, Session *session) +{ + if (doc != nullptr && session != nullptr) + { + XMLElement *notes = doc->NewElement("Notes"); + for (auto nit = session->beginNotes(); nit != session->endNotes(); ++nit) { + XMLElement *note = doc->NewElement( "Note" ); + note->SetAttribute("large", (*nit).large ); + note->SetAttribute("stick", (*nit).stick ); + XMLElement *pos = doc->NewElement("pos"); + pos->InsertEndChild( XMLElementFromGLM(doc, (*nit).pos) ); + note->InsertEndChild(pos); + XMLElement *size = doc->NewElement("size"); + size->InsertEndChild( XMLElementFromGLM(doc, (*nit).size) ); + note->InsertEndChild(size); + XMLElement *content = doc->NewElement("text"); + XMLText *text = doc->NewText( (*nit).text.c_str() ); + content->InsertEndChild( text ); + note->InsertEndChild(content); + + notes->InsertEndChild(note); + } + doc->InsertEndChild(notes); + } +} + +void SessionVisitor::savePlayGroups(tinyxml2::XMLDocument *doc, Session *session) +{ + if (doc != nullptr && session != nullptr) + { + XMLElement *playlistNode = doc->NewElement("PlayGroups"); + std::vector pl = session->getPlayGroups(); + for (auto plit = pl.begin(); plit != pl.end(); ++plit) { + XMLElement *list = doc->NewElement("PlayGroup"); + playlistNode->InsertEndChild(list); + for (auto id = plit->begin(); id != plit->end(); ++id) { + XMLElement *sour = doc->NewElement("source"); + sour->SetAttribute("id", *id); + list->InsertEndChild(sour); + } + } + doc->InsertEndChild(playlistNode); + } +} + SessionVisitor::SessionVisitor(tinyxml2::XMLDocument *doc, tinyxml2::XMLElement *root, bool recursive) : Visitor(), recursive_(recursive), xmlCurrent_(root) diff --git a/SessionVisitor.h b/SessionVisitor.h index 35961e0..935e75b 100644 --- a/SessionVisitor.h +++ b/SessionVisitor.h @@ -14,6 +14,11 @@ class SessionVisitor : public Visitor { tinyxml2::XMLDocument *xmlDoc_; tinyxml2::XMLElement *xmlCurrent_; + static void saveConfig(tinyxml2::XMLDocument *doc, Session *session); + static void saveSnapshots(tinyxml2::XMLDocument *doc, Session *session); + static void saveNotes(tinyxml2::XMLDocument *doc, Session *session); + static void savePlayGroups(tinyxml2::XMLDocument *doc, Session *session); + public: SessionVisitor(tinyxml2::XMLDocument *doc = nullptr, tinyxml2::XMLElement *root = nullptr, diff --git a/Settings.cpp b/Settings.cpp index 68b5101..8c8a456 100644 --- a/Settings.cpp +++ b/Settings.cpp @@ -76,6 +76,7 @@ void Settings::Save() widgetsNode->SetAttribute("preview", application.widget.preview); widgetsNode->SetAttribute("history", application.widget.history); widgetsNode->SetAttribute("media_player", application.widget.media_player); + widgetsNode->SetAttribute("timeline_editmode", application.widget.timeline_editmode); widgetsNode->SetAttribute("shader_editor", application.widget.shader_editor); widgetsNode->SetAttribute("stats", application.widget.stats); widgetsNode->SetAttribute("stats_mode", application.widget.stats_mode); @@ -270,6 +271,7 @@ void Settings::Load() widgetsNode->QueryBoolAttribute("preview", &application.widget.preview); widgetsNode->QueryBoolAttribute("history", &application.widget.history); widgetsNode->QueryBoolAttribute("media_player", &application.widget.media_player); + widgetsNode->QueryBoolAttribute("timeline_editmode", &application.widget.timeline_editmode); widgetsNode->QueryBoolAttribute("shader_editor", &application.widget.shader_editor); widgetsNode->QueryBoolAttribute("stats", &application.widget.stats); widgetsNode->QueryIntAttribute("stats_mode", &application.widget.stats_mode); diff --git a/Settings.h b/Settings.h index bc3f026..94bf8bb 100644 --- a/Settings.h +++ b/Settings.h @@ -21,6 +21,7 @@ struct WidgetsConfig bool history; bool media_player; bool media_player_view; + bool timeline_editmode; bool shader_editor; bool toolbox; @@ -33,6 +34,7 @@ struct WidgetsConfig history = false; media_player = false; media_player_view = true; + timeline_editmode = false; shader_editor = false; toolbox = false; } diff --git a/Source.cpp b/Source.cpp index de56205..1212f59 100644 --- a/Source.cpp +++ b/Source.cpp @@ -871,7 +871,6 @@ void CloneSource::setActive (bool on) origin_->touch(); } - uint CloneSource::texture() const { if (origin_ != nullptr) diff --git a/Source.h b/Source.h index 44e9b62..99f1c4c 100644 --- a/Source.h +++ b/Source.h @@ -139,6 +139,13 @@ public: } Workspace; inline Workspace workspace () const { return workspace_; } + // a Source shall define a way to play + virtual bool playable () const = 0; + virtual bool playing () const = 0; + virtual void play (bool on) = 0; + virtual void replay () {} + virtual guint64 playtime () const { return 0; } + // a Source shall informs if the source failed (i.e. shall be deleted) virtual bool failed () const = 0; @@ -299,6 +306,10 @@ public: // implementation of source API void setActive (bool on) override; + bool playing () const override { return true; } + void play (bool) override {} + bool playable () const override { return false; } + void replay () override {} uint texture() const override; bool failed() const override { return origin_ == nullptr; } void accept (Visitor& v) override; diff --git a/SourceList.cpp b/SourceList.cpp index 14130ee..618fe3d 100644 --- a/SourceList.cpp +++ b/SourceList.cpp @@ -14,6 +14,18 @@ bool compare_depth (Source * first, Source * second) return ( first->depth() < second->depth() ); } + +bool notplayable (const Source *s) { return !s->playable(); } + +SourceList playable_only (const SourceList &list) +{ + SourceList pl = list; + + pl.remove_if(notplayable); + + return pl; +} + SourceList depth_sorted(const SourceList &list) { SourceList sl = list; diff --git a/SourceList.h b/SourceList.h index 160b547..549246b 100644 --- a/SourceList.h +++ b/SourceList.h @@ -11,6 +11,7 @@ class Session; typedef std::list SourceList; typedef std::list SourceCoreList; +SourceList playable_only (const SourceList &list); SourceList depth_sorted (const SourceList &list); SourceList mixing_sorted (const SourceList &list, glm::vec2 center = glm::vec2(0.f, 0.f)); SourceList intersect (const SourceList &first, const SourceList &second); diff --git a/Stream.cpp b/Stream.cpp index 0be660e..cc1f509 100644 --- a/Stream.cpp +++ b/Stream.cpp @@ -31,6 +31,7 @@ Stream::Stream() opened_ = false; enabled_ = true; desired_state_ = GST_STATE_PAUSED; + position_ = GST_CLOCK_TIME_NONE; width_ = -1; height_ = -1; @@ -371,7 +372,26 @@ bool Stream::isPlaying(bool testpipeline) const return state == GST_STATE_PLAYING; } +void Stream::rewind() +{ + if ( pipeline_ == nullptr ) + return; + GstEvent *seek_event = gst_event_new_seek (1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, + GST_SEEK_TYPE_SET, 0, GST_SEEK_TYPE_END, 0); + gst_element_send_event(pipeline_, seek_event); +} + +GstClockTime Stream::position() +{ + if (position_ == GST_CLOCK_TIME_NONE && pipeline_ != nullptr) { + gint64 p = GST_CLOCK_TIME_NONE; + if ( gst_element_query_position (pipeline_, GST_FORMAT_TIME, &p) ) + position_ = p; + } + + return position_; +} void Stream::init_texture(guint index) { @@ -545,6 +565,9 @@ void Stream::update() frame_[read_index].unmap(); } + // we just displayed a vframe : set position time to frame PTS + position_ = frame_[read_index].position; + // avoid reading it again frame_[read_index].status = INVALID; } diff --git a/Stream.h b/Stream.h index f4c6a4b..4526e55 100644 --- a/Stream.h +++ b/Stream.h @@ -83,6 +83,14 @@ public: * Performs a full check of the Gstreamer pipeline if testpipeline is true * */ bool isPlaying(bool testpipeline = false) const; + /** + * Attempt to restart + * */ + virtual void rewind(); + /** + * Get position time + * */ + virtual GstClockTime position(); /** * Get rendering update framerate * measured during play @@ -126,6 +134,7 @@ protected: bool live_; // GST & Play status + GstClockTime position_; GstState desired_state_; GstElement *pipeline_; GstVideoInfo v_frame_video_info_; diff --git a/StreamSource.cpp b/StreamSource.cpp index 0a50533..470cc76 100644 --- a/StreamSource.cpp +++ b/StreamSource.cpp @@ -102,13 +102,49 @@ void StreamSource::setActive (bool on) { bool was_active = active_; + // try to activate (may fail if source is cloned) Source::setActive(on); - // change status of media player (only if status changed) - if ( active_ != was_active ) { - if (stream_) - stream_->enable(active_); - } + // change status of stream (only if status changed) + if ( stream_ && active_ != was_active ) + stream_->enable(active_); + +} + + +bool StreamSource::playing () const +{ + if ( stream_ ) + return stream_->isPlaying(); + return false; +} + +void StreamSource::play (bool on) +{ + if ( stream_ ) + stream_->play(on); +} + + +bool StreamSource::playable () const +{ + if ( stream_ ) + return !stream_->singleFrame(); + return false; +} + +void StreamSource::replay () +{ + if ( stream_ ) + stream_->rewind(); +} + +guint64 StreamSource::playtime () const +{ + if ( stream_ ) + return stream_->position(); + else + return 0; } void StreamSource::update(float dt) diff --git a/StreamSource.h b/StreamSource.h index 41b944d..cbc6df8 100644 --- a/StreamSource.h +++ b/StreamSource.h @@ -30,6 +30,11 @@ public: // implementation of source API void update (float dt) override; void setActive (bool on) override; + bool playing () const override; + void play (bool) override; + bool playable () const override; + void replay () override; + guint64 playtime () const override; bool failed() const override; uint texture() const override; diff --git a/UserInterfaceManager.cpp b/UserInterfaceManager.cpp index 2053a2e..3363572 100644 --- a/UserInterfaceManager.cpp +++ b/UserInterfaceManager.cpp @@ -69,7 +69,7 @@ static TextEditor editor; #include "UserInterfaceManager.h" #define PLOT_ARRAY_SIZE 180 -#define LABEL_AUTO_MEDIA_PLAYER "Active source" +#define LABEL_AUTO_MEDIA_PLAYER "Selected sources" // utility functions void ShowAboutGStreamer(bool* p_open); @@ -141,7 +141,6 @@ bool UserInterface::Init() ImGuiToolkit::SetFont(ImGuiToolkit::FONT_BOLD, "Roboto-Bold", int(base_font_size) ); ImGuiToolkit::SetFont(ImGuiToolkit::FONT_ITALIC, "Roboto-Italic", int(base_font_size) ); ImGuiToolkit::SetFont(ImGuiToolkit::FONT_MONO, "Hack-Regular", int(base_font_size) - 2); - // font for Navigator = 1.5 x base size (with low oversampling) ImGuiToolkit::SetFont(ImGuiToolkit::FONT_LARGE, "Hack-Regular", MIN(int(base_font_size * 1.5f), 50), 1 ); // info @@ -216,10 +215,10 @@ void UserInterface::handleKeyboard() // New Session Mixer::manager().close(); } -// else if (ImGui::IsKeyPressed( GLFW_KEY_SPACE )) { -// // New Session -// Mixer::manager().session()->setActive( !Mixer::manager().session()->active() ); -// } + else if (ImGui::IsKeyPressed( GLFW_KEY_SPACE )) { + // New Session + Mixer::manager().session()->setActive( !Mixer::manager().session()->active() ); + } else if (ImGui::IsKeyPressed( GLFW_KEY_L )) { // Logs Settings::application.widget.logs = !Settings::application.widget.logs; @@ -752,7 +751,8 @@ void UserInterface::Render() if (Settings::application.widget.history) RenderHistory(); if (Settings::application.widget.media_player) - mediacontrol.Render(); + sourcecontrol.Render(); +// mediacontrol.Render(); if (Settings::application.widget.shader_editor) RenderShaderEditor(); if (Settings::application.widget.logs) @@ -2293,7 +2293,7 @@ void MediaController::Render() if ( ImGuiToolkit::EditPlotHistoLines("##TimelineArray", mp_->timeline()->gapsArray(), mp_->timeline()->fadingArray(), - MAX_TIMELINE_ARRAY, 0.f, 1.f, &released, size) ) { + MAX_TIMELINE_ARRAY, 0.f, 1.f, true, &released, size) ) { mp_->timeline()->update(); } else if (released) { @@ -2343,6 +2343,668 @@ void MediaController::Render() ImGui::End(); } + +/// +/// SOURCE CONTROLLER +/// +SourceController::SourceController() : active_label_(LABEL_AUTO_MEDIA_PLAYER), + active_selection_(-1), media_playing_mode_(false), slider_pressed_(false) +{ +} + + + +void SourceController::Render() +{ +// ImGui::SetNextWindowPos(ImVec2(1180, 400), ImGuiCond_FirstUseEver); +// ImGui::SetNextWindowSize(ImVec2(400, 400), ImGuiCond_FirstUseEver); + + // estimate window size + const ImGuiContext& g = *GImGui; + _h_space = g.Style.WindowPadding.x; + _v_space = g.Style.FramePadding.y; + _buttons_height = g.FontSize + _v_space * 4.0f ; + _min_width = 6.f * _buttons_height; + _timeline_height = (g.FontSize + _v_space) * 2.0f ; // double line for each timeline + _scrollbar = g.Style.ScrollbarSize; + // all together: 1 title bar + spacing + 1 toolbar + spacing + 2 timelines + scrollbar + _mediaplayer_height = _buttons_height + 2.f * _timeline_height + _scrollbar + 2.f * _v_space; + + // constraint position + static ImVec2 source_window_pos = ImVec2(1180, 20); + static ImVec2 source_window_size = ImVec2(400, 260); + SetNextWindowVisible(source_window_pos, source_window_size); + + ImGui::SetNextWindowSizeConstraints(ImVec2(_min_width, 2.f * _mediaplayer_height), ImVec2(FLT_MAX, FLT_MAX)); + + if ( !ImGui::Begin(IMGUI_TITLE_MEDIAPLAYER, &Settings::application.widget.media_player, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse )) + { + ImGui::End(); + return; + } + source_window_pos = ImGui::GetWindowPos(); + source_window_size = ImGui::GetWindowSize(); + + + // menu (no title bar) + if (ImGui::BeginMenuBar()) + { + if (ImGuiToolkit::IconButton(4,16)) + Settings::application.widget.media_player = false; + if (ImGui::BeginMenu(IMGUI_TITLE_MEDIAPLAYER)) + { + + if ( ImGui::MenuItem( ICON_FA_TIMES " Close", CTRL_MOD "P") ) + Settings::application.widget.media_player = false; + + ImGui::EndMenu(); + } + + if (ImGui::BeginMenu(active_label_.c_str())) + { + if (ImGui::MenuItem("Selected sources")) { + active_selection_ = -1; + active_label_ = LABEL_AUTO_MEDIA_PLAYER; + } + + // display list of available media + for (size_t i = 0 ; i < Mixer::manager().session()->numPlayGroups(); ++i) + { + std::string label = std::string("Selection #") + std::to_string(i); + if (ImGui::MenuItem( label.c_str() )) { + active_selection_ = i; + active_label_ = label; + } + } + + if (ImGui::MenuItem("New selection")) + { + active_selection_ = Mixer::manager().session()->numPlayGroups(); + active_label_ = std::string("Selection #") + std::to_string(active_selection_); + Mixer::manager().session()->addPlayGroup( ids(playable_only(Mixer::selection().getCopy())) ); + } + + + ImGui::EndMenu(); + } + + ImGui::EndMenuBar(); + } + + + if (active_selection_ > -1) { + selection_ = Mixer::manager().session()->playGroup(active_selection_); + RenderSelection(active_selection_); + } + else { + selection_ = playable_only(Mixer::selection().getCopy()); + RenderSelectedSources(); + } + + ImGui::End(); + +} + +void SourceController::RenderSelection(size_t i) +{ + ImVec2 top = ImGui::GetCursorScreenPos(); + ImVec2 rendersize = ImGui::GetContentRegionAvail() - ImVec2(0, _buttons_height + _scrollbar + _v_space); + ImVec2 bottom = ImVec2(top.x, top.y + rendersize.y + _v_space); + + int numsources = selection_.size(); + // no source selected + if (numsources < 1) + { + + } + else { + /// + /// Sources grid + /// + ImGui::BeginChild("##v_scroll", rendersize, false, ImGuiWindowFlags_AlwaysVerticalScrollbar); + { + // area horizontal pack + int numcolumns = CLAMP( int(ceil(1.0f * rendersize.x / rendersize.y)), 1, numsources ); + ImGui::Columns( numcolumns, "##selectiongrid", false); + + for (auto source = selection_.begin(); source != selection_.end(); ++source) { + + FrameBuffer *frame = (*source)->frame(); + ImVec2 framesize(ImGui::GetColumnWidth(), ImGui::GetColumnWidth() / frame->aspectRatio()); + + ImVec2 image_top = ImGui::GetCursorPos(); + ImGui::SetCursorPosX(image_top.x -_h_space ); + ImGui::Image((void*)(uintptr_t) (*source)->texture(), framesize); + ImVec2 image_bottom = ImGui::GetCursorPos(); + + // Play icon lower left corner + ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO); + ImGui::SetCursorPos(image_top + ImVec2( numcolumns > 1 ? 0.f : _h_space, framesize.y - ImGui::GetTextLineHeightWithSpacing())); + if ((*source)->active()) + ImGui::Text("%s %s", (*source)->playing() ? ICON_FA_PLAY : ICON_FA_PAUSE, GstToolkit::time_to_string((*source)->playtime()).c_str() ); + else + ImGui::Text("%s %s", ICON_FA_SNOWFLAKE, GstToolkit::time_to_string((*source)->playtime()).c_str() ); + ImGui::PopFont(); + + ImGui::SetCursorPos(image_bottom); + ImGui::NextColumn(); + } + + ImGui::Columns(1); + } + ImGui::EndChild(); + } + + /// + /// Play button bar + /// + DrawButtonBar(bottom, rendersize.x); + + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.00f, 0.00f, 0.00f, 0.00f)); + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.00f, 0.00f, 0.00f, 0.00f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(0.14f, 0.14f, 0.14f, 0.7f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.14f, 0.14f, 0.14f, 0.7f)); + + ImGui::SetCursorScreenPos(bottom + ImVec2(rendersize.x * 0.6f, _v_space) ); + ImGui::SetNextItemWidth(-_h_space); + std::string label = std::to_string(numsources) + ( numsources > 1 ? " sources" : " source"); + if (ImGui::BeginCombo("##SelectionImport", label.c_str())) + { + for (auto s = Mixer::manager().session()->begin(); s != Mixer::manager().session()->end(); ++s) { + if ( (*s)->playable() ) { + + if (std::find(selection_.begin(),selection_.end(),*s) == selection_.end()) { + if (ImGui::MenuItem( (*s)->name().c_str() )) + Mixer::manager().session()->addToPlayGroup(i, *s); + } + else { + if (ImGui::MenuItem( (*s)->name().c_str(), ICON_FA_CHECK )) + Mixer::manager().session()->removeFromPlayGroup(i, *s); + } + } + } + ImGui::EndCombo(); + } + + ImGui::PopStyleColor(4); + + +// const float preview_height = 4.5f * ImGui::GetFrameHeightWithSpacing(); + +// ImGui::Columns(2, NULL, true); + +// // selection_ = Mixer::manager().session()->getDepthSortedList(); + +// Source *_toremove = nullptr; +// for (auto source = selection_.begin(); source != selection_.end(); ++source) { + +// FrameBuffer *frame = (*source)->frame(); +// float width = ImGui::GetColumnWidth(); +// float height = width / frame->aspectRatio(); +// if (height > preview_height) { +// height = preview_height; +// width = height * frame->aspectRatio(); +// } +// ImVec2 top = ImGui::GetCursorPos(); +// ImGui::Image((void*)(uintptr_t) (*source)->texture(), ImVec2(width, height)); +// ImVec2 bottom = ImGui::GetCursorPos(); + +// // Context menu button up-left corner +// if (ImGui::IsItemHovered(ImGuiHoveredFlags_RectOnly)) +// { +// ImGui::SetCursorPos(top + ImVec2(_h_space, _v_space)); +// if (ImGuiToolkit::ButtonIcon(5, 8)) +// ImGui::OpenPopup( "MenuSourceControl" ); +// if (ImGui::BeginPopup( "MenuSourceControl" )) +// { +// if (ImGui::MenuItem(ICON_FA_MINUS_SQUARE " Remove from selection" )){ +// _toremove = *source; +// } +// if (ImGui::MenuItem(ICON_FA_FILM " Open in Player" )){ + +// } +// ImGui::EndPopup(); +// } +// } + +// // Play icon lower left corner +// ImGui::SetCursorPos(top + ImVec2(_h_space, height - ImGui::GetTextLineHeightWithSpacing())); +// if ((*source)->active()) +// ImGui::Text("%s", (*source)->playing() ? ICON_FA_PLAY : ICON_FA_PAUSE ); +// else +// ImGui::Text(ICON_FA_SNOWFLAKE); + + +// ImGui::SetCursorPos(bottom + ImVec2(0, _v_space)); +// ImGui::NextColumn(); + +// } +// // apply source removal +// if (_toremove) +// selection_.remove(_toremove); + +// // Add source in selection +// ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE); +// if (ImGuiToolkit::IconButton(ICON_FA_PLUS_SQUARE)) +// ImGui::OpenPopup( "SelectionAddSourceList" ); +// ImGui::PopFont(); +// if (ImGui::BeginPopup( "SelectionAddSourceList" )) +// { +// uint count = 0; +// for (auto s = Mixer::manager().session()->begin(); s != Mixer::manager().session()->end(); ++s) { +// if ( (*s)->playable() && std::find(selection_.begin(),selection_.end(),*s) == selection_.end()) { +// if (ImGui::MenuItem( (*s)->name().c_str() )) +// selection_.push_back( *s ); +// ++count; +// } +// } +// if (count<1) +// ImGui::MenuItem( "No playable source available ", 0, false, false); +// ImGui::EndPopup(); +// } + +// ImGui::Columns(1); + + +} + +void SourceController::RenderSelectedSources() +{ + + ImVec2 top = ImGui::GetCursorScreenPos(); + ImVec2 rendersize = ImGui::GetContentRegionAvail() - ImVec2(0, _buttons_height + _scrollbar + _v_space); + ImVec2 bottom = ImVec2(top.x, top.y + rendersize.y + _v_space); + + int numsources = selection_.size(); + // no source selected + if (numsources < 1) + { + /// + /// Centered text + /// + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.6, 0.6, 0.6, 0.5f)); + ImVec2 center = rendersize * ImVec2(0.5f, 0.5f); + ImGuiToolkit::PushFont(ImGuiToolkit::FONT_ITALIC); + center.x -= ImGui::GetTextLineHeight() * 2.f; + ImGui::SetCursorScreenPos(top + center); + ImGui::Text("Nothing to play"); + ImGui::PopFont(); + ImGui::PopStyleColor(1); + /// + /// Play button bar + /// + DrawButtonBar(bottom, rendersize.x); + + } + // single source selected + else if (numsources < 2) + { + /// + /// Sources display + /// + RenderSingleSource( selection_.front() ); + } + // Several sources selected + else { + + /// + /// Sources grid + /// + ImGui::BeginChild("##v_scroll", rendersize, false, ImGuiWindowFlags_AlwaysVerticalScrollbar); + { + // area horizontal pack + int numcolumns = CLAMP( int(ceil(1.0f * rendersize.x / rendersize.y)), 1, numsources ); + ImGui::Columns( numcolumns, "##selectiongrid", false); + + for (auto source = selection_.begin(); source != selection_.end(); ++source) { + + FrameBuffer *frame = (*source)->frame(); + ImVec2 framesize(ImGui::GetColumnWidth(), ImGui::GetColumnWidth() / frame->aspectRatio()); + + ImVec2 image_top = ImGui::GetCursorPos(); + ImGui::SetCursorPosX(image_top.x -_h_space ); + ImGui::Image((void*)(uintptr_t) (*source)->texture(), framesize); + ImVec2 image_bottom = ImGui::GetCursorPos(); + + // Play icon lower left corner + ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO); + ImGui::SetCursorPos(image_top + ImVec2( numcolumns > 1 ? 0.f : _h_space, framesize.y - ImGui::GetTextLineHeightWithSpacing())); + if ((*source)->active()) + ImGui::Text("%s %s", (*source)->playing() ? ICON_FA_PLAY : ICON_FA_PAUSE, GstToolkit::time_to_string((*source)->playtime()).c_str() ); + else + ImGui::Text("%s %s", ICON_FA_SNOWFLAKE, GstToolkit::time_to_string((*source)->playtime()).c_str() ); + ImGui::PopFont(); + + ImGui::SetCursorPos(image_bottom); + ImGui::NextColumn(); + } + + ImGui::Columns(1); + } + ImGui::EndChild(); + + /// + /// Play button bar + /// + DrawButtonBar(bottom, rendersize.x); + } + +} + +void SourceController::RenderSingleSource(Source *s) +{ + if ( s == nullptr) + return; + + // in case of a MediaSource + MediaSource *ms = dynamic_cast(s); + if ( ms != nullptr) { + RenderMediaPlayer( ms->mediaplayer() ); + } + else + { + ImVec2 top = ImGui::GetCursorScreenPos(); + ImVec2 rendersize = ImGui::GetContentRegionAvail() - ImVec2(0, _buttons_height + _scrollbar + _v_space); + ImVec2 bottom = ImVec2(top.x, top.y + rendersize.y + _v_space); + + /// + /// Centered frame + /// + FrameBuffer *frame = s->frame(); + ImVec2 framesize = rendersize; + ImVec2 corner(0.f, 0.f); + ImVec2 tmp = ImVec2(framesize.y * frame->aspectRatio(), framesize.x / frame->aspectRatio()); + if (tmp.x > framesize.x) { + corner.y = (framesize.y - tmp.y) / 2.f; + framesize.y = tmp.y; + } + else { + corner.x = (framesize.x - tmp.x) / 2.f; + framesize.x = tmp.x; + } + + ImGui::SetCursorScreenPos(top + corner); + ImGui::Image((void*)(uintptr_t) s->texture(), framesize); + + // Play icon lower left corner + ImGuiToolkit::PushFont(framesize.x > 200.f ? ImGuiToolkit::FONT_LARGE : ImGuiToolkit::FONT_MONO); + ImGui::SetCursorScreenPos(top + corner + ImVec2(_h_space, framesize.y - ImGui::GetTextLineHeightWithSpacing())); + if (s->active()) + ImGui::Text("%s %s", s->playing() ? ICON_FA_PLAY : ICON_FA_PAUSE, GstToolkit::time_to_string(s->playtime()).c_str() ); + else + ImGui::Text("%s %s", ICON_FA_SNOWFLAKE, GstToolkit::time_to_string(s->playtime()).c_str() ); + ImGui::PopFont(); + + /// + /// Play source button bar + /// + DrawButtonBar(bottom, rendersize.x); + + } +} + +void SourceController::RenderMediaPlayer(MediaPlayer *mp) +{ + static float timeline_zoom = 1.f; + const float slider_zoom_width = _timeline_height / 2.f; + + ImVec2 top = ImGui::GetCursorScreenPos(); + ImVec2 rendersize = ImGui::GetContentRegionAvail() - ImVec2(0, _mediaplayer_height); + ImVec2 bottom = ImVec2(top.x, top.y + rendersize.y + _v_space); + + /// + /// Centered frame + /// + ImVec2 framesize = rendersize; + ImVec2 corner(0.f, 0.f); + ImVec2 tmp = ImVec2(framesize.y * mp->aspectRatio(), framesize.x / mp->aspectRatio()); + if (tmp.x > framesize.x) { + corner.y = (framesize.y - tmp.y) / 2.f; + framesize.y = tmp.y; + } + else { + corner.x = (framesize.x - tmp.x) / 2.f; + framesize.x = tmp.x; + } + + ImGui::SetCursorScreenPos(top + corner); + ImGui::Image((void*)(uintptr_t) mp->texture(), framesize); + + // Play icon lower left corner + ImGuiToolkit::PushFont(framesize.x > 200.f ? ImGuiToolkit::FONT_LARGE : ImGuiToolkit::FONT_MONO); + ImGui::SetCursorScreenPos(top + corner + ImVec2(_h_space, framesize.y - ImGui::GetTextLineHeightWithSpacing())); + if (mp->isEnabled()) + ImGui::Text("%s %s", mp->isPlaying() ? ICON_FA_PLAY : ICON_FA_PAUSE, GstToolkit::time_to_string(mp->position()).c_str() ); + else + ImGui::Text("%s %s", ICON_FA_SNOWFLAKE, GstToolkit::time_to_string(mp->position()).c_str() ); + ImGui::PopFont(); + + /// + /// media player buttons bar + /// + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + draw_list->AddRectFilled(bottom, bottom + ImVec2(rendersize.x, _buttons_height), ImGui::GetColorU32(ImGuiCol_FrameBg), _h_space); + + // buttons style + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.00f, 0.00f, 0.00f, 0.00f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.24f, 0.24f, 0.24f, 0.7f)); + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.24f, 0.24f, 0.24f, 0.2f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(0.14f, 0.14f, 0.14f, 0.4f)); + + ImGui::SetCursorScreenPos(bottom + ImVec2(_h_space, _v_space) ); + if (ImGui::Button(mp->playSpeed() > 0 ? ICON_FA_FAST_BACKWARD :ICON_FA_FAST_FORWARD)) + mp->rewind(); + + // ignore actual play status of mediaplayer when slider is pressed + if (!slider_pressed_) + media_playing_mode_ = mp->isPlaying(); + + // display buttons Play/Stop depending on current playing mode + ImGui::SameLine(0, _h_space); + if (media_playing_mode_) { + if (ImGui::Button(ICON_FA_PAUSE)) + media_playing_mode_ = false; + ImGui::SameLine(0, _h_space); + + ImGui::PushButtonRepeat(true); + if (ImGui::Button( mp->playSpeed() < 0 ? ICON_FA_BACKWARD :ICON_FA_FORWARD)) + mp->jump (); + ImGui::PopButtonRepeat(); + } + else { + if (ImGui::Button(ICON_FA_PLAY)) + media_playing_mode_ = true; + ImGui::SameLine(0, _h_space); + + ImGui::PushButtonRepeat(true); + if (ImGui::Button( mp->playSpeed() < 0 ? ICON_FA_STEP_BACKWARD : ICON_FA_STEP_FORWARD)) + mp->step(); + ImGui::PopButtonRepeat(); + } + + // loop modes button + ImGui::SameLine(0, _h_space); + static int current_loop = 0; + static std::vector< std::pair > iconsloop = { {0,15}, {1,15}, {19,14} }; + current_loop = (int) mp->loop(); + if ( ImGuiToolkit::ButtonIconMultistate(iconsloop, ¤t_loop) ) + mp->setLoop( (MediaPlayer::LoopMode) current_loop ); + + // speed slider (if enough space) + float speed = static_cast(mp->playSpeed()); + if ( rendersize.x > _min_width * 1.4f ) { + ImGui::SameLine(0, MAX(_h_space * 2.f, rendersize.x - _min_width * 1.6f) ); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - _buttons_height ); + if (ImGui::DragFloat( "##Speed", &speed, 0.01f, -10.f, 10.f, "Speed x %.1f", 2.f)) + mp->setPlaySpeed( static_cast(speed) ); + } + + ImGui::SameLine(); + ImGui::SetCursorPosX(rendersize.x - _buttons_height / 1.5f); + + // Timeline popup menu + if (ImGuiToolkit::IconButton(5,8) ) + ImGui::OpenPopup( "MenuTimeline" ); + if (ImGui::BeginPopup( "MenuTimeline" )) + { + if (ImGui::MenuItem("Reset Speed" )){ + speed = 1.f; + mp->setPlaySpeed( static_cast(speed) ); + } + if (ImGui::MenuItem( "Reset Timeline" )){ + timeline_zoom = 1.f; + mp->timeline()->clearFading(); + mp->timeline()->clearGaps(); + Action::manager().store("Timeline Reset"); + } + 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) { + if (ImGui::MenuItem(names[i])) { + mp->timeline()->smoothFading( 10 * (int) pow(4, i) ); + Action::manager().store("Timeline Smooth curve"); + } + } + ImGui::EndMenu(); + } + 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) { + if (ImGui::MenuItem(names[i])) { + mp->timeline()->autoFading( 250 * (int ) pow(2, i) ); + mp->timeline()->smoothFading( 10 * (i + 1) ); + Action::manager().store("Timeline Auto fading"); + } + } + ImGui::EndMenu(); + } + if (Settings::application.render.gpu_decoding && ImGui::BeginMenu("Hardware Decoding")) + { + bool hwdec = !mp->softwareDecodingForced(); + if (ImGui::MenuItem("Auto", "", &hwdec )) + mp->setSoftwareDecodingForced(false); + hwdec = mp->softwareDecodingForced(); + if (ImGui::MenuItem("Disabled", "", &hwdec )) + mp->setSoftwareDecodingForced(true); + ImGui::EndMenu(); + } + ImGui::EndPopup(); + } + + // restore buttons style + ImGui::PopStyleColor(5); + + /// + /// media player timelines + /// + ImGui::SetCursorScreenPos(bottom + ImVec2(0, _buttons_height + _v_space) ); + + // seek position + guint64 seek_t = mp->position(); + + // scrolling sub-window + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(1.f, 1.f)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 1.f); + + ImVec2 top_scrollwindow = ImGui::GetCursorPos(); + ImVec2 scrollwindow = ImVec2(ImGui::GetContentRegionAvail().x - slider_zoom_width - 3.0, + 2.f * _timeline_height + _scrollbar ); + + ImGui::BeginChild("##scrolling", scrollwindow, false, ImGuiWindowFlags_HorizontalScrollbar); + { + ImVec2 size = ImGui::CalcItemSize(ImVec2(-FLT_MIN, 0.0f), ImGui::CalcItemWidth(), _timeline_height -1); + size.x *= timeline_zoom; + + bool released = false; + if ( ImGuiToolkit::EditPlotHistoLines("##TimelineArray", + mp->timeline()->gapsArray(), + mp->timeline()->fadingArray(), + MAX_TIMELINE_ARRAY, 0.f, 1.f, + Settings::application.widget.timeline_editmode, &released, size) ) { + mp->timeline()->update(); + } + else if (released) { + Action::manager().store("Timeline change"); + } + + // custom timeline slider + slider_pressed_ = ImGuiToolkit::TimelineSlider("##timeline", &seek_t, mp->timeline()->begin(), + mp->timeline()->end(), mp->timeline()->step(), size.x); + + } + ImGui::EndChild(); + + ImGui::PopStyleVar(2); + + // action mode + ImGui::SetCursorPos(top_scrollwindow + ImVec2(scrollwindow.x + 3.f, 0)); + ImGuiToolkit::IconToggle(7,4,8,3,&Settings::application.widget.timeline_editmode); + + // zoom slider + ImGui::SetCursorPos(top_scrollwindow + ImVec2(scrollwindow.x + 3.f, 0.5f * _timeline_height + 3.f)); + ImGui::VSliderFloat("##TimelineZoom", ImVec2(slider_zoom_width, 1.5f * _timeline_height - 3.f), &timeline_zoom, 1.0, 5.f, ""); + + /// + /// media player actions + /// + /// + // request seek (ASYNC) + if ( slider_pressed_ && mp->go_to(seek_t) ) + slider_pressed_ = false; + + // play/stop command should be following the playing mode (buttons) + // AND force to stop when the slider is pressed + bool media_play = media_playing_mode_ & (!slider_pressed_); + + // apply play action to media only if status should change + if ( mp->isPlaying() != media_play ) { + mp->play( media_play ); + } +} + +void SourceController::DrawButtonBar(ImVec2 bottom, float width) +{ + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + draw_list->AddRectFilled(bottom, bottom + ImVec2(width, _buttons_height), ImGui::GetColorU32(ImGuiCol_FrameBg), _h_space); + + // buttons style: inactive if no source in selection + if (selection_.empty()) { + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.6, 0.6, 0.6, 0.5f)); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.00f, 0.00f, 0.00f, 0.00f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.00f, 0.00f, 0.00f, 0.00f)); + } + else { + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.00f, 0.00f, 0.00f, 0.00f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.24f, 0.24f, 0.24f, 0.7f)); + } + + ImGui::SetCursorScreenPos(bottom + ImVec2(_h_space, _v_space) ); + if (ImGui::Button(ICON_FA_FAST_BACKWARD)) + { + for (auto source = selection_.begin(); source != selection_.end(); ++source) + (*source)->replay(); + } + ImGui::SameLine(0, _h_space); + if (ImGui::Button(ICON_FA_PLAY)) + { + for (auto source = selection_.begin(); source != selection_.end(); ++source) + (*source)->play(true); + } + ImGui::SameLine(0, _h_space); + if (ImGui::Button(ICON_FA_PAUSE)) + { + for (auto source = selection_.begin(); source != selection_.end(); ++source) + (*source)->play(false); + } + + // restore + ImGui::PopStyleColor(3); +} + /// /// NAVIGATOR /// diff --git a/UserInterfaceManager.h b/UserInterfaceManager.h index d5f579c..460f415 100644 --- a/UserInterfaceManager.h +++ b/UserInterfaceManager.h @@ -10,8 +10,9 @@ #define NAV_MENU 66 #define NAV_TRANS 67 +#include "SourceList.h" + struct ImVec2; -class Source; class MediaPlayer; class FrameBufferImage; class FrameGrabber; @@ -119,6 +120,36 @@ public: void Render(); }; +class SourceController +{ + float _min_width; + float _h_space; + float _v_space; + float _buttons_height; + float _timeline_height; + float _scrollbar; + float _mediaplayer_height; + + std::string active_label_; + int active_selection_; + + SourceList selection_; + void DrawButtonBar(ImVec2 bottom, float width); + + void RenderSelectedSources(); + void RenderSelection(size_t i); + void RenderSingleSource(Source *s); + + bool media_playing_mode_; + bool slider_pressed_; + void RenderMediaPlayer(MediaPlayer *mp); + +public: + SourceController(); + + void Render(); +}; + class UserInterface { @@ -126,6 +157,7 @@ class UserInterface Navigator navigator; ToolBox toolbox; MediaController mediacontrol; + SourceController sourcecontrol; bool ctrl_modifier_active; bool alt_modifier_active;