diff --git a/rsc/images/icons.dds b/rsc/images/icons.dds index 92c9236..098aad7 100644 Binary files a/rsc/images/icons.dds and b/rsc/images/icons.dds differ diff --git a/src/ImGuiToolkit.cpp b/src/ImGuiToolkit.cpp index 4ea0b64..0ef7c78 100644 --- a/src/ImGuiToolkit.cpp +++ b/src/ImGuiToolkit.cpp @@ -20,6 +20,7 @@ #include #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"