From 94fbe58fb737fbd27a656e9db24d134f4cdf0eee Mon Sep 17 00:00:00 2001 From: Bruno Herbelin Date: Sat, 13 Jul 2024 12:32:05 +0200 Subject: [PATCH] New toolbox Timeline editor DRAFT implementation of a new toolbox to apply fade-in and fade-out and to cut the timeline. --- rsc/images/icons.dds | Bin 1638528 -> 1638528 bytes src/ImGuiToolkit.cpp | 439 ++++++----------- src/ImGuiToolkit.h | 9 +- src/MousePointer.h | 2 +- src/ShaderEditWindow.cpp | 3 - src/SourceControlWindow.cpp | 911 ++++++++++++++++++++++++++++++++--- src/SourceControlWindow.h | 2 + src/Timeline.cpp | 331 +++++++++---- src/Timeline.h | 22 +- src/TransitionView.cpp | 4 +- src/UserInterfaceManager.cpp | 7 +- src/defines.h | 3 +- 12 files changed, 1276 insertions(+), 457 deletions(-) diff --git a/rsc/images/icons.dds b/rsc/images/icons.dds index 92c92365aaa0fe7d64f692e3b06d5d9d09d43963..098aad72d45e67d977c195fc1bfb90824427fc3c 100644 GIT binary patch delta 27239 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 UuMqx<;BOTCjn=nKDDK$xKL~;qkpKVy delta 18985 zcmeHv3wTsTvT&byzvoOQJX}ID;VB5oMDT&2nJ6kEkO?cI-!78)K;7Tng!O^`t4>Bi z(5q&~x!U+lvhErmyCzY&N3Xjkcy-0cH6Xh_b`3*7)OD2+1rm}>`d0OsKIcqub-n-o z-}istf6tJaQ>UxCs;jH3tE>Ca7{9a;{;ikRo1QcGzfLz=oqpxI8oA7@6z(%uNXnRf z=J4PBrYpuvzfiXS(Ol{rw}TAUvO^57C*i6m851jF?bNZ)+^g)n)GbNM*nMWNq

G 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 #include #include +#include #include #ifdef _WIN32 @@ -35,6 +36,7 @@ #include "Resource.h" #include "GstToolkit.h" +#include "BaseToolkit.h" #include "SystemToolkit.h" #include "ImGuiToolkit.h" @@ -203,7 +205,7 @@ void ImGuiToolkit::Icon(int i, int j, bool enabled) ImGui::Image((void*)(intptr_t)textureicons, ImVec2(ImGui::GetTextLineHeightWithSpacing(), ImGui::GetTextLineHeightWithSpacing()), uv0, uv1, tint_color); } -bool ImGuiToolkit::ButtonIcon(int i, int j, const char *tooltip, bool expanded) +bool ImGuiToolkit::ButtonIcon(int i, int j, const char *tooltip, bool enabled, bool expanded) { ImGuiContext& g = *GImGui; bool ret = false; @@ -233,9 +235,16 @@ bool ImGuiToolkit::ButtonIcon(int i, int j, const char *tooltip, bool expanded) ImVec2 uv1( uv0.x + 0.05, uv0.y + 0.05 ); ImGui::PushID( i*20 + j); - ret = ImGui::ImageButton((void*)(intptr_t)textureicons, - ImVec2(g.FontSize, g.FontSize), - uv0, uv1, g.Style.FramePadding.y); + if (enabled) + ret = ImGui::ImageButton((void*)(intptr_t)textureicons, + ImVec2(g.FontSize, g.FontSize), + uv0, uv1, g.Style.FramePadding.y); + else + ImGui::ImageButton((void*)(intptr_t)textureicons, + ImVec2(g.FontSize, g.FontSize), + uv0, uv1, g.Style.FramePadding.y, + ImVec4(0.f, 0.f, 0.f, 0.f), + ImVec4(0.6f, 0.6f, 0.6f, 0.8f)); ImGui::PopID(); if (tooltip != nullptr && ImGui::IsItemHovered()) @@ -760,18 +769,18 @@ bool ImGuiToolkit::SliderTiming (const char* label, uint* ms, uint v_min, uint v if (min > 0) { if (milisec>0) - ImFormatString(text_buf, IM_ARRAYSIZE(text_buf), "%d min %02d s %03d ms", min, sec%60, milisec); + ImFormatString(text_buf, IM_ARRAYSIZE(text_buf), "%dmin %02ds %03d ms", min, sec%60, milisec); else - ImFormatString(text_buf, IM_ARRAYSIZE(text_buf), "%d min %02d s", min, sec%60); + ImFormatString(text_buf, IM_ARRAYSIZE(text_buf), "%dmin %02ds", min, sec%60); } else if (sec > 0) { if (milisec>0) - ImFormatString(text_buf, IM_ARRAYSIZE(text_buf), "%d s %03d ms", sec, milisec); + ImFormatString(text_buf, IM_ARRAYSIZE(text_buf), "%ds %03dms", sec, milisec); else - ImFormatString(text_buf, IM_ARRAYSIZE(text_buf), "%d s", sec); + ImFormatString(text_buf, IM_ARRAYSIZE(text_buf), "%ds", sec); } else - ImFormatString(text_buf, IM_ARRAYSIZE(text_buf), "%03d ms", milisec); + ImFormatString(text_buf, IM_ARRAYSIZE(text_buf), "%03dms", milisec); } else { ImFormatString(text_buf, IM_ARRAYSIZE(text_buf), "%s", text_max); @@ -786,6 +795,8 @@ bool ImGuiToolkit::SliderTiming (const char* label, uint* ms, uint v_min, uint v bool ret = ImGui::SliderFloat(label, &val, v_min / v_step, v_max / v_step, text_buf, 2.f); *ms = int(floor(val)) * v_step; + + return ret; } @@ -1279,305 +1290,62 @@ bool ImGuiToolkit::InvisibleSliderFloat (const char* label, float *index, float return value_changed; } -bool ImGuiToolkit::EditPlotLines (const char* label, float *array, int values_count, float values_min, float values_max, const ImVec2 size) +bool HSliderScalar(const char* label, const ImVec2& size, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max) { - bool array_changed = false; - - // get window ImGuiWindow* window = ImGui::GetCurrentWindow(); if (window->SkipItems) return false; - // capture coordinates before any draw or action - ImVec2 canvas_pos = ImGui::GetCursorScreenPos(); - ImVec2 mouse_pos_in_canvas = ImVec2(ImGui::GetIO().MousePos.x - canvas_pos.x, ImGui::GetIO().MousePos.y - canvas_pos.y); - - // 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 false; - - // read user input and activate widget - const bool left_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) - { - const bool focus_requested = ImGui::FocusableItemRegister(window, id); - if (focus_requested || (hovered && left_mouse_press) ) - { - ImGui::SetActiveID(id, window); - ImGui::SetFocusID(id, window); - ImGui::FocusWindow(window); - } - } - else - return false; - - ImVec4* colors = ImGui::GetStyle().Colors; - ImVec4 bg_color = hovered ? colors[ImGuiCol_FrameBgHovered] : colors[ImGuiCol_FrameBg]; - - // enter edit if widget is active - if (ImGui::GetActiveID() == id) { - - static uint previous_index = UINT32_MAX; - bg_color = colors[ImGuiCol_FrameBgActive]; - - // keep active area while mouse is pressed - if (left_mouse_press) - { - float x = (float) values_count * mouse_pos_in_canvas.x / bbox.GetWidth(); - uint index = CLAMP( (int) floor(x), 0, values_count-1); - - float y = mouse_pos_in_canvas.y / bbox.GetHeight(); - y = CLAMP( (y * (values_max-values_min)) + values_min, values_min, values_max); - - - if (previous_index == UINT32_MAX) - previous_index = index; - - array[index] = values_max - y; - for (int i = MIN(previous_index, index); i < MAX(previous_index, index); ++i) - array[i] = values_max - y; - - previous_index = index; - - array_changed = true; - } - // release active widget on mouse release - else { - ImGui::ClearActiveID(); - previous_index = UINT32_MAX; - } - - } - - // back to draw - ImGui::SetCursorScreenPos(canvas_pos); - - // plot lines - char buf[128]; - snprintf(buf, 128, "##Lines%s", label); - - ImGui::PushStyleColor(ImGuiCol_FrameBg, bg_color); - ImGui::PlotLines(buf, array, values_count, 0, NULL, values_min, values_max, size); - ImGui::PopStyleColor(1); - - return array_changed; -} - - -bool ImGuiToolkit::EditPlotHistoLines (const char* label, float *histogram_array, float *lines_array, - int values_count, float values_min, float values_max, guint64 begin, guint64 end, - bool edit_histogram, bool *released, const ImVec2 size) -{ - bool array_changed = false; - - // get window - ImGuiWindow* window = ImGui::GetCurrentWindow(); - if (window->SkipItems) - return false; - - // capture coordinates before any draw or action - const ImVec2 canvas_pos = ImGui::GetCursorScreenPos(); - ImVec2 mouse_pos_in_canvas = ImVec2(ImGui::GetIO().MousePos.x - canvas_pos.x, ImGui::GetIO().MousePos.y - canvas_pos.y); - - // 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 false; - - *released = false; - - // read user input and activate widget - 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) - { - const bool focus_requested = ImGui::FocusableItemRegister(window, id); - if (focus_requested || (hovered && mouse_press) ) - { - ImGui::SetActiveID(id, window); - ImGui::SetFocusID(id, window); - ImGui::FocusWindow(window); - } - } - else - return false; - - const ImGuiContext& g = *GImGui; + ImGuiContext& g = *GImGui; const ImGuiStyle& style = g.Style; - const float _h_space = style.WindowPadding.x; - ImVec4 bg_color = hovered ? style.Colors[ImGuiCol_FrameBgHovered] : style.Colors[ImGuiCol_FrameBg]; - - // prepare index - double x = (mouse_pos_in_canvas.x - _h_space) / (size.x - 2.f * _h_space); - size_t index = CLAMP( (int) floor(static_cast(values_count) * x), 0, values_count); - char cursor_text[64]; - guint64 time = begin + (index * end) / static_cast(values_count); - ImFormatString(cursor_text, IM_ARRAYSIZE(cursor_text), "%s", - GstToolkit::time_to_string(time, GstToolkit::TIME_STRING_MINIMAL).c_str()); - - // enter edit if widget is active - if (ImGui::GetActiveID() == id) { - - bg_color = style.Colors[ImGuiCol_FrameBgActive]; - - // keep active area while mouse is pressed - static bool active = false; - static size_t previous_index = UINT32_MAX; - if (mouse_press) - { - float val = mouse_pos_in_canvas.y / bbox.GetHeight(); - val = CLAMP( (val * (values_max-values_min)) + values_min, values_min, values_max); - - if (previous_index == UINT32_MAX) - previous_index = index; - - const size_t left = MIN(previous_index, index); - const size_t right = MAX(previous_index, index); - - if (edit_histogram){ - static float target_value = values_min; - - // toggle value histo - if (!active) { - target_value = histogram_array[index] > 0.f ? 0.f : 1.f; - active = true; - } - - for (size_t i = left; i < right; ++i) - histogram_array[i] = target_value; - } - else { - const float target_value = values_max - val; - - for (size_t i = left; i < right; ++i) - lines_array[i] = target_value; - - } - - previous_index = index; - array_changed = true; - } - // release active widget on mouse release - else { - active = false; - ImGui::ClearActiveID(); - previous_index = UINT32_MAX; - *released = true; - } - - } - - // back to draw - ImGui::SetCursorScreenPos(canvas_pos); - - // plot histogram (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, values_count, 0, NULL, values_min, values_max, size); - ImGui::PopStyleColor(2); - - ImGui::SetCursorScreenPos(canvas_pos); - - // plot (transparent) lines - ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0,0,0,0)); - snprintf(buf, 128, "##Lines%s", label); - ImGui::PlotLines(buf, lines_array, values_count, 0, NULL, values_min, values_max, size); - ImGui::PopStyleColor(1); - - // draw the cursor - if (hovered) { - // prepare color and text - const ImU32 cur_color = ImGui::GetColorU32(ImGuiCol_CheckMark); - ImGui::PushStyleColor(ImGuiCol_Text, cur_color); - ImVec2 label_size = ImGui::CalcTextSize(cursor_text, NULL); - - // render cursor depending on action - mouse_pos_in_canvas.x = CLAMP(mouse_pos_in_canvas.x, _h_space, size.x - _h_space); - ImVec2 cursor_pos = canvas_pos; - if (edit_histogram) { - 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); - } - else { - cursor_pos = cursor_pos + mouse_pos_in_canvas; - window->DrawList->AddCircleFilled( cursor_pos, 3.f, cur_color, 8); - } - - // draw text - cursor_pos.y = canvas_pos.y + size.y - label_size.y - 1.f; - if (mouse_pos_in_canvas.x + label_size.x < size.x - 2.f * _h_space) - cursor_pos.x += _h_space; - else - cursor_pos.x -= label_size.x + _h_space; - ImGui::RenderTextClipped(cursor_pos, cursor_pos + label_size, cursor_text, NULL, &label_size); - - ImGui::PopStyleColor(1); - } - - 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 ImVec2 label_size = ImGui::CalcTextSize(label, NULL, true); + const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size); + const ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); - const ImGuiContext& g = *GImGui; - const ImGuiStyle& style = g.Style; - ImVec4 bg_color = style.Colors[ImGuiCol_FrameBg]; + ImGui::ItemSize(bb, style.FramePadding.y); + if (!ImGui::ItemAdd(frame_bb, id)) + return false; - // back to draw - ImGui::SetCursorScreenPos(canvas_pos); + const bool hovered = ImGui::ItemHoverable(frame_bb, id); + if ((hovered && g.IO.MouseClicked[0]) || g.NavActivateId == id || g.NavInputId == id) + { + ImGui::SetActiveID(id, window); + ImGui::SetFocusID(id, window); + ImGui::FocusWindow(window); + g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down); + } - // plot transparent histogram - ImGui::PushStyleColor(ImGuiCol_FrameBg, bg_color); - ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0, 0, 0, 250)); - char buf[128]; - snprintf(buf, 128, "##Histo%s", label); - ImGui::PlotHistogram(buf, histogram_array, values_count, 0, NULL, values_min, values_max, size); - ImGui::PopStyleColor(2); + // Draw frame + const ImU32 frame_col = ImGui::GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); + ImGui::RenderNavHighlight(frame_bb, id); + ImGui::RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding); - ImGui::SetCursorScreenPos(canvas_pos); + // Slider behavior + ImRect grab_bb; + const bool value_changed = ImGui::SliderBehavior(frame_bb, id, data_type, p_data, p_min, p_max, "", 1.f, ImGuiSliderFlags_None, &grab_bb); + if (value_changed) + ImGui::MarkItemEdited(id); - // plot lines - ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0,0,0,0)); - snprintf(buf, 128, "##Lines%s", label); - ImGui::PlotLines(buf, lines_array, values_count, 0, NULL, values_min, values_max, size); - ImGui::PopStyleColor(1); + // Render grab + if (grab_bb.Max.y > grab_bb.Min.y) + window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, ImGui::GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding); + return value_changed; } +bool ImGuiToolkit::HSliderInt(const char *id, const ImVec2 &size, int *v, int v_min, int v_max) +{ + return HSliderScalar(id, size, ImGuiDataType_S32, v, &v_min, &v_max); +} + +bool ImGuiToolkit::HSliderUInt64(const char *id, const ImVec2 &size, guint64 *v, guint64 v_min, guint64 v_max) +{ + return HSliderScalar(id, size, ImGuiDataType_U64, v, &v_min, &v_max); +} + + void ImGuiToolkit::ValueBar(float fraction, const ImVec2& size_arg) { ImGuiWindow* window = ImGui::GetCurrentWindow(); @@ -1909,6 +1677,93 @@ void word_wrap(std::string *str, unsigned per_line) +bool ImGuiToolkit::InputTime(const char *label, guint64 *time, ImGuiInputTextFlags flag) +{ + bool changed = false; + + // filtering for reading MM:SS.MS text entry + static bool valid = false; + static std::regex RegExTime("([0-9]+\\:)?([0-9]+\\:)?([0-5][0-9]|[0-9])((\\.|\\,)[0-9]+)?"); + struct TextFilters + { + static int FilterTime(ImGuiInputTextCallbackData *data) + { + if (data->EventChar < 256 && strchr("0123456789.,:", (char) data->EventChar)) + return 0; + return 1; + } + }; + + // convert gst time to hh mm s.ms + guint64 ms = GST_TIME_AS_MSECONDS(*time); + guint64 hh = ms / 3600000; + guint64 mm = (ms % 3600000) / 60000; + ms -= (hh * 3600000 + mm * 60000); + float sec = (float) (ms) / 1000.f; + + // write time into string + char buf_time_input[64] = ""; + snprintf(buf_time_input, 64, "%02lu:%02lu:%04.1f", (unsigned long) hh, (unsigned long) mm, sec); + + // draw input text field + const ImGuiContext &g = *GImGui; + ImGui::PushStyleColor(ImGuiCol_Text, + flag & ImGuiInputTextFlags_ReadOnly + ? g.Style.Colors[ImGuiCol_TextDisabled] + : ImVec4(1.0f, valid ? 1.0f : 0.2f, valid ? 1.0f : 0.2f, 1.f)); + ImGui::PushStyleColor(ImGuiCol_FrameBg, + flag & ImGuiInputTextFlags_ReadOnly ? g.Style.Colors[ImGuiCol_WindowBg] + : g.Style.Colors[ImGuiCol_FrameBg]); + ImGuiToolkit::PushFont(FONT_MONO); + ImGui::InputText(label, + buf_time_input, + 64, + ImGuiInputTextFlags_CallbackCharFilter | flag, + TextFilters::FilterTime); + ImGui::PopFont(); + ImGui::PopStyleColor(2); + + // test string format with regular expression + valid = std::regex_match(buf_time_input, RegExTime); + + if (ImGui::IsItemDeactivatedAfterEdit()) { + if (valid) { + ms = 0; + sec = 0.f; + // user confirmed the entry and the input is valid + // split the "HH:MM:SS.ms" string in HH MM SS.ms + std::string timing(buf_time_input); + std::size_t found = timing.find_last_of(':'); + // read the right part SS.ms as a value + if (std::string::npos != found + && BaseToolkit::is_a_value(timing.substr(found + 1), &sec)) { + ms = (guint64) (sec * 1000.f); + // read right part MM as a number + timing = timing.substr(0, found); + found = timing.find_last_of(':'); + int min = 0; + if (std::string::npos != found + && BaseToolkit::is_a_number(timing.substr(found + 1), &min)) { + ms += 60000 * (guint64) min; + // read right part HH as a number + timing = timing.substr(0, found); + int hour = 0; + if (std::string::npos != found && BaseToolkit::is_a_number(timing, &hour)) { + ms += 3600000 * (guint64) hour; + } + } + } + // set time + *time = GST_MSECOND * ms; + changed = true; + } + // force to test validity next frame + valid = false; + } + + return changed; +} + static int InputTextCallback(ImGuiInputTextCallbackData* data) { std::string* str = static_cast(data->UserData); diff --git a/src/ImGuiToolkit.h b/src/ImGuiToolkit.h index e626e92..ad62ce9 100644 --- a/src/ImGuiToolkit.h +++ b/src/ImGuiToolkit.h @@ -25,7 +25,7 @@ namespace ImGuiToolkit void ShowIconsWindow(bool* p_open); // buttons and gui items with icon - bool ButtonIcon (int i, int j, const char* tooltip = nullptr, bool expanded = false); + bool ButtonIcon (int i, int j, const char* tooltip = nullptr, bool enabled = true, bool expanded = false); bool ButtonIconToggle (int i, int j, bool* toggle, const char *tooltip = nullptr); bool ButtonIconMultistate (std::vector > icons, int* state, std::vector tooltips); bool BeginMenuIcon(int i, int j, const char *label, bool enabled = true); @@ -55,10 +55,8 @@ namespace ImGuiToolkit void RenderTimelineBPM (ImVec2 min_bbox, ImVec2 max_bbox, double tempo, double quantum, guint64 begin, guint64 end, guint64 step, bool verticalflip = false); bool InvisibleSliderInt(const char* label, uint *index, uint min, uint max, const ImVec2 size); bool InvisibleSliderFloat(const char* label, float *index, float min, float 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, guint64 begin, guint64 end, 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); - + bool HSliderInt(const char *id, const ImVec2 &size, int *v, int v_min, int v_max); + bool HSliderUInt64(const char *id, const ImVec2 &size, guint64 *v, guint64 v_min, guint64 v_max); void ValueBar(float fraction, const ImVec2& size_arg); // fonts from resources 'fonts/' @@ -75,6 +73,7 @@ namespace ImGuiToolkit void Spacing(); // text input + bool InputTime(const char *label, guint64 *time, ImGuiInputTextFlags flag = 0); bool InputText(const char* label, std::string* str, ImGuiInputTextFlags flag = ImGuiInputTextFlags_CharsNoBlank); bool InputTextMultiline(const char* label, std::string* str, const ImVec2& size = ImVec2(0, 0), int *numline = NULL); void TextMultiline(const char* label, const std::string &str, float width); diff --git a/src/MousePointer.h b/src/MousePointer.h index 121deca..51afd48 100644 --- a/src/MousePointer.h +++ b/src/MousePointer.h @@ -11,7 +11,7 @@ #define ICON_POINTER_SPRING 13, 9 #define ICON_POINTER_LINEAR 14, 9 #define ICON_POINTER_GRID 15, 9 -#define ICON_POINTER_WIGGLY 10, 3 +#define ICON_POINTER_WIGGLY 16, 9 #define ICON_POINTER_METRONOME 6, 13 /// diff --git a/src/ShaderEditWindow.cpp b/src/ShaderEditWindow.cpp index 11b3961..291acb2 100644 --- a/src/ShaderEditWindow.cpp +++ b/src/ShaderEditWindow.cpp @@ -363,9 +363,6 @@ void ShaderEditWindow::Render() } // cancel edit clone else { - // possibility that source was removed - g_printerr("cancel edit clone %ld\n", current_); - // disable editor _editor.SetReadOnly(true); _editor.SetColorizerEnable(false); diff --git a/src/SourceControlWindow.cpp b/src/SourceControlWindow.cpp index c9c5dda..4f47e14 100644 --- a/src/SourceControlWindow.cpp +++ b/src/SourceControlWindow.cpp @@ -33,10 +33,8 @@ #include "DialogToolkit.h" #include "GstToolkit.h" #include "Resource.h" -#include "PatternSource.h" #include "Mixer.h" -#include "CloneSource.h" #include "MediaSource.h" #include "StreamSource.h" #include "MediaPlayer.h" @@ -48,6 +46,44 @@ #define CHECKER_RESOLUTION 6000 class Stream *checker_background_ = new Stream; +typedef struct payload +{ + enum Action { + FADE_IN, + FADE_OUT, + FADE_IN_OUT, + FADE_OUT_IN, + CUT, + CUT_MERGE, + CUT_ERASE + }; + Action action; + guint64 drop_time; + guint64 timing; + int argument; + + payload() + { + action = FADE_IN; + drop_time = GST_CLOCK_TIME_NONE; + timing = GST_CLOCK_TIME_NONE; + argument = 0; + } + + payload(payload const &pl) + : action(pl.action) + , drop_time(pl.drop_time) + , timing(pl.timing) + , argument(pl.argument){ + } + + payload(Action a, guint64 t, int arg) + : action(a), timing(t), argument(arg) + { + drop_time = GST_CLOCK_TIME_NONE; + } + +} TimelinePayload; SourceControlWindow::SourceControlWindow() : WorkspaceWindow("SourceController"), min_width_(0.f), h_space_(0.f), v_space_(0.f), scrollbar_(0.f), @@ -57,7 +93,7 @@ SourceControlWindow::SourceControlWindow() : WorkspaceWindow("SourceController") selection_context_menu_(false), selection_mediaplayer_(nullptr), selection_target_slower_(0), selection_target_faster_(0), mediaplayer_active_(nullptr), mediaplayer_edit_fading_(false), mediaplayer_set_duration_(0), mediaplayer_edit_pipeline_(false), mediaplayer_mode_(false), mediaplayer_slider_pressed_(false), mediaplayer_timeline_zoom_(1.f), - magnifying_glass(false) + mediaplayer_edit_panel_(false), magnifying_glass(false) { info_.setExtendedStringMode(); @@ -437,8 +473,8 @@ void SourceControlWindow::Render() _alpha_fading ? MediaPlayer::FADING_ALPHA : MediaPlayer::FADING_COLOR); } - if (ImGui::MenuItem(LABEL_EDIT_FADING)) - mediaplayer_edit_fading_ = true; + // if (ImGui::MenuItem(LABEL_EDIT_FADING)) + // mediaplayer_edit_fading_ = true; if (ImGui::BeginMenu(ICON_FA_CLOCK " Metronome")) { @@ -526,6 +562,445 @@ void DrawTimeScale(const char* label, guint64 duration, double width_ratio) } + + +bool EditPlotHistoLines (const char* label, float *histogram_array, float *lines_array, + int values_count, float values_min, float values_max, guint64 begin, guint64 end, + bool edit_histogram, bool *released, TimelinePayload **payload, const ImVec2 size) +{ + bool array_changed = false; + + // get window + ImGuiWindow* window = ImGui::GetCurrentWindow(); + if (window->SkipItems) + return false; + + // capture coordinates before any draw or action + const ImVec2 canvas_pos = ImGui::GetCursorScreenPos(); + ImVec2 mouse_pos_in_canvas = ImVec2(ImGui::GetIO().MousePos.x - canvas_pos.x, ImGui::GetIO().MousePos.y - canvas_pos.y); + + // 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 false; + + *released = false; + + // read user input and activate widget + const bool mouse_press = ImGui::IsMouseDown(ImGuiMouseButton_Left); + bool hovered = ImGui::ItemHoverable(bbox, id); + bool temp_input_is_active = ImGui::TempInputIsActive(id); + if (!temp_input_is_active) + { + const bool focus_requested = ImGui::FocusableItemRegister(window, id); + if (focus_requested || (hovered && mouse_press) ) + { + ImGui::SetActiveID(id, window); + ImGui::SetFocusID(id, window); + ImGui::FocusWindow(window); + } + } + else + return false; + + const ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const float _h_space = style.WindowPadding.x; + ImVec4 bg_color = hovered ? style.Colors[ImGuiCol_FrameBgHovered] : style.Colors[ImGuiCol_FrameBg]; + + // prepare index + double x = (mouse_pos_in_canvas.x - _h_space) / (size.x - 2.f * _h_space); + size_t index = CLAMP( (int) floor(static_cast(values_count) * x), 0, values_count); + char cursor_text[64]; + guint64 time = begin + (index * end) / static_cast(values_count); + + // enter edit if widget is active + if (ImGui::GetActiveID() == id) { + + bg_color = style.Colors[ImGuiCol_FrameBgActive]; + + // keep active area while mouse is pressed + static bool active = false; + static size_t previous_index = UINT32_MAX; + if (mouse_press) + { + float val = mouse_pos_in_canvas.y / bbox.GetHeight(); + val = CLAMP( (val * (values_max-values_min)) + values_min, values_min, values_max); + + if (previous_index == UINT32_MAX) + previous_index = index; + + const size_t left = MIN(previous_index, index); + const size_t right = MAX(previous_index, index); + + if (edit_histogram){ + static float target_value = values_min; + + // toggle value histo + if (!active) { + target_value = histogram_array[index] > 0.f ? 0.f : 1.f; + active = true; + } + + for (size_t i = left; i < right; ++i) + histogram_array[i] = target_value; + } + else { + const float target_value = values_max - val; + + for (size_t i = left; i < right; ++i) + lines_array[i] = target_value; + + } + + previous_index = index; + array_changed = true; + } + // release active widget on mouse release + else { + active = false; + ImGui::ClearActiveID(); + previous_index = UINT32_MAX; + *released = true; + } + + } + + // accept drops on timeline plot-histogram + else if (ImGui::BeginDragDropTarget()) { + const ImGuiPayload *tmp = ImGui::GetDragDropPayload(); + if (tmp && tmp->IsDataType("DND_TIMELINE")) { + TimelinePayload *pl = (TimelinePayload *) tmp->Data; + if (pl->action != TimelinePayload::CUT + && pl->action != TimelinePayload::CUT_ERASE) { + hovered = true; + edit_histogram = true; + } + } + const ImGuiPayload *accepted = ImGui::AcceptDragDropPayload("DND_TIMELINE"); + if (accepted) { + *payload = (TimelinePayload *) accepted->Data; + (*payload)->drop_time = time; + } + ImGui::EndDragDropTarget(); + } + + // back to draw + ImGui::SetCursorScreenPos(canvas_pos); + + // plot histogram (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, values_count, 0, NULL, values_min, values_max, size); + ImGui::PopStyleColor(2); + + // back to draw + ImGui::SetCursorScreenPos(canvas_pos); + + // plot (transparent) lines + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0,0,0,0)); + snprintf(buf, 128, "##Lines%s", label); + ImGui::PlotLines(buf, lines_array, values_count, 0, NULL, values_min, values_max, size); + ImGui::PopStyleColor(1); + + + // draw the cursor + if (hovered) { + + ImFormatString(cursor_text, IM_ARRAYSIZE(cursor_text), "%s", + GstToolkit::time_to_string(time).c_str()); + + // prepare color and text + const ImU32 cur_color = ImGui::GetColorU32(ImGuiCol_CheckMark); + ImGui::PushStyleColor(ImGuiCol_Text, cur_color); + ImVec2 label_size = ImGui::CalcTextSize(cursor_text, NULL); + + // render cursor depending on action + mouse_pos_in_canvas.x = CLAMP(mouse_pos_in_canvas.x, _h_space, size.x - _h_space); + ImVec2 cursor_pos = canvas_pos; + if (edit_histogram) { + 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); + } + else { + cursor_pos = cursor_pos + mouse_pos_in_canvas; + window->DrawList->AddCircleFilled( cursor_pos, 3.f, cur_color, 8); + } + + // draw text + cursor_pos.y = canvas_pos.y + size.y - label_size.y - 1.f; + if (mouse_pos_in_canvas.x > label_size.x * 1.5f + 2.f * _h_space) + cursor_pos.x -= label_size.x + _h_space; + else + cursor_pos.x += _h_space + style.WindowPadding.x; + ImGui::RenderTextClipped(cursor_pos, cursor_pos + label_size, cursor_text, NULL, &label_size); + + ImGui::PopStyleColor(1); + } + + return array_changed; +} + + + +bool EditTimeline(const char *label, + Timeline *tl, + bool edit_histogram, + bool *released, + const ImVec2 size) +{ + const float values_min = 0.f; + const float values_max = 1.f; + const guint64 begin = tl->begin(); + const guint64 end = tl->end(); + + bool cursor_dot = !edit_histogram; + Timeline _tl; + bool array_changed = false; + float *lines_array = tl->fadingArray(); + float *histogram_array = tl->gapsArray(); + + // get window + ImGuiWindow* window = ImGui::GetCurrentWindow(); + if (window->SkipItems) + return false; + + // capture coordinates before any draw or action + const ImVec2 canvas_pos = ImGui::GetCursorScreenPos(); + ImVec2 mouse_pos_in_canvas = ImVec2(ImGui::GetIO().MousePos.x - canvas_pos.x, ImGui::GetIO().MousePos.y - canvas_pos.y); + + // 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 false; + + *released = false; + + // read user input and activate widget + const bool mouse_press = ImGui::IsMouseDown(ImGuiMouseButton_Left); + bool hovered = ImGui::ItemHoverable(bbox, id); + bool temp_input_is_active = ImGui::TempInputIsActive(id); + if (!temp_input_is_active) + { + const bool focus_requested = ImGui::FocusableItemRegister(window, id); + if (focus_requested || (hovered && mouse_press) ) + { + ImGui::SetActiveID(id, window); + ImGui::SetFocusID(id, window); + ImGui::FocusWindow(window); + } + } + else + return false; + + const ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const float _h_space = style.WindowPadding.x; + ImVec4 bg_color = hovered ? style.Colors[ImGuiCol_FrameBgHovered] : style.Colors[ImGuiCol_FrameBg]; + + // prepare index + double x = (mouse_pos_in_canvas.x - _h_space) / (size.x - 2.f * _h_space); + 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); + + // enter edit if widget is active + if (ImGui::GetActiveID() == id) { + + bg_color = style.Colors[ImGuiCol_FrameBgActive]; + + // keep active area while mouse is pressed + static bool active = false; + static size_t previous_index = UINT32_MAX; + if (mouse_press) + { + float val = mouse_pos_in_canvas.y / bbox.GetHeight(); + val = CLAMP( (val * (values_max-values_min)) + values_min, values_min, values_max); + + if (previous_index == UINT32_MAX) + previous_index = index; + + const size_t left = MIN(previous_index, index); + const size_t right = MAX(previous_index, index); + + if (edit_histogram){ + static float target_value = values_min; + + // toggle value histo + if (!active) { + target_value = histogram_array[index] > 0.f ? 0.f : 1.f; + active = true; + } + + for (size_t i = left; i < right; ++i) + histogram_array[i] = target_value; + } + else { + const float target_value = values_max - val; + + for (size_t i = left; i < right; ++i) + lines_array[i] = target_value; + + } + + previous_index = index; + array_changed = true; + } + // release active widget on mouse release + else { + active = false; + ImGui::ClearActiveID(); + previous_index = UINT32_MAX; + *released = true; + } + + } + + // accept drops on timeline plot-histogram + else if (ImGui::BeginDragDropTarget()) { + const ImGuiPayload *tmp = ImGui::GetDragDropPayload(); + if (tmp && tmp->IsDataType("DND_TIMELINE") && tmp->Data != nullptr) { + + // get payload data (tested above) + TimelinePayload *pl = (TimelinePayload *) tmp->Data; + + // fake mouse hovered + hovered = true; + cursor_dot = false; + + // if a timing is given, ignore cursor timing + if (pl->timing != GST_CLOCK_TIME_NONE) { + // replace drop time by payload timing + // time = pl->timing; + // // replace mouse coordinate by position from time + // size_t index = ((pl->timing - begin) * static_cast(MAX_TIMELINE_ARRAY)) / end ; + // double x = static_cast(index) / static_cast(MAX_TIMELINE_ARRAY); + // mouse_pos_in_canvas.x = ( x * (size.x - 2.f * _h_space)) + _h_space; + + } + + // dragged onto the timeline : apply changes on local copy + switch (pl->action) { + case TimelinePayload::CUT: + _tl = *tl; + _tl.cut(time, (bool) pl->argument); + histogram_array = _tl.gapsArray(); + break; + case TimelinePayload::CUT_MERGE: + _tl = *tl; + _tl.mergeGapstAt(time); + histogram_array = _tl.gapsArray(); + break; + case TimelinePayload::CUT_ERASE: + _tl = *tl; + _tl.removeGaptAt(time); + histogram_array = _tl.gapsArray(); + break; + case TimelinePayload::FADE_IN: + _tl = *tl; + _tl.fadeIn(time, pl->timing, (Timeline::FadingCurve) pl->argument); + lines_array = _tl.fadingArray(); + break; + case TimelinePayload::FADE_OUT: + _tl = *tl; + _tl.fadeOut(time, pl->timing, (Timeline::FadingCurve) pl->argument); + lines_array = _tl.fadingArray(); + break; + case TimelinePayload::FADE_IN_OUT: + _tl = *tl; + _tl.fadeInOutRange(time, pl->timing, true, (Timeline::FadingCurve) pl->argument); + lines_array = _tl.fadingArray(); + break; + case TimelinePayload::FADE_OUT_IN: + _tl = *tl; + _tl.fadeInOutRange(time, pl->timing, false, (Timeline::FadingCurve) pl->argument); + lines_array = _tl.fadingArray(); + break; + default: + break; + } + } + + // dropped into the timeline : confirm change to timeline + if (ImGui::AcceptDragDropPayload("DND_TIMELINE")) { + // copy temporary timeline into given timeline + *tl = _tl; + // like a mouse release + *released = true; + } + ImGui::EndDragDropTarget(); + } + + // back to draw + ImGui::SetCursorScreenPos(canvas_pos); + + // plot histogram (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::PopStyleColor(2); + + // back to draw + ImGui::SetCursorScreenPos(canvas_pos); + + // plot lines (transparent background) + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0,0,0,0)); + snprintf(buf, 128, "##Lines%s", label); + ImGui::PlotLines(buf, lines_array, MAX_TIMELINE_ARRAY, 0, NULL, values_min, values_max, size); + ImGui::PopStyleColor(1); + + + // draw the cursor + if (hovered) { + + ImFormatString(cursor_text, IM_ARRAYSIZE(cursor_text), "%s", + GstToolkit::time_to_string(time).c_str()); + + // prepare color and text + const ImU32 cur_color = ImGui::GetColorU32(ImGuiCol_CheckMark); + ImGui::PushStyleColor(ImGuiCol_Text, cur_color); + ImVec2 label_size = ImGui::CalcTextSize(cursor_text, NULL); + + // render cursor depending on action + mouse_pos_in_canvas.x = CLAMP(mouse_pos_in_canvas.x, _h_space, size.x - _h_space); + ImVec2 cursor_pos = canvas_pos; + if (cursor_dot) { + cursor_pos = cursor_pos + mouse_pos_in_canvas; + window->DrawList->AddCircleFilled( cursor_pos, 3.f, cur_color, 8); + } + 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); + } + + // draw text + cursor_pos.y = canvas_pos.y + size.y - label_size.y - 1.f; + if (mouse_pos_in_canvas.x > label_size.x * 1.5f + 2.f * _h_space) + cursor_pos.x -= label_size.x + _h_space; + else + cursor_pos.x += _h_space + style.WindowPadding.x; + ImGui::RenderTextClipped(cursor_pos, cursor_pos + label_size, cursor_text, NULL, &label_size); + + ImGui::PopStyleColor(1); + } + + return array_changed; +} + std::list< std::pair > DrawTimeline(const char* label, Timeline *timeline, guint64 time, double width_ratio, float height) { @@ -596,7 +1071,7 @@ std::list< std::pair > DrawTimeline(const char* label, Timeline // adjust bbox of section and render a timeline ImRect section_bbox(section_bbox_min, section_bbox_max); // render the timeline - ImGuiToolkit::RenderTimeline(section_bbox_min, section_bbox_max, section->begin, section->end, timeline->step()); + ImGuiToolkit::RenderTimeline(section_bbox_min, section_bbox_max, section->begin, section->end, timeline->step()); // draw the cursor float time_ = static_cast ( static_cast(time - section->begin) / static_cast(section->duration()) ); @@ -1487,7 +1962,7 @@ void SourceControlWindow::RenderSingleSource(Source *s) width = buttons_width_ - ImGui::GetTextLineHeightWithSpacing(); ImGui::SameLine(0, space -width); ImGui::SetNextItemWidth(width); - if (ImGuiToolkit::ButtonIcon( 0, 14, LABEL_ADD_TIMELINE, space > buttons_width_ )) { + if (ImGuiToolkit::ButtonIcon( 0, 14, LABEL_ADD_TIMELINE, true, space > buttons_width_ )) { // activate mediaplayer mediaplayer_active_ = ms->mediaplayer(); @@ -1550,6 +2025,22 @@ void SourceControlWindow::RenderSingleSource(Source *s) } } + +void DragButtonIcon(int i, int j, const char *tooltip, TimelinePayload payload) +{ + + + ImGuiToolkit::ButtonIcon(i, j, tooltip); + if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) { + // _payload.action = TimelinePayload::FADE_OUT_IN; + // _payload.timing = d * GST_MSECOND; + // _payload.argument = Timeline::FADING_SMOOTH; + ImGui::SetDragDropPayload("DND_TIMELINE", &payload, sizeof(TimelinePayload)); + ImGuiToolkit::Icon(i, j); + ImGui::EndDragDropSource(); + } +} + void SourceControlWindow::RenderMediaPlayer(MediaSource *ms) { static bool show_overlay_info = false; @@ -1693,26 +2184,25 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms) if (tl->is_valid()) { bool released = false; - if ( ImGuiToolkit::EditPlotHistoLines("##TimelineArray", tl->gapsArray(), tl->fadingArray(), - MAX_TIMELINE_ARRAY, 0.f, 1.f, tl->begin(), tl->end(), - Settings::application.widget.media_player_timeline_editmode, &released, size) ) { + if ( EditTimeline("##TimelineArray", tl, + Settings::application.widget.media_player_timeline_editmode, + &released, size) ) { tl->update(); } + // end of mouse down to draw timeline else if (released) { tl->refresh(); if (Settings::application.widget.media_player_timeline_editmode) oss << ": Timeline cut"; else - oss << ": Timeline opacity"; + oss << ": Timeline fading"; Action::manager().store(oss.str()); } - // custom timeline slider // TODO : if (mediaplayer_active_->syncToMetronome() > Metronome::SYNC_NONE) mediaplayer_slider_pressed_ = ImGuiToolkit::TimelineSlider("##timeline", &seek_t, tl->begin(), tl->first(), tl->end(), tl->step(), size.x); - } } ImGui::EndChild(); @@ -1721,51 +2211,13 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms) bottom += ImVec2(scrollwindow.x + 2.f, 0.f); draw_list->AddRectFilled(bottom, bottom + ImVec2(slider_zoom_width, timeline_height_ -1.f), ImGui::GetColorU32(ImGuiCol_FrameBg)); ImGui::SetCursorScreenPos(bottom + ImVec2(1.f, 0.f)); - const char *tooltip[2] = {"Fading draw tool", "Timeline cut tool"}; - ImGuiToolkit::IconToggle(7,4,8,3, &Settings::application.widget.media_player_timeline_editmode, tooltip); - + if (!mediaplayer_edit_panel_ && ImGuiToolkit::IconButton(11, 0, "Open panel")) + mediaplayer_edit_panel_ = true; + if (mediaplayer_edit_panel_ && ImGuiToolkit::IconButton(10, 0, "Close panel")) + mediaplayer_edit_panel_ = false; ImGui::SetCursorScreenPos(bottom + ImVec2(1.f, 0.5f * timeline_height_)); - if (Settings::application.widget.media_player_timeline_editmode) { - // action cut - if (mediaplayer_active_->isPlaying()) { - ImGuiToolkit::Indication("Pause to enable cut at cursor", 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 (mediaplayer_active_->timeline()->cut(mediaplayer_active_->position(), true, false)) { - oss << ": Timeline cut"; - Action::manager().store(oss.str()); - } - } - if (ImGuiToolkit::MenuItemIcon(2,0,"Cut right")){ - if (mediaplayer_active_->timeline()->cut(mediaplayer_active_->position(), false, false)){ - oss << ": Timeline cut"; - Action::manager().store(oss.str()); - } - } - ImGui::EndPopup(); - } - } - else { - static int _actionsmooth = 0; - - // action smooth - ImGui::PushButtonRepeat(true); - if (ImGuiToolkit::IconButton(13, 12, "Smooth fading curve")){ - mediaplayer_active_->timeline()->smoothFading( 5 ); - ++_actionsmooth; - } - ImGui::PopButtonRepeat(); - - if (_actionsmooth > 0 && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) { - oss << ": Timeline opacity smooth"; - Action::manager().store(oss.str()); - _actionsmooth = 0; - } - } + const char *tooltip[2] = {"Fading tool", "Cutting tool"}; + ImGuiToolkit::IconToggle(7,4,8,3, &Settings::application.widget.media_player_timeline_editmode, tooltip); // zoom slider ImGui::SetCursorScreenPos(bottom + ImVec2(0.f, timeline_height_)); @@ -1931,7 +2383,346 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms) ImGui::EndPopup(); } + + /// + /// Window area to edit gaps or fading + /// + if (mediaplayer_edit_panel_) { + const ImVec2 gap_dialog_size(buttons_width_ * 3.0f, buttons_height_ * 1.42); + const ImVec2 gap_dialog_pos = rendersize + ImVec2(h_space_, buttons_height_) - gap_dialog_size; + + ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::GetStyle().Colors[ImGuiCol_PopupBg] ); + ImGui::SetCursorPos(gap_dialog_pos); + ImGui::BeginChild("##EditGapsWindow", + gap_dialog_size, true, + ImGuiWindowFlags_NoScrollbar); + + // variables state of panel + static int current_curve = 2; + static uint d = UINT_MAX; + static guint64 target_time = 30000000000; + + // timeline to edit + Timeline *tl = mediaplayer_active_->timeline(); + + /// + /// PANEL FOR CUT TIMELINE MODE + /// + ImGui::Spacing(); + if (Settings::application.widget.media_player_timeline_editmode) { + + // PANEL WITH LARGE FONTS + ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE); + + /// + /// CUT LEFT OF CURSOR + /// + DragButtonIcon(9, 3, "Drop in timeline to\nCut left", + TimelinePayload(TimelinePayload::CUT, 0, 1) ); + /// + /// CUT RIGHT OF CURSOR + /// + ImGui::SameLine(0, IMGUI_SAME_LINE); + DragButtonIcon(10, 3, "Drop in timeline to\nCut right", + TimelinePayload(TimelinePayload::CUT, 0, 0) ); + + if (tl->numGaps() > 0) { + /// + /// MERGE CUTS AT CURSOR + /// + ImGui::SameLine(0, IMGUI_SAME_LINE); + DragButtonIcon(19, 3, "Drop in timeline to\nMerge right", + TimelinePayload(TimelinePayload::CUT_MERGE, 0, 0) ); + /// + /// ERASE CUT AT CURSOR + /// + ImGui::SameLine(0, IMGUI_SAME_LINE); + DragButtonIcon(0, 4, "Drop in timeline to\nErase right", + TimelinePayload(TimelinePayload::CUT_ERASE, 0, 0) ); + } + else { + /// + /// DISABLED ICONS IF NO GAP + /// + ImGui::SameLine(0, IMGUI_SAME_LINE); + ImGuiToolkit::ButtonIcon(19, 3, "No gap to merge", false); + ImGui::SameLine(0, IMGUI_SAME_LINE); + ImGuiToolkit::ButtonIcon(0, 4, "No gap to erase", false); + } + + /// + /// SEPARATOR + /// + ImGui::SameLine(0, 0); + ImGui::Text("|"); + + /// + /// Enter time or percentage of time + /// + target_time = MIN(target_time, tl->duration()); + + ImGui::SameLine(0, 0); + ImVec2 draw_pos = ImGui::GetCursorPos(); + ImGui::SetNextItemWidth(180); // TODO VARIABLE WIDTH + ImGuiToolkit::InputTime("##Time", &target_time); + + ImGui::SetCursorPos(ImVec2(draw_pos.x, draw_pos.y + 35)); + ImGuiToolkit::HSliderUInt64("#SliderTime", ImVec2(180, 14), &target_time, 0, tl->duration()); + + // static int p = 5; + // ImGuiToolkit::HSliderInt("#toto", ImVec2(140, 14), &p, 1, 9); + // if (ImGui::IsItemActive()) { + // ImGuiToolkit::PushFont(ImGuiToolkit::FONT_DEFAULT); + // ImGui::SetTooltip("%d%%", p * 10); + // ImGui::PopFont(); + // } + // if (ImGui::IsItemDeactivatedAfterEdit()){ + + // } + + // ImGui::SameLine(0, IMGUI_SAME_LINE); + // ImVec2 draw_pos = ImGui::GetCursorPos() + ImVec2(0, v_space_); + // ImGui::SetCursorPos(ImVec2(gap_dialog_size.x - ImGui::GetTextLineHeightWithSpacing()- IMGUI_SAME_LINE, + // draw_pos.y)); + // static bool percentage = false; + // ImGuiToolkit::IconToggle(19, 10, 3, 7, &percentage); + // ImGui::SetCursorPos(draw_pos); + // if (percentage) { + // int target_percent = (int) ( (100 * target_time) / tl->duration() ); + // ImGui::SetNextItemWidth(-ImGui::GetTextLineHeightWithSpacing()); + // ImGui::DragInt("##percent", &target_percent, 1, 0, 100); + // target_time = (tl->duration() * (guint64) target_percent) / 100; + // } + // else { + // ImGui::SetNextItemWidth(-ImGui::GetTextLineHeightWithSpacing()); + // ImGuiToolkit::InputTime("##Time", &target_time); + // } + + // Action buttons + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); + + ImGui::SetCursorPos( + ImVec2(gap_dialog_size.x - 4.f * ImGui::GetTextLineHeightWithSpacing() - IMGUI_SAME_LINE, + draw_pos.y)); + + // ImGui::SameLine(0, IMGUI_SAME_LINE); + if (ImGuiToolkit::ButtonIcon(17, 3, "Cut left at given time")) { + tl->cut(target_time, true); + tl->refresh(); + } + + ImGui::SameLine(0, IMGUI_SAME_LINE); + if (ImGuiToolkit::ButtonIcon(18, 3, "Cut right at given time")){ + tl->cut(target_time, false); + tl->refresh(); + } + + ImGui::SameLine(0, IMGUI_SAME_LINE); + if (ImGuiToolkit::ButtonIcon(11, 14, "Clear all gaps")) + tl->clearGaps(); + + ImGui::PopStyleColor(); + + // end icons + ImGui::PopFont(); + } + /// + /// FADING + /// + else { + // Icons for Drag & Drop + ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE); + + /// + /// CURVE MODE + /// + const std::vector tooltips_curve = {{"Fading Sharp"}, + {"Fading Linear"}, + {"Fading Smooth"}}; + const std::vector > options_curve + = {{12, 12}, + {13, 12}, + {14, 12}}; + + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); + // ImGuiToolkit::ButtonIconMultistate(options_curve, ¤t_curve, tooltips_curve); + ImGuiToolkit::IconMultistate(options_curve, ¤t_curve, tooltips_curve); + + ImGui::PopStyleColor(); + + /// + /// FADING SHARP + /// + if (current_curve == 0) { + /// + /// FADE IN + /// + ImGui::SameLine(0, IMGUI_SAME_LINE); + DragButtonIcon(14, 10, "Drop in timeline to insert\nSharp fade-in", + TimelinePayload(TimelinePayload::FADE_IN, d*GST_MSECOND, Timeline::FADING_SHARP)); + /// + /// FADE OUT + /// + ImGui::SameLine(0, IMGUI_SAME_LINE); + DragButtonIcon(11, 10, "Drop in timeline to insert\nSharp fade-out", + TimelinePayload(TimelinePayload::FADE_OUT, d*GST_MSECOND, Timeline::FADING_SHARP)); + /// + /// FADE IN & OUT + /// + ImGui::SameLine(0, IMGUI_SAME_LINE); + DragButtonIcon(9, 10, "Drop in timeline to insert\nSharp fade in & out", + TimelinePayload(TimelinePayload::FADE_IN_OUT, d*GST_MSECOND, Timeline::FADING_SHARP)); + /// + /// FADE OUT & IN + /// + ImGui::SameLine(0, IMGUI_SAME_LINE); + DragButtonIcon(10, 10, "Drop in timeline to insert\nSharp fade out & in", + TimelinePayload(TimelinePayload::FADE_OUT_IN, d*GST_MSECOND, Timeline::FADING_SHARP)); + } + /// + /// FADE LINEAR + /// + else if (current_curve == 1) { + /// + /// FADE IN + /// + ImGui::SameLine(0, IMGUI_SAME_LINE); + DragButtonIcon(15, 10, "Drop in timeline to insert\nLinear fade-in", + TimelinePayload(TimelinePayload::FADE_IN, d*GST_MSECOND, Timeline::FADING_LINEAR)); + /// + /// FADE OUT + /// + ImGui::SameLine(0, IMGUI_SAME_LINE); + DragButtonIcon(12, 10, "Drop in timeline to insert\nLinear fade-out", + TimelinePayload(TimelinePayload::FADE_OUT, d*GST_MSECOND, Timeline::FADING_LINEAR)); + /// + /// FADE IN & OUT + /// + ImGui::SameLine(0, IMGUI_SAME_LINE); + DragButtonIcon(7, 10, "Drop in timeline to insert\nLinear fade in & out", + TimelinePayload(TimelinePayload::FADE_IN_OUT, d*GST_MSECOND, Timeline::FADING_LINEAR)); + /// + /// FADE OUT & IN + /// + ImGui::SameLine(0, IMGUI_SAME_LINE); + DragButtonIcon(8, 10, "Drop in timeline to insert\nLinear fade out & in", + TimelinePayload(TimelinePayload::FADE_OUT_IN, d*GST_MSECOND, Timeline::FADING_LINEAR)); + } + /// + /// FADE SMOOTH + /// + else if (current_curve == 2) { + /// + /// FADE IN + /// + ImGui::SameLine(0, IMGUI_SAME_LINE); + DragButtonIcon(16, 10, "Drop in timeline to insert\nSmooth fade-in", + TimelinePayload(TimelinePayload::FADE_IN, d*GST_MSECOND, Timeline::FADING_SMOOTH)); + /// + /// FADE OUT + /// + ImGui::SameLine(0, IMGUI_SAME_LINE); + DragButtonIcon(13, 10, "Drop in timeline to insert\nSmooth fade-out", + TimelinePayload(TimelinePayload::FADE_OUT, d*GST_MSECOND, Timeline::FADING_SMOOTH)); + /// + /// FADE IN & OUT + /// + ImGui::SameLine(0, IMGUI_SAME_LINE); + DragButtonIcon(17, 10, "Drop in timeline to insert\nSmooth fade in & out", + TimelinePayload(TimelinePayload::FADE_IN_OUT, d*GST_MSECOND, Timeline::FADING_SMOOTH)); + /// + /// FADE OUT & IN + /// + ImGui::SameLine(0, IMGUI_SAME_LINE); + DragButtonIcon(18, 10, "Drop in timeline to insert\nSmooth fade out & in", + TimelinePayload(TimelinePayload::FADE_OUT_IN, d*GST_MSECOND, Timeline::FADING_SMOOTH)); + + } + + // float md = 10000; + ImGui::SameLine(0, IMGUI_SAME_LINE); + ImGui::SetNextItemWidth(180); + float seconds = (float) d / 1000.f; + + if (current_curve > 0) { + // ImGuiToolkit::SliderTiming("##Duration", &d, 60, md+50, 50, "Auto"); + // if (d > md) d = UINT_MAX; + if (ImGui::SliderFloat("##DurationFading", + &seconds, + 0.05f, + 60.f, + seconds < 59.5f ? "%.2f s" : "Auto")) { + if (seconds > 59.5f) + d = UINT_MAX; + else + d = (uint) floor(seconds * 1000); + } + } else { + // ImGui::TextDisabled("%.2f s", seconds); + static char dummy_str[512]; + if (seconds < 59.5f ) + snprintf(dummy_str, 512, "%.2f s", seconds); + else + snprintf(dummy_str, 512, "Auto"); + + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.14f, 0.14f, 0.14f, 0.9f)); + ImGui::InputText("##disabledduration", dummy_str, IM_ARRAYSIZE(dummy_str), ImGuiInputTextFlags_ReadOnly); + ImGui::PopStyleColor(1); + + } + + + // Action buttons + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); + + /// + /// SEPARATOR + /// + ImGui::SameLine(0, 0); + ImGui::Text("|"); + + // action smooth + static int _actionsmooth = 0; + ImGui::SameLine(0, 0); + ImGui::PushButtonRepeat(true); + if (ImGuiToolkit::ButtonIcon(2, 7, "Apply smoothing filter")){ + tl->smoothFading( 5 ); + ++_actionsmooth; + } + ImGui::PopButtonRepeat(); + if (_actionsmooth > 0 && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) { + oss << ": Timeline opacity smooth"; + Action::manager().store(oss.str()); + _actionsmooth = 0; + } + + + // ImGui::SameLine(0, IMGUI_SAME_LINE); + // ImGui::SetNextItemWidth(140); // TODO VARIABLE WIDTH + // ImVec2 draw_pos = ImGui::GetCursorPos(); + // ImGui::SetCursorPosY(draw_pos.y + 8); // TODO VARIABLE H + // ImGuiToolkit::InputTime("##Time", &target_time); + + + /// + /// CLEAR + /// + ImGui::SameLine(0, IMGUI_SAME_LINE); + if (ImGuiToolkit::ButtonIcon(11, 14, "Clear Fading curve")) + tl->clearFading(); + + ImGui::PopStyleColor(); + + // end icons + ImGui::PopFont(); + } + + ImGui::EndChild(); + ImGui::PopStyleColor(); + } + + /// /// Dialog to edit timeline fade in and out /// @@ -1989,15 +2780,15 @@ void SourceControlWindow::RenderMediaPlayer(MediaSource *ms) Timeline *tl = mediaplayer_active_->timeline(); switch (l) { case 0: - tl->fadeIn(d, (Timeline::FadingCurve) c); + tl->fadeIn( static_cast(d) * GST_MSECOND, (Timeline::FadingCurve) c); oss << ": Timeline Fade in " << d; break; case 1: - tl->fadeOut(d, (Timeline::FadingCurve) c); + tl->fadeOut( static_cast(d) * GST_MSECOND, (Timeline::FadingCurve) c); oss << ": Timeline Fade out " << d; break; case 2: - tl->autoFading(d, (Timeline::FadingCurve) c); + tl->autoFading( static_cast(d) * GST_MSECOND, (Timeline::FadingCurve) c); oss << ": Timeline Fade in&out " << d; break; default: diff --git a/src/SourceControlWindow.h b/src/SourceControlWindow.h index 2fba440..7de3df1 100644 --- a/src/SourceControlWindow.h +++ b/src/SourceControlWindow.h @@ -54,6 +54,8 @@ class SourceControlWindow : public WorkspaceWindow bool mediaplayer_mode_; bool mediaplayer_slider_pressed_; float mediaplayer_timeline_zoom_; + + bool mediaplayer_edit_panel_; void RenderMediaPlayer(MediaSource *ms); // draw methods diff --git a/src/Timeline.cpp b/src/Timeline.cpp index 9e635b4..75b6630 100644 --- a/src/Timeline.cpp +++ b/src/Timeline.cpp @@ -23,6 +23,25 @@ #include "defines.h" #include "Timeline.h" +float clamp(float x, float lowerlimit = 0.0f, float upperlimit = 1.0f) { + if (x < lowerlimit) return lowerlimit; + if (x > upperlimit) return upperlimit; + return x; +} + +float smootherstep(size_t edge0, size_t edge1, size_t i) { + float x = clamp( static_cast(i - edge0) / static_cast(edge1 - edge0)); + return x * x * x * (x * (6.0f * x - 15.0f) + 10.0f); + // return x * x * (3.0f - 2.0f * x); +} + +float linestep(size_t edge0, size_t edge1, size_t i) { + float x = clamp( static_cast(i - edge0) / static_cast(edge1 - edge0)); + return x; +} + + + static float empty_zeros[MAX_TIMELINE_ARRAY] = {}; static float empty_ones[MAX_TIMELINE_ARRAY] = {}; @@ -200,7 +219,12 @@ bool Timeline::cut(GstClockTime t, bool left, bool join_extremity) break; } if (join_extremity) { - //TODO + if (previous != gaps_.end()) { + for (auto g = gaps_.begin(); g != gaps_.find(*previous); ) { + g = gaps_.erase(g); + } + } + ret = addGap( TimeInterval(t, timing_.begin) ); } else { if (previous == gaps_.end()) @@ -271,6 +295,9 @@ void Timeline::setGaps(const TimeIntervalSet &g) bool Timeline::removeGaptAt(GstClockTime t) { + if (gaps_.empty()) + return false; + TimeIntervalSet::const_iterator s = std::find_if(gaps_.begin(), gaps_.end(), includesTime(t)); if ( s != gaps_.end() ) { @@ -282,6 +309,41 @@ bool Timeline::removeGaptAt(GstClockTime t) return false; } +bool Timeline::mergeGapstAt(GstClockTime t) +{ + if (gaps_.empty()) + return false; + + auto first = gaps_.end(); + auto second = gaps_.end(); + + for (auto g = gaps_.begin(); g != gaps_.end(); g++) { + if (g->includes(t)) { + return false; + } else if (t > g->end) { + first = g; + } else if (t < g->begin) { + second = g; + break; + } + } + + if (first != gaps_.end() && second != gaps_.end()) { + gaps_.erase(first); + gaps_.erase(second); + addGap(first->begin, second->end); + } + else if (second != gaps_.end()) { + gaps_.erase(second); + addGap(timing_.begin, second->end); + } + else if (first != gaps_.end()) { + gaps_.erase(first); + addGap(first->begin, timing_.end); + } + + return gaps_array_need_update_; +} GstClockTime Timeline::sectionsDuration() const { @@ -478,38 +540,60 @@ void Timeline::clearFading() memcpy(fadingArray_, empty_ones, MAX_TIMELINE_ARRAY * sizeof(float)); } -void Timeline::smoothFading(uint N) +void Timeline::smoothFading(uint N, TimeInterval interval) { const float kernel[7] = { 2.f, 22.f, 97.f, 159.f, 97.f, 22.f, 2.f}; float tmparray[MAX_TIMELINE_ARRAY]; - for (uint n = 0; n < N; ++n) { + // default to cover entire array + long s = 0; + long e = MAX_TIMELINE_ARRAY; - for (long i = 0; i < MAX_TIMELINE_ARRAY; ++i) { - tmparray[i] = 0.f; - float divider = 0.f; - for( long j = 0; j < 7; ++j) { - long k = i - 3 + j; - if (k > -1 && k < MAX_TIMELINE_ARRAY-1) { - tmparray[i] += fadingArray_[k] * kernel[j]; - divider += kernel[j]; + // if a valid interval is given in argument + if (interval.is_valid()) { + + // get index of begining of interval + s = (interval.begin * MAX_TIMELINE_ARRAY) / timing_.end; + // get index of ending of interval + e = (interval.end * MAX_TIMELINE_ARRAY) / timing_.end; + + // iterate a given amount of times + for (uint n = 0; n < N; ++n) { + // copy to tmp array + memcpy(tmparray, fadingArray_, MAX_TIMELINE_ARRAY * sizeof(float)); + // apply gaussian filter on the interval + for (long i = s; i < e; ++i) { + tmparray[i] = 0.f; + float divider = 0.f; + for (long j = 0; j < 7; ++j) { + long k = i - 3 + j; + if (k > -1 && k < MAX_TIMELINE_ARRAY - 1) { + tmparray[i] += fadingArray_[k] * kernel[j]; + divider += kernel[j]; + } } + tmparray[i] *= 1.f / divider; } - tmparray[i] *= 1.f / divider; + // copy back to array + memcpy(fadingArray_, tmparray, MAX_TIMELINE_ARRAY * sizeof(float)); + } + } + // in absence of interval given, loop over all sections + else { + TimeIntervalSet _intervals = sections(); + for (auto inter = _intervals.begin(); inter != _intervals.end(); inter++) { + smoothFading(N, *inter); } - - memcpy( fadingArray_, tmparray, MAX_TIMELINE_ARRAY * sizeof(float)); } } -void Timeline::autoFading(uint milisecond, FadingCurve curve) +void Timeline::autoFading(const GstClockTime duration, FadingCurve curve) { // mow many index values of timeline array for this duration? size_t N = MAX_TIMELINE_ARRAY -1; - GstClockTime d = static_cast(milisecond) * 1000000; - if ( d < timing_.end ) - N = d / (timing_.end / MAX_TIMELINE_ARRAY); + if ( duration < timing_.end ) + N = duration / (timing_.end / MAX_TIMELINE_ARRAY); // clear with static array memcpy(fadingArray_, empty_zeros, MAX_TIMELINE_ARRAY * sizeof(float)); @@ -534,9 +618,9 @@ void Timeline::autoFading(uint milisecond, FadingCurve curve) size_t i = s; for (; i < s+n; ++i){ const float x = static_cast(i-s) / static_cast(n); - if (curve==FADING_BILINEAR) + if (curve==FADING_LINEAR) fadingArray_[i] = x * x; - else if (curve==FADING_BILINEAR_INV) + else if (curve==FADING_SMOOTH) fadingArray_[i] = 1.f - ((x- 1.f) * (x - 1.f)); else fadingArray_[i] = x; @@ -547,9 +631,9 @@ void Timeline::autoFading(uint milisecond, FadingCurve curve) // linear fade out ending at e for (; i < e; ++i) { const float x = static_cast(e-i) / static_cast(n); - if (curve==FADING_BILINEAR) + if (curve==FADING_LINEAR) fadingArray_[i] = x * x; - else if (curve==FADING_BILINEAR_INV) + else if (curve==FADING_SMOOTH) fadingArray_[i] = 1.f - ((x- 1.f) * (x - 1.f)); else fadingArray_[i] = x; @@ -557,94 +641,177 @@ void Timeline::autoFading(uint milisecond, FadingCurve curve) } } - -void Timeline::fadeIn(uint milisecond, FadingCurve curve) +void Timeline::fadeOut(const GstClockTime from, const GstClockTime duration, FadingCurve curve) { - // mow many index values of timeline array for this duration? - size_t N = MAX_TIMELINE_ARRAY -1; - GstClockTime d = static_cast(milisecond) * 1000000; - if ( d < timing_.end ) - N = d / (timing_.end / MAX_TIMELINE_ARRAY); + GstClockTime to = from + duration; - // get sections (inverse of gaps) is never empty - TimeIntervalSet sec = sections(); - auto it = sec.cbegin(); + if (duration > timing_.end) { + to = timing_.end; + for (auto g = gaps_.begin(); g != gaps_.end(); ++g) { + // gap after target? + if (g->begin > from) { + to = g->begin; + break; + } + } + } - // get index of begining of section - const size_t s = ( it->begin * MAX_TIMELINE_ARRAY ) / timing_.end; + // get index of begining of fading curve + const size_t s = ( from * MAX_TIMELINE_ARRAY ) / timing_.end; - // get index of ending of section - const size_t e = ( it->end * MAX_TIMELINE_ARRAY ) / timing_.end; + // get index of ending of fading curve + const size_t e = ( to * MAX_TIMELINE_ARRAY ) / timing_.end; + + // how many index values of timeline array for this duration? + size_t N = MAX_TIMELINE_ARRAY - 1; + N = MIN( ( MIN( duration, timing_.end - from ) * MAX_TIMELINE_ARRAY )/ timing_.end, N ); // calculate size of the smooth transition in [s e] interval const size_t n = MIN( e-s, N ); - // linear fade in starting at s + // if transition too short for a linear or smooth + if (N < 3) { + curve = FADING_SHARP; + N = 2; + } + + // linear fade out starts at s size_t i = s; - float val = fadingArray_[s+n]; - for (; i < s+n; ++i) { - const float x = static_cast(i-s) / static_cast(n); - if (curve==FADING_BILINEAR) - fadingArray_[i] = x * x; - else if (curve==FADING_BILINEAR_INV) - fadingArray_[i] = 1.f - ((x- 1.f) * (x - 1.f)); - else + // float val = fadingArray_[s]; + for (; i <= s+n; ++i) { + const float x = static_cast(e-i) / static_cast(n); + // const float x = 1.f - static_cast(i-s) / static_cast(n); + if (curve==FADING_LINEAR) fadingArray_[i] = x; - fadingArray_[i] *= val; + else if (curve==FADING_SMOOTH) + fadingArray_[i] = 1.f - smootherstep(s, s+n, i); + else + fadingArray_[i] = 0.f; + // fadingArray_[i] *= val; } - // add a bit of buffer to avoid jump to previous frame - size_t b = s - (step_ / 2) / (timing_.end / MAX_TIMELINE_ARRAY); - if (b > 0) { - for (size_t j = b; j < s; ++j) - fadingArray_[j] = 0.f; - } } -void Timeline::fadeOut(uint milisecond, FadingCurve curve) +void Timeline::fadeIn(const GstClockTime to, const GstClockTime duration, FadingCurve curve) { - // mow many index values of timeline array for this duration? - size_t N = MAX_TIMELINE_ARRAY -1; - GstClockTime d = static_cast(milisecond) * 1000000; - if ( d < timing_.end ) - N = d / (timing_.end / MAX_TIMELINE_ARRAY); + GstClockTime from = to - duration; - // get sections (inverse of gaps) is never empty - TimeIntervalSet sec = sections(); - auto it = sec.end(); - --it; + if (duration > timing_.end) { + for (auto g = gaps_.begin(); g != gaps_.end(); ++g) { + // gap before target? + if ( g->end < to ) + from = g->end; + else + break; + } + } - // get index of begining of section - const size_t s = ( it->begin * MAX_TIMELINE_ARRAY ) / timing_.end; + // get index of begining of fading curve + const size_t s = ( from * MAX_TIMELINE_ARRAY ) / timing_.end; - // get index of ending of section - const size_t e = ( it->end * MAX_TIMELINE_ARRAY ) / timing_.end; + // get index of ending of fading curve + const size_t e = ( to * MAX_TIMELINE_ARRAY ) / timing_.end; + + // how many index values of timeline array for this duration? + size_t N = MAX_TIMELINE_ARRAY - 1; + N = MIN( ( MIN( duration, to - timing_.begin ) * MAX_TIMELINE_ARRAY )/ timing_.end, N ); + + // if transition too short for a linear or smooth + if (N < 3) { + curve = FADING_SHARP; + N = 2; + } // calculate size of the smooth transition in [s e] interval const size_t n = MIN( e-s, N ); - // linear fade out ending at e + // linear fade in ends at e size_t i = e-n; - float val = fadingArray_[i]; - for (; i < e; ++i){ - const float x = static_cast(e-i) / static_cast(n); - if (curve==FADING_BILINEAR) - fadingArray_[i] = x * x; - else if (curve==FADING_BILINEAR_INV) - fadingArray_[i] = 1.f - ((x- 1.f) * (x - 1.f)); - else + // float val = fadingArray_[e]; + for (; i < e; ++i) { + // const float x = static_cast(i-s) / static_cast(n); + const float x = 1.f - static_cast(e - i) / static_cast(n); + if (curve==FADING_LINEAR) fadingArray_[i] = x; - fadingArray_[i] *= val; + else if (curve==FADING_SMOOTH) + fadingArray_[i] = smootherstep(e-n, e, i); + else + fadingArray_[i] = 0.f; + // fadingArray_[i] *= val; } - // add a bit of buffer to avoid jump to next frame - size_t b = e + (1000 + step_) / (timing_.end / MAX_TIMELINE_ARRAY); - if (b < MAX_TIMELINE_ARRAY) { - for (size_t j = e; j < b; ++j) - fadingArray_[j] = 0.f; - } } +void Timeline::fadeInOutRange(const GstClockTime t, const GstClockTime duration, bool in_and_out, FadingCurve curve) +{ + // init range to whole timeline + TimeInterval range = timing_; + + // find cuts at left and right of given time + for (auto g = gaps_.begin(); g != gaps_.end(); g++) { + if (g->begin < t) { + if (g->end > t) { + // inside a gap + range.begin = g->begin; + range.end = g->end; + break; + } else { + // after a gap + range.begin = g->end; + } + } else { + // before a gap + range.end = g->begin; + break; + } + } + + // get index of begining of section + const size_t s = (range.begin * MAX_TIMELINE_ARRAY) / timing_.end; + + // get index of ending of section + const size_t e = (range.end * MAX_TIMELINE_ARRAY) / timing_.end; + + // get index of time in section + const size_t m = (t * MAX_TIMELINE_ARRAY) / timing_.end; + + size_t l = m; + size_t r = m; + + // if duration too short for a linear or smooth + if (duration < 2 * step_) { + curve = FADING_SHARP; + } + // if duration allows to fade in and out + else if (2 * duration < range.duration()) { + l = ( (range.begin + duration) * MAX_TIMELINE_ARRAY) / timing_.end; + r = ( (range.end - duration) * MAX_TIMELINE_ARRAY) / timing_.end; + } + + // fill values inside range + for (size_t k = s; k < e; ++k) { + + if (curve == FADING_LINEAR){ + if (k > profile_options = { - {18, 3, "Linear"}, - {19, 3, "Quadratic"} + {11, 12, "Linear"}, + {10, 12, "Quadratic"} }; ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); tmp = Settings::application.transition.profile ? 1 : 0; diff --git a/src/defines.h b/src/defines.h index ac03bbc..c2200d7 100644 --- a/src/defines.h +++ b/src/defines.h @@ -258,7 +258,8 @@ #define LABEL_AUTO_MEDIA_PLAYER ICON_FA_USER_CIRCLE " User selection" #define LABEL_STORE_SELECTION " Create batch" -#define LABEL_EDIT_FADING ICON_FA_RANDOM " Fade in & out" +#define LABEL_EDIT_FADING ICON_FA_RANDOM " Edit timeline fading" +#define LABEL_EDIT_GAPS ICON_FA_CUT " Cut timeline" #define LABEL_VIDEO_SEQUENCE " Encode an image sequence" #define LABEL_ADD_TIMELINE "Add timeline" #define DIALOG_TIMELINE_DURATION ICON_FA_HOURGLASS_HALF " Set timeline duration"