Files
vimix/src/SourceControlWindow.cpp

3247 lines
134 KiB
C++

/*
* This file is part of vimix - video live mixer
*
* **Copyright** (C) 2019-2023 Bruno Herbelin <bruno.herbelin@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/
#include <iostream>
#include <iomanip>
#include <thread>
#include <algorithm>
#include <gst/gst.h>
// ImGui
#include "ImGuiToolkit.h"
#include "Timeline.h"
#include "gst/gstclock.h"
#include "imgui.h"
#include "imgui_internal.h"
#include "defines.h"
#include "Log.h"
#include "Settings.h"
#include "SystemToolkit.h"
#include "DialogToolkit.h"
#include "GstToolkit.h"
#include "Resource.h"
#include "Mixer.h"
#include "MediaSource.h"
#include "StreamSource.h"
#include "MediaPlayer.h"
#include "ActionManager.h"
#include "UserInterfaceManager.h"
#include "SourceControlWindow.h"
#define CHECKER_RESOLUTION 5000
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,
FLAG_ADD,
FLAG_REMOVE
};
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),
timeline_height_(0.f), mediaplayer_height_(0.f), buttons_width_(0.f), buttons_height_(0.f),
play_toggle_request_(false), replay_request_(false), pending_(false),
active_label_(LABEL_PLAYER_SELECTION), active_selection_(-1),
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),
mediaplayer_edit_panel_(false), magnifying_glass(false)
{
info_.setExtendedStringMode();
captureFolderDialog = new DialogToolkit::OpenFolderDialog("Capture frame Location");
// initialize checkerboard background texture
checker_background_->open("videotestsrc name=bgchecker pattern=checkers-8 ! "
"videobalance saturation=0 contrast=1",
CHECKER_RESOLUTION, CHECKER_RESOLUTION);
checker_background_->play(false);
while (checker_background_->texture() == Resource::getTextureBlack()){
std::this_thread::sleep_for (std::chrono::milliseconds(30));
checker_background_->update();
}
checker_background_->close();
}
void SourceControlWindow::resetActiveSelection()
{
info_.reset();
active_selection_ = -1;
active_label_ = LABEL_PLAYER_SELECTION;
play_toggle_request_ = false;
replay_request_ = false;
capture_request_ = false;
}
void SourceControlWindow::setVisible(bool on)
{
magnifying_glass = false;
// restore workspace to show the window
if (WorkspaceWindow::clear_workspace_enabled) {
WorkspaceWindow::restoreWorkspace(on);
// do not change status if ask to hide (consider user asked to toggle because the window was not visible)
if (!on) return;
}
if (Settings::application.widget.media_player_view > 0 && Settings::application.widget.media_player_view != Settings::application.current_view) {
Settings::application.widget.media_player_view = -1;
on = true;
}
// if no selection in the player and in the source selection, show all sources
if (on && selection_.empty() && Mixer::selection().empty() )
selection_ = valid_only( Mixer::manager().session()->getDepthSortedList() );
Settings::application.widget.media_player = on;
}
bool SourceControlWindow::Visible() const
{
return ( Settings::application.widget.media_player
&& (Settings::application.widget.media_player_view < 0 || Settings::application.widget.media_player_view == Settings::application.current_view )
);
}
void SourceControlWindow::Update()
{
WorkspaceWindow::Update();
SourceList selectedsources;
if (Settings::application.widget.media_player == false)
selection_.clear();
// get new selection or validate previous list if selection was not updated
selectedsources = Mixer::manager().validate(selection_);
if (selectedsources.empty() && !Mixer::selection().empty())
selectedsources = valid_only(Mixer::selection().getCopy());
// compute number of source selected and playable
size_t n_source = selectedsources.size();
size_t n_play = 0;
for (auto source = selectedsources.begin(); source != selectedsources.end(); ++source){
if ( (*source)->active() && (*source)->playing() )
n_play++;
}
//
// Play button or keyboard [Space] was pressed
//
if ( play_toggle_request_ ) {
for (auto source = selectedsources.begin(); source != selectedsources.end(); ++source)
(*source)->play( n_play < n_source );
Action::manager().store( n_play < n_source ? "Sources Play" : "Sources Pause" );
play_toggle_request_ = false;
}
//
// Replay / rewind button or keyboard [CTRL+Space] was pressed
//
if ( replay_request_ ) {
for (auto source = selectedsources.begin(); source != selectedsources.end(); ++source)
(*source)->replay();
replay_request_ = false;
}
//
// return from thread for selecting capture folder
//
if (captureFolderDialog->closed() && !captureFolderDialog->path().empty())
// get the folder from this file dialog
Settings::application.source.capture_path = captureFolderDialog->path();
//
// Capture frame on current selection
//
Source *s = nullptr;
if ( selection_.size() == 1 )
s = selection_.front();
if ( s != nullptr) {
// back from capture of FBO: can save file
if ( capture.isFull() ){
std::string filename;
// if sequencial naming of file is selected
if (Settings::application.source.capture_naming == 0 )
filename = SystemToolkit::filename_sequential(Settings::application.source.capture_path, s->name(), "png");
else
filename = SystemToolkit::filename_dateprefix(Settings::application.source.capture_path, s->name(), "png");
// save capture and inform user
capture.save( filename );
Log::Notify("Frame saved in %s", filename.c_str() );
}
// request capture : initiate capture of FBO
if ( capture_request_ ) {
capture.captureFramebuffer( s->frame() );
capture_request_ = false;
}
}
// reset on session change
static Session *__session = nullptr;
if ( Mixer::manager().session() != __session ) {
__session = Mixer::manager().session();
resetActiveSelection();
}
}
FrameBuffer *SourceControlWindow::renderedFramebuffer() const
{
if (selection_.size() == 1)
return selection_.front()->frame();
return nullptr;
}
void SourceControlWindow::Render()
{
// estimate window size
const ImGuiContext& g = *GImGui;
h_space_ = g.Style.ItemInnerSpacing.x;
v_space_ = g.Style.FramePadding.y;
buttons_height_ = g.FontSize + v_space_ * 4.0f ;
buttons_width_ = g.FontSize * 8.0f ;
min_width_ = 6.f * buttons_height_;
timeline_height_ = (g.FontSize + v_space_) * 2.0f ; // double line for each timeline
scrollbar_ = g.Style.ScrollbarSize;
// all together: 1 title bar + spacing + 1 toolbar + spacing + 2 timelines + scrollbar
mediaplayer_height_ = buttons_height_ + 2.f * timeline_height_ + 2.f * scrollbar_ + v_space_;
// constraint size
ImGui::SetNextWindowSizeConstraints(ImVec2(min_width_, 2.f * mediaplayer_height_), ImVec2(FLT_MAX, FLT_MAX));
ImGui::SetNextWindowPos(ImVec2(1180, 400), ImGuiCond_FirstUseEver);
ImGui::SetNextWindowSize(ImVec2(400, 400), ImGuiCond_FirstUseEver);
// Window named "SourceController" at instanciation
if ( !ImGui::Begin(name_, &Settings::application.widget.media_player,
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse ))
{
ImGui::End();
return;
}
// menu (no title bar)
if (ImGui::BeginMenuBar())
{
if (ImGuiToolkit::IconButton(4,16)){
Settings::application.widget.media_player = false;
}
if (ImGui::BeginMenu(IMGUI_TITLE_MEDIAPLAYER))
{
// Preview and output menu
if (ImGuiToolkit::MenuItemIcon(ICON_PREVIEW, MENU_PREVIEW, SHORTCUT_PREVIEW_SRC, false, renderedFramebuffer()!=nullptr) )
UserInterface::manager().show_preview = UserInterface::PREVIEW_SOURCE;
//
// Menu section for play control
//
if (ImGui::MenuItem( MENU_PLAY_BEGIN, SHORTCUT_PLAY_BEGIN, nullptr, !selection_.empty()))
replay_request_ = true;
if (ImGui::MenuItem( MENU_PLAY_PAUSE, SHORTCUT_PLAY_PAUSE, nullptr, !selection_.empty()))
play_toggle_request_ = true;
ImGui::Separator();
//
// Menu section for display
//
if (ImGui::BeginMenu( ICON_FA_IMAGE " Displayed image"))
{
if (ImGuiToolkit::MenuItemIcon(8, 9, " Render"))
Settings::application.widget.media_player_slider = 0.0;
if (ImGuiToolkit::MenuItemIcon(6, 9, " Split"))
Settings::application.widget.media_player_slider = 0.5;
if (ImGuiToolkit::MenuItemIcon(7, 9, " Input"))
Settings::application.widget.media_player_slider = 1.0;
ImGui::EndMenu();
}
if (ImGui::MenuItem( ICON_FA_TH " List all")) {
selection_.clear();
resetActiveSelection();
Mixer::manager().unsetCurrentSource();
Mixer::selection().clear();
selection_ = valid_only(Mixer::manager().session()->getDepthSortedList());
}
if (ImGui::MenuItem( ICON_FA_MINUS " Clear")) {
selection_.clear();
resetActiveSelection();
Mixer::manager().unsetCurrentSource();
Mixer::selection().clear();
}
//
// Menu section for window management
//
ImGui::Separator();
bool pinned = Settings::application.widget.media_player_view == Settings::application.current_view;
std::string menutext = std::string( ICON_FA_MAP_PIN " Stick to ") + Settings::application.views[Settings::application.current_view].name + " view";
if ( ImGui::MenuItem( menutext.c_str(), nullptr, &pinned) ){
if (pinned)
Settings::application.widget.media_player_view = Settings::application.current_view;
else
Settings::application.widget.media_player_view = -1;
}
if ( ImGui::MenuItem( MENU_CLOSE, SHORTCUT_PLAYER) ) {
Settings::application.widget.media_player = false;
selection_.clear();
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu(active_label_.c_str()))
{
// info on selection status
size_t N = Mixer::manager().session()->numBatch();
bool enabled = !selection_.empty() && active_selection_ < 0;
// Menu : Dynamic selection
if (ImGui::MenuItem(LABEL_PLAYER_SELECTION, NULL, active_selection_ < 0))
resetActiveSelection();
// Menu : list of selections
if (N>0) {
for (size_t i = 0 ; i < N; ++i)
{
std::string label = std::string(LABEL_PLAYER_BATCH) + std::to_string(i);
if (ImGui::MenuItem( label.c_str(), NULL, active_selection_ == (int) i ))
{
active_selection_ = i;
active_label_ = label;
info_.reset();
}
}
}
// Menu : store selection
ImGui::Separator();
if (ImGui::MenuItem(ICON_FA_PLUS_CIRCLE LABEL_PLAYER_BATCH_ADD, NULL, false, enabled))
{
active_selection_ = N;
active_label_ = std::string(LABEL_PLAYER_BATCH) + std::to_string(active_selection_);
Mixer::manager().session()->addBatch( ids(selection_) );
info_.reset();
}
ImGui::EndMenu();
}
//
// Menu for capture frame
//
if ( ImGui::BeginMenu(ICON_FA_ARROW_ALT_CIRCLE_DOWN " Capture", selection_.size() == 1 ) )
{
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_CAPTURE, 0.8f));
if (ImGui::MenuItem( MENU_CAPTUREFRAME, "F10" ))
capture_request_ = true;
ImGui::PopStyleColor(1);
// separator and hack for extending menu width
ImGui::Separator();
ImGui::MenuItem("Settings ", nullptr, false, false);
float combo_width = ImGui::GetTextLineHeightWithSpacing() * 7.f;
// path menu selection
static char* name_path[4] = { nullptr };
if ( name_path[0] == nullptr ) {
for (int i = 0; i < 4; ++i)
name_path[i] = (char *) malloc( 1024 * sizeof(char));
snprintf( name_path[1], 1024, "%s", ICON_FA_HOME " Home");
snprintf( name_path[2], 1024, "%s", ICON_FA_FOLDER " File location");
snprintf( name_path[3], 1024, "%s", ICON_FA_FOLDER_PLUS " Select");
}
if (Settings::application.source.capture_path.empty())
Settings::application.source.capture_path = SystemToolkit::home_path();
snprintf( name_path[0], 1024, "%s", Settings::application.source.capture_path.c_str());
int selected_path = 0;
ImGui::SetNextItemWidth(combo_width);
ImGui::Combo("##Path", &selected_path, name_path, 4);
if (selected_path > 2)
captureFolderDialog->open();
else if (selected_path > 1) {
// file location of media player
if (mediaplayer_active_)
Settings::application.source.capture_path = SystemToolkit::path_filename( mediaplayer_active_->filename() );
// else file location of session
else
Settings::application.source.capture_path = SystemToolkit::path_filename( Mixer::manager().session()->filename() );
}
else if (selected_path > 0)
Settings::application.source.capture_path = SystemToolkit::home_path();
ImGui::SameLine(0, IMGUI_SAME_LINE);
if (ImGuiToolkit::TextButton("Path"))
Settings::application.source.capture_path = SystemToolkit::home_path();
// offer to open folder location
ImVec2 draw_pos = ImGui::GetCursorPos();
ImGui::SetCursorPos(draw_pos + ImVec2(combo_width + 3.f * ImGui::GetTextLineHeight() , -ImGui::GetFrameHeight()) );
if (ImGuiToolkit::IconButton( 3, 5, Settings::application.source.capture_path.c_str()))
SystemToolkit::open(Settings::application.source.capture_path);
ImGui::SetCursorPos(draw_pos);
// Naming menu selection
static const char* naming_style[2] = { ICON_FA_SORT_NUMERIC_DOWN " Sequential", ICON_FA_CALENDAR " Date prefix" };
ImGui::SetNextItemWidth(combo_width);
ImGui::Combo("##Filename", &Settings::application.source.capture_naming, naming_style, IM_ARRAYSIZE(naming_style));
ImGui::SameLine(0, IMGUI_SAME_LINE);
if (ImGuiToolkit::TextButton("Filename"))
Settings::application.source.capture_naming = 0;
ImGui::EndMenu();
}
//
// Menu for video Mediaplayer control
//
if (ImGui::BeginMenu(ICON_FA_FILM " Timeline", mediaplayer_active_) )
{
if ( mediaplayer_active_->isImage()) {
if ( ImGuiToolkit::MenuItemIcon(1, 14, "Remove")){
// set empty timeline
Timeline tl;
mediaplayer_active_->setTimeline(tl);
mediaplayer_active_->play(false);
// re-open the image with NO timeline
mediaplayer_active_->reopen();
}
if ( ImGui::MenuItem(ICON_FA_HOURGLASS_HALF " Duration")){
mediaplayer_set_duration_ = 1;
}
}
if (ImGui::MenuItem(ICON_FA_WINDOW_CLOSE " Reset")){
mediaplayer_timeline_zoom_ = 1.f;
mediaplayer_active_->timeline()->clearFading();
mediaplayer_active_->timeline()->clearGaps();
mediaplayer_active_->timeline()->clearFlags();
mediaplayer_active_->setVideoEffect("");
std::ostringstream oss;
oss << SystemToolkit::base_filename( mediaplayer_active_->filename() );
oss << ": Reset timeline";
Action::manager().store(oss.str());
}
bool _alpha_fading = mediaplayer_active_->timelineFadingMode()
== MediaPlayer::FADING_ALPHA;
if (ImGui::MenuItem(ICON_FA_FONT " Alpha fading", NULL, &_alpha_fading)) {
mediaplayer_active_->setTimelineFadingMode(
_alpha_fading ? MediaPlayer::FADING_ALPHA : MediaPlayer::FADING_COLOR);
}
if (ImGuiToolkit::BeginMenuIcon(4, 13, "Metronome"))
{
Metronome::Synchronicity sync = mediaplayer_active_->syncToMetronome();
bool active = sync == Metronome::SYNC_NONE;
if (ImGuiToolkit::MenuItemIcon(5, 13, " Not synchronized", NULL, active ))
mediaplayer_active_->setSyncToMetronome(Metronome::SYNC_NONE);
active = sync == Metronome::SYNC_BEAT;
if (ImGuiToolkit::MenuItemIcon(6, 13, " Sync to beat", NULL, active ))
mediaplayer_active_->setSyncToMetronome(Metronome::SYNC_BEAT);
active = sync == Metronome::SYNC_PHASE;
if (ImGuiToolkit::MenuItemIcon(7, 13, " Sync to phase", NULL, active ))
mediaplayer_active_->setSyncToMetronome(Metronome::SYNC_PHASE);
ImGui::EndMenu();
}
ImGui::Separator();
if (ImGuiToolkit::MenuItemIcon(16, 16, "Gstreamer effect", nullptr,
!mediaplayer_active_->videoEffect().empty(),
mediaplayer_active_->videoEffectAvailable()) )
mediaplayer_edit_pipeline_ = true;
ImGui::EndMenu();
}
// button to activate the magnifying glass at top right corner
ImVec2 p = g.CurrentWindow->Pos;
p.x += g.CurrentWindow->Size.x - 2.1f * g.FontSize;
if (g.CurrentWindow->DC.CursorPos.x < p.x)
{
ImGui::SetCursorScreenPos(p);
if (selection_.size() == 1) {
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.f, 0.f, 0.f, 0.f));
ImGuiToolkit::ButtonToggle( ICON_FA_SEARCH, &magnifying_glass);
ImGui::PopStyleColor();
}
else
ImGui::TextDisabled(" " ICON_FA_SEARCH);
}
ImGui::EndMenuBar();
}
// disable magnifying glass if window is deactivated
if ( g.NavWindow != g.CurrentWindow )
magnifying_glass = false;
// reset mediaplayer ptr
mediaplayer_active_ = nullptr;
// render with appropriate method
if (active_selection_ > -1)
RenderSelection(active_selection_);
else
RenderSelectedSources();
ImGui::End();
}
void DrawTimeScale(const char* label, guint64 duration, double width_ratio)
{
// get window
ImGuiWindow* window = ImGui::GetCurrentWindow();
if (window->SkipItems)
return;
// get style & id
const ImGuiContext& g = *GImGui;
const ImGuiStyle& style = g.Style;
const ImGuiID id = window->GetID(label);
const ImVec2 timeline_size( static_cast<float>( static_cast<double>(duration) * width_ratio ), 2.f * g.FontSize);
const ImVec2 pos = window->DC.CursorPos;
const ImVec2 frame_size( timeline_size.x + 2.f * style.FramePadding.x, timeline_size.y + style.FramePadding.y);
const ImRect bbox(pos, pos + frame_size);
ImGui::ItemSize(frame_size, style.FramePadding.y);
if (!ImGui::ItemAdd(bbox, id))
return;
const ImVec2 timescale_pos = pos + ImVec2(style.FramePadding.x, 0.f);
ImGuiToolkit::RenderTimeline(timescale_pos, timescale_pos + timeline_size, 0, duration, 1000, true);
}
bool EditTimeline(const char *label,
Timeline *tl,
int edit_mode,
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_mode == 1;
int cursor_flag = -1;
Timeline _tl;
bool array_changed = false;
float *lines_array = tl->fadingArray();
float *gaps_array = tl->gapsArray();
float *flags_array = tl->flagsArray();
// 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);
static guint64 removed_flag_time = 0;
static int removed_flag_type = -1;
// 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_mode == 0) {
static float target_value = values_min;
// toggle value histo
if (!active) {
target_value = gaps_array[index] > 0.f ? 0.f : 1.f;
active = true;
}
for (size_t i = left; i < right; ++i)
gaps_array[i] = target_value;
}
else if (edit_mode == 1) {
const float target_value = values_max - val;
for (size_t i = left; i < right; ++i)
lines_array[i] = target_value;
}
else if (edit_mode == 2) {
if (!active) {
// remove flag on mouse press
if ( tl->isFlagged(time) ) {
removed_flag_time = time;
removed_flag_type = tl->flagTypeAt(time);
tl->removeFlagAt(time);
}
// add flag on mouse release
else
active = true;
}
else if ( !tl->isFlagged(time) ) {
cursor_flag = tl->flagTypeAt(time);;
}
}
previous_index = index;
array_changed = true;
}
// release active widget on mouse release
else {
// add flag on mouse release
if (edit_mode == 2 && active) {
// exception: if flag was removed at same time, do not add it back
if ( removed_flag_time != time ) {
if (removed_flag_type >= 0)
tl->addFlagAt(time, removed_flag_type);
else
tl->addFlagAt(time, Settings::application.widget.media_player_timeline_flag);
}
removed_flag_time = 0;
removed_flag_type = -1;
}
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;
// dragged onto the timeline : apply changes on local copy
switch (pl->action) {
case TimelinePayload::CUT:
_tl = *tl;
_tl.cut(time, (bool) pl->argument);
gaps_array = _tl.gapsArray();
break;
case TimelinePayload::CUT_MERGE:
_tl = *tl;
_tl.mergeGapstAt(time);
gaps_array = _tl.gapsArray();
break;
case TimelinePayload::CUT_ERASE:
_tl = *tl;
_tl.removeGaptAt(time);
gaps_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;
case TimelinePayload::FLAG_ADD:
_tl = *tl;
_tl.addFlagAt(time, pl->argument);
flags_array = _tl.flagsArray();
break;
case TimelinePayload::FLAG_REMOVE:
_tl = *tl;
_tl.removeFlagAt(time);
flags_array = _tl.flagsArray();
break;
default:
break;
}
}
// dropped into the timeline : confirm change to timeline
if (ImGui::AcceptDragDropPayload("DND_TIMELINE", ImGuiDragDropFlags_AcceptNoDrawDefaultRect)) {
// copy temporary timeline into given timeline
*tl = _tl;
// like a mouse release
*released = true;
}
ImGui::EndDragDropTarget();
}
else {
if ( edit_mode == 2 && tl->isFlagged(time) ) {
cursor_flag = tl->flagTypeAt(time);;
}
}
// back to draw
ImGui::SetCursorScreenPos(canvas_pos);
// plot gaps (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, gaps_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);
// back to draw
ImGui::SetCursorScreenPos(canvas_pos);
// plot flags (transparent background)
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0,0,0,0));
ImGui::PushStyleColor(ImGuiCol_PlotHistogram, style.Colors[ImGuiCol_TabActive]); // cursor color
snprintf(buf, 128, "##Flags%s", label);
ImGui::PlotHistogram(buf, flags_array, MAX_TIMELINE_ARRAY, 0, NULL, values_min, values_max, size);
ImGui::PopStyleColor(2);
// 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 if (cursor_flag >= 0) {
cursor_pos = cursor_pos + ImVec2(mouse_pos_in_canvas.x, 12.f);
window->DrawList->AddLine( cursor_pos, cursor_pos + ImVec2(0.f, size.y - 8.f), cur_color);
_drawIcon(cursor_pos - ImVec2(2.f, 1.f), 11 + cursor_flag, 6, true, window);
}
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;
}
bool TimelineSlider (const char* label, guint64 *time, TimeInterval *flag, Timeline *tl, const float width)
{
// get window
ImGuiWindow* window = ImGui::GetCurrentWindow();
if (window->SkipItems)
return false;
// get style & id
const ImGuiContext& g = *GImGui;
const ImGuiStyle& style = g.Style;
const float fontsize = g.FontSize;
const ImGuiID id = window->GetID(label);
//
// PREPARE data structures
//
// widget bounding box
const float height = 2.f * (fontsize + style.FramePadding.y);
ImVec2 pos = window->DC.CursorPos;
ImVec2 size = ImVec2(width, height);
ImRect bbox(pos, pos + size);
ImGui::ItemSize(size, style.FramePadding.y);
if (!ImGui::ItemAdd(bbox, id))
return false;
// cursor size
const float cursor_width = 0.5f * fontsize;
// TIMELINE is inside the bbox, in a slightly smaller bounding box
ImRect timeline_bbox(bbox);
timeline_bbox.Expand( ImVec2() - style.FramePadding );
// SLIDER is inside the timeline
ImRect slider_bbox( timeline_bbox.GetTL() + ImVec2(-cursor_width + 2.f, cursor_width + 4.f ), timeline_bbox.GetBR() + ImVec2( cursor_width - 2.f, 0.f ) );
// units conversion: from time to float (calculation made with higher precision first)
float time_ = static_cast<float> ( static_cast<double>(*time - tl->begin()) / static_cast<double>(tl->duration()) );
//
// GET INPUT
//
// read user input from system
bool left_mouse_press = false;
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);
left_mouse_press = hovered && ImGui::IsMouseDown(ImGuiMouseButton_Left);
if (focus_requested || left_mouse_press || g.NavActivateId == id || g.NavInputId == id)
{
ImGui::SetActiveID(id, window);
ImGui::SetFocusID(id, window);
ImGui::FocusWindow(window);
}
}
//
// BACKGROUND
//
// Render the bounding box
const ImU32 frame_col = ImGui::GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
ImGui::RenderFrame(bbox.Min, bbox.Max, frame_col, true, style.FrameRounding);
// render the timeline
ImGuiToolkit::RenderTimeline(timeline_bbox.Min, timeline_bbox.Max, tl->begin(), tl->end(), tl->step());
//
// FLAGS
//
bool flag_pressed = false;
const TimeIntervalSet flags = tl->flags();
for (const auto &flag_Interval : flags) {
// set position in screen corresponding to flag time
GstClockTime flag_time = flag_Interval.midpoint();
float flag_pos_ = static_cast<float> ( static_cast<double>(flag_time - tl->begin()) / static_cast<double>(tl->duration()) );
ImVec2 draw_pos = ImLerp(timeline_bbox.GetTL(), timeline_bbox.GetTR(), flag_pos_);
draw_pos -= ImVec2(2.f, -3.f);
// simulate Button behavior : if mouse hovering flag and mouse pressed
ImRect bb(draw_pos, draw_pos + ImVec2(ImGui::GetTextLineHeightWithSpacing(), ImGui::GetTextLineHeightWithSpacing()));
bool hovered = ImGui::ItemHoverable(bb, id);
if (hovered && left_mouse_press) {
flag_pressed = true;
*flag = flag_Interval;
ImGui::MarkItemEdited(id);
}
// icon depends on flag type & color on hovered
ImGui::PushStyleColor( ImGuiCol_Text, ImGui::GetColorU32(ImGuiCol_NavHighlight) );
_drawIcon(draw_pos, 11 + flag_Interval.type, 6, hovered, window);
ImGui::PopStyleColor();
// show time when hovering
if (hovered)
ImGui::SetTooltip(" %s ", GstToolkit::time_to_string(flag_time).c_str());
}
//
// CURSOR
//
bool cursor_pressed = false;
ImRect grab_slider_bb;
ImU32 grab_slider_color = ImGui::GetColorU32(ImGuiCol_SliderGrab);
if (!flag_pressed)
{
float time_slider = time_ * 10.f; // x 10 precision on grab
float time_zero = 0.f;
float time_end = 10.f;
cursor_pressed = ImGui::SliderBehavior(slider_bbox, id, ImGuiDataType_Float, &time_slider, &time_zero,
&time_end, "%.2f", 1.f, ImGuiSliderFlags_None, &grab_slider_bb);
if (cursor_pressed){
*time = static_cast<guint64> ( 0.1 * static_cast<double>(time_slider) * static_cast<double>(tl->duration()) );
if (tl->first() != GST_CLOCK_TIME_NONE)
*time -= tl->first();
grab_slider_color = ImGui::GetColorU32(ImGuiCol_SliderGrabActive);
ImGui::MarkItemEdited(id);
}
}
// draw slider grab handle
if (grab_slider_bb.Max.x > grab_slider_bb.Min.x) {
window->DrawList->AddRectFilled(grab_slider_bb.Min, grab_slider_bb.Max, grab_slider_color, style.GrabRounding);
}
// draw the cursor
pos = ImLerp(timeline_bbox.GetTL(), timeline_bbox.GetTR(), time_) - ImVec2(cursor_width, 2.f);
ImGui::RenderArrow(window->DrawList, pos, ImGui::GetColorU32(ImGuiCol_SliderGrab), ImGuiDir_Up);
return cursor_pressed;
}
std::list< std::pair<float, guint64> > DrawTimeline(const char* label, Timeline *timeline, guint64 time,
double width_ratio, float height)
{
std::list< std::pair<float, guint64> > ret;
// get window
ImGuiWindow* window = ImGui::GetCurrentWindow();
if (window->SkipItems)
return ret;
// get style & id
const ImGuiContext& g = *GImGui;
const ImGuiStyle& style = g.Style;
const float fontsize = g.FontSize;
const ImGuiID id = window->GetID(label);
//
// FIRST PREPARE ALL data structures
//
// fixed elements of timeline
float *lines_array = timeline->fadingArray();
const guint64 duration = timeline->sectionsDuration();
TimeIntervalSet se = timeline->sections();
const ImVec2 timeline_size( static_cast<float>( static_cast<double>(duration) * width_ratio ), 2.f * fontsize);
// widget bounding box
const ImVec2 frame_pos = window->DC.CursorPos;
const ImVec2 frame_size( timeline_size.x + 2.f * style.FramePadding.x, height);
const ImRect bbox(frame_pos, frame_pos + frame_size);
ImGui::ItemSize(frame_size, style.FramePadding.y);
if (!ImGui::ItemAdd(bbox, id))
return ret;
// capture hover to avoid tooltip on plotlines
ImGui::ItemHoverable(bbox, id);
// cursor size
const float cursor_width = 0.5f * fontsize;
// TIMELINE is inside the bbox, at the bottom
const ImVec2 timeline_pos = frame_pos + ImVec2(style.FramePadding.x, frame_size.y - timeline_size.y -style.FramePadding.y);
const ImRect timeline_bbox( timeline_pos, timeline_pos + timeline_size);
// PLOT of opacity is inside the bbox, at the top
const ImVec2 plot_pos = frame_pos + style.FramePadding;
const ImRect plot_bbox( plot_pos, plot_pos + ImVec2(timeline_size.x, frame_size.y - 4.f * style.FramePadding.y - timeline_size.y));
//
// THIRD RENDER
//
// Render the bounding box frame
ImGui::RenderFrame(bbox.Min, bbox.Max, ImGui::GetColorU32(ImGuiCol_FrameBgActive), true, style.FrameRounding);
// loop over sections of sources' timelines
guint64 d = 0;
guint64 e = 0;
ImVec2 section_bbox_min = timeline_bbox.Min;
for (auto section = se.begin(); section != se.end(); ++section) {
// increment duration to adjust horizontal position
d += section->duration();
e = section->end;
const float percent = static_cast<float>(d) / static_cast<float>(duration) ;
ImVec2 section_bbox_max = ImLerp(timeline_bbox.GetBL(), timeline_bbox.GetBR(), percent);
// 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());
// draw the cursor
float time_ = static_cast<float> ( static_cast<double>(time - section->begin) / static_cast<double>(section->duration()) );
if ( time_ > -FLT_EPSILON && time_ < 1.f ) {
ImVec2 pos = ImLerp(section_bbox.GetTL(), section_bbox.GetTR(), time_) - ImVec2(cursor_width, 2.f);
ImGui::RenderArrow(window->DrawList, pos, ImGui::GetColorU32(ImGuiCol_SliderGrab), ImGuiDir_Up);
}
// draw plot of lines
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0,0,0,0));
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.f, 0.f));
ImGui::SetCursorScreenPos(ImVec2(section_bbox_min.x, plot_bbox.Min.y));
// find the index in timeline array of the section start time
size_t i = timeline->fadingIndexAt(section->begin);
// number of values is the index after end time of section (+1), minus the start index
size_t values_count = 1 + timeline->fadingIndexAt(section->end) - i;
ImGui::PlotLines("##linessection", lines_array + i, values_count, 0, NULL, 0.f, 1.f, ImVec2(section_bbox.GetWidth(), plot_bbox.GetHeight()));
ImGui::PopStyleColor(1);
ImGui::PopStyleVar(1);
// detect if there was a gap before
if (i > 0)
window->DrawList->AddRectFilled(ImVec2(section_bbox_min.x -2.f, plot_bbox.Min.y), ImVec2(section_bbox_min.x + 2.f, plot_bbox.Max.y), ImGui::GetColorU32(ImGuiCol_TitleBg));
ret.push_back( std::pair<float, guint64>(section_bbox_min.x,section->begin ) );
ret.push_back( std::pair<float, guint64>(section_bbox_max.x,section->end ) );
// iterate: next bbox of section starts at end of current
section_bbox_min.x = section_bbox_max.x;
}
// detect if there is a gap after
if (e < timeline->duration())
window->DrawList->AddRectFilled(ImVec2(section_bbox_min.x -2.f, plot_bbox.Min.y), ImVec2(section_bbox_min.x + 2.f, plot_bbox.Max.y), ImGui::GetColorU32(ImGuiCol_TitleBg));
return ret;
}
void SourceControlWindow::RenderSelection(size_t i)
{
ImVec2 top = ImGui::GetCursorScreenPos();
ImVec2 rendersize = ImGui::GetContentRegionAvail() - ImVec2(0, buttons_height_ + scrollbar_ + v_space_);
ImVec2 bottom = ImVec2(top.x, top.y + rendersize.y + v_space_);
selection_ = Mixer::manager().session()->getBatch(i);
int numsources = selection_.size();
// no source selected
if (numsources < 1)
{
///
/// Centered text
///
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.6, 0.6, 0.6, 0.5f));
ImVec2 center = rendersize * ImVec2(0.5f, 0.5f);
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_ITALIC);
center.x -= ImGui::GetTextLineHeight() * 2.f;
ImGui::SetCursorScreenPos(top + center);
ImGui::Text("Empty selection");
ImGui::PopFont();
ImGui::PopStyleColor(1);
}
else {
///
/// Sources LIST
///
///
// get max duration and max frame width
GstClockTime maxduration = 0;
std::list<guint64> durations;
float maxframewidth = 0.f;
for (auto source = selection_.begin(); source != selection_.end(); ++source) {
// collect durations of all media sources
MediaSource *ms = dynamic_cast<MediaSource *>(*source);
if (ms != nullptr && !ms->mediaplayer()->singleFrame())
durations.push_back(static_cast<guint64>(static_cast<double>(ms->mediaplayer()->timeline()->sectionsDuration()) / fabs(ms->mediaplayer()->playSpeed())));
// compute the displayed width of frames of this source, and keep the max to align afterwards
float w = 1.5f * timeline_height_ * (*source)->frame()->aspectRatio();
if ( w > maxframewidth)
maxframewidth = w;
}
if (durations.size()>0) {
durations.sort();
durations.unique();
maxduration = durations.back();
}
// compute the ratio for timeline rendering : width (pixel) per time unit (ms)
const float w = rendersize.x -maxframewidth - 3.f * h_space_ - scrollbar_;
const double width_ratio = static_cast<double>(w) / static_cast<double>(maxduration);
// draw list in a scroll area
ImGui::BeginChild("##v_scroll2", rendersize, false);
{
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.f, v_space_));
// draw play time scale if a duration is set
if (maxduration > 0) {
ImGui::SetCursorPos(ImGui::GetCursorPos() + ImVec2( maxframewidth + h_space_, 0));
DrawTimeScale("##timescale", maxduration, width_ratio);
}
// loop over all sources
SourceList selection_sources = selection_;
///
/// First pass: loop over media sources only (with timeline)
///
for (auto source = selection_sources.begin(); source != selection_sources.end(); ) {
// get media source
MediaSource *ms = dynamic_cast<MediaSource *>(*source);
if (ms == nullptr || ms->mediaplayer()->singleFrame()) {
// leave the source in the list and continue
++source;
continue;
}
// ok, get the media player of the media source
MediaPlayer *mp = ms->mediaplayer();
///
/// Source Image Button
///
ImVec2 image_top = ImGui::GetCursorPos();
const ImVec2 framesize(1.5f * timeline_height_ * (*source)->frame()->aspectRatio(), 1.5f * timeline_height_);
int action = SourceButton(*source, framesize);
if (action > 1) {
(*source)->play( ! (*source)->playing() );
Action::manager().store((*source)->playing() ? "Source Play" : "Source Pause" );
}
else if (action > 0)
UserInterface::manager().showSourceEditor(*source);
// icon and text below thumbnail
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO);
ImGuiToolkit::Icon( (*source)->icon().x, (*source)->icon().y);
if ((*source)->playable()) {
ImGui::SameLine(ImGui::GetTextLineHeight(), 0);
ImGui::Text(" %s", GstToolkit::time_to_string((*source)->playtime()).c_str() );
ImGui::SameLine(framesize.x - ImGui::GetTextLineHeightWithSpacing());
if ( mp->syncToMetronome() > Metronome::SYNC_NONE )
ImGuiToolkit::Icon( mp->syncToMetronome() > Metronome::SYNC_BEAT ? 7 : 6, 13);
}
ImGui::PopFont();
// start to draw timeline aligned at maximum frame width + horizontal space
ImVec2 pos = image_top + ImVec2( maxframewidth + h_space_, 0);
ImGui::SetCursorPos(pos);
// draw the mediaplayer's timeline, with the indication of cursor position
// NB: use the same width/time ratio for all to ensure timing vertical correspondance
// TODO : if (mp->syncToMetronome() > Metronome::SYNC_NONE)
DrawTimeline("##timeline_mediaplayer", mp->timeline(), mp->position(),
width_ratio / fabs(mp->playSpeed()), framesize.y);
if ( w > maxframewidth ) {
// next icon buttons are small
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(3.f, 3.f));
// next buttons sub id
ImGui::PushID( static_cast<int>(mp->id()));
// display play speed
ImGui::SetCursorPos(pos + ImVec2( 0.f, framesize.y + v_space_));
ImGui::Text(UNICODE_MULTIPLY " %.2f", mp->playSpeed());
// if not 1x speed, offer to reset
if ( fabs( fabs(mp->playSpeed()) - 1.0 ) > EPSILON ) {
ImGui::SameLine(0,h_space_);
if (ImGuiToolkit::ButtonIcon(19, 15, "Reset speed"))
mp->setPlaySpeed( 1.0 );
}
// if more than one duration of media players, add buttons to adjust
if (durations.size() > 1)
{
for (auto d = durations.crbegin(); d != durations.crend(); ++d) {
// next buttons sub id
ImGui::PushID( static_cast<int>(*d));
// calculate position of icons
double x = static_cast<double>(*d) * width_ratio;
ImGui::SetCursorPos(pos + ImVec2( static_cast<float>(x) - 2.f, framesize.y + v_space_) );
// depending on position relative to media play duration, offer corresponding action
double secdur = static_cast<double>(mp->timeline()->sectionsDuration());
guint64 playdur = static_cast<guint64>( secdur / fabs(mp->playSpeed()) );
// last icon in the timeline
if ( playdur == (*d) ) {
// not the minimum duration :
if (playdur > durations.front() ) {
// offer to speed up or slow down [<>]
if (playdur < durations.back() ) {
if ( ImGuiToolkit::ButtonIcon(0, 12, "Adjust duration") ) {
auto prev = d;
prev--;
selection_target_slower_ = SIGN(mp->playSpeed()) * secdur / static_cast<double>(*prev);
auto next = d;
next++;
selection_target_faster_ = SIGN(mp->playSpeed()) * secdur / static_cast<double>(*next);
selection_mediaplayer_ = mp;
selection_context_menu_ = true;
}
}
// offer to speed up [< ]
else if ( ImGuiToolkit::ButtonIcon(8, 12, "Adjust duration") ) {
auto next = d;
next++;
selection_target_faster_ = SIGN(mp->playSpeed()) * secdur / static_cast<double>(*next);
selection_target_slower_ = 0.0;
selection_mediaplayer_ = mp;
selection_context_menu_ = true;
}
}
// minimum duration : offer to slow down [ >]
else if ( ImGuiToolkit::ButtonIcon(9, 12, "Adjust duration") ) {
selection_target_faster_ = 0.0;
auto prev = d;
prev--;
selection_target_slower_ = SIGN(mp->playSpeed()) * secdur / static_cast<double>(*prev);
selection_mediaplayer_ = mp;
selection_context_menu_ = true;
}
}
// middle buttons : offer to cut at this position
else if ( playdur > (*d) ) {
char text_buf[256];
GstClockTime cutposition = mp->timeline()->sectionsTimeAt( (*d) * fabs(mp->playSpeed()) );
ImFormatString(text_buf, IM_ARRAYSIZE(text_buf), "Cut at %s",
GstToolkit::time_to_string(cutposition, GstToolkit::TIME_STRING_MINIMAL).c_str());
if ( ImGuiToolkit::ButtonIcon(11, 3, text_buf) ) {
if ( mp->timeline()->cut(cutposition, false, true) ) {
std::ostringstream info;
info << SystemToolkit::base_filename( mp->filename() ) << ": Timeline " <<text_buf;
Action::manager().store(info.str());
}
}
}
ImGui::PopID();
}
}
// special case when all media players are (cut to) the same size
else if ( durations.size() > 0) {
// calculate position of icon
double x = static_cast<double>(durations.front()) * width_ratio;
ImGui::SetCursorPos(pos + ImVec2( static_cast<float>(x) - 2.f, framesize.y + v_space_) );
// offer only to adjust size by removing ending gap
if ( mp->timeline()->gapAt( mp->timeline()->end() ) ) {
if ( ImGuiToolkit::ButtonIcon(7, 0, "Remove end gap" )){
if ( mp->timeline()->removeGaptAt(mp->timeline()->end()) ) {
std::ostringstream info;
info << SystemToolkit::base_filename( mp->filename() ) << ": Timeline Remove end gap";
Action::manager().store(info.str());
}
}
}
}
ImGui::PopStyleVar();
ImGui::PopID();
}
// next line position
ImGui::SetCursorPos(image_top + ImVec2(0, 2.0f * timeline_height_ + 2.f * v_space_));
// next source
source = selection_sources.erase(source);
}
ImGui::Spacing();
///
/// Second pass: loop over remaining sources (no timeline)
///
ImGui::Columns( MAX( 1, MIN( int(ceil(w / 250.f)), (int)selection_sources.size()) ), "##selectioncolumns", false);
for (auto source = selection_sources.begin(); source != selection_sources.end(); ++source) {
///
/// Source Image Button
///
const ImVec2 framesize(1.5f * timeline_height_ * (*source)->frame()->aspectRatio(), 1.5f * timeline_height_);
int action = SourceButton(*source, framesize);
if (action > 1) {
(*source)->play( ! (*source)->playing() );
Action::manager().store((*source)->playing() ? "Source Play" : "Source Pause" );
}
else if (action > 0)
UserInterface::manager().showSourceEditor(*source);
// icon and text below thumbnail
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO);
ImGuiToolkit::Icon( (*source)->icon().x, (*source)->icon().y);
if ((*source)->playable()) {
ImGui::SameLine(ImGui::GetTextLineHeight(), 0);
ImGui::Text(" %s", GstToolkit::time_to_string((*source)->playtime()).c_str() );
}
ImGui::PopFont();
// next line position
ImGui::Spacing();
ImGui::NextColumn();
}
ImGui::Columns(1);
ImGui::PopStyleVar();
}
ImGui::EndChild();
}
///
/// context menu from actions above
///
RenderSelectionContextMenu();
///
/// Play button bar
///
DrawButtonBar(bottom, rendersize.x);
///
/// Selection of sources
///
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.00f, 0.00f, 0.00f, 0.00f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.14f, 0.14f, 0.14f, 0.5f));
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.00f, 0.00f, 0.00f, 0.00f));
ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(0.14f, 0.14f, 0.14f, 0.7f));
float width_combo = ImGui::GetContentRegionAvail().x - buttons_height_;
if (width_combo > buttons_width_)
{
ImGui::SameLine(0, width_combo -buttons_width_ );
ImGui::SetNextItemWidth(buttons_width_);
std::string label = std::string(ICON_FA_CHECK_CIRCLE " ") + std::to_string(numsources) + ( numsources > 1 ? " sources" : " source");
if (ImGui::BeginCombo("##SelectionImport", label.c_str()))
{
// select all playable sources
for (auto s = Mixer::manager().session()->begin(); s != Mixer::manager().session()->end(); ++s) {
if ( !(*s)->failed() ) {
std::string label = std::string((*s)->initials()) + " - " + (*s)->name();
if (std::find(selection_.begin(),selection_.end(),*s) == selection_.end()) {
if (ImGui::MenuItem( label.c_str() , 0, false ))
Mixer::manager().session()->addSourceToBatch(i, *s);
}
else {
if (ImGui::MenuItem( label.c_str(), 0, true ))
Mixer::manager().session()->removeSourceFromBatch(i, *s);
}
}
}
ImGui::EndCombo();
}
}
ImGui::SameLine();
ImGui::SetCursorPosX(rendersize.x - buttons_height_ / 1.3f);
if (ImGui::Button(ICON_FA_TIMES_CIRCLE)) {
resetActiveSelection();
Mixer::manager().session()->deleteBatch(i);
}
if (ImGui::IsItemHovered())
ImGuiToolkit::ToolTip("Delete batch");
ImGui::PopStyleColor(4);
}
void SourceControlWindow::RenderSelectionContextMenu()
{
if (selection_mediaplayer_ == nullptr)
return;
if (selection_context_menu_) {
ImGui::OpenPopup("source_controller_selection_context_menu");
selection_context_menu_ = false;
}
if (ImGui::BeginPopup("source_controller_selection_context_menu"))
{
std::ostringstream info;
info << SystemToolkit::base_filename( selection_mediaplayer_->filename() );
if ( ImGuiToolkit::MenuItemIcon(14, 16, ICON_FA_CARET_LEFT " Accelerate", NULL, false, fabs(selection_target_faster_) > 0 )){
selection_mediaplayer_->setPlaySpeed( selection_target_faster_ );
info << ": Speed x" << std::setprecision(3) << selection_target_faster_;
Action::manager().store(info.str());
}
if ( ImGuiToolkit::MenuItemIcon(15, 16, "Slow down " ICON_FA_CARET_RIGHT, NULL, false, fabs(selection_target_slower_) > 0 )){
selection_mediaplayer_->setPlaySpeed( selection_target_slower_ );
info << ": Speed x" << std::setprecision(3) << selection_target_slower_;
Action::manager().store(info.str());
}
if ( selection_mediaplayer_->timeline()->gapAt( selection_mediaplayer_->timeline()->end()) ) {
if ( ImGuiToolkit::MenuItemIcon(7, 0, "Restore ending" )){
info << ": Restore ending";
if ( selection_mediaplayer_->timeline()->removeGaptAt(selection_mediaplayer_->timeline()->end()) )
Action::manager().store(info.str());
}
}
ImGui::EndPopup();
}
}
void DrawInspector(uint texture, ImVec2 texturesize, ImVec2 cropsize, ImVec2 origin)
{
if (Settings::application.source.inspector_zoom > 0 && ImGui::IsWindowFocused())
{
// region size is computed with zoom factor from settings
float region_sz = texturesize.x / Settings::application.source.inspector_zoom;
// get coordinates of area to zoom at mouse position
const ImGuiIO& io = ImGui::GetIO();
float region_x = io.MousePos.x - origin.x - region_sz * 0.5f;
if (region_x < 0.f)
region_x = 0.f;
else if (region_x > texturesize.x - region_sz)
region_x = texturesize.x - region_sz;
float region_y = io.MousePos.y - origin.y - region_sz * 0.5f;
if (region_y < 0.f)
region_y = 0.f;
else if (region_y > texturesize.y - region_sz)
region_y = texturesize.y - region_sz;
// Tooltip without border and 100% opaque
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 1.f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.f, 0.f) );
ImGui::BeginTooltip();
// compute UV and display image in tooltip
ImVec2 uv0 = ImVec2((region_x) / cropsize.x, (region_y) / cropsize.y);
ImVec2 uv1 = ImVec2((region_x + region_sz) / cropsize.x, (region_y + region_sz) / cropsize.y);
ImVec2 uv2 = ImClamp( uv1, ImVec2(0.f, 0.f), ImVec2(1.f, 1.f));
uv0 += (uv2-uv1);
ImGui::Image((void*)(intptr_t)texture, ImVec2(texturesize.x / 3.f, texturesize.x / 3.f), uv0, uv2, ImVec4(1.0f, 1.0f, 1.0f, 1.0f), ImVec4(1.0f, 1.0f, 1.0f, 0.5f));
ImGui::EndTooltip();
ImGui::PopStyleVar(3);
}
}
void SourceControlWindow::DrawSource(Source *s, ImVec2 framesize, ImVec2 top_image, bool withslider, bool withinspector)
{
if (!s)
return;
// 100% opacity for the image (ensure true colors)
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 1.f);
// pre-draw background with checkerboard pattern
ImGui::Image((void*)(uintptr_t) checker_background_->texture(), framesize,
ImVec2(0,0), ImVec2(framesize.x/CHECKER_RESOLUTION, framesize.y/CHECKER_RESOLUTION));
// get back to top image corner after draw
ImGui::SetCursorScreenPos(top_image);
// pre-calculate cropping areas of source and cropped rendering area
const glm::vec4 _crop = s->frame()->projectionArea();
const ImVec2 _cropsize = framesize * ImVec2(0.5f * (_crop[1] - _crop[0]),
0.5f * (_crop[2] - _crop[3]));
const ImVec2 _croptop = framesize * ImVec2(0.5f * (1.f + _crop[0]),
0.5f * (1.f - _crop[2]) );
// pre-calculate slider coordinates in rendering area
ImVec2 slider = framesize * ImVec2(Settings::application.widget.media_player_slider,1.f);
// draw pre and post-processed parts if necessary
if ( s->texturePostProcessed() )
{
//
// LEFT of slider : original texture
//
ImGui::Image((void*)(uintptr_t) s->texture(), slider, ImVec2(0.f,0.f), ImVec2(Settings::application.widget.media_player_slider,1.f));
//
// RIGHT of slider : post-processed image (after crop and color correction)
//
// no overlap of slider with cropped area
if (slider.x < _croptop.x) {
// draw cropped area
ImGui::SetCursorScreenPos( top_image + _croptop );
ImGui::Image((void*)(uintptr_t) s->frame()->texture(), _cropsize, ImVec2(0.f, 0.f), ImVec2(1.f,1.f));
}
// overlap of slider with cropped area (horizontally)
else if (slider.x < _croptop.x + _cropsize.x ) {
// compute slider ratio of cropped area
float cropped_slider = (slider.x - _croptop.x) / _cropsize.x;
// top x moves with slider
ImGui::SetCursorScreenPos( top_image + ImVec2(slider.x, _croptop.y) );
// size is reduced by slider
ImGui::Image((void*)(uintptr_t) s->frame()->texture(), _cropsize * ImVec2(1.f -cropped_slider, 1.f), ImVec2(cropped_slider, 0.f), ImVec2(1.f,1.f));
}
// else : no render of cropped area
//
// SLIDER
//
ImDrawList* draw_list = ImGui::GetWindowDrawList();
ImU32 slider_color = ImGui::GetColorU32(ImGuiCol_NavWindowingHighlight);
if (withslider && !withinspector)
{
// user input : move slider horizontally
ImGui::SetCursorScreenPos(top_image + ImVec2(- 20.f, 0.5f * framesize.y - 20.0f));
ImGuiToolkit::InvisibleSliderFloat("#media_player_slider2", &Settings::application.widget.media_player_slider, 0.f, 1.f, ImVec2(framesize.x + 40.f, 40.0f) );
// affordance: cursor change to horizontal arrows
if (ImGui::IsItemHovered() || ImGui::IsItemFocused()) {
ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW);
slider_color = ImGui::GetColorU32(ImGuiCol_Text);
}
// graphical indication of slider
draw_list->AddCircleFilled(top_image + slider * ImVec2(1.f, 0.5f), 20.f, slider_color, 26);
}
// graphical indication of separator (vertical line)
draw_list->AddLine(top_image + slider * ImVec2(1.f,0.0f), top_image + slider, slider_color, 1);
}
// no post-processed to show: draw simple texture
else {
ImGui::Image((void *) (uintptr_t) s->frame()->texture(), framesize);
slider = framesize;
}
//
// Handle mouse clic and hovering on image
//
const ImRect bb(top_image, top_image + framesize);
const ImGuiID id = ImGui::GetCurrentWindow()->GetID("##source-texture");
bool hovered, held;
bool pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_PressedOnClick);
// toggle preview on double clic
if (pressed) {
// trigger to show preview (will be ignored if not double clic)
UserInterface::manager().show_preview = UserInterface::PREVIEW_SOURCE;
// cancel the button behavior to let window move on drag
ImGui::SetActiveID(0, ImGui::GetCurrentWindow());
ImGui::SetHoveredID(0);
}
// show magnifying glass if hovering and inspector is active
else if (hovered && withinspector) {
// different texture to show depending on mouse position of slider
if (ImGui::IsMouseHoveringRect(top_image, top_image + slider))
// inspector over left side of slider
DrawInspector(s->texture(), framesize, framesize, top_image);
else
// inspector over right side of slider
DrawInspector(s->frame()->texture(), framesize, _cropsize, top_image + _croptop);
}
// pop ImGuiStyleVar_Alpha
ImGui::PopStyleVar();
}
ImRect SourceControlWindow::DrawSourceWithSlider(Source *s, ImVec2 top, ImVec2 rendersize, bool with_inspector)
{
///
/// Centered frame
///
FrameBuffer *frame = s->frame();
ImVec2 framesize = rendersize;
ImVec2 corner(0.f, 0.f);
ImVec2 tmp = ImVec2(framesize.y * frame->aspectRatio(), framesize.x / frame->aspectRatio());
if (tmp.x > framesize.x) {
//vertically centered, modulo height of font for display of info under frame
corner.y = MAX( (framesize.y - tmp.y) / 2.f - ImGui::GetStyle().IndentSpacing, 0.f);
framesize.y = tmp.y;
}
else {
// horizontally centered
// corner.x = (framesize.x - tmp.x) - MAX( (framesize.x - tmp.x) / 2.f - ImGui::GetStyle().IndentSpacing, 0.f);
corner.x = (framesize.x - tmp.x) / 2.f;
framesize.x = tmp.x;
}
///
/// Image
///
const ImVec2 top_image = top + corner;
ImGui::SetCursorScreenPos(top_image);
// pre-draw background with checkerboard pattern
ImGui::Image((void*)(uintptr_t) checker_background_->texture(), framesize,
ImVec2(0,0), ImVec2(framesize.x/CHECKER_RESOLUTION, framesize.y/CHECKER_RESOLUTION));
// draw source
if (s->ready()) {
ImGui::SetCursorScreenPos(top_image);
DrawSource(s, framesize, top_image, true, with_inspector);
}
return ImRect( top_image, top_image + framesize);
}
int SourceControlWindow::SourceButton(Source *s, ImVec2 framesize)
{
// returns > 0 if clicked, >1 if clicked on center play/pause button
int ret = 0;
// all subsequent buttons are identified under a unique source id
ImGui::PushID(s->id());
// Adjust size of font to frame size
ImGuiToolkit::PushFont(framesize.x > 350.f ? ImGuiToolkit::FONT_LARGE : ImGuiToolkit::FONT_MONO);
ImDrawList* draw_list = ImGui::GetWindowDrawList();
const float H = ImGui::GetTextLineHeight();
const ImVec2 frame_top = ImGui::GetCursorScreenPos();
const ImVec2 frame_center = frame_top + ImVec2((framesize.x - H) / 2.f, (framesize.y - H) / 2.f);
ImU32 frame_color = ImGui::GetColorU32(ImGuiCol_Text);
ImU32 icon_color = ImGui::GetColorU32(ImGuiCol_NavWindowingHighlight);
// 1. draw texture of source
if (s->ready())
DrawSource(s, framesize, frame_top);
// 2. interactive centered button Play / Pause
if (s->active() && s->playable()) {
//draw_list->AddText(frame_center, ImGui::GetColorU32(ImGuiCol_Text), SourcePlayIcon(s));
ImGui::SetCursorScreenPos(frame_center - ImVec2(H * 0.2f, H * 0.2f));
ImGui::InvisibleButton("##sourcebutton_icon", ImVec2(H * 1.2f, H * 1.2f));
if (ImGui::IsItemHovered() || ImGui::IsItemActive()){
frame_color = ImGui::GetColorU32(ImGuiCol_NavWindowingHighlight);
icon_color = ImGui::GetColorU32(ImGuiCol_Text);
}
if (ImGui::IsItemClicked()) {
ret = 2;
}
}
// back to draw overlay
ImGui::SetCursorScreenPos(frame_top + ImVec2(1.f, 0.f) );
// 3. draw initials in up-left corner
draw_list->AddText(frame_top + ImVec2(H * 0.2f, H * 0.1f), ImGui::GetColorU32(ImGuiCol_Text), s->initials());
// 4. interactive surface on whole texture with frame overlay on mouse over
ImGui::InvisibleButton("##sourcebutton", framesize);
if (ImGui::IsItemHovered() || ImGui::IsItemClicked())
{
// border
draw_list->AddRect(frame_top, frame_top + framesize - ImVec2(1.f, 0.f), frame_color, 0, 0, 3.f);
// centered icon in front of dark background
if (s->active() && s->playable()) {
draw_list->AddRectFilled(frame_center - ImVec2(H * 0.3f, H * 0.2f),
frame_center + ImVec2(H * 1.1f, H * 1.2f), ImGui::GetColorU32(ImGuiCol_TitleBgCollapsed), 6.f);
draw_list->AddText(frame_center, icon_color, s->playing() ? ICON_FA_PAUSE : ICON_FA_PLAY);
}
}
if (ImGui::IsItemClicked()) {
ret = 1;
}
// pops
ImGui::PopFont();
ImGui::PopID();
return ret;
}
void SourceControlWindow::RenderSelectedSources()
{
ImVec2 top = ImGui::GetCursorScreenPos();
ImVec2 rendersize = ImGui::GetContentRegionAvail() - ImVec2(0, buttons_height_ + scrollbar_ + v_space_);
ImVec2 bottom = ImVec2(top.x, top.y + rendersize.y + v_space_);
// get new selection or validate previous list if selection was not updated
if (Mixer::selection().empty())
selection_ = valid_only(Mixer::manager().validate(selection_));
else
selection_ = valid_only(Mixer::selection().getCopy());
int numsources = selection_.size();
// no source selected
if (numsources < 1)
{
///
/// Centered text
///
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.6, 0.6, 0.6, 0.5f));
ImVec2 center = rendersize * ImVec2(0.5f, 0.5f);
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_ITALIC);
center.x -= ImGui::GetTextLineHeight() * 2.f;
ImGui::SetCursorScreenPos(top + center);
ImGui::Text("Nothing to play");
ImGui::PopFont();
ImGui::PopStyleColor(1);
///
/// Play button bar (automatically disabled)
///
DrawButtonBar(bottom, rendersize.x);
}
// single source selected
else if (numsources < 2)
{
///
/// Single Source display
///
RenderSingleSource( selection_.front() );
}
// Several sources selected
else {
///
/// Sources grid
///
ImGui::BeginChild("##v_scroll", rendersize, false);
{
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.f, 2.f * v_space_));
// area horizontal
rendersize.y -= buttons_height_ + 2.f * v_space_;
int numcolumns = CLAMP( int(ceil(rendersize.x / rendersize.y)), 1, numsources );
ImGui::Columns( numcolumns, "##selectiongrid", false);
float widthcolumn = rendersize.x / static_cast<float>(numcolumns);
widthcolumn -= scrollbar_;
// loop over sources in grid
for (auto source = selection_.begin(); source != selection_.end(); ++source) {
///
/// Source Image Button
///
ImVec2 image_top = ImGui::GetCursorPos();
ImVec2 framesize(widthcolumn, widthcolumn / (*source)->frame()->aspectRatio());
int action = SourceButton(*source, framesize);
if (action > 1) {
(*source)->play( ! (*source)->playing() );
Action::manager().store((*source)->playing() ? "Source Play" : "Source Pause" );
}
else if (action > 0)
UserInterface::manager().showSourceEditor(*source);
// source icon lower left corner
ImGuiToolkit::PushFont(framesize.x > 350.f ? ImGuiToolkit::FONT_LARGE : ImGuiToolkit::FONT_MONO);
float h = ImGui::GetTextLineHeightWithSpacing();
ImGui::SetCursorPos(image_top + ImVec2( h_space_, framesize.y -v_space_ - h ));
ImGuiToolkit::Icon( (*source)->icon().x, (*source)->icon().y);
if ((*source)->playable()) {
ImGui::SameLine();
ImGui::Text(" %s", GstToolkit::time_to_string((*source)->playtime()).c_str() );
}
ImGui::PopFont();
ImGui::Spacing();
ImGui::NextColumn();
}
ImGui::Columns(1);
ImGui::PopStyleVar();
}
ImGui::EndChild();
///
/// Play button bar
///
DrawButtonBar(bottom, rendersize.x);
///
/// Menu to store Selection from current sources
///
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.00f, 0.00f, 0.00f, 0.00f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.14f, 0.14f, 0.14f, 0.5f));
float space = ImGui::GetContentRegionAvail().x;
float width = buttons_height_;
std::string label(ICON_FA_PLUS_CIRCLE);
if (space > buttons_width_) { // enough space to show full button with label text
label += LABEL_PLAYER_BATCH_ADD;
width = buttons_width_ - ImGui::GetTextLineHeightWithSpacing();
}
ImGui::SameLine(0, space -width);
ImGui::SetNextItemWidth(width);
if (ImGui::Button( label.c_str() )) {
active_selection_ = Mixer::manager().session()->numBatch();
active_label_ = std::string("Batch #") + std::to_string(active_selection_);
Mixer::manager().session()->addBatch( ids(selection_) );
}
if (space < buttons_width_ && ImGui::IsItemHovered())
ImGuiToolkit::ToolTip(LABEL_PLAYER_BATCH_ADD);
ImGui::PopStyleColor(2);
}
}
void SourceControlWindow::RenderSingleSource(Source *s)
{
static bool show_overlay_info = false;
if ( s == nullptr)
return;
// in case of a MediaSource
MediaSource *ms = dynamic_cast<MediaSource *>(s);
if ( ms != nullptr && ms->playable() ) {
RenderMediaPlayer( ms );
}
else
{
///
/// Draw centered Image
///
ImVec2 top = ImGui::GetCursorScreenPos();
ImVec2 rendersize = ImGui::GetContentRegionAvail() - ImVec2(0, buttons_height_ + scrollbar_ + v_space_);
ImVec2 bottom = ImVec2(top.x, top.y + rendersize.y + v_space_);
ImRect imgarea = DrawSourceWithSlider(s, top, rendersize, magnifying_glass);
///
/// Info overlays
///
if (!show_overlay_info){
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.f, 0.f, 0.f, 0.8f));
ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2(h_space_-1.f, v_space_-1.f));
ImGui::Text("%s", s->initials());
ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2(h_space_+1.f, v_space_+1.f));
ImGui::Text("%s", s->initials());
ImGui::PopStyleColor(1);
ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2(h_space_, v_space_));
ImGui::Text("%s", s->initials());
ImGui::PopFont();
}
if (!magnifying_glass) {
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.f, 0.f, 0.f, 0.8f));
ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2(imgarea.GetWidth() - ImGui::GetTextLineHeightWithSpacing(), v_space_));
ImGui::Text(ICON_FA_CIRCLE);
ImGui::PopStyleColor(1);
ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2(imgarea.GetWidth() - ImGui::GetTextLineHeightWithSpacing(), v_space_));
ImGui::Text(ICON_FA_INFO_CIRCLE);
show_overlay_info = ImGui::IsItemHovered();
if (show_overlay_info){
// fill info string
s->accept(info_);
// draw overlay frame and text
float tooltip_height = 3.f * ImGui::GetTextLineHeightWithSpacing();
ImGui::GetWindowDrawList()->AddRectFilled(imgarea.GetTL(), imgarea.GetTL() + ImVec2(imgarea.GetWidth(), tooltip_height), IMGUI_COLOR_OVERLAY);
ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2(h_space_, v_space_));
ImGui::Text("%s", info_.str().c_str());
// special case Streams: print framerate
StreamSource *sts = dynamic_cast<StreamSource*>(s);
if (sts && s->playing()) {
ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2(imgarea.GetWidth() - 1.5f * buttons_height_, 0.5f * tooltip_height));
ImGui::Text("%.1f Hz", sts->stream()->updateFrameRate());
}
}
else
// make sure next ItemHovered refreshes the info_
info_.reset();
}
///
/// icon & timing in lower left corner
///
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
ImGui::SetCursorScreenPos(bottom + ImVec2(h_space_, -ImGui::GetTextLineHeightWithSpacing() - h_space_ ));
ImGuiToolkit::Icon( s->icon().x, s->icon().y);
ImGui::SameLine();
ImGui::Text("%s", s->playable() ? GstToolkit::time_to_string(s->playtime()).c_str() : " " );
ImGui::PopFont();
///
/// Play button bar
///
DrawButtonBar(bottom, rendersize.x);
///
/// Special possibly : selected a media source that is not playable
/// then offer to make it playable by adding a timeline
///
if ( ms != nullptr )
{
if (ms->mediaplayer()->isImage()) {
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.00f, 0.00f, 0.00f, 0.00f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.14f, 0.14f, 0.14f, 0.5f));
float space = ImGui::GetContentRegionAvail().x;
float width = buttons_height_ ;
if (space > buttons_width_)
width = buttons_width_ - ImGui::GetTextLineHeightWithSpacing();
ImGui::SameLine(0, space -width);
ImGui::SetNextItemWidth(width);
if (ImGuiToolkit::ButtonIcon( 0, 14, LABEL_PLAYER_TIMELINE_ADD, true, space > buttons_width_ )) {
// activate mediaplayer
mediaplayer_active_ = ms->mediaplayer();
// set timeline to default 1 second
Timeline tl;
TimeInterval interval(0, GST_SECOND);
// set fixed framerate to 25 FPS
tl.setTiming( interval, 40 * GST_MSECOND);
mediaplayer_active_->setTimeline(tl);
// set to play
mediaplayer_active_->play(true);
// re-open the image with a timeline
mediaplayer_active_->reopen();
// open dialog to set duration
mediaplayer_set_duration_ = 2;
}
ImGui::PopStyleColor(2);
}
}
///
/// Not a media source, but playable source
/// Offer context menu if it is a Stream source
///
else if ( s->active() && s->playable() ) {
StreamSource *ss = dynamic_cast<StreamSource *>(s);
if ( ss != nullptr ) {
static uint counter_menu_timeout = 0;
ImGui::SameLine();
ImGui::SetCursorPosX(rendersize.x - buttons_height_ / 1.4f);
if (ImGuiToolkit::IconButton(5, 8) || ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) {
counter_menu_timeout=0;
ImGui::OpenPopup( "MenuStreamOptions" );
}
if (ImGui::BeginPopup( "MenuStreamOptions" ))
{
if (ImGui::MenuItem(ICON_FA_REDO_ALT " Reload"))
ss->reload();
// NB: ss is playable (tested above), and thus ss->stream() is not null
bool option = ss->stream()->rewindOnDisabled();
if (ImGui::MenuItem(ICON_FA_SNOWFLAKE " Restart on reactivation", NULL, &option ))
ss->stream()->setRewindOnDisabled(option);
if (ImGui::IsWindowHovered())
counter_menu_timeout=0;
else if (++counter_menu_timeout > 10)
ImGui::CloseCurrentPopup();
ImGui::EndPopup();
}
}
}
}
}
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();
}
if (ImGui::IsItemHovered())
ImGui::SetMouseCursor(7); // ImGuiMouseCursor_Hand
}
void SourceControlWindow::RenderMediaPlayer(MediaSource *ms)
{
static bool show_overlay_info = false;
static std::vector< std::pair<int,int> > editmode_icon = { {8, 3}, {7, 4}, {12, 6} };
editmode_icon[2] = { Settings::application.widget.media_player_timeline_flag + 11, 6 };
static std::vector< std::string > editmode_tooltip = { "Cutting tool", "Fading tool", "Flag tool" };
mediaplayer_active_ = ms->mediaplayer();
// for action manager
std::ostringstream oss;
oss << SystemToolkit::base_filename( mediaplayer_active_->filename() );
// for draw
const float slider_zoom_width = timeline_height_ / 2.f;
ImDrawList* draw_list = ImGui::GetWindowDrawList();
///
/// Draw centered Image
///
const ImVec2 top = ImGui::GetCursorScreenPos();
const ImVec2 rendersize = ImGui::GetContentRegionAvail() - ImVec2(0, mediaplayer_height_);
ImVec2 bottom = ImVec2(top.x, top.y + rendersize.y + v_space_);
ImRect imgarea = DrawSourceWithSlider(ms, top, rendersize, magnifying_glass);
///
/// Info overlays
///
if (!show_overlay_info){
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.f, 0.f, 0.f, 0.8f));
ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2(h_space_-1.f, v_space_-1.f));
ImGui::Text("%s", ms->initials());
ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2(h_space_+1.f, v_space_+1.f));
ImGui::Text("%s", ms->initials());
ImGui::PopStyleColor(1);
ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2(h_space_, v_space_));
if ( Settings::application.accept_audio && ms->audioFlags() & Source::Audio_enabled )
// Icon to inform audio decoding
ImGui::Text("%s " ICON_FA_VOLUME_UP, ms->initials());
else
ImGui::Text("%s", ms->initials());
ImGui::PopFont();
}
if (!magnifying_glass) {
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.f, 0.f, 0.f, 0.8f));
ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2(imgarea.GetWidth() - ImGui::GetTextLineHeightWithSpacing(), v_space_));
ImGui::Text(ICON_FA_CIRCLE);
ImGui::PopStyleColor(1);
ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2(imgarea.GetWidth() - ImGui::GetTextLineHeightWithSpacing(), v_space_));
ImGui::Text(ICON_FA_INFO_CIRCLE);
show_overlay_info = ImGui::IsItemHovered();
if (show_overlay_info){
// information visitor
mediaplayer_active_->accept(info_);
float tooltip_height = 3.f * ImGui::GetTextLineHeightWithSpacing();
draw_list->AddRectFilled(imgarea.GetTL(), imgarea.GetTL() + ImVec2(imgarea.GetWidth(), tooltip_height), IMGUI_COLOR_OVERLAY);
ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2(h_space_, v_space_));
ImGui::Text("%s", info_.str().c_str());
// Icon to inform audio decoding
if ( ms->audioFlags() & Source::Audio_enabled ) {
ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2( imgarea.GetWidth() - 2.f * ImGui::GetTextLineHeightWithSpacing(), 0.35f * tooltip_height));
ImGui::Text(ICON_FA_VOLUME_UP);
}
// Icon to inform hardware decoding
if ( mediaplayer_active_->decoderName().compare("software") != 0) {
ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2( imgarea.GetWidth() - ImGui::GetTextLineHeightWithSpacing(), 0.35f * tooltip_height));
ImGui::Text(ICON_FA_MICROCHIP);
}
// refresh frequency
if ( mediaplayer_active_->isPlaying()) {
ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2( imgarea.GetWidth() - 1.5f * buttons_height_, 0.667f * tooltip_height));
ImGui::Text("%.1f Hz", mediaplayer_active_->updateFrameRate());
}
}
}
///
/// icon & timing in lower left corner
///
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
ImVec2 S (h_space_, -ImGui::GetTextLineHeightWithSpacing() - h_space_ );
ImGui::SetCursorScreenPos(bottom + S);
ImGuiToolkit::Icon( ms->icon().x, ms->icon().y);
ImGui::SameLine();
ImGui::Text( "%s", GstToolkit::time_to_string(mediaplayer_active_->position()).c_str() );
///
/// Sync info lower right corner
///
Metronome::Synchronicity sync = mediaplayer_active_->syncToMetronome();
if ( sync > Metronome::SYNC_NONE) {
static bool show = true;
if (mediaplayer_active_->pending())
show = !show;
else
show = true;
if (show) {
S.x = rendersize.x + S.y;
ImGui::SetCursorScreenPos(bottom + S);
ImGuiToolkit::Icon( sync > Metronome::SYNC_BEAT ? 7 : 6, 13);
}
}
ImGui::PopFont();
///
/// media player timelines
///
static std::vector< std::pair<int, int> > icons_loop = { {0, 15}, {1, 15}, {19, 14}, {18, 14} };
static std::vector< std::string > tooltips_loop = { "Stop at end", "Loop to start", "Bounce (reverse speed)", "Stop and blackout at end" };
static std::vector< std::pair<int, int> > icons_flags = { {11, 6}, {12, 6}, {13, 6} };
static std::vector< std::string > tooltips_flags = { " Bookmark", " Stop Flag", " Blackout Flag" };
double current_play_speed = mediaplayer_active_->playSpeed();
static uint counter_menu_timeout = 0;
const ImVec2 scrollwindow = ImVec2(ImGui::GetContentRegionAvail().x - slider_zoom_width - 3.0,
2.f * timeline_height_ + scrollbar_ );
if ( mediaplayer_active_->isEnabled() ) {
// ignore actual play status of mediaplayer when slider is pressed
if (!mediaplayer_slider_pressed_)
mediaplayer_mode_ = mediaplayer_active_->isPlaying();
// seek position
guint64 seek_t = mediaplayer_active_->position();
TimeInterval seek_flag;
// scrolling sub-window
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(1.f, 1.f));
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 1.f);
ImGui::PushStyleColor(ImGuiCol_ScrollbarBg, ImVec4(0.f, 0.f, 0.f, 0.0f));
ImGui::SetCursorScreenPos(bottom + ImVec2(1.f, 0.f));
ImGui::BeginChild("##scrolling", scrollwindow, false, ImGuiWindowFlags_HorizontalScrollbar);
{
ImVec2 size = ImGui::CalcItemSize(ImVec2(-FLT_MIN, 0.0f), ImGui::CalcItemWidth(), timeline_height_ -1);
size.x *= mediaplayer_timeline_zoom_;
Timeline *tl = mediaplayer_active_->timeline();
if (tl->is_valid())
{
bool released = false;
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 == 0)
oss << ": Timeline cut";
else if (Settings::application.widget.media_player_timeline_editmode == 1)
oss << ": Timeline fading";
else
oss << ": Timeline flags";
Action::manager().store(oss.str());
}
// custom timeline slider
// TODO : if (mediaplayer_active_->syncToMetronome() > Metronome::SYNC_NONE)
mediaplayer_slider_pressed_ = TimelineSlider("##timeline", &seek_t, &seek_flag, tl, size.x);
}
}
ImGui::EndChild();
// action mode
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));
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_));
// select timeline editmode; cut, fade, flag
ImGuiToolkit::IconMultistate(editmode_icon, &Settings::application.widget.media_player_timeline_editmode, editmode_tooltip );
// zoom slider
ImGui::SetCursorScreenPos(bottom + ImVec2(0.f, timeline_height_));
ImGui::VSliderFloat("##TimelineZoom", ImVec2(slider_zoom_width, timeline_height_), &mediaplayer_timeline_zoom_, 1.0, 5.f, "");
ImGui::PopStyleVar(2);
ImGui::PopStyleColor(1);
///
/// media player buttons bar (custom)
///
bottom.x = top.x;
bottom.y += 2.f * timeline_height_ + scrollbar_;
draw_list->AddRectFilled(bottom, bottom + ImVec2(rendersize.x, buttons_height_), ImGui::GetColorU32(ImGuiCol_FrameBg), h_space_);
// buttons style
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.00f, 0.00f, 0.00f, 0.00f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.14f, 0.14f, 0.14f, 0.5f));
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.24f, 0.24f, 0.24f, 0.2f));
ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(0.14f, 0.14f, 0.14f, 0.5f));
ImGui::SetCursorScreenPos(bottom + ImVec2(h_space_, v_space_) );
if (ImGui::Button(mediaplayer_active_->playSpeed() > 0 ? ICON_FA_FAST_BACKWARD :ICON_FA_FAST_FORWARD))
mediaplayer_active_->rewind();
// display buttons Play/Stop depending on current playing mode
ImGui::SameLine(0, h_space_);
if (mediaplayer_mode_) {
if (ImGui::Button(ICON_FA_PAUSE)){
mediaplayer_mode_ = false;
oss << ": Pause";
Action::manager().store(oss.str());
}
ImGui::SameLine(0, h_space_);
ImGui::PushButtonRepeat(true);
if (ImGui::Button( mediaplayer_active_->playSpeed() < 0 ? ICON_FA_BACKWARD :ICON_FA_FORWARD,
ImVec2(ImGui::GetFrameHeightWithSpacing(), 0)) )
mediaplayer_active_->jump ();
ImGui::PopButtonRepeat();
}
else {
if (ImGui::Button(ICON_FA_PLAY)) {
mediaplayer_mode_ = true;
oss << ": Play";
Action::manager().store(oss.str());
}
ImGui::SameLine(0, h_space_);
ImGui::PushButtonRepeat(true);
if (ImGui::Button( mediaplayer_active_->playSpeed() < 0 ? ICON_FA_STEP_BACKWARD : ICON_FA_STEP_FORWARD,
ImVec2(ImGui::GetFrameHeightWithSpacing(), 0)))
mediaplayer_active_->step();
ImGui::PopButtonRepeat();
}
// flag buttons
if (rendersize.x > buttons_height_ * 6.0f) {
if ( !mediaplayer_mode_ && mediaplayer_active_->timeline()->numFlags() > 0 ) {
GstClockTime _paused_time = mediaplayer_active_->position();
ImGui::SameLine(0, h_space_);
if( mediaplayer_active_->playSpeed() < 0 ) {
// Go to previous flag when playing backward
TimeInterval target_flag = mediaplayer_active_->timeline()->getPreviousFlag( _paused_time );
bool has_prev = target_flag.is_valid() &&
!( mediaplayer_active_->currentFlag().is_valid() && mediaplayer_active_->timeline()->numFlags() == 1) &&
( mediaplayer_active_->loop() == MediaPlayer::LOOP_REWIND || (target_flag.end < _paused_time) );
if( ImGuiToolkit::ButtonIcon(6, 0, "Go to previous flag", has_prev) )
mediaplayer_active_->go_to_flag( target_flag );
}
else {
// go to next flag when playing forward
TimeInterval target_flag = mediaplayer_active_->timeline()->getNextFlag( _paused_time );
bool has_next = target_flag.is_valid() &&
!( mediaplayer_active_->currentFlag().is_valid() && mediaplayer_active_->timeline()->numFlags() == 1) &&
( mediaplayer_active_->loop() == MediaPlayer::LOOP_REWIND || (target_flag.begin > _paused_time) );
if( ImGuiToolkit::ButtonIcon(5, 0, "Go to next flag", has_next) )
mediaplayer_active_->go_to_flag( target_flag );
}
// if stopped at a flag, show flag menu
if (mediaplayer_active_->currentFlag().is_valid()) {
ImGui::SameLine(0, h_space_);
if (ImGuiToolkit::IconButton(3, 0) || ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) {
counter_menu_timeout=0;
ImGui::OpenPopup( "MenuMediaPlayerFlags" );
}
}
}
else {
ImGui::SameLine(0, h_space_);
ImGuiToolkit::ButtonIcon(mediaplayer_active_->playSpeed() < 0 ? 6 : 5, 0, nullptr, false);
}
}
// right aligned buttons (if enough space)
if ( rendersize.x > buttons_height_ * 9.5f ) {
ImGui::SameLine();
ImGui::SetCursorPosX(rendersize.x - buttons_height_ * 4.f);
// loop modes button
static int current_loop = 0;
current_loop = (int) mediaplayer_active_->loop();
if ( ImGuiToolkit::IconMultistate(icons_loop, &current_loop, tooltips_loop) )
mediaplayer_active_->setLoop( (MediaPlayer::LoopMode) current_loop );
// speed slider
ImGui::SameLine(0, h_space_);
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - buttons_height_ - h_space_);
float s = fabs(static_cast<float>(current_play_speed));
if (ImGui::DragFloat( "##Speed", &s, 0.01f, 0.1f, 10.f, UNICODE_MULTIPLY " %.2f"))
mediaplayer_active_->setPlaySpeed( SIGN(current_play_speed) * static_cast<double>(s) );
// store action on mouse release
if (ImGui::IsItemDeactivatedAfterEdit()){
oss << ": Speed x" << std::setprecision(3) << s;
Action::manager().store(oss.str());
}
if (ImGui::IsItemHovered())
ImGuiToolkit::ToolTip("Play speed");
}
ImGui::SameLine();
ImGui::SetCursorPosX(rendersize.x - buttons_height_ / 1.4f);
if (ImGuiToolkit::IconButton(5, 8) || ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) {
counter_menu_timeout=0;
ImGui::OpenPopup( "MenuMediaPlayerOptions" );
}
// restore buttons style
ImGui::PopStyleColor(5);
if (mediaplayer_active_->pending())
{
draw_list->AddRectFilled(bottom, bottom + ImVec2(rendersize.x, buttons_height_), ImGui::GetColorU32(ImGuiCol_ScrollbarBg), h_space_);
}
///
/// media player timeline actions
///
// request seek (ASYNC)
if ( mediaplayer_slider_pressed_ && mediaplayer_active_->go_to(seek_t) )
mediaplayer_slider_pressed_ = false;
// play/stop command should be following the playing mode (buttons)
// AND force to stop when the slider is pressed
bool media_play = mediaplayer_mode_ & (!mediaplayer_slider_pressed_);
// Flag pressed in timeline
if (seek_flag.is_valid()) {
// go to the flag position
if ( mediaplayer_active_->go_to_flag(seek_flag) ){
// stop if flag type is 'Stop' (1) or 'Blackout' (2)
if (seek_flag.type > 0)
media_play = false;
}
}
// apply play action to media only if status should change
if ( mediaplayer_active_->isPlaying() != media_play ) {
mediaplayer_active_->play( media_play );
}
}
else {
///
/// Disabled areas for timeline and button bar
///
// disabled timeline
ImGui::SetCursorScreenPos(bottom + ImVec2(1.f, 0.f));
const ImGuiContext& g = *GImGui;
const double width_ratio = static_cast<double>(scrollwindow.x - slider_zoom_width + g.Style.FramePadding.x ) / static_cast<double>(mediaplayer_active_->timeline()->sectionsDuration());
DrawTimeline("##timeline_mediaplayers", mediaplayer_active_->timeline(), mediaplayer_active_->position(), width_ratio, 2.f * timeline_height_);
// disabled area for timeline actions and zoom
bottom += ImVec2(scrollwindow.x + 2.f, 0.f);
draw_list->AddRectFilled(bottom, bottom + ImVec2(slider_zoom_width, 2.f * timeline_height_ -1.f),
ImGui::GetColorU32(ImGuiCol_FrameBgActive));
// disabled button bar
bottom.x = top.x;
bottom.y += 2.f * timeline_height_ + scrollbar_;
DrawButtonBar(bottom, rendersize.x);
}
if (ImGui::BeginPopup( "MenuMediaPlayerOptions" ))
{
if (ImGuiToolkit::MenuItemIcon(8,0, "Play forward", nullptr, current_play_speed>0)) {
mediaplayer_active_->setPlaySpeed( ABS(mediaplayer_active_->playSpeed()) );
oss << ": Play forward";
Action::manager().store(oss.str());
}
if (ImGuiToolkit::MenuItemIcon(9,0, "Play backward", nullptr, current_play_speed<0)) {
mediaplayer_active_->setPlaySpeed( - ABS(mediaplayer_active_->playSpeed()) );
oss << ": Play backward";
Action::manager().store(oss.str());
}
if (ImGuiToolkit::MenuItemIcon(19,15, "Reset speed")) {
mediaplayer_active_->setPlaySpeed(1.0);
oss << ": Speed x 1.0";
Action::manager().store(oss.str());
}
ImGui::Separator();
if (ImGui::MenuItem( ICON_FA_REDO_ALT " Reload" ))
mediaplayer_active_->reopen();
bool option = mediaplayer_active_->rewindOnDisabled();
if (ImGui::MenuItem(ICON_FA_SNOWFLAKE " Restart on reactivation", NULL, &option )) {
mediaplayer_active_->setRewindOnDisabled(option);
}
if (ImGui::IsWindowHovered())
counter_menu_timeout=0;
else if (++counter_menu_timeout > 10)
ImGui::CloseCurrentPopup();
ImGui::EndPopup();
}
if (ImGui::BeginPopup( "MenuMediaPlayerFlags" ))
{
int num_flags = static_cast<int>(icons_flags.size());
TimeInterval copy_flag = mediaplayer_active_->currentFlag();
for (int i = 0; i < num_flags; ++i) {
if (ImGuiToolkit::MenuItemIcon(icons_flags[i].first,icons_flags[i].second,
tooltips_flags[i].c_str(), nullptr, copy_flag.type == i )) {
copy_flag.type = i;
mediaplayer_active_->timeline()->replaceFlag( copy_flag );
mediaplayer_active_->setCurrentFlag( copy_flag );
oss << ": Flag changed";
Action::manager().store(oss.str());
}
}
ImGui::Separator();
if (ImGuiToolkit::MenuItemIcon(2,0, "Delete flag")) {
mediaplayer_active_->timeline()->removeFlagAt(mediaplayer_active_->currentFlag().midpoint() );
oss << ": Flag removed";
Action::manager().store(oss.str());
}
if (ImGui::IsWindowHovered())
counter_menu_timeout=0;
else if (++counter_menu_timeout > 10)
ImGui::CloseCurrentPopup();
ImGui::EndPopup();
}
///
/// Window area to edit gaps or fading
///
if (mediaplayer_edit_panel_ && mediaplayer_active_->isEnabled()) {
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 = 1000000000;
static bool target_time_valid = true;
// timeline to edit
Timeline *tl = mediaplayer_active_->timeline();
///
/// PANEL FOR CUT TIMELINE MODE
///
ImGui::Spacing();
if (Settings::application.widget.media_player_timeline_editmode == 0) {
// 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 two gaps",
TimelinePayload(TimelinePayload::CUT_MERGE, 0, 0) );
///
/// ERASE CUT AT CURSOR
///
ImGui::SameLine(0, IMGUI_SAME_LINE);
DragButtonIcon(0, 4, "Drop in timeline to\nErase a gap",
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);
}
///
/// SECTION WITH BUTTONS
///
ImGui::SameLine(0, 0);
ImGui::Text("|");
/// Enter time value for CUT
ImGui::SameLine(0, 0);
ImVec2 draw_pos = ImGui::GetCursorPos();
float w = gap_dialog_size.x - 4.f * ImGui::GetTextLineHeightWithSpacing() ;
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10, 11));
ImGui::SetNextItemWidth(w - draw_pos.x - IMGUI_SAME_LINE); // VARIABLE WIDTH
ImGuiToolkit::InputTime("##TimeCut", &target_time, tl->duration(), &target_time_valid);
ImGui::PopStyleVar();
/// CUT LEFT TIME
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
ImGui::SetCursorPos( ImVec2(w, draw_pos.y));
if (ImGuiToolkit::ButtonIcon(17, 3, "Cut left at given time", target_time_valid)) {
tl->cut(target_time, true);
tl->refresh();
oss << ": Timeline cut";
Action::manager().store(oss.str());
}
/// CUT RIGHT TIME
ImGui::SameLine(0, IMGUI_SAME_LINE);
if (ImGuiToolkit::ButtonIcon(18, 3, "Cut right at given time", target_time_valid)){
tl->cut(target_time, false);
tl->refresh();
oss << ": Timeline cut";
Action::manager().store(oss.str());
}
/// CLEAR
ImGui::SameLine(0, IMGUI_SAME_LINE);
if (ImGuiToolkit::ButtonIcon(11, 14, "Clear all gaps")) {
tl->clearGaps();
oss << ": Timeline clear gaps";
Action::manager().store(oss.str());
}
ImGui::PopStyleColor();
// end icons
ImGui::PopFont();
}
///
/// PANEL FOR FADING
///
else if (Settings::application.widget.media_player_timeline_editmode == 1) {
// 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::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 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 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));
}
else {
}
///
/// DURATION SLIDER
///
float seconds = (float) d / 1000.f;
float maxi = (float) GST_TIME_AS_MSECONDS(tl->duration()) / 1000.f;
float w = gap_dialog_size.x - 4.f * ImGui::GetTextLineHeightWithSpacing();
ImGui::SameLine(0, IMGUI_SAME_LINE);
ImVec2 draw_pos = ImGui::GetCursorPos();
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO);
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10, 11));
ImGui::SetNextItemWidth(w - draw_pos.x);
if (ImGui::SliderFloat("##DurationFading",
&seconds,
0.05f,
maxi,
d < UINT_MAX ? "%.2f s" : "Auto")) {
if (seconds > maxi - 0.05f)
d = UINT_MAX;
else
d = (uint) floor(seconds * 1000);
}
ImGui::PopStyleVar();
ImGui::PopFont();
///
/// SECTION WITH BUTTONS
///
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
ImGui::SetCursorPos( ImVec2(w, draw_pos.y));
ImGui::Text("|");
/// SMOOTH Curve
static int _actionsmooth = 0;
ImGui::SameLine(0, 0);
ImGui::PushButtonRepeat(true);
if (ImGuiToolkit::ButtonIcon(2, 7, "Apply smoothing filter")){
tl->smoothFading( 15 );
++_actionsmooth;
}
ImGui::PopButtonRepeat();
if (_actionsmooth > 0 && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
oss << ": Timeline smooth curve";
Action::manager().store(oss.str());
_actionsmooth = 0;
}
/// CLEANUP
ImGui::SameLine(0, 0);
if (ImGuiToolkit::ButtonIcon(3, 7, "Clean curve in gaps")) {
tl->autoFadeInGaps();
oss << ": Timeline cleanup curve";
Action::manager().store(oss.str());
}
/// CLEAR
ImGui::SameLine(0, 0);
if (ImGuiToolkit::ButtonIcon(11, 14, "Clear fading curve")){
tl->clearFading();
oss << ": Timeline clear fading";
Action::manager().store(oss.str());
}
ImGui::PopStyleColor();
// end icons
ImGui::PopFont();
}
///
/// PANEL FOR FLAGS
///
else if (Settings::application.widget.media_player_timeline_editmode == 2) {
// PANEL WITH LARGE FONTS
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
///
/// DROP ICONS
///
DragButtonIcon(11, 6, "Drop in timeline to\nAdd a Bookmark",
TimelinePayload(TimelinePayload::FLAG_ADD, 0, 0) );
ImGui::SameLine(0, IMGUI_SAME_LINE);
DragButtonIcon(12, 6, "Drop in timeline to\nAdd a Stop Flag",
TimelinePayload(TimelinePayload::FLAG_ADD, 0, 1) );
ImGui::SameLine(0, IMGUI_SAME_LINE);
DragButtonIcon(13, 6, "Drop in timeline to\nAdd a Blackout Flag",
TimelinePayload(TimelinePayload::FLAG_ADD, 0, 2) );
ImGui::SameLine(0, IMGUI_SAME_LINE);
DragButtonIcon(2, 0, "Drop in timeline to\nErase a flag",
TimelinePayload(TimelinePayload::FLAG_REMOVE, 0, 0) );
///
/// SECTION WITH BUTTONS
///
ImGui::SameLine(0, 0);
ImGui::Text("|");
ImGui::SameLine(0, 0);
ImGuiToolkit::IconMultistate(icons_flags,
&Settings::application.widget.media_player_timeline_flag, tooltips_flags);
/// Enter time value for Flag
ImGui::SameLine(0, IMGUI_SAME_LINE);
ImVec2 draw_pos = ImGui::GetCursorPos();
float w = gap_dialog_size.x - 4.f * ImGui::GetTextLineHeightWithSpacing() ;
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10, 11));
ImGui::SetNextItemWidth(w - draw_pos.x - IMGUI_SAME_LINE); // VARIABLE WIDTH
ImGuiToolkit::InputTime("##TimeFlag", &target_time, tl->duration(), &target_time_valid);
ImGui::PopStyleVar();
/// FLAG AT TIME
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
ImGui::SetCursorPos( ImVec2(w, draw_pos.y));
if (ImGuiToolkit::ButtonIcon(1, 0, "Add flag at given time", target_time_valid)) {
tl->removeFlagAt(target_time);
tl->addFlagAt(target_time, Settings::application.widget.media_player_timeline_flag);
tl->refresh();
oss << ": Timeline flag add";
Action::manager().store(oss.str());
}
/// Add
ImGui::SameLine(0, IMGUI_SAME_LINE);
if (ImGuiToolkit::ButtonIcon(0, 0, "Add flag at cursor position", !mediaplayer_active_->isPlaying()) ) {
tl->removeFlagAt(mediaplayer_active_->position());
tl->addFlagAt(mediaplayer_active_->position(), Settings::application.widget.media_player_timeline_flag);
tl->refresh();
oss << ": Timeline flag add";
Action::manager().store(oss.str());
}
/// CLEAR
ImGui::SameLine(0, IMGUI_SAME_LINE);
if (ImGuiToolkit::ButtonIcon(11, 14, "Clear all flags")) {
tl->clearFlags();
oss << ": Timeline flag clear";
Action::manager().store(oss.str());
}
ImGui::PopStyleColor();
// end icons
ImGui::PopFont();
}
ImGui::EndChild();
ImGui::PopStyleColor();
}
///
/// Dialog to edit timeline fade in and out
///
if (mediaplayer_edit_fading_) {
ImGui::OpenPopup(LABEL_EDIT_FADING);
mediaplayer_edit_fading_ = false;
}
const ImVec2 mp_dialog_size(buttons_width_ * 2.f, buttons_height_ * 6);
ImGui::SetNextWindowSize(mp_dialog_size, ImGuiCond_Always);
const ImVec2 mp_dialog_pos = top + rendersize * 0.5f - mp_dialog_size * 0.5f;
ImGui::SetNextWindowPos(mp_dialog_pos, ImGuiCond_Always);
if (ImGui::BeginPopupModal(LABEL_EDIT_FADING, NULL, ImGuiWindowFlags_NoResize))
{
const ImVec2 pos = ImGui::GetCursorPos();
const ImVec2 area = ImGui::GetContentRegionAvail();
ImGui::Spacing();
ImGui::Text("Set parameters and apply:");
ImGui::Spacing();
static int l = 0;
static std::vector< std::tuple<int, int, std::string> > fading_options = {
{19, 7, "Fade in"},
{18, 7, "Fade out"},
{ 0, 8, "Auto fade in & out"}
};
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
ImGuiToolkit::ComboIcon("Fading", &l, fading_options);
static int c = 0;
static std::vector< std::tuple<int, int, std::string> > curve_options = {
{18, 3, "Linear"},
{19, 3, "Progressive"},
{17, 3, "Abrupt"}
};
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
ImGuiToolkit::ComboIcon("Curve", &c, curve_options);
static uint d = 1000;
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
ImGuiToolkit::SliderTiming ("Duration", &d, 200, 5050, 50, "Maximum");
if (d > 5000)
d = UINT_MAX;
bool close = false;
ImGui::SetCursorPos(pos + ImVec2(0.f, area.y - buttons_height_));
if (ImGui::Button(ICON_FA_TIMES " Cancel", ImVec2(area.x * 0.3f, 0)))
close = true;
ImGui::SetCursorPos(pos + ImVec2(area.x * 0.7f, area.y - buttons_height_));
ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetStyleColorVec4(ImGuiCol_Tab));
if (ImGui::Button(ICON_FA_CHECK " Apply", ImVec2(area.x * 0.3f, 0))
|| ImGui::IsKeyPressedMap(ImGuiKey_Enter) || ImGui::IsKeyPressedMap(ImGuiKey_KeyPadEnter) ) {
close = true;
// timeline to edit
Timeline *tl = mediaplayer_active_->timeline();
switch (l) {
case 0:
tl->fadeIn( static_cast<GstClockTime>(d) * GST_MSECOND, (Timeline::FadingCurve) c);
oss << ": Timeline Fade in " << d;
break;
case 1:
tl->fadeOut( static_cast<GstClockTime>(d) * GST_MSECOND, (Timeline::FadingCurve) c);
oss << ": Timeline Fade out " << d;
break;
case 2:
tl->autoFading( static_cast<GstClockTime>(d) * GST_MSECOND, (Timeline::FadingCurve) c);
oss << ": Timeline Fade in&out " << d;
break;
default:
break;
}
tl->smoothFading( 2 );
Action::manager().store(oss.str());
}
ImGui::PopStyleColor(1);
if (close)
ImGui::CloseCurrentPopup();
ImGui::EndPopup();
}
///
/// Dialog to set gstreamer video effect
///
static std::string _effect_description = "";
static bool _effect_description_changed = false;
if (mediaplayer_edit_pipeline_) {
// open dialog
ImGui::OpenPopup(DIALOG_GST_EFFECT);
mediaplayer_edit_pipeline_ = false;
// initialize dialog
_effect_description = mediaplayer_active_->videoEffect();
_effect_description_changed = true;
}
const ImVec2 mpp_dialog_size(buttons_width_ * 3.f, buttons_height_ * 6.2f);
ImGui::SetNextWindowSize(mpp_dialog_size, ImGuiCond_Always);
const ImVec2 mpp_dialog_pos = top + rendersize * 0.5f - mpp_dialog_size * 0.5f;
ImGui::SetNextWindowPos(mpp_dialog_pos, ImGuiCond_Always);
if (ImGui::BeginPopupModal(DIALOG_GST_EFFECT, NULL, ImGuiWindowFlags_NoResize))
{
const ImVec2 pos = ImGui::GetCursorPos();
const ImVec2 area = ImGui::GetContentRegionAvail();
static uint _status = 0; // 0:unknown, 1:ok, 2:error
static std::string _status_message = "";
static std::vector< std::pair< std::string, std::string> > _examples = { {"Primary color", "frei0r-filter-primaries" },
{"Histogram", "frei0r-filter-levels"},
{"Emboss", "frei0r-filter-emboss"},
{"Denoise", "frei0r-filter-hqdn3d spatial=0.05 temporal=0.1"},
{"Thermal", "coloreffects preset=heat"},
{"Afterimage", "streaktv"}
};
static ImVec2 fieldsize(ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN, 70);
static int numlines = 0;
const ImGuiContext& g = *GImGui;
fieldsize.y = MAX(2.5, numlines) * g.FontSize + g.Style.ItemSpacing.y + g.Style.FramePadding.y;
ImGui::Spacing();
ImGui::Text("Enter a gstreamer video effect description and apply.\nLeave empty for no effect.");
ImGui::SameLine();
ImGuiToolkit::HelpToolTip("Video effects are directly integrated in the gstreamer pipeline "
"and performed on CPU (might be slow). Vimix recommends using "
"GPU accelerated filters by cloning the source.");
ImGui::Spacing();
// Editor
if ( ImGuiToolkit::InputCodeMultiline("Effect", &_effect_description, fieldsize, &numlines) )
_effect_description_changed = true;
if ( ImGui::IsItemActive() )
_status = 0;
// Local menu for list of examples
ImVec2 pos_bot = ImGui::GetCursorPos();
ImGui::SetCursorPos( pos_bot + ImVec2(fieldsize.x + IMGUI_SAME_LINE, -ImGui::GetFrameHeightWithSpacing()));
if (ImGui::BeginCombo("##ExamplesVideoEffect", "ExamplesVideoEffect", ImGuiComboFlags_NoPreview | ImGuiComboFlags_HeightLarge)) {
ImGui::TextDisabled("Examples");
for (auto it = _examples.begin(); it != _examples.end(); ++it) {
if (ImGui::Selectable( it->first.c_str() ) ) {
_effect_description = it->second;
_effect_description_changed = true;
}
}
ImGui::Separator();
ImGui::TextDisabled("Explore online");
if (ImGui::Selectable( ICON_FA_EXTERNAL_LINK_ALT " Frei0r" ) )
SystemToolkit::open("https://gstreamer.freedesktop.org/documentation/frei0r");
if (ImGui::Selectable( ICON_FA_EXTERNAL_LINK_ALT " Effectv" ) )
SystemToolkit::open("https://gstreamer.freedesktop.org/documentation/effectv");
if (ImGui::Selectable( ICON_FA_EXTERNAL_LINK_ALT " Gaudi" ) )
SystemToolkit::open("https://gstreamer.freedesktop.org/documentation/gaudieffects");
if (ImGui::Selectable( ICON_FA_EXTERNAL_LINK_ALT " Geometric" ) )
SystemToolkit::open("https://gstreamer.freedesktop.org/documentation/geometrictransform");
ImGui::EndCombo();
}
// Local menu for clearing
ImGui::SameLine();
if ( ImGuiToolkit::ButtonIcon(11,13,"Clear") ) {
_effect_description = "";
_effect_description_changed = true;
}
// if desciption changed, start a timeout to test the pipeline
if ( _effect_description_changed ){
// reset to status 'unknown' and no message
_status = 0;
_status_message.clear();
// No description, no effect
if ( _effect_description.empty() ) {
_status = 1;
_status_message = "(no video effect)";
}
// has a description, we test it
else {
GError *error = NULL;
GstElement *effect = gst_parse_launch( _effect_description.c_str(), &error);
// on error
if (effect == NULL || error != NULL) {
_status = 2;
if (error != NULL)
_status_message = error->message;
g_clear_error (&error);
if (effect)
gst_object_unref(effect);
}
// on success
else
_status = 1;
}
// done
_effect_description_changed = false;
}
// display message line
if (_status > 1) {
// On Error
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0, 0.2, 0.2, 0.95f));
ImGui::TextWrapped("Error - %s", _status_message.c_str());
ImGui::PopStyleColor(1);
}
else if (_status > 0) {
// All ok
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.2, 1.0, 0.2, 0.85f));
ImGui::Text("Ok %s", _status_message.c_str());
ImGui::PopStyleColor(1);
}
bool close = false;
ImGui::SetCursorPos(pos + ImVec2(0.f, area.y - buttons_height_));
if (ImGui::Button(ICON_FA_TIMES " Cancel", ImVec2(area.x * 0.3f, 0)))
close = true;
ImGui::SetCursorPos(pos + ImVec2(area.x * 0.7f, area.y - buttons_height_));
// on success status, offer to apply
if (_status == 1) {
ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetStyleColorVec4(ImGuiCol_Tab));
if (ImGui::Button(ICON_FA_CHECK " Apply", ImVec2(area.x * 0.3f, 0))
|| ImGui::IsKeyPressedMap(ImGuiKey_Enter) || ImGui::IsKeyPressedMap(ImGuiKey_KeyPadEnter) ) {
close = true;
// apply to pipeline
mediaplayer_active_->setVideoEffect(_effect_description);
oss << ": gstreamer effect";
Action::manager().store(oss.str());
}
ImGui::PopStyleColor(1);
}
else
ImGuiToolkit::ButtonDisabled(ICON_FA_CHECK " Apply", ImVec2(area.x * 0.3f, 0));
if (close)
ImGui::CloseCurrentPopup();
ImGui::EndPopup();
}
////
/// Dialog to set timeline duration
///
static double timeline_duration_ = 0.0;
static double timeline_duration_previous = 0.0;
if (mediaplayer_set_duration_) {
// get current duration of mediaplayer
GstClockTime end = mediaplayer_active_->timeline()->end();
timeline_duration_ = (double) (GST_TIME_AS_MSECONDS(end)) / 1000.f;
// remember previous duration for Cancel
// NB: trick with var 'mediaplayer_set_duration_' set to 2 when first time created
timeline_duration_previous = mediaplayer_set_duration_ > 1 ? 0.0 : timeline_duration_;
// open dialog to change duration
ImGui::OpenPopup(DIALOG_TIMELINE_DURATION);
// only once
mediaplayer_set_duration_ = 0;
}
const ImVec2 tld_dialog_size(buttons_width_ * 2.f, buttons_height_ * 4);
ImGui::SetNextWindowSize(tld_dialog_size, ImGuiCond_Always);
const ImVec2 tld_dialog_pos = top + rendersize * 0.5f - tld_dialog_size * 0.5f;
ImGui::SetNextWindowPos(tld_dialog_pos, ImGuiCond_Always);
if (ImGui::BeginPopupModal(DIALOG_TIMELINE_DURATION, NULL, ImGuiWindowFlags_NoResize))
{
const ImVec2 pos = ImGui::GetCursorPos();
const ImVec2 area = ImGui::GetContentRegionAvail();
ImGui::Spacing();
ImGui::Text("Set the duration of the timeline");
ImGui::Spacing();
// get current timeline
ImGui::InputDouble("second", &timeline_duration_, 1.0f, 10.0f, "%.2f");
timeline_duration_ = ABS(timeline_duration_);
bool close = false;
ImGui::SetCursorPos(pos + ImVec2(0.f, area.y - buttons_height_));
if (ImGui::Button(ICON_FA_TIMES " Cancel", ImVec2(area.x * 0.3f, 0))) {
// restore previous timeline duration
timeline_duration_ = timeline_duration_previous;
// close dialog
close = true;
}
ImGui::SetCursorPos(pos + ImVec2(area.x * 0.7f, area.y - buttons_height_));
ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetStyleColorVec4(ImGuiCol_Tab));
if (ImGui::Button(ICON_FA_CHECK " Apply", ImVec2(area.x * 0.3f, 0))
|| ImGui::IsKeyPressedMap(ImGuiKey_Enter) || ImGui::IsKeyPressedMap(ImGuiKey_KeyPadEnter) ) {
// close dialog
close = true;
}
ImGui::PopStyleColor(1);
if (close) {
// zero duration requested : delete timeline
if (timeline_duration_ < 0.01) {
// set empty timeline
Timeline tl;
mediaplayer_active_->setTimeline(tl);
mediaplayer_active_->play(false);
// re-open the image with NO timeline
mediaplayer_active_->reopen();
}
// else normal change timeline end
else
mediaplayer_active_->timeline()->setEnd( GST_MSECOND * (GstClockTime) ( timeline_duration_ * 1000.f ) );
// close popup window
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
}
void SourceControlWindow::DrawButtonBar(ImVec2 bottom, float width)
{
// draw box
ImDrawList* draw_list = ImGui::GetWindowDrawList();
draw_list->AddRectFilled(bottom, bottom + ImVec2(width, buttons_height_), ImGui::GetColorU32(ImGuiCol_FrameBg), h_space_);
// prepare position to draw text
ImGui::SetCursorScreenPos(bottom + ImVec2(h_space_, v_space_) );
// play bar is enabled if only one source selected is enabled
bool enabled = false;
size_t n_play = 0;
for (auto source = selection_.begin(); source != selection_.end(); ++source){
if ( (*source)->active() && (*source)->playable())
enabled = true;
if ( (*source)->playing() )
n_play++;
}
// buttons style for disabled / enabled bar
if (enabled) {
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.00f, 0.00f, 0.00f, 0.00f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.14f, 0.14f, 0.14f, 0.5f));
}
else {
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.6, 0.6, 0.6, 0.5f));
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.00f, 0.00f, 0.00f, 0.00f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.00f, 0.00f, 0.00f, 0.00f));
}
// Always the rewind button
if (ImGui::Button(ICON_FA_FAST_BACKWARD) && enabled) {
for (auto source = selection_.begin(); source != selection_.end(); ++source)
(*source)->replay();
}
ImGui::SameLine(0, h_space_);
// unique play / pause button
if (n_play < 1 || selection_.size() == n_play) {
if (n_play) {
if (ImGui::Button(ICON_FA_PAUSE) && enabled) {
for (auto source = selection_.begin(); source != selection_.end(); ++source)
(*source)->play(false);
Action::manager().store(std::string("Pause ") +
std::to_string(selection_.size()) +
" sources");
}
}
else {
if (ImGui::Button(ICON_FA_PLAY) && enabled){
for (auto source = selection_.begin(); source != selection_.end(); ++source)
(*source)->play(true);
Action::manager().store(std::string("Play ") +
std::to_string(selection_.size()) +
" sources");
}
}
}
// separate play & pause buttons for disagreeing sources
else {
if (ImGui::Button(ICON_FA_PLAY) && enabled) {
for (auto source = selection_.begin(); source != selection_.end(); ++source)
(*source)->play(true);
Action::manager().store(std::string("Play " ) +
std::to_string(selection_.size()) +
" sources");
}
ImGui::SameLine(0, h_space_);
if (ImGui::Button(ICON_FA_PAUSE) && enabled) {
for (auto source = selection_.begin(); source != selection_.end(); ++source)
(*source)->play(false);
Action::manager().store(std::string("Pause ") +
std::to_string(selection_.size()) +
" sources");
}
}
ImGui::SameLine(0, h_space_);
// restore style
ImGui::PopStyleColor(3);
}