New toolbox Timeline editor

DRAFT implementation of a new toolbox to apply fade-in and fade-out and to cut the timeline.
This commit is contained in:
Bruno Herbelin
2024-07-13 12:32:05 +02:00
parent 7092de8809
commit 94fbe58fb7
12 changed files with 1276 additions and 457 deletions

Binary file not shown.

View File

@@ -20,6 +20,7 @@
#include <map>
#include <algorithm>
#include <string>
#include <regex>
#include <cctype>
#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<double>(values_count) * x), 0, values_count);
char cursor_text[64];
guint64 time = begin + (index * end) / static_cast<guint64>(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<std::string*>(data->UserData);

View File

@@ -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<std::pair<int, int> > icons, int* state, std::vector<std::string> 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);

View File

@@ -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
///

View File

@@ -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);

View File

@@ -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<double>(values_count) * x), 0, values_count);
char cursor_text[64];
guint64 time = begin + (index * end) / static_cast<guint64>(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<double>(MAX_TIMELINE_ARRAY) * x), 0, MAX_TIMELINE_ARRAY);
char cursor_text[64];
guint64 time = begin + (index * end) / static_cast<guint64>(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<guint64>(MAX_TIMELINE_ARRAY)) / end ;
// double x = static_cast<double>(index) / static_cast<double>(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<float, guint64> > DrawTimeline(const char* label, Timeline *timeline, guint64 time,
double width_ratio, float height)
{
@@ -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_));
@@ -1932,6 +2384,345 @@ 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<std::string> tooltips_curve = {{"Fading Sharp"},
{"Fading Linear"},
{"Fading Smooth"}};
const std::vector<std::pair<int, int> > options_curve
= {{12, 12},
{13, 12},
{14, 12}};
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
// ImGuiToolkit::ButtonIconMultistate(options_curve, &current_curve, tooltips_curve);
ImGuiToolkit::IconMultistate(options_curve, &current_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<GstClockTime>(d) * GST_MSECOND, (Timeline::FadingCurve) c);
oss << ": Timeline Fade in " << d;
break;
case 1:
tl->fadeOut(d, (Timeline::FadingCurve) c);
tl->fadeOut( static_cast<GstClockTime>(d) * GST_MSECOND, (Timeline::FadingCurve) c);
oss << ": Timeline Fade out " << d;
break;
case 2:
tl->autoFading(d, (Timeline::FadingCurve) c);
tl->autoFading( static_cast<GstClockTime>(d) * GST_MSECOND, (Timeline::FadingCurve) c);
oss << ": Timeline Fade in&out " << d;
break;
default:

View File

@@ -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

View File

