From 37445b885711ff44820e60d864e359c60f7f68f2 Mon Sep 17 00:00:00 2001 From: Bruno Date: Mon, 14 Jun 2021 23:42:20 +0200 Subject: [PATCH] Timeline management in Player Actions at key times (durations of all videos) to allow to adjust other videos duration (change speed of cut) --- ImGuiToolkit.cpp | 22 +++- ImGuiToolkit.h | 3 +- MediaPlayer.cpp | 4 +- Timeline.cpp | 98 ++++++++++++---- Timeline.h | 6 +- UserInterfaceManager.cpp | 247 ++++++++++++++++++++++++++++++++++----- UserInterfaceManager.h | 18 ++- rsc/images/icons.dds | Bin 1638528 -> 1638528 bytes 8 files changed, 338 insertions(+), 60 deletions(-) diff --git a/ImGuiToolkit.cpp b/ImGuiToolkit.cpp index cee822a..7c499e1 100644 --- a/ImGuiToolkit.cpp +++ b/ImGuiToolkit.cpp @@ -145,7 +145,10 @@ bool ImGuiToolkit::ButtonIcon(int i, int j, const char *tooltip) ImVec2 uv1( uv0.x + 0.05, uv0.y + 0.05 ); ImGui::PushID( i*20 + j); - bool ret = ImGui::ImageButton((void*)(intptr_t)textureicons, ImVec2(ImGui::GetTextLineHeightWithSpacing(),ImGui::GetTextLineHeightWithSpacing()), uv0, uv1, 3); + ImGuiContext& g = *GImGui; + bool ret = ImGui::ImageButton((void*)(intptr_t)textureicons, + ImVec2(g.FontSize, g.FontSize), + uv0, uv1, g.Style.FramePadding.y); ImGui::PopID(); if (tooltip != nullptr && ImGui::IsItemHovered()) @@ -353,6 +356,23 @@ bool ImGuiToolkit::ComboIcon (std::vector > icons, std::vect return ret; } +bool ImGuiToolkit::MenuItemIcon (int i, int j, const char* label, bool selected, bool enabled) +{ + ImVec2 draw_pos = ImGui::GetCursorScreenPos(); + + // make some space + char text_buf[256]; + ImFormatString(text_buf, IM_ARRAYSIZE(text_buf), " %s", label); + + // draw menu item + bool ret = ImGui::MenuItem(text_buf, NULL, selected, enabled); + + // draw icon + ImGui::SetCursorScreenPos(draw_pos); + Icon(i, j, enabled); + + return ret; +} void ImGuiToolkit::ShowIconsWindow(bool* p_open) { diff --git a/ImGuiToolkit.h b/ImGuiToolkit.h index 0757e7e..c0f691a 100644 --- a/ImGuiToolkit.h +++ b/ImGuiToolkit.h @@ -17,11 +17,12 @@ namespace ImGuiToolkit bool IconToggle (int i, int j, int i_toggle, int j_toggle, bool* toggle, const char *tooltips[] = nullptr); void ShowIconsWindow(bool* p_open); - // icon buttons + // buttons and gui items with icon bool ButtonIcon (int i, int j, const char* tooltip = nullptr); bool ButtonIconToggle (int i, int j, int i_toggle, int j_toggle, bool* toggle); bool ButtonIconMultistate (std::vector > icons, int* state); bool ComboIcon (std::vector > icons, std::vector labels, int* state); + bool MenuItemIcon (int i, int j, const char* label, bool selected = false, bool enabled = true); // buttons bool ButtonToggle (const char* label, bool* toggle); diff --git a/MediaPlayer.cpp b/MediaPlayer.cpp index 5abe87d..412dc54 100644 --- a/MediaPlayer.cpp +++ b/MediaPlayer.cpp @@ -676,7 +676,7 @@ bool MediaPlayer::go_to(GstClockTime pos) GstClockTime jumpPts = pos; - if (timeline_.gapAt(pos, gap)) { + if (timeline_.getGapAt(pos, gap)) { // if in a gap, find closest seek target if (gap.is_valid()) { // jump in one or the other direction @@ -922,7 +922,7 @@ void MediaPlayer::update() else { // manage timeline: test if position falls into a gap TimeInterval gap; - if (position_ != GST_CLOCK_TIME_NONE && timeline_.gapAt(position_, gap)) { + if (position_ != GST_CLOCK_TIME_NONE && timeline_.getGapAt(position_, gap)) { // if in a gap, seek to next section if (gap.is_valid()) { // jump in one or the other direction diff --git a/Timeline.cpp b/Timeline.cpp index ce4263b..b7c61ec 100644 --- a/Timeline.cpp +++ b/Timeline.cpp @@ -90,7 +90,7 @@ GstClockTime Timeline::next(GstClockTime time) const GstClockTime next_time = time; TimeInterval gap; - if (gapAt(time, gap) && gap.is_valid()) + if (getGapAt(time, gap) && gap.is_valid()) next_time = gap.end; return next_time; @@ -100,7 +100,7 @@ GstClockTime Timeline::previous(GstClockTime time) const { GstClockTime prev_time = time; TimeInterval gap; - if (gapAt(time, gap) && gap.is_valid()) + if (getGapAt(time, gap) && gap.is_valid()) prev_time = gap.begin; return prev_time; @@ -125,7 +125,13 @@ void Timeline::refresh() fillArrayFromGaps(gapsArray_, MAX_TIMELINE_ARRAY); } -bool Timeline::gapAt(const GstClockTime t, TimeInterval &gap) const +bool Timeline::gapAt(const GstClockTime t) const +{ + TimeIntervalSet::const_iterator g = std::find_if(gaps_.begin(), gaps_.end(), includesTime(t)); + return ( g != gaps_.end() ); +} + +bool Timeline::getGapAt(const GstClockTime t, TimeInterval &gap) const { TimeIntervalSet::const_iterator g = std::find_if(gaps_.begin(), gaps_.end(), includesTime(t)); @@ -142,34 +148,78 @@ bool Timeline::addGap(GstClockTime begin, GstClockTime end) return addGap( TimeInterval(begin, end) ); } -bool Timeline::cut(GstClockTime t) +bool Timeline::cut(GstClockTime t, bool left, bool join_extremity) { bool ret = false; if (timing_.includes(t)) { - TimeIntervalSet::iterator g = std::find_if(gaps_.begin(), gaps_.end(), includesTime(t)); - // cut a gap - if ( g != gaps_.end() ) - { - GstClockTime b = g->begin; - gaps_.erase(g); - ret = addGap(b, t); - } - // create a gap - else { - TimeIntervalSet::iterator previous = gaps_.end(); - for (g = gaps_.begin(); g != gaps_.end(); previous = g++) { - if ( g->begin > t) - break; - } - if (previous == gaps_.end()) - ret = addGap( TimeInterval(timing_.begin, t) ); - else { - GstClockTime b = previous->begin; - gaps_.erase(previous); + TimeIntervalSet::iterator gap = std::find_if(gaps_.begin(), gaps_.end(), includesTime(t)); + + // cut left part + if (left) { + // cut a gap + if ( gap != gaps_.end() ) + { + GstClockTime b = gap->begin; + gaps_.erase(gap); ret = addGap(b, t); } + // create a gap + else { + auto previous = gaps_.end(); + for (auto g = gaps_.begin(); g != gaps_.end(); previous = g++) { + if ( g->begin > t) + break; + } + if (join_extremity) { + //TODO + } + else { + if (previous == gaps_.end()) + ret = addGap( TimeInterval(timing_.begin, t) ); + else { + GstClockTime b = previous->begin; + gaps_.erase(previous); + ret = addGap( TimeInterval(b, t) ); + } + } + } + } + // cut right part + else { + // cut a gap + if ( gap != gaps_.end() ) + { + GstClockTime e = gap->end; + gaps_.erase(gap); + ret = addGap(t, e); + } + // create a gap + else { + auto suivant = gaps_.rend(); + for (auto g = gaps_.rbegin(); g != gaps_.rend(); suivant = g++) { + if ( g->end < t) + break; + } + if (join_extremity) { + if (suivant != gaps_.rend()) { + for (auto g = gaps_.find(*suivant); g != gaps_.end(); ) { + g = gaps_.erase(g); + } + } + ret = addGap( TimeInterval(t, timing_.end) ); + } + else { + if (suivant == gaps_.rend()) + ret = addGap( TimeInterval(t, timing_.end) ); + else { + GstClockTime e = suivant->end; + gaps_.erase( gaps_.find(*suivant)); + ret = addGap( TimeInterval(t, e) ); + } + } + } } } diff --git a/Timeline.h b/Timeline.h index 5fd61e0..d376ea6 100644 --- a/Timeline.h +++ b/Timeline.h @@ -118,9 +118,11 @@ public: void setGaps(const TimeIntervalSet &g); bool addGap(TimeInterval s); bool addGap(GstClockTime begin, GstClockTime end); - bool cut(GstClockTime t); + bool cut(GstClockTime t, bool left, bool join_extremity); bool removeGaptAt(GstClockTime t); - bool gapAt(const GstClockTime t, TimeInterval &gap) const; + + bool gapAt(const GstClockTime t) const; + bool getGapAt(const GstClockTime t, TimeInterval &gap) const; // Manipulation of Fading float fadingAt(const GstClockTime t) const; diff --git a/UserInterfaceManager.cpp b/UserInterfaceManager.cpp index 2f66945..b96790e 100644 --- a/UserInterfaceManager.cpp +++ b/UserInterfaceManager.cpp @@ -1986,6 +1986,7 @@ void ToolBox::Render() SourceController::SourceController() : _min_width(0.f), _h_space(0.f), _v_space(0.f), _buttons_height(0.f), _timeline_height(0.f), _scrollbar(0.f), _mediaplayer_height(0.f), _buttons_width(0.f), active_label_(LABEL_AUTO_MEDIA_PLAYER), active_selection_(-1), + _selection_context_menu(false), _selection_mediaplayer(nullptr), _selection_target_slower(0), _selection_target_faster(0), media_playing_mode_(false), slider_pressed_(false) { info_.setExtendedStringMode(); @@ -2275,18 +2276,24 @@ void SourceController::RenderSelection(size_t i) // get max duration and max frame width GstClockTime maxduration = 0; + std::list durations; float maxframewidth = 0.f; for (auto source = selection_.begin(); source != selection_.end(); ++source) { + // collect durations of all media sources MediaSource *ms = dynamic_cast(*source); - if (ms != nullptr) { - GstClockTime d = (static_cast(ms->mediaplayer()->timeline()->sectionsDuration()) / ms->mediaplayer()->playSpeed()); - if ( d > maxduration ) - maxduration = d; - } + if (ms != nullptr) + durations.push_back(static_cast(static_cast(ms->mediaplayer()->timeline()->sectionsDuration()) / ms->mediaplayer()->playSpeed())); + // compute the displayed width of frames of this source, and keep the max to align afterwards float w = 1.5f * _timeline_height * (*source)->frame()->aspectRatio(); if ( w > maxframewidth) maxframewidth = w; } + if (durations.size()>0) { + durations.sort(); + durations.unique(); + maxduration = durations.back(); + } + // compute the ratio for timeline rendering : width (pixel) per time unit (ms) const float w = rendersize.x -maxframewidth - 3.f * _h_space - _scrollbar; const double width_ratio = static_cast(w) / static_cast(maxduration); @@ -2294,6 +2301,7 @@ void SourceController::RenderSelection(size_t i) // draw list in a scroll area ImGui::BeginChild("##v_scroll2", rendersize, false); { + // draw play time scale if a duration is set if (maxduration > 0) { ImGui::SetCursorPos(ImGui::GetCursorPos() + ImVec2( maxframewidth + _h_space, 0)); @@ -2311,7 +2319,9 @@ void SourceController::RenderSelection(size_t i) UserInterface::manager().showSourceEditor(*source); // text below thumbnail to show status - ImGui::Text(" %s %s", SourcePlayIcon(*source), GstToolkit::time_to_string((*source)->playtime()).c_str() ); + ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO); + ImGui::Text("%s %s", SourcePlayIcon(*source), GstToolkit::time_to_string((*source)->playtime()).c_str() ); + ImGui::PopFont(); // get media source MediaSource *ms = dynamic_cast(*source); @@ -2320,21 +2330,124 @@ void SourceController::RenderSelection(size_t i) MediaPlayer *mp = ms->mediaplayer(); // start to draw timeline aligned at maximum frame width + horizontal space - ImGui::SetCursorPos(image_top + ImVec2( maxframewidth + _h_space, 0)); + ImVec2 pos = image_top + ImVec2( maxframewidth + _h_space, 0); + ImGui::SetCursorPos(pos); // draw the mediaplayer's timeline, with the indication of cursor position // NB: use the same width/time ratio for all to ensure timing vertical correspondance DrawTimeline("##timeline_mediaplayer", mp->timeline(), mp->position(), width_ratio / fabs(mp->playSpeed()), framesize.y); - ImGui::SetCursorPos(image_top + ImVec2( maxframewidth + _h_space, framesize.y + _v_space)); - ImGui::Text("%s play time @ %.2f speed / %s (max duration)", - GstToolkit::time_to_string(mp->timeline()->sectionsDuration()).c_str(), - mp->playSpeed(), GstToolkit::time_to_string(maxduration).c_str()); + // next icon buttons are small + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(3.f, 3.f)); + + // next buttons sub id + ImGui::PushID( static_cast(mp->id())); + + // display play speed + ImGui::SetCursorPos(pos + ImVec2( 0.f, framesize.y + _v_space)); + ImGui::Text(UNICODE_MULTIPLY " %.2f", mp->playSpeed()); + // if not 1x speed, offer to reset + if ( fabs( fabs(mp->playSpeed()) - 1.0 ) > EPSILON ) { + ImGui::SameLine(0,_h_space); + if (ImGuiToolkit::ButtonIcon(19, 15, "Reset speed")) + mp->setPlaySpeed( 1.0 ); + } + + // if more than one duration of media players, add buttons to adjust + if (durations.size() > 1) + { + + for (auto d = durations.crbegin(); d != durations.crend(); ++d) { + + ImGui::PushID( static_cast(*d)); + + // calculate position of icons + double x = static_cast(*d) * width_ratio; + ImGui::SetCursorPos(pos + ImVec2( static_cast(x) - 2.f, framesize.y + _v_space) ); + // depending on position relative to media play duration, offer corresponding action + double secdur = static_cast(mp->timeline()->sectionsDuration()); + guint64 playdur = static_cast( secdur / fabs(mp->playSpeed()) ); + // last icon in the timeline + if ( playdur == (*d) ) { + // not the minimum duration : + if (playdur > durations.front() ) { + // offer to speed up or slow down [<>] + if (playdur < durations.back() ) { + if ( ImGuiToolkit::ButtonIcon(0, 12, "Adjust duration") ) { + auto prev = d; + prev--; + _selection_target_slower = SIGN(mp->playSpeed()) * secdur / static_cast(*prev); + auto next = d; + next++; + _selection_target_faster = SIGN(mp->playSpeed()) * secdur / static_cast(*next); + _selection_mediaplayer = mp; + _selection_context_menu = true; + } + } + // offer to speed up [< ] + else if ( ImGuiToolkit::ButtonIcon(8, 12, "Adjust duration") ) { + auto next = d; + next++; + _selection_target_faster = SIGN(mp->playSpeed()) * secdur / static_cast(*next); + _selection_target_slower = 0.0; + _selection_mediaplayer = mp; + _selection_context_menu = true; + } + } + // minimum duration : offer to slow down [ >] + else if ( ImGuiToolkit::ButtonIcon(9, 12, "Adjust duration") ) { + _selection_target_faster = 0.0; + auto prev = d; + prev--; + _selection_target_slower = SIGN(mp->playSpeed()) * secdur / static_cast(*prev); + _selection_mediaplayer = mp; + _selection_context_menu = true; + } + } + // middle buttons : offer to cut at this position + else if ( playdur > (*d) ) { + char text_buf[256]; + GstClockTime cutposition = (*d) * fabs(mp->playSpeed()); + ImFormatString(text_buf, IM_ARRAYSIZE(text_buf), "Cut at %s", + GstToolkit::time_to_string(cutposition, GstToolkit::TIME_STRING_MINIMAL).c_str()); + + if ( ImGuiToolkit::ButtonIcon(9, 3, text_buf) ) { + if ( mp->timeline()->cut(cutposition, false, true) ) { + std::ostringstream info; + info << SystemToolkit::base_filename( mp->filename() ) << ": Timeline " < 0) { + + // calculate position of icon + double x = static_cast(durations.front()) * width_ratio; + ImGui::SetCursorPos(pos + ImVec2( static_cast(x) - 2.f, framesize.y + _v_space) ); + + // offer only to adjust size by removing ending gap + if ( mp->timeline()->gapAt( mp->timeline()->end() ) ) { + if ( ImGuiToolkit::ButtonIcon(7, 0, "Remove end gap" )){ + if ( mp->timeline()->removeGaptAt(mp->timeline()->end()) ) { + std::ostringstream info; + info << SystemToolkit::base_filename( mp->filename() ) << ": Timeline Remove end gap"; + Action::manager().store(info.str()); + } + } + } + } + + ImGui::PopStyleVar(); + ImGui::PopID(); } // next line position - ImGui::SetCursorPos(image_top + ImVec2(0, 2.0f * _timeline_height + _v_space)); -// ImGui::Spacing(); + ImGui::SetCursorPos(image_top + ImVec2(0, 2.0f * _timeline_height + 2.f * _v_space)); } } @@ -2342,6 +2455,11 @@ void SourceController::RenderSelection(size_t i) } + /// + /// context menu from actions above + /// + RenderSelectionContextMenu(); + /// /// Play button bar /// @@ -2391,6 +2509,45 @@ void SourceController::RenderSelection(size_t i) ImGui::PopStyleColor(4); } +void SourceController::RenderSelectionContextMenu() +{ + if (_selection_mediaplayer == nullptr) + return; + + if (_selection_context_menu) { + ImGui::OpenPopup("_speedchange_context_menu"); + _selection_context_menu = false; + } + if (ImGui::BeginPopup("_speedchange_context_menu")) + { + std::ostringstream info; + info << SystemToolkit::base_filename( _selection_mediaplayer->filename() ); + + if ( ImGuiToolkit::MenuItemIcon(14, 16, ICON_FA_CARET_LEFT " Accelerate", false, fabs(_selection_target_faster) > 0 )){ + _selection_mediaplayer->setPlaySpeed( _selection_target_faster ); + info << ": Speed x" << std::setprecision(3) << _selection_target_faster; + Action::manager().store(info.str()); + } + if ( ImGuiToolkit::MenuItemIcon(15, 16, ICON_FA_CARET_RIGHT " Slow down", false, fabs(_selection_target_slower) > 0 )){ + _selection_mediaplayer->setPlaySpeed( _selection_target_slower ); + info << ": Speed x" << std::setprecision(3) << _selection_target_slower; + Action::manager().store(info.str()); + } + if ( _selection_mediaplayer->timeline()->gapAt( _selection_mediaplayer->timeline()->end()) ) { + + if ( ImGuiToolkit::MenuItemIcon(7, 0, "Remove end gap" )){ + info << ": Remove end gap "; + if ( _selection_mediaplayer->timeline()->removeGaptAt(_selection_mediaplayer->timeline()->end()) ) + Action::manager().store(info.str()); + } + + } + ImGui::EndPopup(); + } + +} + + bool SourceController::SourceButton(Source *s, ImVec2 framesize) { bool ret = false; @@ -2722,13 +2879,22 @@ void SourceController::RenderMediaPlayer(MediaPlayer *mp) if ( ImGuiToolkit::ButtonIconMultistate(iconsloop, ¤t_loop) ) mp->setLoop( (MediaPlayer::LoopMode) current_loop ); + + std::ostringstream oss; + oss << SystemToolkit::base_filename( mp->filename() ); + // 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)) + if (ImGui::DragFloat( "##Speed", &speed, 0.01f, -10.f, 10.f, "Speed " UNICODE_MULTIPLY " %.1f", 2.f)) mp->setPlaySpeed( static_cast(speed) ); + + if (ImGui::IsItemDeactivatedAfterEdit()){ + oss << ": Speed x" << std::setprecision(3) << speed; + Action::manager().store(oss.str()); + } } /// @@ -2742,15 +2908,18 @@ void SourceController::RenderMediaPlayer(MediaPlayer *mp) ImGui::OpenPopup( "MenuTimeline" ); if (ImGui::BeginPopup( "MenuTimeline" )) { - if (ImGui::MenuItem(UNICODE_MULTIPLY " " ICON_FA_CARET_RIGHT " Reset speed" )){ + if (ImGuiToolkit::MenuItemIcon(19,15,"Reset speed" )){ speed = 1.f; mp->setPlaySpeed( static_cast(speed) ); + oss << ": Speed x1"; + Action::manager().store(oss.str()); } if (ImGui::MenuItem(ICON_FA_WINDOW_CLOSE " Reset timeline" )){ timeline_zoom = 1.f; mp->timeline()->clearFading(); mp->timeline()->clearGaps(); - Action::manager().store("Timeline Reset"); + oss << ": Reset timeline"; + Action::manager().store(oss.str()); } if (ImGui::BeginMenu(ICON_FA_RANDOM " Auto fading")) { @@ -2759,15 +2928,19 @@ void SourceController::RenderMediaPlayer(MediaPlayer *mp) if (ImGui::MenuItem(names[i])) { mp->timeline()->autoFading( 250 * (int ) pow(2, i) ); mp->timeline()->smoothFading( 2 * (i + 1) ); - Action::manager().store("Timeline Auto fading"); + oss << ": Timeline Auto fading " << 250 * (int ) pow(2, i); + Action::manager().store(oss.str()); } } ImGui::EndMenu(); } if (ImGui::BeginMenu(ICON_FA_CUT " Auto cut" )){ - if (ImGui::MenuItem("Cut faded areas")) - if (mp->timeline()->autoCut()) - Action::manager().store("Timeline Auto cut"); + + if (ImGuiToolkit::MenuItemIcon(14, 12, "Cut faded areas" )) + if (mp->timeline()->autoCut()){ + oss << ": Cut faded areas"; + Action::manager().store(oss.str()); + } ImGui::EndMenu(); } @@ -2820,7 +2993,11 @@ void SourceController::RenderMediaPlayer(MediaPlayer *mp) else if (released) { tl->refresh(); - Action::manager().store("Timeline change"); + if (Settings::application.widget.timeline_editmode) + oss << ": Timeline cut"; + else + oss << ": Timeline opacity"; + Action::manager().store(oss.str()); } // custom timeline slider @@ -2840,10 +3017,27 @@ void SourceController::RenderMediaPlayer(MediaPlayer *mp) ImGui::SetCursorScreenPos(bottom + ImVec2(1.f, 0.5f * _timeline_height)); if (Settings::application.widget.timeline_editmode) { - // action auto cut - if (ImGuiToolkit::IconButton(9, 3, "Cut at cursor")) { - if (mp->timeline()->cut(mp->position())) - Action::manager().store("Timeline Cut"); + // action cut + if (mp->isPlaying()) { + ImGuiToolkit::HelpIcon("Pause video to enable cut options", 9, 3); + } + else if (ImGuiToolkit::IconButton(9, 3, "Cut at cursor")) { + ImGui::OpenPopup("timeline_cut_context_menu"); + } + if (ImGui::BeginPopup("timeline_cut_context_menu")){ + if (ImGuiToolkit::MenuItemIcon(1,0,"Cut left")){ + if (mp->timeline()->cut(mp->position(), true, false)) { + oss << ": Timeline cut"; + Action::manager().store(oss.str()); + } + } + if (ImGuiToolkit::MenuItemIcon(2,0,"Cut right")){ + if (mp->timeline()->cut(mp->position(), false, false)){ + oss << ": Timeline cut"; + Action::manager().store(oss.str()); + } + } + ImGui::EndPopup(); } } else { @@ -2858,7 +3052,8 @@ void SourceController::RenderMediaPlayer(MediaPlayer *mp) ImGui::PopButtonRepeat(); if (_actionsmooth > 0 && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) { - Action::manager().store("Timeline Smooth"); + oss << ": Timeline opacity smooth"; + Action::manager().store(oss.str()); _actionsmooth = 0; } } diff --git a/UserInterfaceManager.h b/UserInterfaceManager.h index 0599e02..3c4a8e3 100644 --- a/UserInterfaceManager.h +++ b/UserInterfaceManager.h @@ -120,14 +120,24 @@ class SourceController InfoVisitor info_; SourceList selection_; + bool _selection_context_menu; + MediaPlayer *_selection_mediaplayer; + double _selection_target_slower; + double _selection_target_faster; + void RenderSelectionContextMenu(); + + // re-usable ui parts void DrawButtonBar(ImVec2 bottom, float width); const char *SourcePlayIcon(Source *s); - bool SourceButton(Source *s, ImVec2 framesize); - void RenderSelectedSources(); - void RenderSelection(size_t i); - void RenderSingleSource(Source *s); + // Render the sources dynamically selected + void RenderSelectedSources(); + // Render a stored selection + void RenderSelection(size_t i); + // Render a single source + void RenderSingleSource(Source *s); + // Render a single media player bool media_playing_mode_; bool slider_pressed_; void RenderMediaPlayer(MediaPlayer *mp); diff --git a/rsc/images/icons.dds b/rsc/images/icons.dds index 4e51eebfc7ab548cf68ad9856e6d35d6eec8d0db..0a46542f93b34c6fc32fc9a9f1d3f2cc4438c8b6 100644 GIT binary patch delta 3773 zcmb_e4Nw%<9e?|A90JEKw2WuYYs$kzA)3M-UXNB?h;{zNE z05pqSYhizp%f-0h#kq5PEajNV*VX$(IZ$e2^Tm3hGE*!mD}UyrPd==}&T`>EjV zQ0wNl*vjM^`b%L>A5p!DY8zqsi-Jd2_qE~hT*e}`GQ*Alx!!|E%Gl>%`HoZ%NmU;V zsGLuTECGbaQ9KM^TNZyirgqOfH{ATHkk_H^bHLzvmmI$-`M??!BEK1odS?0wYeCvU zJs9HA4=4|;LMaiYm|?IzS*tXEQ<7GG+@SR)%+fb}9dkMEuFEWdwE^KCLp>rmH23LI_BcnY1e9PFE0kSBt$w1<5`f8!mO@jLU&xhinNs=z*D{)yHM z&HrGg0^SmDQTRc9WSasXy}?P2rJ#$U)0Wl4Unj~FQEE5Kc=KOMQnRdwCI1wDY^Vdp zIL7&+7o4H6D!PEe`k=ZN2LC2XoDHmlB|YrkuNQG(k$6?$;9hV)DVZ3qC<_1@Z!7N zuK!E=h%x+?%o1&|(yd%n^A~I2+iV*lGtj zzZo24d8tYU)fC4&a|TUCp@^RKx+}!fbu@l(4GZL2%7@X0PeAn(I^`|-=#A))+fhCI z>+e%4HFcHQcZp>rB?v~rP0=zEwJ?r7ZKDY5M7zpdkY_0Wcj%tZT9x_`XagZ;h$9=| zmCn8yR&vWTigi&*)D<<19|XA>zF9fXYqy~klrnB3ihImp7Kfav@-(;wu7l6?Pt<3# z#`zH21-2zOyx;twkehI3YrsQN7c+ro&~>EgAFFFK$FaTGiFO~X>6e1$f4(=iPK5m(@+SnYa;jTKDDzTgrbvj6<+Q24oAD9;lGjcv_YMwdq zGj7=F_9D+T*c^w&xioT)M(;8X88`m<6KP2F9Q!n1z^KZK0QacNW1_n0)N?vhG6Li{@!mebGvti zk_< z3}E9OHFxTWcmkfqTD(4?x^MS7M^UJPGS!a8(6pstIW%s)o}}w6-~bOev+=h=ngmBK zr{_J~9>yDU47^1}EQRQeK-j@TybrqWCkPg9gwKuF0_ve$fcsvNl62b|Hs^-F)GrY&tzQA^s?W7*@a)zF1iv65miIN*m($!hd|8^^_SvLY~y5OaVQ)@?T!5-4eHp(3LA&zFq#{7FP_PJ-P&z z|C`y?(k+RYvM@Q#Zb=#~fUA4hI+H1t4v`-b&I1m^cg`~R>!UWoQ2^>+AEBT~2I zX-Ll3G4{d|^|)xYFbSRHk`;4nXez|9nv-K?CppTO>F)I&(_Jn3rD6?3i6AC=eC$$a z?D+duG6_@}-q#GcJLZArD>vOcq+du6CMZSlPsH&xE~%ccGnekw_R5#wk)yUE&F?nd zuSh;g><5n}R-Jey=e6TT$Ah|B36>K!#%sPD(|%C;l>~p-!`8Pj-4Z4jrumq%G3A&{ J_gzii{{gp$WJ3S| delta 4497 zcmc&&e^3Dxg=iBNPpU>(L`iu`r%@-FdksKT@ z%*GunMfHroEt%X{=Xn;N&nr%A0;xyYh}oJfvylavTCh-lfJFW|$u>zra$-@Ayg+8g z#9ivyyoJ`iEu2hBOC%5ZR=${}MA93=g-#`$i`A0n@%e@{CgPN^n+k(yHxf({Br;bF zU?D4{HkVxCNzH28FVm}F6N~_p-F;Q}O-y5SKMm3#SzKh#CiC*eSxUr>!|T_m9V-te zE8z^>P+_X-5Drmc0394_Elx>&PHm|8o|U-UJ-Id}i%D~=5FaBi6xn56q#9b!IdMzg zD|Y%eD5j)#k7dhah#DWZ04YQ}5>UrDvb~b)#_o!x%$WieZ;kUrsj)339VaK{co*v| zdN9E+10iBxAyDV;d)F~(5NCv(FCuq_K*oT{|{a7ylSdh^ndEk(~4r&Kkn!b-q z{<0c)+-l1|{6;xo>4G>$PZ#sg&{L#_F8ZCM<5S5?_Leh0?|G)-mg;OKzJ-P4kTAGA zA$~v?Kll1>^8P0_zj`;iiznfmNu^lnmIL?Q`auL@FcIpH)pX3nf}v8ErSDU=x6Mcl z;5x!BcLrF=m#H^DznDyFwn#n}^BiW&l%$vy_`pMU)DAMQ6?rfaH`ffWnORDWPwn`x z4CghIWO|(o*-=%){Y#w?jvwF^e_O!33h8;khc$`xeZc&kcfC%jS)g^;IeBz5@j9WI zXyIgTjT1+Xdy=8vOpQeLI;WHAY1}#7`-zQ1q%)PvA{jRBBdufFu$)`Mb|jMiWzJPf zSY}Phs}Y%71+r}9ZWebRs>+tkTr!2S2+ZN4wH?`<){$*V`8heiBx$nb-hKoKAVlCl zhkpMoalFreq;>i&L5@4El$PX{Ao7lSNsTETpSp0Wi<%tu(G;dqt5W3;hfQ zQ4_~3-mIOzVzElnaI}CjLg7hs{D4L*83y=hB#9|GAc#~Oj!ftt6&aIq7$M#scy9QYrSjvPBC2=G^ycf4zPPa zw}FLpB3Lq@^}fn?i0Mcj+qUDeSJ#aWv5jne%0X*;ck*mn*|f1aflWJ`4mKyUnZ)KK zHj~+OYP~xr-`j`1<*nMmOqX9`sko>eoaf4wUJkG{1i&CLrm(O|A72;tAg}&0le_lzWIvBVq{%u;JrK93c6l3*cdK z4?c`w{Qkod!7BZAxqGA=zjGn3-+rdS7Gyy@vYJUDZI@hbEmG^cBL-WL0_Vh}WDcHi zyGP?8`g&X#NbJ{}o!tlp$Kg`X@`!>|Z#rT-R-2Z=_9iP_mIDh=Y zm-7C((C;5w3;K0~^e=7f9vjBbkhO1GWi8y~k|nb2R}-b+8Fo{dyL39;1{b4=!F)cD z7BKO6?Ozo$>*pY^->aAk&d__q(Hu7aIhq!=H6?PRg#23MEVDfrfZOsi3Ynbngj~RY zEWTY3`;!iaaaC1o>sWmBXitx^0`yf?U&1HG?vLTmIQpILguIbR$JjMCtZ)NayPwa~ zB3Hp$VS{*MB~?s#ml{*q5WlVDJrh5fL2 zIgag%f2gJJkV4YZVx$WFBNh+EXg&2AKP!O2DnL6L3Ctk9OgB;NZ>Jxd4ZhabA#rsMUTk2U;}eNX|z+-Ik&lD&Z?Fb&3|*GBPkj5`%^ XwS7$zn;tf&u<2zpMeA!y-C6uU!zs;j