diff --git a/rsc/images/icons.dds b/rsc/images/icons.dds index fbbc4b3..26d142a 100644 Binary files a/rsc/images/icons.dds and b/rsc/images/icons.dds differ diff --git a/src/SessionCreator.cpp b/src/SessionCreator.cpp index ba49d24..285c485 100644 --- a/src/SessionCreator.cpp +++ b/src/SessionCreator.cpp @@ -926,6 +926,18 @@ void SessionLoader::visit(MediaPlayer &n) fadingselement->QueryUnsignedAttribute("mode", &mode); n.setTimelineFadingMode((MediaPlayer::FadingMode) mode); } + XMLElement *flagselement = timelineelement->FirstChildElement("Flags"); + if (flagselement) { + XMLElement* flag = flagselement->FirstChildElement("Interval"); + for( ; flag ; flag = flag->NextSiblingElement()) + { + uint64_t a = GST_CLOCK_TIME_NONE; + uint64_t b = GST_CLOCK_TIME_NONE; + flag->QueryUnsigned64Attribute("begin", &a); + flag->QueryUnsigned64Attribute("end", &b); + tl.addFlag( TimeInterval( (GstClockTime) a, (GstClockTime) b ) ); + } + } n.setTimeline(tl); } diff --git a/src/SessionVisitor.cpp b/src/SessionVisitor.cpp index 5ba7a96..eb5c347 100644 --- a/src/SessionVisitor.cpp +++ b/src/SessionVisitor.cpp @@ -469,6 +469,18 @@ void SessionVisitor::visit(MediaPlayer &n) XMLElement *array = XMLElementEncodeArray(xmlDoc_, n.timeline()->fadingArray(), MAX_TIMELINE_ARRAY * sizeof(float)); fadingelement->InsertEndChild(array); timelineelement->InsertEndChild(fadingelement); + + // flags in timeline + XMLElement *flagselement = xmlDoc_->NewElement("Flags"); + TimeIntervalSet flags = n.timeline()->flags(); + for( auto it = flags.begin(); it!= flags.end(); ++it) { + XMLElement *f = xmlDoc_->NewElement("Interval"); + f->SetAttribute("begin", (uint64_t) (*it).begin); + f->SetAttribute("end", (uint64_t) (*it).end); + flagselement->InsertEndChild(f); + } + timelineelement->InsertEndChild(flagselement); + newelement->InsertEndChild(timelineelement); fadingelement->SetAttribute("mode", (uint) n.timelineFadingMode()); } diff --git a/src/Settings.cpp b/src/Settings.cpp index 5266295..1d1f277 100644 --- a/src/Settings.cpp +++ b/src/Settings.cpp @@ -521,7 +521,7 @@ void Settings::Load(const std::string &filename) widgetsNode->QueryIntAttribute("inputs_view", &application.widget.inputs_view); widgetsNode->QueryBoolAttribute("media_player", &application.widget.media_player); widgetsNode->QueryIntAttribute("media_player_view", &application.widget.media_player_view); - widgetsNode->QueryBoolAttribute("timeline_editmode", &application.widget.media_player_timeline_editmode); + widgetsNode->QueryIntAttribute("timeline_editmode", &application.widget.media_player_timeline_editmode); widgetsNode->QueryFloatAttribute("media_player_slider", &application.widget.media_player_slider); widgetsNode->QueryBoolAttribute("shader_editor", &application.widget.shader_editor); widgetsNode->QueryIntAttribute("shader_editor_view", &application.widget.shader_editor_view); diff --git a/src/Settings.h b/src/Settings.h index f2efe7a..525d976 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -23,7 +23,7 @@ struct WidgetsConfig int preview_view; bool media_player; int media_player_view; - bool media_player_timeline_editmode; + int media_player_timeline_editmode; float media_player_slider; bool timer; int timer_view; @@ -46,7 +46,7 @@ struct WidgetsConfig preview_view = -1; media_player = false; media_player_view = -1; - media_player_timeline_editmode = false; + media_player_timeline_editmode = 0; media_player_slider = 0.f; toolbox = false; help = false; diff --git a/src/SourceControlWindow.cpp b/src/SourceControlWindow.cpp index c086852..eda0473 100644 --- a/src/SourceControlWindow.cpp +++ b/src/SourceControlWindow.cpp @@ -57,7 +57,9 @@ typedef struct payload FADE_OUT_IN, CUT, CUT_MERGE, - CUT_ERASE + CUT_ERASE, + FLAG_ADD, + FLAG_REMOVE }; Action action; guint64 drop_time; @@ -464,6 +466,7 @@ void SourceControlWindow::Render() mediaplayer_timeline_zoom_ = 1.f; mediaplayer_active_->timeline()->clearFading(); mediaplayer_active_->timeline()->clearGaps(); + mediaplayer_active_->timeline()->clearFlags(); mediaplayer_active_->setVideoEffect(""); std::ostringstream oss; oss << SystemToolkit::base_filename( mediaplayer_active_->filename() ); @@ -566,7 +569,7 @@ void DrawTimeScale(const char* label, guint64 duration, double width_ratio) bool EditTimeline(const char *label, Timeline *tl, - bool edit_histogram, + int edit_mode, bool *released, const ImVec2 size) { @@ -575,11 +578,13 @@ bool EditTimeline(const char *label, const guint64 begin = tl->begin(); const guint64 end = tl->end(); - bool cursor_dot = !edit_histogram; + bool cursor_dot = edit_mode == 1; + bool cursor_flag = false; Timeline _tl; bool array_changed = false; float *lines_array = tl->fadingArray(); - float *histogram_array = tl->gapsArray(); + float *gaps_array = tl->gapsArray(); + float *flags_array = tl->flagsArray(); // get window ImGuiWindow* window = ImGui::GetCurrentWindow(); @@ -629,6 +634,7 @@ bool EditTimeline(const char *label, size_t index = CLAMP( (int) floor(static_cast(MAX_TIMELINE_ARRAY) * x), 0, MAX_TIMELINE_ARRAY); char cursor_text[64]; guint64 time = begin + (index * end) / static_cast(MAX_TIMELINE_ARRAY); + static guint64 removed_flag_time = 0; // enter edit if widget is active if (ImGui::GetActiveID() == id) { @@ -649,31 +655,55 @@ bool EditTimeline(const char *label, const size_t left = MIN(previous_index, index); const size_t right = MAX(previous_index, index); - if (edit_histogram){ + if (edit_mode == 0) { static float target_value = values_min; // toggle value histo if (!active) { - target_value = histogram_array[index] > 0.f ? 0.f : 1.f; + target_value = gaps_array[index] > 0.f ? 0.f : 1.f; active = true; } for (size_t i = left; i < right; ++i) - histogram_array[i] = target_value; + gaps_array[i] = target_value; } - else { + else if (edit_mode == 1) { const float target_value = values_max - val; for (size_t i = left; i < right; ++i) lines_array[i] = target_value; } + else if (edit_mode == 2) { + if (!active) { + // remove flag on mouse press + if ( tl->isFlagged(time) ) { + removed_flag_time = time; + tl->removeFlagAt(time); + } + // add flag on mouse release + else + active = true; + } + else if (! tl->isFlagged(time) ) { + cursor_flag = true; + } + } previous_index = index; array_changed = true; } // release active widget on mouse release else { + // add flag on mouse release + if (edit_mode == 2 && active) { + // exception: if flag was removed at same time, do not add it back + if ( removed_flag_time != time ) { + tl->addFlag(time); + } + removed_flag_time = 0; + } + active = false; ImGui::ClearActiveID(); previous_index = UINT32_MAX; @@ -699,17 +729,17 @@ bool EditTimeline(const char *label, case TimelinePayload::CUT: _tl = *tl; _tl.cut(time, (bool) pl->argument); - histogram_array = _tl.gapsArray(); + gaps_array = _tl.gapsArray(); break; case TimelinePayload::CUT_MERGE: _tl = *tl; _tl.mergeGapstAt(time); - histogram_array = _tl.gapsArray(); + gaps_array = _tl.gapsArray(); break; case TimelinePayload::CUT_ERASE: _tl = *tl; _tl.removeGaptAt(time); - histogram_array = _tl.gapsArray(); + gaps_array = _tl.gapsArray(); break; case TimelinePayload::FADE_IN: _tl = *tl; @@ -731,6 +761,16 @@ bool EditTimeline(const char *label, _tl.fadeInOutRange(time, pl->timing, false, (Timeline::FadingCurve) pl->argument); lines_array = _tl.fadingArray(); break; + case TimelinePayload::FLAG_ADD: + _tl = *tl; + _tl.addFlag(time); + flags_array = _tl.flagsArray(); + break; + case TimelinePayload::FLAG_REMOVE: + _tl = *tl; + _tl.removeFlagAt(time); + flags_array = _tl.flagsArray(); + break; default: break; } @@ -745,16 +785,21 @@ bool EditTimeline(const char *label, } ImGui::EndDragDropTarget(); } + else { + if ( edit_mode == 2 && tl->isFlagged(time) ) { + cursor_flag = true; + } + } // back to draw ImGui::SetCursorScreenPos(canvas_pos); - // plot histogram (with frame) + // plot gaps (with frame) ImGui::PushStyleColor(ImGuiCol_FrameBg, bg_color); ImGui::PushStyleColor(ImGuiCol_PlotHistogram, style.Colors[ImGuiCol_ModalWindowDimBg]); // a dark color char buf[128]; snprintf(buf, 128, "##Histo%s", label); - ImGui::PlotHistogram(buf, histogram_array, MAX_TIMELINE_ARRAY, 0, NULL, values_min, values_max, size); + ImGui::PlotHistogram(buf, gaps_array, MAX_TIMELINE_ARRAY, 0, NULL, values_min, values_max, size); ImGui::PopStyleColor(2); // back to draw @@ -766,6 +811,15 @@ bool EditTimeline(const char *label, ImGui::PlotLines(buf, lines_array, MAX_TIMELINE_ARRAY, 0, NULL, values_min, values_max, size); ImGui::PopStyleColor(1); + // back to draw + ImGui::SetCursorScreenPos(canvas_pos); + + // plot flags (transparent background) + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0,0,0,0)); + ImGui::PushStyleColor(ImGuiCol_PlotHistogram, style.Colors[ImGuiCol_TabActive]); // cursor color + snprintf(buf, 128, "##Flags%s", label); + ImGui::PlotHistogram(buf, flags_array, MAX_TIMELINE_ARRAY, 0, NULL, values_min, values_max, size); + ImGui::PopStyleColor(2); // draw the cursor if (hovered) { @@ -785,6 +839,11 @@ bool EditTimeline(const char *label, cursor_pos = cursor_pos + mouse_pos_in_canvas; window->DrawList->AddCircleFilled( cursor_pos, 3.f, cur_color, 8); } + else if (cursor_flag) { + cursor_pos = cursor_pos + ImVec2(mouse_pos_in_canvas.x, 12.f); + window->DrawList->AddLine( cursor_pos, cursor_pos + ImVec2(0.f, size.y - 8.f), cur_color); + _drawIcon(cursor_pos - ImVec2(2.f, 1.f), 12, 6, true, window); + } else { cursor_pos = cursor_pos + ImVec2(mouse_pos_in_canvas.x, 4.f); window->DrawList->AddLine( cursor_pos, cursor_pos + ImVec2(0.f, size.y - 8.f), cur_color); @@ -1850,6 +1909,8 @@ void DragButtonIcon(int i, int j, const char *tooltip, TimelinePayload payload) void SourceControlWindow::RenderMediaPlayer(MediaSource *ms) { static bool show_overlay_info = false; + static std::vector< std::pair > editmode_icon = { {8, 3}, {7, 4}, {12, 6} }; + static std::vector< std::string > editmode_tooltip = { "Cutting tool", "Fading tool", "Flag tool" }; mediaplayer_active_ = ms->mediaplayer(); @@ -1999,10 +2060,12 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms) else if (released) { tl->refresh(); - if (Settings::application.widget.media_player_timeline_editmode) + if (Settings::application.widget.media_player_timeline_editmode == 0) oss << ": Timeline cut"; - else + else if (Settings::application.widget.media_player_timeline_editmode == 1) oss << ": Timeline fading"; + else + oss << ": Timeline flags"; Action::manager().store(oss.str()); } // custom timeline slider @@ -2022,8 +2085,8 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms) if (mediaplayer_edit_panel_ && ImGuiToolkit::IconButton(10, 0, "Close panel")) mediaplayer_edit_panel_ = false; ImGui::SetCursorScreenPos(bottom + ImVec2(1.f, 0.5f * timeline_height_)); - const char *tooltip[2] = {"Fading tool", "Cutting tool"}; - ImGuiToolkit::IconToggle(7,4,8,3, &Settings::application.widget.media_player_timeline_editmode, tooltip); + // select timeline editmode; cut, fade, flag + ImGuiToolkit::IconMultistate(editmode_icon, &Settings::application.widget.media_player_timeline_editmode, editmode_tooltip ); // zoom slider ImGui::SetCursorScreenPos(bottom + ImVec2(0.f, timeline_height_)); @@ -2220,7 +2283,7 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms) /// PANEL FOR CUT TIMELINE MODE /// ImGui::Spacing(); - if (Settings::application.widget.media_player_timeline_editmode) { + if (Settings::application.widget.media_player_timeline_editmode == 0) { // PANEL WITH LARGE FONTS ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE); @@ -2308,9 +2371,9 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms) ImGui::PopFont(); } /// - /// FADING + /// PANEL FOR FADING /// - else { + else if (Settings::application.widget.media_player_timeline_editmode == 1) { // Icons for Drag & Drop ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE); @@ -2398,6 +2461,9 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms) DragButtonIcon(17, 10, "Drop in timeline to insert\nSmooth fade in & out", TimelinePayload(TimelinePayload::FADE_IN_OUT, d*GST_MSECOND, Timeline::FADING_SMOOTH)); } + else { + + } /// /// DURATION SLIDER @@ -2467,6 +2533,28 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms) // end icons ImGui::PopFont(); } + /// + /// PANEL FOR FLAGS + /// + else if (Settings::application.widget.media_player_timeline_editmode == 2) { + + // PANEL WITH LARGE FONTS + ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE); + + /// + /// CUT LEFT OF CURSOR + /// + DragButtonIcon(12, 6, "Drop in timeline to\nAdd flag", + TimelinePayload(TimelinePayload::FLAG_ADD, 0, 1) ); + + ImGui::SameLine(0, IMGUI_SAME_LINE); + + DragButtonIcon(6, 0, "Drop in timeline to\nDelete flag", + TimelinePayload(TimelinePayload::FLAG_REMOVE, 0, 1) ); + + // end icons + ImGui::PopFont(); + } ImGui::EndChild(); ImGui::PopStyleColor(); diff --git a/src/Timeline.cpp b/src/Timeline.cpp index f2c91d6..1c37599 100644 --- a/src/Timeline.cpp +++ b/src/Timeline.cpp @@ -86,6 +86,9 @@ Timeline& Timeline::operator = (const Timeline& b) memcpy( this->gapsArray_, b.gapsArray_, MAX_TIMELINE_ARRAY * sizeof(float)); memcpy( this->fadingArray_, b.fadingArray_, MAX_TIMELINE_ARRAY * sizeof(float)); this->fading_array_changed_ = true; + this->flags_ = b.flags_; + this->flags_array_need_update_ = b.flags_array_need_update_; + memcpy( this->flagsArray_, b.flagsArray_, MAX_TIMELINE_ARRAY * sizeof(float)); } return *this; } @@ -100,6 +103,7 @@ void Timeline::reset() clearGaps(); clearFading(); + clearFlags(); } bool Timeline::is_valid() const @@ -175,6 +179,7 @@ void Timeline::update() void Timeline::refresh() { fillArrayFromGaps(gapsArray_, MAX_TIMELINE_ARRAY); + fillArrayFromFlags(flagsArray_, MAX_TIMELINE_ARRAY); } bool Timeline::gapAt(const GstClockTime t) const @@ -511,8 +516,7 @@ void Timeline::clearGaps() { gaps_.clear(); - for(int i=0;i timing_.begin + (step_ * 2) + && t < timing_.end - (step_ * 2) + && !isFlagged(t)) { + + // compute nearest frame time + GstClockTime t_frame = ( (t - timing_.begin) / step_ ) * step_ + timing_.begin + step_; + + // Flag interval centered on t_frame + TimeInterval f(t_frame - step_ - FLAG_MARGIN, t_frame + step_ + FLAG_MARGIN); + + flags_array_need_update_ = true; + return flags_.insert(f).second; + } + + return false; +} + +bool Timeline::addFlag(TimeInterval s) +{ + if ( s.is_valid() ) { + flags_array_need_update_ = true; + return flags_.insert(s).second; + } + return false; +} + + +bool Timeline::removeFlagAt(GstClockTime t) +{ + if (flags_.empty()) + return false; + + TimeIntervalSet::const_iterator f = std::find_if(flags_.begin(), flags_.end(), includesTime(t)); + + if ( f != flags_.end() ) { + flags_.erase(f); + flags_array_need_update_ = true; + return true; + } + + return false; +} + +bool Timeline::isFlagged(GstClockTime t) const +{ + TimeIntervalSet::const_iterator f = std::find_if(flags_.begin(), flags_.end(), includesTime(t)); + return ( f != flags_.end() ); +} + + +GstClockTime Timeline::getFlagAt(GstClockTime t) const +{ + TimeIntervalSet::const_iterator f = std::find_if(flags_.begin(), flags_.end(), includesTime(t)); + + if ( f != flags_.end() ) { + return ( (*f).begin + step_ + FLAG_MARGIN); + } + + return GST_CLOCK_TIME_NONE; +} + +void Timeline::clearFlags() +{ + flags_.clear(); + memcpy(flagsArray_, empty_zeros, MAX_TIMELINE_ARRAY * sizeof(float)); + + flags_array_need_update_ = true; +} + +void Timeline::fillArrayFromFlags(float *array, size_t array_size) +{ + // fill the array from flags list + if (array != nullptr && array_size > 0 && timing_.is_valid()) { + + // clear with static array + memcpy(flagsArray_, empty_zeros, MAX_TIMELINE_ARRAY * sizeof(float)); + + // for each flag + GstClockTime d = timing_.duration(); + for (auto it = flags_.begin(); it != flags_.end(); ++it) + { + size_t e = ( ( (*it).begin + step_ + FLAG_MARGIN ) * array_size ) / d ; + + // fill with 1 where there is a flag + flagsArray_[e-1] = 1.f; + flagsArray_[ e ] = 1.f; + flagsArray_[e+1] = 1.f; + } + + // done ! + flags_array_need_update_ = false; + } +} \ No newline at end of file diff --git a/src/Timeline.h b/src/Timeline.h index 39cf96e..cc7d5fe 100644 --- a/src/Timeline.h +++ b/src/Timeline.h @@ -137,6 +137,17 @@ public: bool gapAt(const GstClockTime t) const; bool getGapAt(const GstClockTime t, TimeInterval &gap) const; + // Manipulation of flags + inline TimeIntervalSet flags() const { return flags_; }; + inline size_t numFlags() const { return flags_.size(); }; + float *flagsArray(); + bool addFlag(GstClockTime t); + bool addFlag(TimeInterval s); + bool removeFlagAt(GstClockTime t); + bool isFlagged(GstClockTime t) const; + GstClockTime getFlagAt(GstClockTime t) const; + void clearFlags(); + // inverse of gaps: sections of play areas TimeIntervalSet sections() const; GstClockTime sectionsDuration() const; @@ -190,6 +201,11 @@ private: float fadingArray_[MAX_TIMELINE_ARRAY]; bool fading_array_changed_, fading_array_allones_; + TimeIntervalSet flags_; + float flagsArray_[MAX_TIMELINE_ARRAY]; + bool flags_array_need_update_; + // synchronize data structures + void fillArrayFromFlags(float *array, size_t array_size); }; #endif // TIMELINE_H