diff --git a/ImGuiToolkit.cpp b/ImGuiToolkit.cpp index 05aeecc..d8106ea 100644 --- a/ImGuiToolkit.cpp +++ b/ImGuiToolkit.cpp @@ -435,10 +435,10 @@ void ImGuiToolkit::HelpIcon(const char* desc, int i, int j, const char* shortcut #define NUM_MARKS 10 #define LARGE_TICK_INCREMENT 1 #define LABEL_TICK_INCREMENT 3 +static guint64 optimal_tick_marks[NUM_MARKS + LABEL_TICK_INCREMENT] = { 100 * MILISECOND, 500 * MILISECOND, 1 * SECOND, 2 * SECOND, 5 * SECOND, 10 * SECOND, 20 * SECOND, 1 * MINUTE, 2 * MINUTE, 5 * MINUTE, 10 * MINUTE, 60 * MINUTE, 60 * MINUTE }; + bool ImGuiToolkit::TimelineSlider(const char* label, guint64 *time, guint64 start, guint64 end, guint64 step, const float width) { - static guint64 optimal_tick_marks[NUM_MARKS + LABEL_TICK_INCREMENT] = { 100 * MILISECOND, 500 * MILISECOND, 1 * SECOND, 2 * SECOND, 5 * SECOND, 10 * SECOND, 20 * SECOND, 1 * MINUTE, 2 * MINUTE, 5 * MINUTE, 10 * MINUTE, 60 * MINUTE, 60 * MINUTE }; - // get window ImGuiWindow* window = ImGui::GetCurrentWindow(); if (window->SkipItems) @@ -533,16 +533,12 @@ bool ImGuiToolkit::TimelineSlider(const char* label, guint64 *time, guint64 star { large_tick_step = 10 * step; - if (step > 0) { - // try to put a label every second - if ( 1000000000 % step < 1000) - label_tick_step = (1000000000 / step) * step; - // not a round framerate: probalby best to use 10 frames interval - else - label_tick_step = 10 * step; - } + // try to put a label every second + if ( 1000000000 % step < 1000) + label_tick_step = (1000000000 / step) * step; + // not a round framerate: probalby best to use 10 frames interval else - label_tick_step = 30 * step; + label_tick_step = 10 * step; } else { // while there is less than 5 pixels between two tick marks (or at last optimal tick mark) @@ -622,6 +618,153 @@ bool ImGuiToolkit::TimelineSlider(const char* label, guint64 *time, guint64 star return left_mouse_press; } +void ImGuiToolkit::Timeline (const char* label, guint64 time, guint64 start, guint64 end, guint64 step, const float width) +{ + // get window + ImGuiWindow* window = ImGui::GetCurrentWindow(); + if (window->SkipItems) + return; + + // get style & id + const ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const float fontsize = g.FontSize; + const ImGuiID id = window->GetID(label); + + // + // FIRST PREPARE ALL data structures + // + + // widget bounding box + const float height = 2.f * (fontsize + style.FramePadding.y); + ImVec2 pos = window->DC.CursorPos; + ImVec2 size = ImVec2(width, height); + ImRect bbox(pos, pos + size); + ImGui::ItemSize(size, style.FramePadding.y); + if (!ImGui::ItemAdd(bbox, id)) + return; + + // cursor size + const float cursor_scale = 1.f; + const float cursor_width = 0.5f * fontsize * cursor_scale; + + // TIMELINE is inside the bbox, in a slightly smaller bounding box + ImRect timeline_bbox(bbox); + timeline_bbox.Expand( ImVec2(-cursor_width, -style.FramePadding.y) ); + + // SLIDER is inside the timeline +// ImRect slider_bbox( timeline_bbox.GetTL() + ImVec2(-cursor_width + 2.f, cursor_width + 4.f ), timeline_bbox.GetBR() + ImVec2( cursor_width - 2.f, 0.f ) ); + + // units conversion: from time to float (calculation made with higher precision first) + float time_ = static_cast ( static_cast(time - start) / static_cast(end) ); + float step_ = static_cast ( static_cast(step) / static_cast(end) ); + + // + // THIRD RENDER + // + + // 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); + + // by default, put a tick mark at every frame step and a large mark every second + guint64 tick_step = step; + guint64 large_tick_step = optimal_tick_marks[1+LARGE_TICK_INCREMENT]; + guint64 label_tick_step = optimal_tick_marks[1+LABEL_TICK_INCREMENT]; + + // how many pixels to represent one frame step? + float tick_step_pixels = timeline_bbox.GetWidth() * step_; + + // large space + if (tick_step_pixels > 5.f) + { + large_tick_step = 10 * step; + + // try to put a label every second + if ( 1000000000 % step < 1000) + label_tick_step = (1000000000 / step) * step; + // not a round framerate: probalby best to use 10 frames interval + else + label_tick_step = 10 * 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 ) + { + // try to use the optimal tick marks pre-defined + tick_step = optimal_tick_marks[i]; + large_tick_step = optimal_tick_marks[i+LARGE_TICK_INCREMENT]; + label_tick_step = optimal_tick_marks[i+LABEL_TICK_INCREMENT]; + tick_step_pixels = timeline_bbox.GetWidth() * static_cast ( static_cast(tick_step) / static_cast(end) ); + } + } + + // render the tick marks along TIMELINE + ImU32 color = ImGui::GetColorU32( style.Colors[ImGuiCol_Text] ); + pos = timeline_bbox.GetTL(); + guint64 tick = 0; + char overlay_buf[24]; + + // render text duration + ImFormatString(overlay_buf, IM_ARRAYSIZE(overlay_buf), "%s", + GstToolkit::time_to_string(end, GstToolkit::TIME_STRING_MINIMAL).c_str()); + ImVec2 overlay_size = ImGui::CalcTextSize(overlay_buf, NULL); + ImVec2 duration_label = bbox.GetBR() - overlay_size - ImVec2(3.f, 3.f); + if (overlay_size.x > 0.0f) + ImGui::RenderTextClipped( duration_label, bbox.Max, overlay_buf, NULL, &overlay_size); + + // render tick marks + while ( tick < end) + { + // large tick mark + float tick_length = !(tick%large_tick_step) ? fontsize - style.FramePadding.y : style.FramePadding.y; + + // label tick mark + if ( !(tick%label_tick_step) ) { + tick_length = fontsize; + guint64 ticklabel = 100 * (guint64) round( (double)( tick ) / 100.0); // round value to avoid '0.99' and alike + ImFormatString(overlay_buf, IM_ARRAYSIZE(overlay_buf), "%s", + GstToolkit::time_to_string(ticklabel, GstToolkit::TIME_STRING_MINIMAL).c_str()); + overlay_size = ImGui::CalcTextSize(overlay_buf, NULL); + ImVec2 mini = ImVec2( pos.x - overlay_size.x / 2.f, pos.y + tick_length ); + ImVec2 maxi = ImVec2( pos.x + overlay_size.x / 2.f, pos.y + tick_length + overlay_size.y ); + // do not overlap with label for duration + if (maxi.x < duration_label.x) + ImGui::RenderTextClipped(mini, maxi, overlay_buf, NULL, &overlay_size); + } + + // draw the tick mark each step + window->DrawList->AddLine( pos, pos + ImVec2(0.f, tick_length), color); + + // next tick + tick += tick_step; + float tick_percent = static_cast ( static_cast(tick) / static_cast(end) ); + pos = ImLerp(timeline_bbox.GetTL(), timeline_bbox.GetTR(), tick_percent); + } + + // tick EOF + window->DrawList->AddLine( timeline_bbox.GetTR(), timeline_bbox.GetTR() + ImVec2(0.f, fontsize), color); + +// disabled: render position +// ImFormatString(overlay_buf, IM_ARRAYSIZE(overlay_buf), "%s", GstToolkit::time_to_string(*time).c_str()); +// overlay_size = ImGui::CalcTextSize(overlay_buf, NULL); +// overlay_size = ImVec2(3.f, -3.f - overlay_size.y); +// if (overlay_size.x > 0.0f) +// ImGui::RenderTextClipped( bbox.GetBL() + overlay_size, bbox.Max, overlay_buf, NULL, &overlay_size); + +// // 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); +// } + + // draw the cursor + if ( time_ > 0.f && time_ < 1.f ) { + color = ImGui::GetColorU32(style.Colors[ImGuiCol_SliderGrab]); + pos = ImLerp(timeline_bbox.GetTL(), timeline_bbox.GetTR(), time_) - ImVec2(cursor_width, 2.f); + ImGui::RenderArrow(window->DrawList, pos, color, ImGuiDir_Up, cursor_scale); + } +} + //bool ImGuiToolkit::InvisibleSliderInt(const char* label, guint64 *index, guint64 min, guint64 max, ImVec2 size) bool ImGuiToolkit::InvisibleSliderInt(const char* label, uint *index, uint min, uint max, ImVec2 size) { @@ -785,6 +928,7 @@ void FilterCreation(float GKernel[], int N) GKernel[i] /= max; } + bool ImGuiToolkit::EditPlotHistoLines(const char* label, float *histogram_array, float *lines_array, int values_count, float values_min, float values_max, bool edit_histogram, bool *released, const ImVec2 size) { @@ -922,6 +1066,51 @@ bool ImGuiToolkit::EditPlotHistoLines(const char* label, float *histogram_array, return array_changed; } +void ImGuiToolkit::ShowPlotHistoLines(const char* label, float *histogram_array, float *lines_array, int values_count, float values_min, float values_max, const ImVec2 size) +{ + // get window + ImGuiWindow* window = ImGui::GetCurrentWindow(); + if (window->SkipItems) + return; + + // capture coordinates before any draw or action + ImVec2 canvas_pos = ImGui::GetCursorScreenPos(); + + // get id + const ImGuiID id = window->GetID(label); + + // add item + ImVec2 pos = window->DC.CursorPos; + ImRect bbox(pos, pos + size); + ImGui::ItemSize(size); + if (!ImGui::ItemAdd(bbox, id)) + return; + + const ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + ImVec4 bg_color = style.Colors[ImGuiCol_FrameBg]; + + // back to draw + ImGui::SetCursorScreenPos(canvas_pos); + + // plot transparent histogram + ImGui::PushStyleColor(ImGuiCol_FrameBg, bg_color); + ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0, 0, 0, 250)); + char buf[128]; + sprintf(buf, "##Histo%s", label); + ImGui::PlotHistogram(buf, histogram_array, values_count, 0, NULL, values_min, values_max, size); + ImGui::PopStyleColor(2); + + ImGui::SetCursorScreenPos(canvas_pos); + + // plot lines + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0,0,0,0)); + sprintf(buf, "##Lines%s", label); + ImGui::PlotLines(buf, lines_array, values_count, 0, NULL, values_min, values_max, size); + ImGui::PopStyleColor(1); + +} + void ImGuiToolkit::SetFont(ImGuiToolkit::font_style style, const std::string &ttf_font_name, int pointsize, int oversample) { diff --git a/ImGuiToolkit.h b/ImGuiToolkit.h index ef76225..7d5a8cd 100644 --- a/ImGuiToolkit.h +++ b/ImGuiToolkit.h @@ -34,9 +34,11 @@ namespace ImGuiToolkit // utility sliders bool TimelineSlider (const char* label, guint64 *time, guint64 start, guint64 end, guint64 step, const float width); + void Timeline (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 cut, bool *released, const ImVec2 size); + void ShowPlotHistoLines(const char* label, float *histogram_array, float *lines_array, int values_count, float values_min, float values_max, const ImVec2 size); // fonts from ressources 'fonts/' typedef enum { diff --git a/Timeline.cpp b/Timeline.cpp index 799abfc..6fcb6ca 100644 --- a/Timeline.cpp +++ b/Timeline.cpp @@ -6,6 +6,10 @@ #include "Log.h" #include "Timeline.h" + +static float empty_gaps[MAX_TIMELINE_ARRAY] = {}; +static float empty_fade[MAX_TIMELINE_ARRAY] = {}; + struct includesTime: public std::unary_function { inline bool operator()(const TimeInterval s) const @@ -163,6 +167,88 @@ bool Timeline::removeGaptAt(GstClockTime t) } +GstClockTime Timeline::sectionsDuration() const +{ + GstClockTime d = 0; + + // compute the sum of durations of gaps + for (auto it = gaps_.begin(); it != gaps_.end(); ++it) + d += (*it).end - (*it).begin; + + return duration() - d; +} + +size_t Timeline::fillSectionsArrays( float* const gaps, float* const fading) +{ + size_t arraysize = MAX_TIMELINE_ARRAY; + float* gapsptr = gaps; + float* fadingptr = fading; + + if (gaps_array_need_update_) + fillArrayFromGaps(gapsArray_, MAX_TIMELINE_ARRAY); + + if (gaps_.size() > 0) { + + // indices to define [s e[] sections + size_t s = 0; + size_t e = MAX_TIMELINE_ARRAY; + arraysize = 0; + + auto it = gaps_.begin(); + + // cut at the beginning + if ((*it).begin == timing_.begin) { + for (size_t i = 0; i < 5; ++i) + gapsptr[ MAX(i, 0) ] = 1.f; + + s = ( (*it).end * MAX_TIMELINE_ARRAY ) / timing_.end; + ++it; + } + + // loop + for (; it != gaps_.end(); ++it) { + + // [s e] section + e = ( (*it).begin * MAX_TIMELINE_ARRAY ) / timing_.end; + + size_t n = e - s; + memcpy( gapsptr, gapsArray_ + s, n * sizeof(float)); + memcpy( fadingptr, fadingArray_ + s, n * sizeof(float)); + + for (size_t i = -5; i > 0; ++i) + gapsptr[ MAX(n+i, 0) ] = 1.f; + + gapsptr += n; + fadingptr += n; + arraysize += n; + + // next section + s = ( (*it).end * MAX_TIMELINE_ARRAY ) / timing_.end; + } + + // close last [s e] section + if (s != MAX_TIMELINE_ARRAY) { + + // [s e] section + e = MAX_TIMELINE_ARRAY; + + size_t n = e - s; + memcpy( gapsptr, gapsArray_ + s, n * sizeof(float)); + memcpy( fadingptr, fadingArray_ + s, n * sizeof(float)); + arraysize += n; + } + + } + else { + + memcpy( gaps, gapsArray_, MAX_TIMELINE_ARRAY * sizeof(float)); + memcpy( fading, fadingArray_, MAX_TIMELINE_ARRAY * sizeof(float)); + } + + return arraysize; +} + + TimeIntervalSet Timeline::sections() const { TimeIntervalSet sec; @@ -200,7 +286,7 @@ void Timeline::clearGaps() gaps_array_need_update_ = true; } -float Timeline::fadingAt(const GstClockTime t) +float Timeline::fadingAt(const GstClockTime t) const { double true_index = (static_cast(MAX_TIMELINE_ARRAY) * static_cast(t)) / static_cast(timing_.end); double previous_index = floor(true_index); @@ -215,8 +301,13 @@ float Timeline::fadingAt(const GstClockTime t) void Timeline::clearFading() { - for(int i=0;i 0 && timing_.is_valid()) { - for(int i=0;i(*source); + if (ms != nullptr) { + GstClockTime d = ((double) ms->mediaplayer()->timeline()->sectionsDuration() / ms->mediaplayer()->playSpeed()); + if ( d > maxduration ) + maxduration = d; + } + } + + + // draw list + ImGui::BeginChild("##v_scroll2", rendersize, false, ImGuiWindowFlags_AlwaysVerticalScrollbar); { ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.f, _v_space)); for (auto source = selection_.begin(); source != selection_.end(); ++source) { ImVec2 framesize(1.5f * _timeline_height * (*source)->frame()->aspectRatio(), 1.5f * _timeline_height); - ImVec2 image_top = ImGui::GetCursorPos(); + + // Thumbnail of source (clic to open) if (SourceButton(*source, framesize)) UserInterface::manager().showSourceEditor(*source); -// ImGui::SetCursorPos(image_top + ImVec2( _h_space, framesize.y)); + // text below thumbnail to show status + const char *icon = ICON_FA_SNOWFLAKE; 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::SetCursorPos(image_top + ImVec2( framesize.x + _h_space, 0)); + icon = (*source)->playing() ? ICON_FA_PLAY : ICON_FA_PAUSE; + ImGui::Text("%s %s", icon, GstToolkit::time_to_string((*source)->playtime()).c_str() ); MediaSource *ms = dynamic_cast(*source); if (ms != nullptr) { + ImGui::SetCursorPos(image_top + ImVec2( framesize.x + _h_space, 0)); MediaPlayer *mp = ms->mediaplayer(); - ImVec2 size(rendersize.x - framesize.x - _h_space - _scrollbar, 2.f * _timeline_height); - bool released = false; - ImGuiToolkit::EditPlotHistoLines("##TimelineArray", - mp->timeline()->gapsArray(), - mp->timeline()->fadingArray(), - MAX_TIMELINE_ARRAY, 0.f, 1.f, true, &released, size); + Timeline *tl = mp->timeline(); + + // cut gaps +// // timeline of MediaSource to the right +// static float gaps[MAX_TIMELINE_ARRAY]; +// static float fade[MAX_TIMELINE_ARRAY]; +// size_t numsteps = tl->fillSectionsArrays(gaps, fade) - 1; + + ImVec2 size(rendersize.x - framesize.x - _h_space - _scrollbar, 1.5f * _timeline_height); + GstClockTime playtime = 0; + +// GstClockTime d = tl->sectionsDuration(); +// size.x = size.x * d / ( maxduration * mp->playSpeed() ); + +// ImGuiToolkit::ShowPlotHistoLines("##TimelineArray2", gaps, fade, numsteps, 0.f, 1.f, size); + + TimeIntervalSet se = tl->sections(); + for (auto section = se.begin(); section != se.end(); ++section) { + + GstClockTime d = section->duration(); + float w = size.x * d / ( maxduration * mp->playSpeed() ); + ImGuiToolkit::Timeline("##timeline2", mp->position(), section->begin, section->end, tl->step(), w); + ImGui::SameLine(0,1); + + playtime += d; + } + +// ImGuiToolkit::Timeline("##timeline2", mp->position(), tl->begin(), tl->end(), tl->step(), size.x); + + // text below timeline to show info + ImGui::SetCursorPos(image_top + ImVec2( framesize.x + _h_space, 1.5f * _timeline_height + _v_space)); + GstClockTime t = (GstClockTime) ( (double) playtime / mp->playSpeed() ); + ImGui::Text("%s play time / %.2f speed = %s (effective duration)", GstToolkit::time_to_string(playtime).c_str(), mp->playSpeed(), GstToolkit::time_to_string(t).c_str()); } - ImGui::Spacing(); + // next line position + ImGui::SetCursorPos(image_top + ImVec2(0, 2.0f * _timeline_height + _v_space)); +// ImGui::Spacing(); } - ImGui::Columns(1); ImGui::PopStyleVar(); } ImGui::EndChild();