diff --git a/.gitignore b/.gitignore index 55caedb..094ca36 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,4 @@ flatpak/build/ build/ .vscode/ +.cache/ diff --git a/flatpak/README.md b/flatpak/README.md index 58a48fc..00c5c6b 100644 --- a/flatpak/README.md +++ b/flatpak/README.md @@ -36,22 +36,18 @@ Install the runtime environments: flatpak install org.gnome.Platform -_Select version **47** in the list of proposed versions_ +_Select version **49** in the list of proposed versions_ ### 2. Build vimix flatpak -These settings of git are needed to enable clone of local repos during build: +These settings of git are needed to enable clone of local repos during build (done only once): git config --global --add protocol.file.allow always -Get the flatpak manifest for vimix: - - curl -O https://raw.githubusercontent.com/brunoherbelin/vimix/beta/flatpak/io.github.brunoherbelin.Vimix.json - Launch the build of the flatpak: - flatpak-builder --user --install --force-clean build io.github.brunoherbelin.Vimix.json + flatpak-builder --user --install --from-git=https://github.com/brunoherbelin/vimix.git --from-git-branch=beta --delete-build-dirs --force-clean build flatpak/io.github.brunoherbelin.Vimix.json The build will be quite long as some dependencies are also re-build from source. However, the build of dependencies is kept in cache; rebuilding vimix will subsequently be much faster. @@ -85,7 +81,7 @@ To build the vimix flatpak with code from local folder (debugging), change the f "sources": [ { "type":"dir", - "path": "/home/bhbn/Development/vimix", + "path": "[your_development_dir]/vimix", } ] } diff --git a/flatpak/io.github.brunoherbelin.Vimix.json b/flatpak/io.github.brunoherbelin.Vimix.json index 264ef6c..1bdf6c4 100644 --- a/flatpak/io.github.brunoherbelin.Vimix.json +++ b/flatpak/io.github.brunoherbelin.Vimix.json @@ -142,8 +142,8 @@ "sources": [ { "type": "git", - "tag": "1.26.7", - "commit": "c5a5c302f5e7218182c0633decec16b25de82add", + "tag": "1.26.8", + "commit": "16d77e12ad213ef24e76a8cc34d347b8221c9975", "url": "https://gitlab.freedesktop.org/gstreamer/gstreamer.git", "disable-submodules": false } diff --git a/rsc/images/icons.dds b/rsc/images/icons.dds index 91ad4bb..ca11b46 100644 Binary files a/rsc/images/icons.dds and b/rsc/images/icons.dds differ diff --git a/src/ImGuiToolkit.cpp b/src/ImGuiToolkit.cpp index 5c95692..aa70d43 100644 --- a/src/ImGuiToolkit.cpp +++ b/src/ImGuiToolkit.cpp @@ -243,7 +243,9 @@ bool ImGuiToolkit::ButtonIcon(int i, int j, const char *tooltip, bool enabled, b if (enabled) ret = ImGui::ImageButton((void*)(intptr_t)textureicons, ImVec2(g.FontSize, g.FontSize), - uv0, uv1, g.Style.FramePadding.y); + uv0, uv1, g.Style.FramePadding.y, + ImVec4(0.f, 0.f, 0.f, 0.f), + g.Style.Colors[ImGuiCol_Text]); else ImGui::ImageButton((void*)(intptr_t)textureicons, ImVec2(g.FontSize, g.FontSize), @@ -804,12 +806,10 @@ bool ImGuiToolkit::SliderTiming (const char* label, uint* ms, uint v_min, uint v // *ms = val * v_step; // quadratic scale - float val = *ms / v_step; - bool ret = ImGui::SliderFloat(label, &val, v_min / v_step, v_max / v_step, text_buf, 2.f); + float val = (float) (*ms / v_step); + bool ret = ImGui::SliderFloat(label, &val, (float) (v_min / v_step), (float) (v_max / v_step), text_buf, 2.f); *ms = int(floor(val)) * v_step; - - return ret; } @@ -853,23 +853,24 @@ void ImGuiToolkit::RenderTimeline (ImVec2 min_bbox, ImVec2 max_bbox, guint64 beg float tick_step_pixels = timeline_bbox.GetWidth() * step_; // large space - if (tick_step_pixels > 5.f && step > 0) + if (tick_step_pixels > 10.f && step > 0) { - // try to put a label ticks every second - label_tick_step = (SECOND / step) * step; - large_tick_step = label_tick_step % 5 ? (label_tick_step % 2 ? label_tick_step : label_tick_step / 2 ) : label_tick_step / 5; - tick_delta = SECOND - label_tick_step; - // round to nearest if (tick_delta > step / 2) { label_tick_step += step; large_tick_step += step; tick_delta = SECOND - label_tick_step; } + else { + // try to put a label ticks every second + label_tick_step = (SECOND / step) * step; + large_tick_step = label_tick_step % 5 ? (label_tick_step % 2 ? label_tick_step : label_tick_step / 2 ) : label_tick_step / 5; + tick_delta = SECOND - label_tick_step; + } } else { // while there is less than 5 pixels between two tick marks (or at last optimal tick mark) - for ( int i=0; i<10 && tick_step_pixels < 5.f; ++i ) + for ( int i=0; i<10 && tick_step_pixels < 10.f; ++i ) { // try to use the optimal tick marks pre-defined tick_step = optimal_tick_marks[i]; @@ -1327,7 +1328,7 @@ void ImGuiToolkit::SetFont (ImGuiToolkit::font_style style, const std::string &t static const ImWchar icons_ranges[] = { ICON_MIN_FA, ICON_MAX_FA, 0 }; font_config.MergeMode = true; // Merging glyphs into current font font_config.PixelSnapH = true; - font_config.GlyphOffset.y = pointsize/20; + font_config.GlyphOffset.y = (float) (pointsize/20); fontname = "icons" + fontname; fontname.copy(font_config.Name, 40); // load FontAwesome only once diff --git a/src/MediaPlayer.cpp b/src/MediaPlayer.cpp index 09e95f1..f9e0423 100644 --- a/src/MediaPlayer.cpp +++ b/src/MediaPlayer.cpp @@ -22,6 +22,7 @@ // Desktop OpenGL function loader #include +#include "Timeline.h" #include "defines.h" #include "Log.h" #include "Resource.h" @@ -1121,6 +1122,7 @@ bool MediaPlayer::go_to(GstClockTime pos) GstClockTime jumpPts = pos; + // jump in a gap if (timeline_.getGapAt(pos, gap)) { // if in a gap, find closest seek target if (gap.is_valid()) { @@ -1399,18 +1401,23 @@ void MediaPlayer::update() need_loop = true; } } - // test if position is flagged + // detect flags if playing else if ( isPlaying() ) { - int t = timeline_.flagTypeAt(position_); - if ( t > 0 ) { - // Avoid to pause repeatedly when inside a flagged section - if (flag_status_ == LoopStatus::LOOP_STATUS_DEFAULT) { - loop_status_ = flag_status_ = (LoopStatus) t; - play(false); - } - } - else - flag_status_ = LoopStatus::LOOP_STATUS_DEFAULT; + // check if position is flagged + TimeInterval flag = timeline_.getFlagAt(position_); + // if type of flag requires stop and not already current + if ( flag.is_valid() && flag.type > 0 ) { + if (flag != current_flag_) { + // set flag as current + current_flag_ = flag; + // set timeline to temporary state (stop or blackout) + loop_status_ = (LoopStatus)current_flag_.type; + // effectively stop playing + play(false); + } + } + else + current_flag_.reset(); } } @@ -1589,6 +1596,33 @@ void MediaPlayer::setTimeline(const Timeline &tl) timeline_ = tl; } +bool MediaPlayer::go_to_flag(TimeInterval flag) +{ + bool ret = false; + if ( flag.is_valid() ) { + + // flag target position + GstClockTime flagPts = flag.midpoint(); + + if (ABS_DIFF (position_, flagPts) > 2 * timeline_.step() ) { + + // seek if valid target position + ret = true; + seek( flagPts ); + + // will reach that flag + current_flag_ = flag; + + // change timeline status accordingly + if ( flag.type == (int) LoopStatus::LOOP_STATUS_BLACKOUT) + loop_status_ = LoopStatus::LOOP_STATUS_BLACKOUT; + else if ( flag.type == (int) LoopStatus::LOOP_STATUS_STOPPED) + loop_status_ = LoopStatus::LOOP_STATUS_DEFAULT; + } + } + return ret; +} + MediaInfo MediaPlayer::media() const { return media_; diff --git a/src/MediaPlayer.h b/src/MediaPlayer.h index 81e4c63..2b06e7f 100644 --- a/src/MediaPlayer.h +++ b/src/MediaPlayer.h @@ -225,6 +225,17 @@ public: * Set fading mode * */ void setTimelineFadingMode(FadingMode m) { fading_mode_ = m; } + /** + * go to a flag position in media timeline. + * return true if seek is performed + * */ + bool go_to_flag(TimeInterval flag); + /** + * Set / Get the flag where media player currently is. + * Flag is invalid if player is not currenlty on a flag. + * */ + void setCurrentFlag(TimeInterval flag) { current_flag_ = flag; } + TimeInterval currentFlag() const { return current_flag_; }; /** * Get framerate of the media * */ @@ -328,7 +339,7 @@ private: GstClockTime position_; LoopMode loop_; LoopStatus loop_status_; - LoopStatus flag_status_; + TimeInterval current_flag_; GstState desired_state_; GstElement *pipeline_; GstBus *bus_; diff --git a/src/SessionCreator.cpp b/src/SessionCreator.cpp index 3aaf932..9d83aa7 100644 --- a/src/SessionCreator.cpp +++ b/src/SessionCreator.cpp @@ -937,7 +937,9 @@ void SessionLoader::visit(MediaPlayer &n) flag->QueryUnsigned64Attribute("begin", &a); flag->QueryUnsigned64Attribute("end", &b); flag->QueryIntAttribute("type", &t); - tl.addFlag( TimeInterval( (GstClockTime) a, (GstClockTime) b ), t ); + TimeInterval flag((GstClockTime) a, (GstClockTime) b); + flag.type = t; + tl.addFlag( flag ); } } n.setTimeline(tl); diff --git a/src/SourceControlWindow.cpp b/src/SourceControlWindow.cpp index 37fce84..39bb96c 100644 --- a/src/SourceControlWindow.cpp +++ b/src/SourceControlWindow.cpp @@ -26,6 +26,9 @@ // ImGui #include "ImGuiToolkit.h" +#include "Timeline.h" +#include "gst/gstclock.h" +#include "imgui.h" #include "imgui_internal.h" #include "defines.h" @@ -702,9 +705,9 @@ bool EditTimeline(const char *label, // exception: if flag was removed at same time, do not add it back if ( removed_flag_time != time ) { if (removed_flag_type >= 0) - tl->addFlag(time, removed_flag_type); + tl->addFlagAt(time, removed_flag_type); else - tl->addFlag(time, Settings::application.widget.media_player_timeline_flag); + tl->addFlagAt(time, Settings::application.widget.media_player_timeline_flag); } removed_flag_time = 0; removed_flag_type = -1; @@ -769,7 +772,7 @@ bool EditTimeline(const char *label, break; case TimelinePayload::FLAG_ADD: _tl = *tl; - _tl.addFlag(time, pl->argument); + _tl.addFlagAt(time, pl->argument); flags_array = _tl.flagsArray(); break; case TimelinePayload::FLAG_REMOVE: @@ -869,7 +872,7 @@ bool EditTimeline(const char *label, return array_changed; } -bool TimelineSlider (const char* label, guint64 *time, Timeline *tl, const float width) +bool TimelineSlider (const char* label, guint64 *time, TimeInterval *flag, Timeline *tl, const float width) { // get window ImGuiWindow* window = ImGui::GetCurrentWindow(); @@ -908,52 +911,16 @@ bool TimelineSlider (const char* label, guint64 *time, Timeline *tl, const float // units conversion: from time to float (calculation made with higher precision first) float time_ = static_cast ( static_cast(*time - tl->begin()) / static_cast(tl->duration()) ); - // Render the bounding box - const ImU32 frame_col = ImGui::GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); - ImGui::RenderFrame(bbox.Min, bbox.Max, frame_col, true, style.FrameRounding); - - // render the timeline - ImGuiToolkit::RenderTimeline(timeline_bbox.Min, timeline_bbox.Max, tl->begin(), tl->end(), tl->step()); - // - // FLAGS - // - bool flag_clicked = false; - const TimeIntervalSet flags = tl->flags(); - for (const auto &flag_Interval : flags) { - - GstClockTime flag_time = flag_Interval.midpoint(); - float flag_pos_ = static_cast ( static_cast(flag_time - tl->begin()) / static_cast(tl->duration()) ); - ImVec2 flag_pos = ImLerp(timeline_bbox.GetTL(), timeline_bbox.GetTR(), flag_pos_); - flag_pos -= ImVec2(2.f, -3.f); - - bool hovered = false, held = false; - ImRect bb(flag_pos, flag_pos + ImVec2(ImGui::GetTextLineHeightWithSpacing(), ImGui::GetTextLineHeightWithSpacing())); - - const ImGuiID fid = window->GetID((void*)(intptr_t)(flag_time)); - if ( ImGui::ButtonBehavior(bb, fid, &hovered, &held, ImGuiButtonFlags_PressedOnClick) ) { - *time = flag_time; - flag_clicked = true; - } - - // icon depends on flag type - _drawIcon(flag_pos, 11 + flag_Interval.type, 6, hovered, window); - // show time when hovering - if (hovered) - ImGui::SetTooltip(" %s ", GstToolkit::time_to_string(flag_time).c_str()); - } - - // - // GET SLIDER INPUT AND PERFORM CHANGES AND DECISIONS + // GET INPUT // // read user input from system bool left_mouse_press = false; - const bool hovered = ImGui::ItemHoverable(bbox, id); + bool hovered = ImGui::ItemHoverable(bbox, id); bool temp_input_is_active = ImGui::TempInputIsActive(id); - // slider only if no flag clicked - if (!flag_clicked && !temp_input_is_active) + if (!temp_input_is_active) { const bool focus_requested = ImGui::FocusableItemRegister(window, id); left_mouse_press = hovered && ImGui::IsMouseDown(ImGuiMouseButton_Left); @@ -965,30 +932,76 @@ bool TimelineSlider (const char* label, guint64 *time, Timeline *tl, const float } } - // time Slider behavior + // + // BACKGROUND + // + + // Render the bounding box + const ImU32 frame_col = ImGui::GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); + ImGui::RenderFrame(bbox.Min, bbox.Max, frame_col, true, style.FrameRounding); + + // render the timeline + ImGuiToolkit::RenderTimeline(timeline_bbox.Min, timeline_bbox.Max, tl->begin(), tl->end(), tl->step()); + + // + // FLAGS + // + bool flag_pressed = false; + const TimeIntervalSet flags = tl->flags(); + for (const auto &flag_Interval : flags) { + + // set position in screen corresponding to flag time + GstClockTime flag_time = flag_Interval.midpoint(); + float flag_pos_ = static_cast ( static_cast(flag_time - tl->begin()) / static_cast(tl->duration()) ); + ImVec2 draw_pos = ImLerp(timeline_bbox.GetTL(), timeline_bbox.GetTR(), flag_pos_); + draw_pos -= ImVec2(2.f, -3.f); + + // simulate Button behavior : if mouse hovering flag and mouse pressed + ImRect bb(draw_pos, draw_pos + ImVec2(ImGui::GetTextLineHeightWithSpacing(), ImGui::GetTextLineHeightWithSpacing())); + bool hovered = ImGui::ItemHoverable(bb, id); + if (hovered && left_mouse_press) { + flag_pressed = true; + *flag = flag_Interval; + ImGui::MarkItemEdited(id); + } + + // icon depends on flag type & color on hovered + ImGui::PushStyleColor( ImGuiCol_Text, ImGui::GetColorU32(ImGuiCol_NavHighlight) ); + _drawIcon(draw_pos, 11 + flag_Interval.type, 6, hovered, window); + ImGui::PopStyleColor(); + + // show time when hovering + if (hovered) + ImGui::SetTooltip(" %s ", GstToolkit::time_to_string(flag_time).c_str()); + } + + // + // CURSOR + // + bool cursor_pressed = false; ImRect grab_slider_bb; ImU32 grab_slider_color = ImGui::GetColorU32(ImGuiCol_SliderGrab); - float time_slider = time_ * 10.f; // x 10 precision on grab - float time_zero = 0.f; - float time_end = 10.f; - bool value_changed = ImGui::SliderBehavior(slider_bbox, id, ImGuiDataType_Float, &time_slider, &time_zero, - &time_end, "%.2f", 1.f, ImGuiSliderFlags_None, &grab_slider_bb); + if (!flag_pressed) + { + float time_slider = time_ * 10.f; // x 10 precision on grab + float time_zero = 0.f; + float time_end = 10.f; + cursor_pressed = ImGui::SliderBehavior(slider_bbox, id, ImGuiDataType_Float, &time_slider, &time_zero, + &time_end, "%.2f", 1.f, ImGuiSliderFlags_None, &grab_slider_bb); - if (value_changed){ + if (cursor_pressed){ - *time = static_cast ( 0.1 * static_cast(time_slider) * static_cast(tl->duration()) ); - if (tl->first() != GST_CLOCK_TIME_NONE) - *time -= tl->first(); - grab_slider_color = ImGui::GetColorU32(ImGuiCol_SliderGrabActive); + *time = static_cast ( 0.1 * static_cast(time_slider) * static_cast(tl->duration()) ); + if (tl->first() != GST_CLOCK_TIME_NONE) + *time -= tl->first(); + grab_slider_color = ImGui::GetColorU32(ImGuiCol_SliderGrabActive); - ImGui::MarkItemEdited(id); + ImGui::MarkItemEdited(id); + } + } - // - // RENDER CURSOR - // - // draw slider grab handle if (grab_slider_bb.Max.x > grab_slider_bb.Min.x) { window->DrawList->AddRectFilled(grab_slider_bb.Min, grab_slider_bb.Max, grab_slider_color, style.GrabRounding); @@ -998,7 +1011,7 @@ bool TimelineSlider (const char* label, guint64 *time, Timeline *tl, const float pos = ImLerp(timeline_bbox.GetTL(), timeline_bbox.GetTR(), time_) - ImVec2(cursor_width, 2.f); ImGui::RenderArrow(window->DrawList, pos, ImGui::GetColorU32(ImGuiCol_SliderGrab), ImGuiDir_Up); - return (flag_clicked || left_mouse_press); + return cursor_pressed; } @@ -2165,7 +2178,7 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms) static std::vector< std::pair > icons_loop = { {0, 15}, {1, 15}, {19, 14}, {18, 14} }; static std::vector< std::string > tooltips_loop = { "Stop at end", "Loop to start", "Bounce (reverse speed)", "Stop and blackout at end" }; static std::vector< std::pair > icons_flags = { {11, 6}, {12, 6}, {13, 6} }; - static std::vector< std::string > tooltips_flags = { "Bookmark", "Stop Flag", "Blackout Flag" }; + static std::vector< std::string > tooltips_flags = { " Bookmark", " Stop Flag", " Blackout Flag" }; double current_play_speed = mediaplayer_active_->playSpeed(); static uint counter_menu_timeout = 0; @@ -2180,6 +2193,7 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms) // seek position guint64 seek_t = mediaplayer_active_->position(); + TimeInterval seek_flag; // scrolling sub-window ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(1.f, 1.f)); @@ -2215,7 +2229,7 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms) } // custom timeline slider // TODO : if (mediaplayer_active_->syncToMetronome() > Metronome::SYNC_NONE) - mediaplayer_slider_pressed_ = TimelineSlider("##timeline", &seek_t, tl, size.x); + mediaplayer_slider_pressed_ = TimelineSlider("##timeline", &seek_t, &seek_flag, tl, size.x); } } ImGui::EndChild(); @@ -2290,38 +2304,53 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms) } // flag buttons - if ( !mediaplayer_mode_ && mediaplayer_active_->timeline()->numFlags() > 0 ) { + if (rendersize.x > buttons_height_ * 6.0f) { + if ( !mediaplayer_mode_ && mediaplayer_active_->timeline()->numFlags() > 0 ) { - ImGui::SameLine(0, h_space_); - if( ImGuiToolkit::ButtonIcon(3, 0, "Go to next flag") ){ - // find next flag and go to its midpoint - TimeInterval next_flag = mediaplayer_active_->timeline()->getNextFlag( mediaplayer_active_->position() ); - if ( next_flag.is_valid() ) - mediaplayer_active_->go_to( next_flag.midpoint() ); - } + GstClockTime _paused_time = mediaplayer_active_->position(); - // if stopped at a flag, show flag type editor - if (mediaplayer_active_->timeline()->isFlagged( mediaplayer_active_->position() )) { - static int current_flag = 0; - current_flag = mediaplayer_active_->timeline()->flagTypeAt( mediaplayer_active_->position() ); ImGui::SameLine(0, h_space_); - if ( ImGuiToolkit::IconMultistate(icons_flags, ¤t_flag, tooltips_flags) ){ - mediaplayer_active_->timeline()->setFlagTypeAt( mediaplayer_active_->position(), current_flag ); - oss << ": Flag type changed"; - Action::manager().store(oss.str()); + if( mediaplayer_active_->playSpeed() < 0 ) { + // Go to previous flag when playing backward + TimeInterval target_flag = mediaplayer_active_->timeline()->getPreviousFlag( _paused_time ); + bool has_prev = target_flag.is_valid() && + !( mediaplayer_active_->currentFlag().is_valid() && mediaplayer_active_->timeline()->numFlags() == 1) && + ( mediaplayer_active_->loop() == MediaPlayer::LOOP_REWIND || (target_flag.end < _paused_time) ); + if( ImGuiToolkit::ButtonIcon(6, 0, "Go to previous flag", has_prev) ) + mediaplayer_active_->go_to_flag( target_flag ); + } + else { + // go to next flag when playing forward + TimeInterval target_flag = mediaplayer_active_->timeline()->getNextFlag( _paused_time ); + bool has_next = target_flag.is_valid() && + !( mediaplayer_active_->currentFlag().is_valid() && mediaplayer_active_->timeline()->numFlags() == 1) && + ( mediaplayer_active_->loop() == MediaPlayer::LOOP_REWIND || (target_flag.begin > _paused_time) ); + if( ImGuiToolkit::ButtonIcon(5, 0, "Go to next flag", has_next) ) + mediaplayer_active_->go_to_flag( target_flag ); + } + + // if stopped at a flag, show flag menu + if (mediaplayer_active_->currentFlag().is_valid()) { + ImGui::SameLine(0, h_space_); + if (ImGuiToolkit::IconButton(3, 0) || ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) { + counter_menu_timeout=0; + ImGui::OpenPopup( "MenuMediaPlayerFlags" ); + } } } - } - else { - ImGui::SameLine(0, h_space_); - ImGuiToolkit::ButtonIcon(3, 0, nullptr, false); + else { + ImGui::SameLine(0, h_space_); + ImGuiToolkit::ButtonIcon(mediaplayer_active_->playSpeed() < 0 ? 6 : 5, 0, nullptr, false); + } } // right aligned buttons (if enough space) - if ( rendersize.x > min_width_ * 1.5f ) { + if ( rendersize.x > buttons_height_ * 9.5f ) { + + ImGui::SameLine(); + ImGui::SetCursorPosX(rendersize.x - buttons_height_ * 4.f); // loop modes button - ImGui::SameLine(0, MAX(h_space_ , rendersize.x - min_width_ * 1.55f) ); static int current_loop = 0; current_loop = (int) mediaplayer_active_->loop(); if ( ImGuiToolkit::IconMultistate(icons_loop, ¤t_loop, tooltips_loop) ) @@ -2329,7 +2358,7 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms) // speed slider ImGui::SameLine(0, h_space_); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - buttons_height_ ); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - buttons_height_ - h_space_); float s = fabs(static_cast(current_play_speed)); if (ImGui::DragFloat( "##Speed", &s, 0.01f, 0.1f, 10.f, UNICODE_MULTIPLY " %.2f")) mediaplayer_active_->setPlaySpeed( SIGN(current_play_speed) * static_cast(s) ); @@ -2369,6 +2398,16 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms) // AND force to stop when the slider is pressed bool media_play = mediaplayer_mode_ & (!mediaplayer_slider_pressed_); + // Flag pressed in timeline + if (seek_flag.is_valid()) { + // go to the flag position + if ( mediaplayer_active_->go_to_flag(seek_flag) ){ + // stop if flag type is 'Stop' (1) or 'Blackout' (2) + if (seek_flag.type > 0) + media_play = false; + } + } + // apply play action to media only if status should change if ( mediaplayer_active_->isPlaying() != media_play ) { mediaplayer_active_->play( media_play ); @@ -2430,6 +2469,35 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms) ImGui::EndPopup(); } + if (ImGui::BeginPopup( "MenuMediaPlayerFlags" )) + { + int num_flags = static_cast(icons_flags.size()); + TimeInterval copy_flag = mediaplayer_active_->currentFlag(); + for (int i = 0; i < num_flags; ++i) { + if (ImGuiToolkit::MenuItemIcon(icons_flags[i].first,icons_flags[i].second, + tooltips_flags[i].c_str(), nullptr, copy_flag.type == i )) { + copy_flag.type = i; + mediaplayer_active_->timeline()->replaceFlag( copy_flag ); + mediaplayer_active_->setCurrentFlag( copy_flag ); + oss << ": Flag changed"; + Action::manager().store(oss.str()); + } + } + ImGui::Separator(); + if (ImGuiToolkit::MenuItemIcon(2,0, "Delete flag")) { + mediaplayer_active_->timeline()->removeFlagAt(mediaplayer_active_->currentFlag().midpoint() ); + oss << ": Flag removed"; + Action::manager().store(oss.str()); + } + + if (ImGui::IsWindowHovered()) + counter_menu_timeout=0; + else if (++counter_menu_timeout > 10) + ImGui::CloseCurrentPopup(); + + ImGui::EndPopup(); + } + /// /// Window area to edit gaps or fading /// @@ -2756,7 +2824,7 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms) ImGui::SetCursorPos( ImVec2(w, draw_pos.y)); if (ImGuiToolkit::ButtonIcon(1, 0, "Add flag at given time", target_time_valid)) { tl->removeFlagAt(target_time); - tl->addFlag(target_time, Settings::application.widget.media_player_timeline_flag); + tl->addFlagAt(target_time, Settings::application.widget.media_player_timeline_flag); tl->refresh(); oss << ": Timeline flag add"; Action::manager().store(oss.str()); @@ -2766,7 +2834,7 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms) ImGui::SameLine(0, IMGUI_SAME_LINE); if (ImGuiToolkit::ButtonIcon(0, 0, "Add flag at cursor position", !mediaplayer_active_->isPlaying()) ) { tl->removeFlagAt(mediaplayer_active_->position()); - tl->addFlag(mediaplayer_active_->position(), Settings::application.widget.media_player_timeline_flag); + tl->addFlagAt(mediaplayer_active_->position(), Settings::application.widget.media_player_timeline_flag); tl->refresh(); oss << ": Timeline flag add"; Action::manager().store(oss.str()); diff --git a/src/Timeline.cpp b/src/Timeline.cpp index 62463b8..1ab2452 100644 --- a/src/Timeline.cpp +++ b/src/Timeline.cpp @@ -957,7 +957,7 @@ float *Timeline::flagsArray() return flagsArray_; } -bool Timeline::addFlag(GstClockTime t, int type) +bool Timeline::addFlagAt(GstClockTime t, int type) { if (t > timing_.begin + (step_ * 2) && t < timing_.end - (step_ * 2) @@ -979,16 +979,27 @@ bool Timeline::addFlag(GstClockTime t, int type) return false; } -bool Timeline::addFlag(TimeInterval s, int type) +bool Timeline::addFlag(TimeInterval f) { - if ( s.is_valid() ) { - s.type = type; + if ( f.is_valid() ) { flags_array_need_update_ = true; - return flags_.insert(s).second; + return flags_.insert(f).second; } return false; } +bool Timeline::replaceFlag(TimeInterval f) +{ + if ( f.is_valid() ) { + + if (!removeFlagAt(f.midpoint())) + return false; + + flags_array_need_update_ = true; + return flags_.insert(f).second; + } + return false; +} bool Timeline::removeFlagAt(GstClockTime t) { @@ -1033,6 +1044,19 @@ void Timeline::setFlagTypeAt(GstClockTime t, int type) } } +TimeInterval Timeline::getFlagAt(GstClockTime t) const +{ + TimeInterval ret; + + TimeIntervalSet::iterator f = std::find_if(flags_.begin(), flags_.end(), includesTime(t)); + + if ( f != flags_.end() ) { + ret = *f; + } + + return ret; +} + TimeInterval Timeline::getNextFlag(GstClockTime t) const { if ( !flags_.empty() ) { @@ -1054,6 +1078,27 @@ TimeInterval Timeline::getNextFlag(GstClockTime t) const return TimeInterval(); } +TimeInterval Timeline::getPreviousFlag(GstClockTime t) const +{ + if ( !flags_.empty() ) { + // loop over flags + auto f = flags_.rbegin(); + for (; f != flags_.rend(); ++f) { + // gap before target? + if ( f->end < t ) + // done + break; + } + + if ( f != flags_.rend() ) + return (*f); + else + return *(flags_.rbegin()); + } + + return TimeInterval(); +} + void Timeline::clearFlags() { flags_.clear(); diff --git a/src/Timeline.h b/src/Timeline.h index 86a863c..b6e207e 100644 --- a/src/Timeline.h +++ b/src/Timeline.h @@ -69,6 +69,7 @@ struct TimeInterval if (this != &b) { this->begin = b.begin; this->end = b.end; + this->type = b.type; } return *this; } @@ -150,13 +151,16 @@ public: inline TimeIntervalSet flags() const { return flags_; }; inline size_t numFlags() const { return flags_.size(); }; float *flagsArray(); - bool addFlag(GstClockTime t, int type = 0); - bool addFlag(TimeInterval s, int type = 0); + bool addFlag(TimeInterval f); + bool replaceFlag(TimeInterval f); + bool addFlagAt(GstClockTime t, int type = 0); bool removeFlagAt(GstClockTime t); bool isFlagged(GstClockTime t) const; int flagTypeAt(GstClockTime t) const; void setFlagTypeAt(GstClockTime t, int type); + TimeInterval getFlagAt(GstClockTime t) const; TimeInterval getNextFlag(GstClockTime t) const; + TimeInterval getPreviousFlag(GstClockTime t) const; void clearFlags(); // inverse of gaps: sections of play areas