@@ -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<float>(i - edge0) / static_cast<float>(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<float>(i - edge0) / static_cast<float>(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<GstClockTime>(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<float>(i-s) / static_cast<float>(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<float>(e-i) / static_cast<float>(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<GstClockTime>(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<float>(i-s) / static_cast<float>(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<float>(e-i) / static_cast<float>(n);
// const float x = 1.f - static_cast<float>(i-s) / static_cast<float>(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<GstClockTime>(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<float>(e-i) / static_cast<float>(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<float>(i-s) / static_cast<float>(n);
const float x = 1.f - static_cast<float>(e - i) / static_cast<float>(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<l)
fadingArray_[k] = in_and_out ? linestep(s, l, k) : 1.f - linestep(s, l, k);
else if (k<r)
fadingArray_[k] = in_and_out ? 1.f : 0.f;
else
fadingArray_[k] = in_and_out ? 1.f - linestep(r, e, k) : linestep(r, e, k);
}
else if (curve == FADING_SMOOTH) {
if (k<l)
fadingArray_[k] = in_and_out ? smootherstep(s, l, k) : 1.f - smootherstep(s, l, k);
else if (k<r)
fadingArray_[k] = in_and_out ? 1.f : 0.f;
else
fadingArray_[k] = in_and_out ? 1.f - smootherstep(r, e, k) : smootherstep(r, e, k);
}
else // curve == FADING_SHARP
fadingArray_[k] = in_and_out;
}
}
bool Timeline::autoCut()

View File

@@ -131,8 +131,9 @@ public:
void setGaps(const TimeIntervalSet &g);
bool addGap(TimeInterval s);
bool addGap(GstClockTime begin, GstClockTime end);
bool cut(GstClockTime t, bool left, bool join_extremity);
bool cut(GstClockTime t, bool left, bool join_extremity = false);
bool removeGaptAt(GstClockTime t);
bool mergeGapstAt(GstClockTime t);
bool gapAt(const GstClockTime t) const;
bool getGapAt(const GstClockTime t, TimeInterval &gap) const;
@@ -150,14 +151,19 @@ public:
// Edit
typedef enum {
FADING_LINEAR = 0,
FADING_BILINEAR,
FADING_BILINEAR_INV
FADING_SHARP = 0,
FADING_LINEAR,
FADING_SMOOTH
} FadingCurve;
void smoothFading(uint N = 1);
void autoFading(uint milisecond = 100, FadingCurve curve = FADING_LINEAR);
void fadeIn(uint milisecond = 100, FadingCurve curve = FADING_LINEAR);
void fadeOut(uint milisecond = 100, FadingCurve curve = FADING_LINEAR);
void smoothFading(uint N = 1, TimeInterval interval = TimeInterval());
void autoFading(const GstClockTime duration = 10000, FadingCurve curve = FADING_SHARP);
// void fadeIn(const GstClockTime duration = 100000, FadingCurve curve = FADING_LINEAR);
// void fadeOut(const GstClockTime duration = 100000, FadingCurve curve = FADING_LINEAR);
void fadeIn(const GstClockTime from, const GstClockTime duration, FadingCurve curve = FADING_SHARP);
void fadeOut(const GstClockTime to, const GstClockTime duration, FadingCurve curve = FADING_SHARP);
void fadeInOutRange(const GstClockTime t, const GstClockTime duration, bool in_and_out, FadingCurve curve = FADING_SHARP);
bool autoCut();
private:

View File

@@ -234,11 +234,11 @@ void TransitionView::draw()
// toggle transition mode
ImGui::SetCursorScreenPos(ImVec2(pos_tran.x - 60.f, pos_tran.y +2.f));
const char *tooltip[2] = {"Fade to black", "Cross fading"};
ImGuiToolkit::IconToggle(0,2,0,8, &Settings::application.transition.cross_fade, tooltip );
ImGuiToolkit::IconToggle(0, 2, 0, 8, &Settings::application.transition.cross_fade, tooltip );
ImGui::SetCursorScreenPos(ImVec2(pos_tran.x + 10.f, pos_tran.y + 2.f));
const char *_tooltip[2] = {"Linear", "Quadratic"};
ImGuiToolkit::IconToggle(18,3,19,3, &Settings::application.transition.profile, _tooltip );
ImGuiToolkit::IconToggle(11, 12, 10, 12, &Settings::application.transition.profile, _tooltip );
// Duration slider (adjusted width)
const float width = (pos_play.x - pos_canl.x) / 5.0;

View File

@@ -2458,7 +2458,8 @@ void UserInterface::RenderHelp()
ImGui::SetNextWindowSize(ImVec2(460, 800), ImGuiCond_FirstUseEver);
ImGui::SetNextWindowSizeConstraints(ImVec2(350, 300), ImVec2(FLT_MAX, FLT_MAX));
if ( !ImGui::Begin(IMGUI_TITLE_HELP, &Settings::application.widget.help, ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse ) )
if ( !ImGui::Begin(IMGUI_TITLE_HELP, &Settings::application.widget.help,
ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse ) )
{
ImGui::End();
return;
@@ -6027,8 +6028,8 @@ void Navigator::RenderTransitionPannel(const ImVec2 &iconsize)
Settings::application.transition.cross_fade = true;
static std::vector< std::tuple<int, int, std::string> > 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;

View File

@@ -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"