mirror of
https://github.com/brunoherbelin/vimix.git
synced 2025-12-11 18:34:58 +01:00
Management of inputs in Control, Management of callbacks creator per input in Source, Saving and Loading in Session, Unified renaming of SourceCallbacks, User interface window for creating and editing input mapping from Keyboard and Numerical keypad, with appropriate Settings.
7101 lines
295 KiB
C++
7101 lines
295 KiB
C++
/*
|
|
* This file is part of vimix - video live mixer
|
|
*
|
|
* **Copyright** (C) 2019-2022 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 <cstring>
|
|
#include <sstream>
|
|
#include <thread>
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <map>
|
|
#include <iomanip>
|
|
#include <fstream>
|
|
#include <sstream>
|
|
|
|
using namespace std;
|
|
|
|
// ImGui
|
|
#include "imgui.h"
|
|
#define IMGUI_DEFINE_MATH_OPERATORS
|
|
#include "imgui_internal.h"
|
|
#include "imgui_impl_glfw.h"
|
|
#include "imgui_impl_opengl3.h"
|
|
|
|
// Desktop OpenGL function loader
|
|
#include <glad/glad.h>
|
|
|
|
// Include glfw3.h after our OpenGL definitions
|
|
#include <GLFW/glfw3.h>
|
|
|
|
#include <glm/glm.hpp>
|
|
#include <glm/gtc/matrix_transform.hpp>
|
|
#include <glm/gtx/string_cast.hpp>
|
|
|
|
// generic image loader
|
|
#define STB_IMAGE_IMPLEMENTATION
|
|
#include <stb_image.h>
|
|
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
|
#include <stb_image_write.h>
|
|
|
|
#include "defines.h"
|
|
#include "Log.h"
|
|
#include "SystemToolkit.h"
|
|
#include "DialogToolkit.h"
|
|
#include "BaseToolkit.h"
|
|
#include "GlmToolkit.h"
|
|
#include "GstToolkit.h"
|
|
#include "ImGuiToolkit.h"
|
|
#include "ImGuiVisitor.h"
|
|
#include "RenderingManager.h"
|
|
#include "ControlManager.h"
|
|
#include "Connection.h"
|
|
#include "ActionManager.h"
|
|
#include "Resource.h"
|
|
#include "Settings.h"
|
|
#include "SessionCreator.h"
|
|
#include "Mixer.h"
|
|
#include "MixingGroup.h"
|
|
#include "Recorder.h"
|
|
#include "Streamer.h"
|
|
#include "Loopback.h"
|
|
#include "Selection.h"
|
|
#include "FrameBuffer.h"
|
|
#include "MediaPlayer.h"
|
|
#include "SourceCallback.h"
|
|
#include "CloneSource.h"
|
|
#include "RenderSource.h"
|
|
#include "MediaSource.h"
|
|
#include "SessionSource.h"
|
|
#include "PatternSource.h"
|
|
#include "DeviceSource.h"
|
|
#include "NetworkSource.h"
|
|
#include "SrtReceiverSource.h"
|
|
#include "StreamSource.h"
|
|
#include "PickingVisitor.h"
|
|
#include "ImageShader.h"
|
|
#include "ImageProcessingShader.h"
|
|
#include "Metronome.h"
|
|
#include "VideoBroadcast.h"
|
|
|
|
#include "TextEditor.h"
|
|
TextEditor editor;
|
|
|
|
#include "UserInterfaceManager.h"
|
|
#define PLOT_CIRCLE_SEGMENTS 64
|
|
#define PLOT_ARRAY_SIZE 180
|
|
#define LABEL_AUTO_MEDIA_PLAYER ICON_FA_CARET_SQUARE_RIGHT " Dynamic selection"
|
|
#define LABEL_STORE_SELECTION " Store selection"
|
|
#define LABEL_EDIT_FADING ICON_FA_RANDOM " Fade in & out"
|
|
|
|
// utility functions
|
|
void ShowAboutGStreamer(bool* p_open);
|
|
void ShowAboutOpengl(bool* p_open);
|
|
void ShowSandbox(bool* p_open);
|
|
void SetMouseCursor(ImVec2 mousepos, View::Cursor c = View::Cursor());
|
|
|
|
std::string readable_date_time_string(std::string date){
|
|
if (date.length()<12)
|
|
return "";
|
|
std::string s = date.substr(6, 2) + "/" + date.substr(4, 2) + "/" + date.substr(0, 4);
|
|
s += " @ " + date.substr(8, 2) + ":" + date.substr(10, 2);
|
|
return s;
|
|
}
|
|
|
|
// Helper functions for imgui window aspect-ratio constraints
|
|
struct CustomConstraints
|
|
{
|
|
static void AspectRatio(ImGuiSizeCallbackData* data) {
|
|
float *ar = (float*) data->UserData;
|
|
data->DesiredSize.y = (data->CurrentSize.x / (*ar)) + 35.f;
|
|
}
|
|
static void Square(ImGuiSizeCallbackData* data) {
|
|
data->DesiredSize.x = data->DesiredSize.y = (data->DesiredSize.x > data->DesiredSize.y ? data->DesiredSize.x : data->DesiredSize.y);
|
|
}
|
|
};
|
|
|
|
UserInterface::UserInterface()
|
|
{
|
|
start_time = gst_util_get_timestamp ();
|
|
ctrl_modifier_active = false;
|
|
alt_modifier_active = false;
|
|
shift_modifier_active = false;
|
|
show_vimix_about = false;
|
|
show_imgui_about = false;
|
|
show_gst_about = false;
|
|
show_opengl_about = false;
|
|
show_view_navigator = 0;
|
|
target_view_navigator = 1;
|
|
currentTextEdit.clear();
|
|
screenshot_step = 0;
|
|
pending_save_on_exit = false;
|
|
|
|
sessionopendialog = nullptr;
|
|
sessionimportdialog = nullptr;
|
|
sessionsavedialog = nullptr;
|
|
}
|
|
|
|
bool UserInterface::Init()
|
|
{
|
|
if (Rendering::manager().mainWindow().window()== nullptr)
|
|
return false;
|
|
|
|
pending_save_on_exit = false;
|
|
|
|
// Setup Dear ImGui context
|
|
IMGUI_CHECKVERSION();
|
|
ImGui::CreateContext();
|
|
ImGuiIO& io = ImGui::GetIO();
|
|
io.FontGlobalScale = Settings::application.scale;
|
|
|
|
// Setup Platform/Renderer bindings
|
|
ImGui_ImplGlfw_InitForOpenGL(Rendering::manager().mainWindow().window(), true);
|
|
ImGui_ImplOpenGL3_Init(Rendering::manager().glsl_version.c_str());
|
|
|
|
// Setup Dear ImGui style
|
|
ImGuiToolkit::SetAccentColor(static_cast<ImGuiToolkit::accent_color>(Settings::application.accent_color));
|
|
|
|
// Estalish the base size from the resolution of the monitor
|
|
float base_font_size = float(Rendering::manager().mainWindow().pixelsforRealHeight(4.f)) ;
|
|
base_font_size = CLAMP( base_font_size, 8.f, 50.f);
|
|
// Load Fonts (using resource manager, NB: a temporary copy of the raw data is necessary)
|
|
ImGuiToolkit::SetFont(ImGuiToolkit::FONT_DEFAULT, "Roboto-Regular", int(base_font_size) );
|
|
ImGuiToolkit::SetFont(ImGuiToolkit::FONT_BOLD, "Roboto-Bold", int(base_font_size) );
|
|
ImGuiToolkit::SetFont(ImGuiToolkit::FONT_ITALIC, "Roboto-Italic", int(base_font_size) );
|
|
ImGuiToolkit::SetFont(ImGuiToolkit::FONT_MONO, "Hack-Regular", int(base_font_size) - 2);
|
|
ImGuiToolkit::SetFont(ImGuiToolkit::FONT_LARGE, "Hack-Regular", MIN(int(base_font_size * 1.5f), 50) );
|
|
|
|
// info
|
|
// Log::Info("Monitor (%.1f,%.1f)", Rendering::manager().monitorWidth(), Rendering::manager().monitorHeight());
|
|
Log::Info("Font size %d", int(base_font_size) );
|
|
|
|
// Style
|
|
ImGuiStyle& style = ImGui::GetStyle();
|
|
style.WindowPadding.x = base_font_size / 2.5f;
|
|
style.WindowPadding.y = style.WindowPadding.x / 2.f;
|
|
style.FramePadding.x = base_font_size / 2.5f;
|
|
style.FramePadding.y = style.FramePadding.x / 2.f;
|
|
style.IndentSpacing = base_font_size;
|
|
style.ItemSpacing.x = base_font_size / 2.f;
|
|
style.ItemSpacing.y = style.ItemSpacing.x / 3.f;
|
|
style.ItemInnerSpacing.x = base_font_size / 2.5f;
|
|
style.ItemInnerSpacing.y = style.ItemInnerSpacing.x / 2.f;
|
|
style.WindowRounding = base_font_size / 2.5f;
|
|
style.ChildRounding = style.WindowRounding / 2.f;
|
|
style.FrameRounding = style.WindowRounding / 2.f;
|
|
style.PopupRounding = style.WindowRounding / 2.f;
|
|
style.GrabRounding = style.FrameRounding / 2.f;
|
|
style.GrabMinSize = base_font_size / 1.5f;
|
|
style.Alpha = 0.92f;
|
|
|
|
// prevent bug with imgui clipboard (null at start)
|
|
ImGui::SetClipboardText("");
|
|
|
|
// setup settings filename
|
|
std::string inifile = SystemToolkit::full_filename(SystemToolkit::settings_path(), "imgui.ini");
|
|
char *inifilepath = (char *) malloc( (inifile.size() + 1) * sizeof(char) );
|
|
std::sprintf(inifilepath, "%s", inifile.c_str() );
|
|
io.IniFilename = inifilepath;
|
|
|
|
// init dialogs
|
|
sessionopendialog = new DialogToolkit::OpenSessionDialog("Open Session");
|
|
sessionsavedialog = new DialogToolkit::SaveSessionDialog("Save Session");
|
|
sessionimportdialog = new DialogToolkit::OpenSessionDialog("Import Session");
|
|
|
|
// init tooltips
|
|
ImGuiToolkit::setToolTipsEnabled(Settings::application.show_tooptips);
|
|
|
|
return true;
|
|
}
|
|
|
|
uint64_t UserInterface::Runtime() const
|
|
{
|
|
return gst_util_get_timestamp () - start_time;
|
|
}
|
|
|
|
|
|
void UserInterface::handleKeyboard()
|
|
{
|
|
const ImGuiIO& io = ImGui::GetIO();
|
|
alt_modifier_active = io.KeyAlt;
|
|
shift_modifier_active = io.KeyShift;
|
|
ctrl_modifier_active = io.ConfigMacOSXBehaviors ? io.KeySuper : io.KeyCtrl;
|
|
keyboard_available = !io.WantCaptureKeyboard;
|
|
|
|
// Application "CTRL +"" Shortcuts
|
|
if ( ctrl_modifier_active ) {
|
|
|
|
if (ImGui::IsKeyPressed( GLFW_KEY_Q )) {
|
|
// try quit
|
|
if ( TryClose() )
|
|
Rendering::manager().close();
|
|
}
|
|
else if (ImGui::IsKeyPressed( GLFW_KEY_O )) {
|
|
// SHIFT + CTRL + O : reopen current session
|
|
if (shift_modifier_active && !Mixer::manager().session()->filename().empty())
|
|
Mixer::manager().load( Mixer::manager().session()->filename() );
|
|
// CTRL + O : Open session
|
|
else
|
|
selectOpenFilename();
|
|
}
|
|
else if (ImGui::IsKeyPressed( GLFW_KEY_S )) {
|
|
// SHIFT + CTRL + S : save as
|
|
if (shift_modifier_active)
|
|
selectSaveFilename();
|
|
// CTRL + S : save (save as if necessary)
|
|
else
|
|
saveOrSaveAs();
|
|
}
|
|
else if (ImGui::IsKeyPressed( GLFW_KEY_W )) {
|
|
// New Session
|
|
Mixer::manager().close();
|
|
}
|
|
else if (ImGui::IsKeyPressed( GLFW_KEY_SPACE )) {
|
|
// restart media player
|
|
sourcecontrol.Replay();
|
|
}
|
|
else if (ImGui::IsKeyPressed( GLFW_KEY_L )) {
|
|
// Logs
|
|
Settings::application.widget.logs = !Settings::application.widget.logs;
|
|
}
|
|
else if (ImGui::IsKeyPressed( GLFW_KEY_T )) {
|
|
// Timers
|
|
timercontrol.setVisible(!Settings::application.widget.timer);
|
|
}
|
|
else if (ImGui::IsKeyPressed( GLFW_KEY_G )) {
|
|
// Developer toolbox
|
|
Settings::application.widget.toolbox = !Settings::application.widget.toolbox;
|
|
}
|
|
else if (ImGui::IsKeyPressed( GLFW_KEY_H )) {
|
|
// Helper
|
|
Settings::application.widget.help = !Settings::application.widget.help;
|
|
}
|
|
else if (ImGui::IsKeyPressed( GLFW_KEY_E )) {
|
|
// Shader Editor
|
|
Settings::application.widget.shader_editor = !Settings::application.widget.shader_editor;
|
|
}
|
|
else if (ImGui::IsKeyPressed( GLFW_KEY_D )) {
|
|
// Display output
|
|
outputcontrol.setVisible(!Settings::application.widget.preview);
|
|
}
|
|
else if (ImGui::IsKeyPressed( GLFW_KEY_P )) {
|
|
// Media player
|
|
sourcecontrol.setVisible(!Settings::application.widget.media_player);
|
|
}
|
|
else if (ImGui::IsKeyPressed( GLFW_KEY_A )) {
|
|
if (shift_modifier_active)
|
|
{
|
|
// clear selection
|
|
Mixer::manager().unsetCurrentSource();
|
|
Mixer::selection().clear();
|
|
}
|
|
else
|
|
// select all
|
|
Mixer::manager().view()->selectAll();
|
|
}
|
|
else if (ImGui::IsKeyPressed( GLFW_KEY_R )) {
|
|
if (shift_modifier_active) {
|
|
FrameGrabbing::manager().add(new PNGRecorder);
|
|
}
|
|
else {
|
|
// toggle recording stop / start (or save and continue if + ALT modifier)
|
|
outputcontrol.ToggleRecord(alt_modifier_active);
|
|
}
|
|
}
|
|
else if (ImGui::IsKeyPressed( GLFW_KEY_Z )) {
|
|
if (shift_modifier_active)
|
|
Action::manager().redo();
|
|
else
|
|
Action::manager().undo();
|
|
}
|
|
else if (ImGui::IsKeyPressed( GLFW_KEY_C )) {
|
|
std::string clipboard = Mixer::selection().clipboard();
|
|
if (!clipboard.empty())
|
|
ImGui::SetClipboardText(clipboard.c_str());
|
|
}
|
|
else if (ImGui::IsKeyPressed( GLFW_KEY_X )) {
|
|
std::string clipboard = Mixer::selection().clipboard();
|
|
if (!clipboard.empty()) {
|
|
ImGui::SetClipboardText(clipboard.c_str());
|
|
Mixer::manager().deleteSelection();
|
|
}
|
|
}
|
|
else if (ImGui::IsKeyPressed( GLFW_KEY_V )) {
|
|
auto clipboard = ImGui::GetClipboardText();
|
|
if (clipboard != nullptr && strlen(clipboard) > 0)
|
|
Mixer::manager().paste(clipboard);
|
|
}
|
|
else if (ImGui::IsKeyPressed( GLFW_KEY_F )) {
|
|
if (shift_modifier_active)
|
|
Rendering::manager().mainWindow().toggleFullscreen();
|
|
else
|
|
Rendering::manager().outputWindow().toggleFullscreen();
|
|
}
|
|
else if (ImGui::IsKeyPressed( GLFW_KEY_M )) {
|
|
Settings::application.widget.stats = !Settings::application.widget.stats;
|
|
}
|
|
else if (ImGui::IsKeyPressed( GLFW_KEY_K )) {
|
|
Settings::application.widget.inputs = !Settings::application.widget.inputs;
|
|
}
|
|
else if (ImGui::IsKeyPressed( GLFW_KEY_N ) && shift_modifier_active) {
|
|
Mixer::manager().session()->addNote();
|
|
}
|
|
|
|
}
|
|
// No CTRL modifier
|
|
else {
|
|
|
|
// Application F-Keys
|
|
if (ImGui::IsKeyPressed( GLFW_KEY_F1 ))
|
|
Mixer::manager().setView(View::MIXING);
|
|
else if (ImGui::IsKeyPressed( GLFW_KEY_F2 ))
|
|
Mixer::manager().setView(View::GEOMETRY);
|
|
else if (ImGui::IsKeyPressed( GLFW_KEY_F3 ))
|
|
Mixer::manager().setView(View::LAYER);
|
|
else if (ImGui::IsKeyPressed( GLFW_KEY_F4 ))
|
|
Mixer::manager().setView(View::TEXTURE);
|
|
else if (ImGui::IsKeyPressed( GLFW_KEY_F12 ))
|
|
StartScreenshot();
|
|
// button home to toggle menu
|
|
else if (ImGui::IsKeyPressed( GLFW_KEY_HOME ))
|
|
navigator.togglePannelMenu();
|
|
// button home to toggle menu
|
|
else if (ImGui::IsKeyPressed( GLFW_KEY_INSERT ))
|
|
navigator.togglePannelNew();
|
|
// button esc
|
|
else if (ImGui::IsKeyPressed( GLFW_KEY_ESCAPE )) {
|
|
// hide pannel
|
|
navigator.hidePannel();
|
|
// toggle clear workspace
|
|
WorkspaceWindow::toggleClearRestoreWorkspace();
|
|
}
|
|
else if (ImGui::IsKeyPressed( GLFW_KEY_END )) {
|
|
Settings::application.render.disabled = !Settings::application.render.disabled;
|
|
}
|
|
// Space bar
|
|
else if (ImGui::IsKeyPressed( GLFW_KEY_SPACE ))
|
|
// Space bar to toggle play / pause
|
|
sourcecontrol.Play();
|
|
|
|
// normal keys in workspace // make sure no entry / window box is active
|
|
if ( keyboard_available )
|
|
{
|
|
// Backspace to delete source
|
|
if (ImGui::IsKeyPressed( GLFW_KEY_BACKSPACE ) || ImGui::IsKeyPressed( GLFW_KEY_DELETE ))
|
|
Mixer::manager().deleteSelection();
|
|
// button tab to select next
|
|
else if ( !alt_modifier_active && ImGui::IsKeyPressed( GLFW_KEY_TAB )) {
|
|
// cancel selection
|
|
if (!Mixer::selection().empty())
|
|
Mixer::selection().clear();
|
|
if (shift_modifier_active)
|
|
Mixer::manager().setCurrentPrevious();
|
|
else
|
|
Mixer::manager().setCurrentNext();
|
|
}
|
|
// arrow keys to act on current view
|
|
else if (ImGui::IsKeyDown( GLFW_KEY_LEFT ))
|
|
Mixer::manager().view()->arrow( glm::vec2(-1.f, 0.f) );
|
|
else if (ImGui::IsKeyDown( GLFW_KEY_RIGHT ))
|
|
Mixer::manager().view()->arrow( glm::vec2(+1.f, 0.f) );
|
|
if (ImGui::IsKeyDown( GLFW_KEY_UP ))
|
|
Mixer::manager().view()->arrow( glm::vec2(0.f, -1.f) );
|
|
else if (ImGui::IsKeyDown( GLFW_KEY_DOWN ))
|
|
Mixer::manager().view()->arrow( glm::vec2(0.f, +1.f) );
|
|
|
|
if (ImGui::IsKeyReleased( GLFW_KEY_LEFT ) ||
|
|
ImGui::IsKeyReleased( GLFW_KEY_RIGHT ) ||
|
|
ImGui::IsKeyReleased( GLFW_KEY_UP ) ||
|
|
ImGui::IsKeyReleased( GLFW_KEY_DOWN ) ){
|
|
Mixer::manager().view()->terminate();
|
|
}
|
|
}
|
|
}
|
|
|
|
// special case: CTRL + TAB is ALT + TAB in OSX
|
|
if (io.ConfigMacOSXBehaviors ? io.KeyAlt : io.KeyCtrl) {
|
|
if (ImGui::IsKeyPressed( GLFW_KEY_TAB ))
|
|
show_view_navigator += shift_modifier_active ? 3 : 1;
|
|
}
|
|
else if (show_view_navigator > 0) {
|
|
show_view_navigator = 0;
|
|
Mixer::manager().setView((View::Mode) target_view_navigator);
|
|
}
|
|
|
|
}
|
|
|
|
void UserInterface::handleMouse()
|
|
{
|
|
|
|
ImGuiIO& io = ImGui::GetIO();
|
|
glm::vec2 mousepos(io.MousePos.x * io.DisplayFramebufferScale.x, io.MousePos.y * io.DisplayFramebufferScale.y);
|
|
mousepos = glm::clamp(mousepos, glm::vec2(0.f), glm::vec2(io.DisplaySize.x * io.DisplayFramebufferScale.x, io.DisplaySize.y * io.DisplayFramebufferScale.y));
|
|
|
|
static glm::vec2 mouse_smooth = mousepos;
|
|
|
|
static glm::vec2 mouseclic[2];
|
|
mouseclic[ImGuiMouseButton_Left] = glm::vec2(io.MouseClickedPos[ImGuiMouseButton_Left].x * io.DisplayFramebufferScale.y, io.MouseClickedPos[ImGuiMouseButton_Left].y* io.DisplayFramebufferScale.x);
|
|
mouseclic[ImGuiMouseButton_Right] = glm::vec2(io.MouseClickedPos[ImGuiMouseButton_Right].x * io.DisplayFramebufferScale.y, io.MouseClickedPos[ImGuiMouseButton_Right].y* io.DisplayFramebufferScale.x);
|
|
|
|
static bool mousedown = false;
|
|
static View *view_drag = nullptr;
|
|
static std::pair<Node *, glm::vec2> picked = { nullptr, glm::vec2(0.f) };
|
|
|
|
// steal focus on right button clic
|
|
if (!io.WantCaptureMouse)
|
|
if (ImGui::IsMouseClicked(ImGuiMouseButton_Right) /*|| ImGui::IsMouseClicked(ImGuiMouseButton_Middle)*/)
|
|
ImGui::FocusWindow(NULL);
|
|
|
|
//
|
|
// Mouse over
|
|
//
|
|
{
|
|
View::Cursor c = Mixer::manager().view()->over(mousepos);
|
|
if (c.type > 0)
|
|
SetMouseCursor(io.MousePos, c);
|
|
}
|
|
|
|
// if not on any window
|
|
if ( !ImGui::IsAnyWindowHovered() && !ImGui::IsAnyWindowFocused() )
|
|
{
|
|
//
|
|
// Mouse wheel over background
|
|
//
|
|
if ( io.MouseWheel != 0) {
|
|
// scroll => zoom current view
|
|
Mixer::manager().view()->zoom( io.MouseWheel );
|
|
}
|
|
// TODO : zoom with center on source if over current
|
|
|
|
//
|
|
// RIGHT Mouse button
|
|
//
|
|
if ( ImGui::IsMouseDragging(ImGuiMouseButton_Right, 10.0f) )
|
|
{
|
|
// right mouse drag => drag current view
|
|
View::Cursor c = Mixer::manager().view()->drag( mouseclic[ImGuiMouseButton_Right], mousepos);
|
|
SetMouseCursor(io.MousePos, c);
|
|
}
|
|
else if ( ImGui::IsMouseDown(ImGuiMouseButton_Right))
|
|
{
|
|
Mixer::manager().unsetCurrentSource();
|
|
navigator.hidePannel();
|
|
// glm::vec3 point = Rendering::manager().unProject(mousepos, Mixer::manager().currentView()->scene.root()->transform_ );
|
|
}
|
|
|
|
if ( ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Right) )
|
|
{
|
|
Mixer::manager().view()->recenter();
|
|
}
|
|
|
|
//
|
|
// LEFT Mouse button
|
|
//
|
|
if ( ImGui::IsMouseDown(ImGuiMouseButton_Left) ) {
|
|
|
|
if ( !mousedown )
|
|
{
|
|
mousedown = true;
|
|
mouse_smooth = mousepos;
|
|
|
|
// ask the view what was picked
|
|
picked = Mixer::manager().view()->pick(mousepos);
|
|
|
|
bool clear_selection = false;
|
|
// if nothing picked,
|
|
if ( picked.first == nullptr ) {
|
|
clear_selection = true;
|
|
}
|
|
// something was picked
|
|
else {
|
|
// get if a source was picked
|
|
Source *s = Mixer::manager().findSource(picked.first);
|
|
if (s != nullptr)
|
|
{
|
|
// CTRL + clic = multiple selection
|
|
if (ctrl_modifier_active) {
|
|
if ( !Mixer::selection().contains(s) )
|
|
Mixer::selection().add( s );
|
|
else {
|
|
Mixer::selection().remove( s );
|
|
if ( Mixer::selection().size() > 1 )
|
|
s = Mixer::selection().front();
|
|
else {
|
|
s = nullptr;
|
|
}
|
|
}
|
|
}
|
|
// making the picked source the current one
|
|
if (s)
|
|
Mixer::manager().setCurrentSource( s );
|
|
else
|
|
Mixer::manager().unsetCurrentSource();
|
|
if (navigator.pannelVisible())
|
|
navigator.showPannelSource( Mixer::manager().indexCurrentSource() );
|
|
|
|
// indicate to view that an action can be initiated (e.g. grab)
|
|
Mixer::manager().view()->initiate();
|
|
}
|
|
// no source is selected
|
|
else
|
|
Mixer::manager().unsetCurrentSource();
|
|
}
|
|
if (clear_selection) {
|
|
// unset current
|
|
Mixer::manager().unsetCurrentSource();
|
|
navigator.hidePannel();
|
|
// clear selection
|
|
Mixer::selection().clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left) )
|
|
{
|
|
// double clic in Transition view means quit
|
|
if (Settings::application.current_view == View::TRANSITION) {
|
|
Mixer::manager().setView(View::MIXING);
|
|
}
|
|
// double clic in other views means toggle pannel
|
|
else {
|
|
if (navigator.pannelVisible())
|
|
// discard current to select front most source
|
|
// (because single clic maintains same source active)
|
|
Mixer::manager().unsetCurrentSource();
|
|
|
|
// display source in left pannel
|
|
navigator.showPannelSource( Mixer::manager().indexCurrentSource() );
|
|
}
|
|
}
|
|
|
|
// if ( mousedown && glm::distance(mouseclic[ImGuiMouseButton_Left], mousepos) > 3.f )
|
|
if ( ImGui::IsMouseDragging(ImGuiMouseButton_Left, 5.0f) )
|
|
{
|
|
if(view_drag == nullptr) {
|
|
view_drag = Mixer::manager().view();
|
|
|
|
// indicate to view that an action can be initiated (e.g. grab)
|
|
Mixer::manager().view()->initiate();
|
|
}
|
|
|
|
// only operate if the view didn't change
|
|
if (view_drag == Mixer::manager().view()) {
|
|
|
|
if ( picked.first != nullptr ) {
|
|
// Smooth cursor
|
|
if (Settings::application.smooth_cursor) {
|
|
// TODO : physics implementation
|
|
float smoothing = 10.f / ( MAX(io.Framerate, 1.f) );
|
|
glm::vec2 d = mousepos - mouse_smooth;
|
|
mouse_smooth += smoothing * d;
|
|
ImVec2 start = ImVec2(mouse_smooth.x / io.DisplayFramebufferScale.x, mouse_smooth.y / io.DisplayFramebufferScale.y);
|
|
ImGui::GetBackgroundDrawList()->AddLine(io.MousePos, start, ImGui::GetColorU32(ImGuiCol_HeaderActive), 5.f);
|
|
}
|
|
else
|
|
mouse_smooth = mousepos;
|
|
|
|
// action on current source
|
|
Source *current = Mixer::manager().currentSource();
|
|
if (current)
|
|
{
|
|
if (!shift_modifier_active) {
|
|
// grab others from selection
|
|
for (auto it = Mixer::selection().begin(); it != Mixer::selection().end(); ++it) {
|
|
if ( *it != current )
|
|
Mixer::manager().view()->grab(*it, mouseclic[ImGuiMouseButton_Left], mouse_smooth, picked);
|
|
}
|
|
}
|
|
// grab current sources
|
|
View::Cursor c = Mixer::manager().view()->grab(current, mouseclic[ImGuiMouseButton_Left], mouse_smooth, picked);
|
|
SetMouseCursor(io.MousePos, c);
|
|
}
|
|
// action on other (non-source) elements in the view
|
|
else
|
|
{
|
|
View::Cursor c = Mixer::manager().view()->grab(nullptr, mouseclic[ImGuiMouseButton_Left], mouse_smooth, picked);
|
|
SetMouseCursor(io.MousePos, c);
|
|
}
|
|
}
|
|
// Selection area
|
|
else {
|
|
// highlight-colored selection rectangle
|
|
ImVec4 color = ImGuiToolkit::HighlightColor();
|
|
ImGui::GetBackgroundDrawList()->AddRect(io.MouseClickedPos[ImGuiMouseButton_Left], io.MousePos, ImGui::GetColorU32(color));
|
|
color.w = 0.12; // transparent
|
|
ImGui::GetBackgroundDrawList()->AddRectFilled(io.MouseClickedPos[ImGuiMouseButton_Left], io.MousePos, ImGui::GetColorU32(color));
|
|
|
|
// Bounding box multiple sources selection
|
|
Mixer::manager().view()->select(mouseclic[ImGuiMouseButton_Left], mousepos);
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// cancel all operations on view when interacting on GUI
|
|
view_drag = nullptr;
|
|
mousedown = false;
|
|
Mixer::manager().view()->terminate();
|
|
}
|
|
|
|
|
|
if ( ImGui::IsMouseReleased(ImGuiMouseButton_Left) || ImGui::IsMouseReleased(ImGuiMouseButton_Right) )
|
|
{
|
|
view_drag = nullptr;
|
|
mousedown = false;
|
|
picked = { nullptr, glm::vec2(0.f) };
|
|
Mixer::manager().view()->terminate();
|
|
SetMouseCursor(io.MousePos);
|
|
|
|
// special case of one single source in selection : make current after release
|
|
if (Mixer::selection().size() == 1)
|
|
Mixer::manager().setCurrentSource( Mixer::selection().front() );
|
|
}
|
|
}
|
|
|
|
|
|
bool UserInterface::saveOrSaveAs(bool force_versioning)
|
|
{
|
|
bool finished = false;
|
|
|
|
if (Mixer::manager().session()->filename().empty())
|
|
selectSaveFilename();
|
|
else {
|
|
Mixer::manager().save(force_versioning || Settings::application.save_version_snapshot);
|
|
finished = true;
|
|
}
|
|
return finished;
|
|
}
|
|
|
|
|
|
bool UserInterface::TryClose()
|
|
{
|
|
// cannot close if a file dialog is pending
|
|
if (DialogToolkit::FileDialog::busy())
|
|
return false;
|
|
|
|
// always stop all recordings
|
|
FrameGrabbing::manager().stopAll();
|
|
|
|
// force close if trying to close again although it is already pending for save
|
|
if (pending_save_on_exit)
|
|
return true;
|
|
|
|
// save on exit
|
|
pending_save_on_exit = false;
|
|
if (Settings::application.recentSessions.save_on_exit && !Mixer::manager().session()->empty())
|
|
{
|
|
// determine if a pending save of session is required
|
|
if (Mixer::manager().session()->filename().empty())
|
|
// need to wait for user to give a filename
|
|
pending_save_on_exit = true;
|
|
else
|
|
// ok to save the session
|
|
Mixer::manager().save(false);
|
|
}
|
|
|
|
// say we can close if no pending save of session is needed
|
|
return !pending_save_on_exit;
|
|
}
|
|
|
|
void UserInterface::selectSaveFilename()
|
|
{
|
|
if (sessionsavedialog) {
|
|
if (!Mixer::manager().session()->filename().empty())
|
|
sessionsavedialog->setFolder( Mixer::manager().session()->filename() );
|
|
|
|
sessionsavedialog->open();
|
|
}
|
|
|
|
navigator.hidePannel();
|
|
}
|
|
|
|
void UserInterface::selectOpenFilename()
|
|
{
|
|
// launch file dialog to select a session filename to open
|
|
if (sessionopendialog)
|
|
sessionopendialog->open();
|
|
|
|
navigator.hidePannel();
|
|
}
|
|
|
|
void UserInterface::NewFrame()
|
|
{
|
|
// Start the Dear ImGui frame
|
|
ImGui_ImplOpenGL3_NewFrame();
|
|
ImGui_ImplGlfw_NewFrame();
|
|
ImGui::NewFrame();
|
|
|
|
// deal with keyboard and mouse events
|
|
handleKeyboard();
|
|
handleMouse();
|
|
handleScreenshot();
|
|
|
|
// handle FileDialogs
|
|
if (sessionopendialog && sessionopendialog->closed() && !sessionopendialog->path().empty())
|
|
Mixer::manager().open(sessionopendialog->path());
|
|
|
|
if (sessionimportdialog && sessionimportdialog->closed() && !sessionimportdialog->path().empty())
|
|
Mixer::manager().import(sessionimportdialog->path());
|
|
|
|
if (sessionsavedialog && sessionsavedialog->closed() && !sessionsavedialog->path().empty())
|
|
Mixer::manager().saveas(sessionsavedialog->path(), Settings::application.save_version_snapshot);
|
|
|
|
// overlay to ensure file dialog is modal
|
|
if (DialogToolkit::FileDialog::busy()){
|
|
if (!ImGui::IsPopupOpen("Busy"))
|
|
ImGui::OpenPopup("Busy");
|
|
if (ImGui::BeginPopupModal("Busy", NULL, ImGuiWindowFlags_AlwaysAutoResize)) {
|
|
ImGui::Text("Close file dialog box to resume.");
|
|
ImGui::EndPopup();
|
|
}
|
|
}
|
|
|
|
// popup to inform to save before close
|
|
if (pending_save_on_exit) {
|
|
if (!ImGui::IsPopupOpen(MENU_SAVE_ON_EXIT))
|
|
ImGui::OpenPopup(MENU_SAVE_ON_EXIT);
|
|
if (ImGui::BeginPopupModal(MENU_SAVE_ON_EXIT, NULL, ImGuiWindowFlags_AlwaysAutoResize))
|
|
{
|
|
ImGui::Spacing();
|
|
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_ITALIC);
|
|
ImGui::Text("Looks like you started some work");
|
|
ImGui::Text("but didn't save the session.");
|
|
ImGui::PopFont();
|
|
ImGui::Spacing();
|
|
if (ImGui::Button(MENU_SAVEAS_FILE, ImVec2(ImGui::GetWindowContentRegionWidth(), 0))) {
|
|
pending_save_on_exit = false;
|
|
saveOrSaveAs();
|
|
ImGui::CloseCurrentPopup();
|
|
}
|
|
if (ImGui::Button(MENU_QUIT, ImVec2(ImGui::GetWindowContentRegionWidth(), 0))) {
|
|
Rendering::manager().close();
|
|
ImGui::CloseCurrentPopup();
|
|
}
|
|
ImGui::Spacing();
|
|
ImGui::EndPopup();
|
|
}
|
|
}
|
|
|
|
// navigator bar first
|
|
navigator.Render();
|
|
}
|
|
|
|
void UserInterface::Render()
|
|
{
|
|
// update windows before render
|
|
outputcontrol.Update();
|
|
sourcecontrol.Update();
|
|
timercontrol.Update();
|
|
inputscontrol.Update();
|
|
|
|
// warnings and notifications
|
|
Log::Render(&Settings::application.widget.logs);
|
|
|
|
if ( WorkspaceWindow::clear())
|
|
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.4);
|
|
|
|
// Output controller
|
|
if (outputcontrol.Visible())
|
|
outputcontrol.Render();
|
|
|
|
// Source controller
|
|
if (sourcecontrol.Visible())
|
|
sourcecontrol.Render();
|
|
|
|
// Timer controller
|
|
if (timercontrol.Visible())
|
|
timercontrol.Render();
|
|
|
|
// Keyboards controller
|
|
if (inputscontrol.Visible())
|
|
inputscontrol.Render();
|
|
|
|
// Logs
|
|
if (Settings::application.widget.logs)
|
|
Log::ShowLogWindow(&Settings::application.widget.logs);
|
|
|
|
// stats in the corner
|
|
if (Settings::application.widget.stats)
|
|
RenderMetrics(&Settings::application.widget.stats,
|
|
&Settings::application.widget.stats_corner,
|
|
&Settings::application.widget.stats_mode);
|
|
|
|
if ( WorkspaceWindow::clear())
|
|
ImGui::PopStyleVar();
|
|
// All other windows are simply not rendered if workspace is clear
|
|
else {
|
|
// windows
|
|
if (Settings::application.widget.shader_editor)
|
|
RenderShaderEditor();
|
|
if (Settings::application.widget.help)
|
|
helpwindow.Render();
|
|
if (Settings::application.widget.toolbox)
|
|
toolbox.Render();
|
|
|
|
// About
|
|
if (show_vimix_about)
|
|
RenderAbout(&show_vimix_about);
|
|
if (show_imgui_about)
|
|
ImGui::ShowAboutWindow(&show_imgui_about);
|
|
if (show_gst_about)
|
|
ShowAboutGStreamer(&show_gst_about);
|
|
if (show_opengl_about)
|
|
ShowAboutOpengl(&show_opengl_about);
|
|
}
|
|
|
|
// Notes
|
|
RenderNotes();
|
|
|
|
// Navigator
|
|
if (show_view_navigator > 0)
|
|
target_view_navigator = RenderViewNavigator( &show_view_navigator );
|
|
|
|
// all IMGUI Rendering
|
|
ImGui::Render();
|
|
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
|
|
|
}
|
|
|
|
void UserInterface::Terminate()
|
|
{
|
|
// restore windows position for saving
|
|
WorkspaceWindow::restoreWorkspace(true);
|
|
|
|
// Cleanup
|
|
ImGui_ImplOpenGL3_Shutdown();
|
|
ImGui_ImplGlfw_Shutdown();
|
|
ImGui::DestroyContext();
|
|
}
|
|
|
|
void UserInterface::showMenuEdit()
|
|
{
|
|
bool has_selection = !Mixer::selection().empty();
|
|
const char *clipboard = ImGui::GetClipboardText();
|
|
bool has_clipboard = (clipboard != nullptr && strlen(clipboard) > 0 && SessionLoader::isClipboard(clipboard));
|
|
|
|
if (ImGui::MenuItem( MENU_CUT, SHORTCUT_CUT, false, has_selection)) {
|
|
std::string copied_text = Mixer::selection().clipboard();
|
|
if (!copied_text.empty()) {
|
|
ImGui::SetClipboardText(copied_text.c_str());
|
|
Mixer::manager().deleteSelection();
|
|
}
|
|
navigator.hidePannel();
|
|
}
|
|
if (ImGui::MenuItem( MENU_COPY, SHORTCUT_COPY, false, has_selection)) {
|
|
std::string copied_text = Mixer::selection().clipboard();
|
|
if (!copied_text.empty())
|
|
ImGui::SetClipboardText(copied_text.c_str());
|
|
navigator.hidePannel();
|
|
}
|
|
if (ImGui::MenuItem( MENU_PASTE, SHORTCUT_PASTE, false, has_clipboard)) {
|
|
Mixer::manager().paste(clipboard);
|
|
navigator.hidePannel();
|
|
}
|
|
if (ImGui::MenuItem( MENU_SELECTALL, SHORTCUT_SELECTALL)) {
|
|
Mixer::manager().view()->selectAll();
|
|
navigator.hidePannel();
|
|
}
|
|
ImGui::Separator();
|
|
if ( ImGui::MenuItem( MENU_UNDO, SHORTCUT_UNDO) )
|
|
Action::manager().undo();
|
|
if ( ImGui::MenuItem( MENU_REDO, SHORTCUT_REDO) )
|
|
Action::manager().redo();
|
|
}
|
|
|
|
void UserInterface::showMenuFile()
|
|
{
|
|
if (ImGui::MenuItem( MENU_NEW_FILE, SHORTCUT_NEW_FILE)) {
|
|
Mixer::manager().close();
|
|
navigator.hidePannel();
|
|
}
|
|
ImGui::SetNextItemWidth( ImGui::GetContentRegionAvail().x * 0.54f);
|
|
ImGui::Combo("Ratio", &Settings::application.render.ratio, FrameBuffer::aspect_ratio_name, IM_ARRAYSIZE(FrameBuffer::aspect_ratio_name) );
|
|
ImGui::SetNextItemWidth( ImGui::GetContentRegionAvail().x * 0.54f);
|
|
ImGui::Combo("Height", &Settings::application.render.res, FrameBuffer::resolution_name, IM_ARRAYSIZE(FrameBuffer::resolution_name) );
|
|
|
|
ImGui::Separator();
|
|
|
|
ImGui::MenuItem( ICON_FA_LEVEL_UP_ALT " Open last on start", nullptr, &Settings::application.recentSessions.load_at_start);
|
|
|
|
if (ImGui::MenuItem( MENU_OPEN_FILE, SHORTCUT_OPEN_FILE))
|
|
selectOpenFilename();
|
|
if (ImGui::MenuItem( MENU_REOPEN_FILE, SHORTCUT_REOPEN_FILE))
|
|
Mixer::manager().load( Mixer::manager().session()->filename() );
|
|
|
|
if (ImGui::MenuItem( ICON_FA_FILE_EXPORT " Import") && sessionimportdialog) {
|
|
// launch file dialog to open a session file
|
|
sessionimportdialog->open();
|
|
navigator.hidePannel();
|
|
}
|
|
if (ImGui::MenuItem( MENU_SAVE_FILE, SHORTCUT_SAVE_FILE)) {
|
|
if (saveOrSaveAs())
|
|
navigator.hidePannel();
|
|
}
|
|
if (ImGui::MenuItem( MENU_SAVEAS_FILE, SHORTCUT_SAVEAS_FILE))
|
|
selectSaveFilename();
|
|
|
|
ImGui::MenuItem( MENU_SAVE_ON_EXIT, nullptr, &Settings::application.recentSessions.save_on_exit);
|
|
|
|
ImGui::Separator();
|
|
if (ImGui::MenuItem( IMGUI_TITLE_HELP, SHORTCUT_HELP))
|
|
Settings::application.widget.help = true;
|
|
if (ImGui::MenuItem( MENU_QUIT, SHORTCUT_QUIT))
|
|
TryClose();
|
|
|
|
}
|
|
|
|
void UserInterface::StartScreenshot()
|
|
{
|
|
screenshot_step = 1;
|
|
}
|
|
|
|
void UserInterface::handleScreenshot()
|
|
{
|
|
// taking screenshot is in 3 steps
|
|
// 1) wait 1 frame that the menu / action showing button to take screenshot disapears
|
|
// 2) wait 1 frame that rendering manager takes the actual screenshot
|
|
// 3) if rendering manager current screenshot is ok, save it
|
|
if (screenshot_step > 0) {
|
|
|
|
switch(screenshot_step) {
|
|
case 1:
|
|
screenshot_step = 2;
|
|
break;
|
|
case 2:
|
|
Rendering::manager().requestScreenshot();
|
|
screenshot_step = 3;
|
|
break;
|
|
case 3:
|
|
{
|
|
if ( Rendering::manager().currentScreenshot()->isFull() ){
|
|
std::string filename = SystemToolkit::full_filename( SystemToolkit::home_path(), SystemToolkit::date_time_string() + "_vmixcapture.png" );
|
|
Rendering::manager().currentScreenshot()->save( filename );
|
|
Log::Notify("Screenshot saved %s", filename.c_str() );
|
|
}
|
|
screenshot_step = 4;
|
|
}
|
|
break;
|
|
default:
|
|
screenshot_step = 0;
|
|
break;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
int UserInterface::RenderViewNavigator(int *shift)
|
|
{
|
|
// calculate potential target view index :
|
|
// - shift increment : minus 1 to not react to first trigger
|
|
// - current_view : indices are between 1 (Mixing) and 5 (Appearance)
|
|
// - Modulo 4 to allow multiple repetition of shift increment
|
|
int target_index = ( (Settings::application.current_view -1)+ (*shift -1) )%4 + 1;
|
|
|
|
// prepare rendering of centered, fixed-size, semi-transparent window;
|
|
const ImGuiIO& io = ImGui::GetIO();
|
|
ImVec2 window_pos = ImVec2(io.DisplaySize.x / 2.f, io.DisplaySize.y / 2.f);
|
|
ImVec2 window_pos_pivot = ImVec2(0.5f, 0.5f);
|
|
ImGui::SetNextWindowPos(window_pos, ImGuiCond_Always, window_pos_pivot);
|
|
ImGui::SetNextWindowSize(ImVec2(500.f, 120.f + 2.f * ImGui::GetTextLineHeight()), ImGuiCond_Always);
|
|
ImGui::SetNextWindowBgAlpha(0.85f);
|
|
|
|
// show window
|
|
if (ImGui::Begin("Views", NULL, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav))
|
|
{
|
|
// prepare rendering of the array of selectable icons
|
|
bool selected_view[View::INVALID] = { };
|
|
selected_view[ target_index ] = true;
|
|
ImVec2 iconsize(120.f, 120.f);
|
|
|
|
// draw icons centered horizontally and vertically
|
|
ImVec2 alignment = ImVec2(0.4f, 0.5f);
|
|
ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, alignment);
|
|
|
|
// draw in 4 columns
|
|
ImGui::Columns(4, NULL, false);
|
|
|
|
// 4 selectable large icons
|
|
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
|
|
if (ImGui::Selectable( ICON_FA_BULLSEYE, &selected_view[1], 0, iconsize))
|
|
{
|
|
Mixer::manager().setView(View::MIXING);
|
|
*shift = 0;
|
|
}
|
|
ImGui::NextColumn();
|
|
if (ImGui::Selectable( ICON_FA_OBJECT_UNGROUP , &selected_view[2], 0, iconsize))
|
|
{
|
|
Mixer::manager().setView(View::GEOMETRY);
|
|
*shift = 0;
|
|
}
|
|
ImGui::NextColumn();
|
|
if (ImGui::Selectable( ICON_FA_LAYER_GROUP, &selected_view[3], 0, iconsize))
|
|
{
|
|
Mixer::manager().setView(View::LAYER);
|
|
*shift = 0;
|
|
}
|
|
ImGui::NextColumn();
|
|
if (ImGui::Selectable( ICON_FA_CHESS_BOARD, &selected_view[4], 0, iconsize))
|
|
{
|
|
Mixer::manager().setView(View::TEXTURE);
|
|
*shift = 0;
|
|
}
|
|
ImGui::PopFont();
|
|
|
|
// 4 subtitles (text centered in column)
|
|
for (int v = View::MIXING; v < View::TRANSITION; ++v) {
|
|
ImGui::NextColumn();
|
|
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (ImGui::GetColumnWidth() - ImGui::CalcTextSize(Settings::application.views[v].name.c_str()).x) * 0.5f - ImGui::GetStyle().ItemSpacing.x);
|
|
ImGuiToolkit::PushFont(Settings::application.current_view == 4 ? ImGuiToolkit::FONT_BOLD : ImGuiToolkit::FONT_DEFAULT);
|
|
ImGui::Text(Settings::application.views[v].name.c_str());
|
|
ImGui::PopFont();
|
|
}
|
|
|
|
ImGui::Columns(1);
|
|
ImGui::PopStyleVar();
|
|
|
|
ImGui::End();
|
|
}
|
|
|
|
return target_index;
|
|
}
|
|
|
|
void UserInterface::showSourceEditor(Source *s)
|
|
{
|
|
Mixer::manager().unsetCurrentSource();
|
|
Mixer::selection().clear();
|
|
|
|
if (s) {
|
|
Mixer::manager().setCurrentSource( s );
|
|
if (s->playable()) {
|
|
sourcecontrol.setVisible(true);
|
|
sourcecontrol.resetActiveSelection();
|
|
return;
|
|
}
|
|
CloneSource *cs = dynamic_cast<CloneSource *>(s);
|
|
if (cs != nullptr) {
|
|
Mixer::manager().setCurrentSource( cs->origin() );
|
|
return;
|
|
}
|
|
RenderSource *rs = dynamic_cast<RenderSource *>(s);
|
|
if (rs != nullptr) {
|
|
outputcontrol.setVisible(true);
|
|
return;
|
|
}
|
|
navigator.showPannelSource( Mixer::manager().indexCurrentSource() );
|
|
}
|
|
}
|
|
|
|
void UserInterface::fillShaderEditor(const std::string &text)
|
|
{
|
|
static bool initialized = false;
|
|
if (!initialized) {
|
|
auto lang = TextEditor::LanguageDefinition::GLSL();
|
|
|
|
static const char* const keywords[] = {
|
|
"discard", "attribute", "varying", "uniform", "in", "out", "inout", "bvec2", "bvec3", "bvec4", "dvec2",
|
|
"dvec3", "dvec4", "ivec2", "ivec3", "ivec4", "uvec2", "uvec3", "uvec4", "vec2", "vec3", "vec4", "mat2",
|
|
"mat3", "mat4", "dmat2", "dmat3", "dmat4", "sampler1D", "sampler2D", "sampler3D", "samplerCUBE", "samplerbuffer",
|
|
"sampler1DArray", "sampler2DArray", "sampler1DShadow", "sampler2DShadow", "vec4", "vec4", "smooth", "flat",
|
|
"precise", "coherent", "uint", "struct", "switch", "unsigned", "void", "volatile", "while", "readonly"
|
|
};
|
|
for (auto& k : keywords)
|
|
lang.mKeywords.insert(k);
|
|
|
|
static const char* const identifiers[] = {
|
|
"radians", "degrees", "sin", "cos", "tan", "asin", "acos", "atan", "pow", "exp2", "log2", "sqrt", "inversesqrt",
|
|
"abs", "sign", "floor", "ceil", "fract", "mod", "min", "max", "clamp", "mix", "step", "smoothstep", "length", "distance",
|
|
"dot", "cross", "normalize", "ftransform", "faceforward", "reflect", "matrixcompmult", "lessThan", "lessThanEqual",
|
|
"greaterThan", "greaterThanEqual", "equal", "notEqual", "any", "all", "not", "texture1D", "texture1DProj", "texture1DLod",
|
|
"texture1DProjLod", "texture", "texture2D", "texture2DProj", "texture2DLod", "texture2DProjLod", "texture3D",
|
|
"texture3DProj", "texture3DLod", "texture3DProjLod", "textureCube", "textureCubeLod", "shadow1D", "shadow1DProj",
|
|
"shadow1DLod", "shadow1DProjLod", "shadow2D", "shadow2DProj", "shadow2DLod", "shadow2DProjLod",
|
|
"dFdx", "dFdy", "fwidth", "noise1", "noise2", "noise3", "noise4", "refract", "exp", "log", "mainImage",
|
|
};
|
|
for (auto& k : identifiers)
|
|
{
|
|
TextEditor::Identifier id;
|
|
id.mDeclaration = "Added function";
|
|
lang.mIdentifiers.insert(std::make_pair(std::string(k), id));
|
|
}
|
|
// init editor
|
|
editor.SetLanguageDefinition(lang);
|
|
initialized = true;
|
|
}
|
|
|
|
// remember text
|
|
currentTextEdit = text;
|
|
// fill editor
|
|
editor.SetText(currentTextEdit);
|
|
}
|
|
|
|
void UserInterface::RenderShaderEditor()
|
|
{
|
|
static bool show_statusbar = true;
|
|
|
|
if ( !ImGui::Begin(IMGUI_TITLE_SHADEREDITOR, &Settings::application.widget.shader_editor, ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_MenuBar) )
|
|
{
|
|
ImGui::End();
|
|
return;
|
|
}
|
|
|
|
ImGui::SetWindowSize(ImVec2(800, 600), ImGuiCond_FirstUseEver);
|
|
if (ImGui::BeginMenuBar())
|
|
{
|
|
if (ImGui::BeginMenu("Edit"))
|
|
{
|
|
bool ro = editor.IsReadOnly();
|
|
if (ImGui::MenuItem("Read-only mode", nullptr, &ro))
|
|
editor.SetReadOnly(ro);
|
|
ImGui::Separator();
|
|
|
|
if (ImGui::MenuItem( MENU_UNDO, SHORTCUT_UNDO, nullptr, !ro && editor.CanUndo()))
|
|
editor.Undo();
|
|
if (ImGui::MenuItem( MENU_REDO, SHORTCUT_REDO, nullptr, !ro && editor.CanRedo()))
|
|
editor.Redo();
|
|
|
|
ImGui::Separator();
|
|
|
|
if (ImGui::MenuItem( MENU_COPY, SHORTCUT_COPY, nullptr, editor.HasSelection()))
|
|
editor.Copy();
|
|
if (ImGui::MenuItem( MENU_CUT, SHORTCUT_CUT, nullptr, !ro && editor.HasSelection()))
|
|
editor.Cut();
|
|
if (ImGui::MenuItem( MENU_DELETE, SHORTCUT_DELETE, nullptr, !ro && editor.HasSelection()))
|
|
editor.Delete();
|
|
if (ImGui::MenuItem( MENU_PASTE, SHORTCUT_PASTE, nullptr, !ro && ImGui::GetClipboardText() != nullptr))
|
|
editor.Paste();
|
|
|
|
ImGui::Separator();
|
|
|
|
if (ImGui::MenuItem( "Select all", nullptr, nullptr))
|
|
editor.SetSelection(TextEditor::Coordinates(), TextEditor::Coordinates(editor.GetTotalLines(), 0));
|
|
|
|
ImGui::EndMenu();
|
|
}
|
|
|
|
if (ImGui::BeginMenu("View"))
|
|
{
|
|
bool ws = editor.IsShowingWhitespaces();
|
|
if (ImGui::MenuItem( ICON_FA_LONG_ARROW_ALT_RIGHT " Whitespace", nullptr, &ws))
|
|
editor.SetShowWhitespaces(ws);
|
|
ImGui::MenuItem( ICON_FA_WINDOW_MAXIMIZE " Statusbar", nullptr, &show_statusbar);
|
|
ImGui::EndMenu();
|
|
}
|
|
ImGui::EndMenuBar();
|
|
}
|
|
|
|
if (show_statusbar) {
|
|
auto cpos = editor.GetCursorPosition();
|
|
ImGui::Text("%6d/%-6d %6d lines | %s | %s | %s ", cpos.mLine + 1, cpos.mColumn + 1, editor.GetTotalLines(),
|
|
editor.IsOverwrite() ? "Ovr" : "Ins",
|
|
editor.CanUndo() ? "*" : " ",
|
|
editor.GetLanguageDefinition().mName.c_str());
|
|
}
|
|
|
|
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO);
|
|
editor.Render("ShaderEditor");
|
|
ImGui::PopFont();
|
|
|
|
ImGui::End();
|
|
}
|
|
|
|
void UserInterface::RenderMetrics(bool *p_open, int* p_corner, int *p_mode)
|
|
{
|
|
if (!p_corner || !p_open)
|
|
return;
|
|
|
|
const float DISTANCE = 10.0f;
|
|
int corner = *p_corner;
|
|
ImGuiIO& io = ImGui::GetIO();
|
|
if (corner != -1)
|
|
{
|
|
ImVec2 window_pos = ImVec2((corner & 1) ? io.DisplaySize.x - DISTANCE : DISTANCE, (corner & 2) ? io.DisplaySize.y - DISTANCE : DISTANCE);
|
|
ImVec2 window_pos_pivot = ImVec2((corner & 1) ? 1.0f : 0.0f, (corner & 2) ? 1.0f : 0.0f);
|
|
ImGui::SetNextWindowPos(window_pos, ImGuiCond_Always, window_pos_pivot);
|
|
}
|
|
|
|
ImGui::SetNextWindowBgAlpha(0.35f); // Transparent background
|
|
|
|
if (!ImGui::Begin("Metrics", NULL, (corner != -1 ? ImGuiWindowFlags_NoMove : 0) | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav))
|
|
{
|
|
ImGui::End();
|
|
return;
|
|
}
|
|
|
|
ImGui::SetNextItemWidth(200);
|
|
ImGui::Combo("##mode", p_mode,
|
|
ICON_FA_TACHOMETER_ALT " Performance\0"
|
|
ICON_FA_HOURGLASS_HALF " Runtime\0"
|
|
ICON_FA_VECTOR_SQUARE " Source\0");
|
|
|
|
ImGui::SameLine();
|
|
if (ImGuiToolkit::IconButton(5,8))
|
|
ImGui::OpenPopup("metrics_menu");
|
|
ImGui::Spacing();
|
|
|
|
if (*p_mode > 1) {
|
|
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO);
|
|
Source *s = Mixer::manager().currentSource();
|
|
if (s) {
|
|
float rightalign = -2.5f * ImGui::GetTextLineHeightWithSpacing();
|
|
std::ostringstream info;
|
|
info << s->name() << ": ";
|
|
|
|
float v = s->alpha();
|
|
ImGui::SetNextItemWidth(rightalign);
|
|
if ( ImGui::DragFloat("Alpha", &v, 0.01f, 0.f, 1.f) )
|
|
s->call(new SetAlpha(v));
|
|
if ( ImGui::IsItemDeactivatedAfterEdit() ) {
|
|
info << "Alpha " << std::fixed << std::setprecision(3) << v;
|
|
Action::manager().store(info.str());
|
|
}
|
|
|
|
Group *n = s->group(View::GEOMETRY);
|
|
float translation[2] = { n->translation_.x, n->translation_.y};
|
|
ImGui::SetNextItemWidth(rightalign);
|
|
if ( ImGui::DragFloat2("Pos", translation, 0.01f, -MAX_SCALE, MAX_SCALE, "%.2f") ) {
|
|
n->translation_.x = translation[0];
|
|
n->translation_.y = translation[1];
|
|
s->touch();
|
|
}
|
|
if ( ImGui::IsItemDeactivatedAfterEdit() ){
|
|
info << "Position " << std::setprecision(3) << n->translation_.x << ", " << n->translation_.y;
|
|
Action::manager().store(info.str());
|
|
}
|
|
float scale[2] = { n->scale_.x, n->scale_.y} ;
|
|
ImGui::SetNextItemWidth(rightalign);
|
|
if ( ImGui::DragFloat2("Scale", scale, 0.01f, -MAX_SCALE, MAX_SCALE, "%.2f") )
|
|
{
|
|
n->scale_.x = CLAMP_SCALE(scale[0]);
|
|
n->scale_.y = CLAMP_SCALE(scale[1]);
|
|
s->touch();
|
|
}
|
|
if ( ImGui::IsItemDeactivatedAfterEdit() ){
|
|
info << "Scale " << std::setprecision(3) << n->scale_.x << " x " << n->scale_.y;
|
|
Action::manager().store(info.str());
|
|
}
|
|
|
|
ImGui::SetNextItemWidth(rightalign);
|
|
if ( ImGui::SliderAngle("Angle", &(n->rotation_.z), -180.f, 180.f) )
|
|
s->touch();
|
|
if ( ImGui::IsItemDeactivatedAfterEdit() ) {
|
|
info << "Angle " << std::setprecision(3) << n->rotation_.z * 180.f / M_PI;
|
|
Action::manager().store(info.str());
|
|
}
|
|
}
|
|
else
|
|
ImGui::Text("No source selected");
|
|
ImGui::PopFont();
|
|
}
|
|
else if (*p_mode > 0) {
|
|
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO);
|
|
ImGui::Text("Session %s", GstToolkit::time_to_string(Mixer::manager().session()->runtime(), GstToolkit::TIME_STRING_READABLE).c_str());
|
|
uint64_t time = Runtime();
|
|
ImGui::Text("Program %s", GstToolkit::time_to_string(time, GstToolkit::TIME_STRING_READABLE).c_str());
|
|
time += Settings::application.total_runtime;
|
|
ImGui::Text("Total %s", GstToolkit::time_to_string(time, GstToolkit::TIME_STRING_READABLE).c_str());
|
|
ImGui::PopFont();
|
|
}
|
|
else {
|
|
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO);
|
|
ImGui::Text("Window %.0f x %.0f", io.DisplaySize.x, io.DisplaySize.y);
|
|
// ImGui::Text("HiDPI (retina) %s", io.DisplayFramebufferScale.x > 1.f ? "on" : "off");
|
|
ImGui::Text("Refresh %.1f FPS", io.Framerate);
|
|
ImGui::Text("Memory %s", BaseToolkit::byte_to_string( SystemToolkit::memory_usage()).c_str() );
|
|
ImGui::PopFont();
|
|
|
|
}
|
|
|
|
if (ImGui::BeginPopup("metrics_menu"))
|
|
{
|
|
ImGui::TextDisabled("Metrics");
|
|
if (ImGui::MenuItem( ICON_FA_ANGLE_UP " Top", NULL, corner == 1)) *p_corner = 1;
|
|
if (ImGui::MenuItem( ICON_FA_ANGLE_DOWN " Bottom", NULL, corner == 3)) *p_corner = 3;
|
|
if (ImGui::MenuItem( ICON_FA_EXPAND_ARROWS_ALT " Free position", NULL, corner == -1)) *p_corner = -1;
|
|
if (p_open && ImGui::MenuItem( ICON_FA_TIMES " Close")) *p_open = false;
|
|
ImGui::EndPopup();
|
|
}
|
|
|
|
ImGui::End();
|
|
}
|
|
|
|
void UserInterface::RenderAbout(bool* p_open)
|
|
{
|
|
ImGui::SetNextWindowPos(ImVec2(1100, 20), ImGuiCond_FirstUseEver);
|
|
if (!ImGui::Begin("About " APP_TITLE, p_open, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_AlwaysAutoResize))
|
|
{
|
|
ImGui::End();
|
|
return;
|
|
}
|
|
|
|
#ifdef VIMIX_VERSION_MAJOR
|
|
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_BOLD);
|
|
ImGui::Text("%s %d.%d.%d", APP_NAME, VIMIX_VERSION_MAJOR, VIMIX_VERSION_MINOR, VIMIX_VERSION_PATCH);
|
|
ImGui::PopFont();
|
|
#else
|
|
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_BOLD);
|
|
ImGui::Text("%s", APP_NAME);
|
|
ImGui::PopFont();
|
|
#endif
|
|
|
|
ImGui::Separator();
|
|
ImGui::Text("vimix performs graphical mixing and blending of\nseveral movie clips and computer generated graphics,\nwith image processing effects in real-time.");
|
|
ImGui::Text("\nvimix is licensed under GNU GPL version 3 or later.\n" UNICODE_COPYRIGHT " 2019-2022 Bruno Herbelin.");
|
|
|
|
ImGui::Spacing();
|
|
ImGuiToolkit::ButtonOpenUrl("Visit vimix website", "https://brunoherbelin.github.io/vimix/", ImVec2(ImGui::GetContentRegionAvail().x, 0));
|
|
|
|
ImGui::Spacing();
|
|
ImGui::Text("\nvimix is built using the following libraries:");
|
|
|
|
// tinyfd_inputBox("tinyfd_query", NULL, NULL);
|
|
// ImGui::Text("- Tinyfiledialogs v%s mode '%s'", tinyfd_version, tinyfd_response);
|
|
|
|
ImGui::Columns(3, "abouts");
|
|
ImGui::Separator();
|
|
|
|
ImGui::Text("Dear ImGui");
|
|
ImGui::PushID("dearimguiabout");
|
|
if ( ImGui::Button("More info", ImVec2(ImGui::GetContentRegionAvail().x, 0)))
|
|
show_imgui_about = true;
|
|
ImGui::PopID();
|
|
|
|
ImGui::NextColumn();
|
|
|
|
ImGui::Text("GStreamer");
|
|
ImGui::PushID("gstreamerabout");
|
|
if ( ImGui::Button("More info", ImVec2(ImGui::GetContentRegionAvail().x, 0)))
|
|
show_gst_about = true;
|
|
ImGui::PopID();
|
|
|
|
ImGui::NextColumn();
|
|
|
|
ImGui::Text("OpenGL");
|
|
ImGui::PushID("openglabout");
|
|
if ( ImGui::Button("More info", ImVec2(ImGui::GetContentRegionAvail().x, 0)))
|
|
show_opengl_about = true;
|
|
ImGui::PopID();
|
|
|
|
ImGui::Columns(1);
|
|
|
|
|
|
ImGui::End();
|
|
}
|
|
|
|
void UserInterface::showPannel(int id)
|
|
{
|
|
if (id == NAV_MENU)
|
|
navigator.togglePannelMenu();
|
|
else if (id == NAV_NEW)
|
|
navigator.togglePannelNew();
|
|
else
|
|
navigator.showPannelSource(id);
|
|
}
|
|
|
|
void UserInterface::RenderNotes()
|
|
{
|
|
Session *se = Mixer::manager().session();
|
|
if (se!=nullptr && se->beginNotes() != se->endNotes()) {
|
|
|
|
ImVec4 color = ImGui::GetStyle().Colors[ImGuiCol_ResizeGripHovered];
|
|
color.w = 0.35f;
|
|
ImGui::PushStyleColor(ImGuiCol_WindowBg, color);
|
|
ImGui::PushStyleColor(ImGuiCol_TitleBg, color);
|
|
ImGui::PushStyleColor(ImGuiCol_TitleBgActive, color);
|
|
ImGui::PushStyleColor(ImGuiCol_TitleBgCollapsed, color);
|
|
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4());
|
|
|
|
for (auto note = se->beginNotes(); note != se->endNotes(); ) {
|
|
// detect close clic
|
|
bool close = false;
|
|
|
|
if ( (*note).stick < 1 || (*note).stick == Settings::application.current_view)
|
|
{
|
|
// window
|
|
ImGui::SetNextWindowSizeConstraints(ImVec2(150, 150), ImVec2(500, 500));
|
|
ImGui::SetNextWindowPos(ImVec2( (*note).pos.x, (*note).pos.y ), ImGuiCond_Once);
|
|
ImGui::SetNextWindowSize(ImVec2((*note).size.x, (*note).size.y), ImGuiCond_Once);
|
|
ImGui::SetNextWindowBgAlpha(color.w); // Transparent background
|
|
|
|
// draw
|
|
if (ImGui::Begin((*note).label.c_str(), NULL, ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoFocusOnAppearing |
|
|
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoSavedSettings))
|
|
{
|
|
ImVec2 size = ImGui::GetContentRegionAvail();
|
|
ImVec2 pos = ImGui::GetCursorPos();
|
|
// close & delete
|
|
if (ImGuiToolkit::IconButton(4,16)) close = true;
|
|
if (ImGui::IsItemHovered())
|
|
ImGuiToolkit::ToolTip("Delete");
|
|
|
|
if (ImGui::IsWindowFocused()) {
|
|
// font size
|
|
pos.x = size.x - 2.f * ImGui::GetTextLineHeightWithSpacing();
|
|
ImGui::SetCursorPos( pos );
|
|
if (ImGuiToolkit::IconButton(1, 13) )
|
|
(*note).large = !(*note).large ;
|
|
// stick to views icon
|
|
pos.x = size.x - ImGui::GetTextLineHeightWithSpacing() + 8.f;
|
|
ImGui::SetCursorPos( pos );
|
|
bool s = (*note).stick > 0;
|
|
if (ImGuiToolkit::IconToggle(5, 2, 4, 2, &s) )
|
|
(*note).stick = s ? Settings::application.current_view : 0;
|
|
}
|
|
|
|
// Text area
|
|
size.y -= ImGui::GetTextLineHeightWithSpacing() + 2.f;
|
|
ImGuiToolkit::PushFont( (*note).large ? ImGuiToolkit::FONT_LARGE : ImGuiToolkit::FONT_MONO );
|
|
ImGuiToolkit::InputTextMultiline("##notes", &(*note).text, size);
|
|
ImGui::PopFont();
|
|
|
|
// TODO smart detect when window moves
|
|
ImVec2 p = ImGui::GetWindowPos();
|
|
(*note).pos = glm::vec2( p.x, p.y);
|
|
p = ImGui::GetWindowSize();
|
|
(*note).size = glm::vec2( p.x, p.y);
|
|
|
|
ImGui::End();
|
|
}
|
|
}
|
|
// loop
|
|
if (close)
|
|
note = se->deleteNote(note);
|
|
else
|
|
++note;
|
|
}
|
|
|
|
|
|
ImGui::PopStyleColor(5);
|
|
}
|
|
|
|
}
|
|
|
|
///
|
|
/// TOOLBOX
|
|
///
|
|
ToolBox::ToolBox()
|
|
{
|
|
show_demo_window = false;
|
|
show_icons_window = false;
|
|
show_sandbox = false;
|
|
}
|
|
|
|
void ToolBox::Render()
|
|
{
|
|
static bool record_ = false;
|
|
static std::ofstream csv_file_;
|
|
|
|
// first run
|
|
ImGui::SetNextWindowPos(ImVec2(40, 40), ImGuiCond_FirstUseEver);
|
|
ImGui::SetNextWindowSize(ImVec2(400, 300), ImGuiCond_FirstUseEver);
|
|
ImGui::SetNextWindowSizeConstraints(ImVec2(350, 300), ImVec2(FLT_MAX, FLT_MAX));
|
|
if ( !ImGui::Begin(IMGUI_TITLE_TOOLBOX, &Settings::application.widget.toolbox, ImGuiWindowFlags_MenuBar) )
|
|
{
|
|
ImGui::End();
|
|
return;
|
|
}
|
|
|
|
// Menu Bar
|
|
if (ImGui::BeginMenuBar())
|
|
{
|
|
if (ImGui::BeginMenu("Render"))
|
|
{
|
|
if ( ImGui::MenuItem( ICON_FA_CAMERA_RETRO " Screenshot", "F12") )
|
|
UserInterface::manager().StartScreenshot();
|
|
|
|
ImGui::EndMenu();
|
|
}
|
|
if (ImGui::BeginMenu("Gui"))
|
|
{
|
|
ImGui::MenuItem("Sandbox", nullptr, &show_sandbox);
|
|
ImGui::MenuItem("Icons", nullptr, &show_icons_window);
|
|
ImGui::MenuItem("Demo ImGui", nullptr, &show_demo_window);
|
|
|
|
ImGui::EndMenu();
|
|
}
|
|
if (ImGui::BeginMenu("Stats"))
|
|
{
|
|
if (ImGui::MenuItem("Record", nullptr, &record_) )
|
|
{
|
|
if ( record_ )
|
|
csv_file_.open( SystemToolkit::home_path() + std::to_string(BaseToolkit::uniqueId()) + ".csv", std::ofstream::out | std::ofstream::app);
|
|
else
|
|
csv_file_.close();
|
|
}
|
|
ImGui::EndMenu();
|
|
}
|
|
ImGui::EndMenuBar();
|
|
}
|
|
|
|
//
|
|
// display histogram of update time and plot framerate
|
|
//
|
|
// keep array of 180 values, i.e. approx 3 seconds of recording
|
|
static float recorded_values[3][PLOT_ARRAY_SIZE] = {{}};
|
|
static float recorded_sum[3] = { 0.f, 0.f, 0.f };
|
|
static float recorded_bounds[3][2] = { {40.f, 65.f}, {0.f, 50.f}, {0.f, 50.f} };
|
|
static float refresh_rate = -1.f;
|
|
static int values_index = 0;
|
|
float megabyte = static_cast<float>( static_cast<double>(SystemToolkit::memory_usage()) / 1048576.0 );
|
|
|
|
// init
|
|
if (refresh_rate < 0.f) {
|
|
|
|
const GLFWvidmode* mode = glfwGetVideoMode(Rendering::manager().outputWindow().monitor());
|
|
refresh_rate = float(mode->refreshRate);
|
|
if (Settings::application.render.vsync > 0)
|
|
refresh_rate /= Settings::application.render.vsync;
|
|
else
|
|
refresh_rate = 0.f;
|
|
recorded_bounds[0][0] = refresh_rate - 15.f; // min fps
|
|
recorded_bounds[0][1] = refresh_rate + 10.f; // max
|
|
|
|
for(int i = 0; i<PLOT_ARRAY_SIZE; ++i) {
|
|
recorded_values[0][i] = refresh_rate;
|
|
recorded_sum[0] += recorded_values[0][i];
|
|
recorded_values[1][i] = 1.f / refresh_rate;
|
|
recorded_sum[1] += recorded_values[1][i];
|
|
recorded_values[2][i] = megabyte;
|
|
recorded_sum[2] += recorded_values[2][i];
|
|
}
|
|
}
|
|
|
|
// compute average step 1: remove previous value from the sum
|
|
recorded_sum[0] -= recorded_values[0][values_index];
|
|
recorded_sum[1] -= recorded_values[1][values_index];
|
|
recorded_sum[2] -= recorded_values[2][values_index];
|
|
|
|
// store values
|
|
recorded_values[0][values_index] = MINI(ImGui::GetIO().Framerate, 1000.f);
|
|
recorded_values[1][values_index] = MINI(Mixer::manager().dt(), 100.f);
|
|
recorded_values[2][values_index] = megabyte;
|
|
|
|
// compute average step 2: add current value to the sum
|
|
recorded_sum[0] += recorded_values[0][values_index];
|
|
recorded_sum[1] += recorded_values[1][values_index];
|
|
recorded_sum[2] += recorded_values[2][values_index];
|
|
|
|
// move inside array
|
|
values_index = (values_index+1) % PLOT_ARRAY_SIZE;
|
|
|
|
// non-vsync fixed FPS : have to calculate plot dimensions based on past values
|
|
if (refresh_rate < 1.f) {
|
|
recorded_bounds[0][0] = recorded_sum[0] / float(PLOT_ARRAY_SIZE) - 15.f;
|
|
recorded_bounds[0][1] = recorded_sum[0] / float(PLOT_ARRAY_SIZE) + 10.f;
|
|
}
|
|
|
|
recorded_bounds[2][0] = recorded_sum[2] / float(PLOT_ARRAY_SIZE) - 400.f;
|
|
recorded_bounds[2][1] = recorded_sum[2] / float(PLOT_ARRAY_SIZE) + 300.f;
|
|
|
|
// plot values, with title overlay to display the average
|
|
ImVec2 plot_size = ImGui::GetContentRegionAvail();
|
|
plot_size.y *= 0.32;
|
|
char overlay[128];
|
|
sprintf(overlay, "Rendering %.1f FPS", recorded_sum[0] / float(PLOT_ARRAY_SIZE));
|
|
ImGui::PlotLines("LinesRender", recorded_values[0], PLOT_ARRAY_SIZE, values_index, overlay, recorded_bounds[0][0], recorded_bounds[0][1], plot_size);
|
|
sprintf(overlay, "Update time %.1f ms (%.1f FPS)", recorded_sum[1] / float(PLOT_ARRAY_SIZE), (float(PLOT_ARRAY_SIZE) * 1000.f) / recorded_sum[1]);
|
|
ImGui::PlotHistogram("LinesMixer", recorded_values[1], PLOT_ARRAY_SIZE, values_index, overlay, recorded_bounds[1][0], recorded_bounds[1][1], plot_size);
|
|
sprintf(overlay, "Memory %.1f MB / %s", recorded_values[2][(values_index+PLOT_ARRAY_SIZE-1) % PLOT_ARRAY_SIZE], BaseToolkit::byte_to_string(SystemToolkit::memory_max_usage()).c_str() );
|
|
ImGui::PlotLines("LinesMemo", recorded_values[2], PLOT_ARRAY_SIZE, values_index, overlay, recorded_bounds[2][0], recorded_bounds[2][1], plot_size);
|
|
|
|
ImGui::End();
|
|
|
|
// save to file
|
|
if ( record_ && csv_file_.is_open()) {
|
|
csv_file_ << megabyte << ", " << ImGui::GetIO().Framerate << std::endl;
|
|
}
|
|
|
|
// About and other utility windows
|
|
if (show_icons_window)
|
|
ImGuiToolkit::ShowIconsWindow(&show_icons_window);
|
|
if (show_sandbox)
|
|
ShowSandbox(&show_sandbox);
|
|
if (show_demo_window)
|
|
ImGui::ShowDemoWindow(&show_demo_window);
|
|
|
|
}
|
|
|
|
|
|
///
|
|
/// SESSION REPAIR WINDOW
|
|
///
|
|
HelperToolbox::HelperToolbox()
|
|
{
|
|
|
|
}
|
|
|
|
void HelperToolbox::Render()
|
|
{
|
|
// first run
|
|
ImGui::SetNextWindowPos(ImVec2(520, 20), ImGuiCond_FirstUseEver);
|
|
ImGui::SetNextWindowSize(ImVec2(460, 800), ImGuiCond_FirstUseEver);
|
|
ImGui::SetNextWindowSizeConstraints(ImVec2(350, 300), ImVec2(FLT_MAX, FLT_MAX));
|
|
|
|
if ( !ImGui::Begin(IMGUI_TITLE_HELP, &Settings::application.widget.help, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse ) )
|
|
{
|
|
ImGui::End();
|
|
return;
|
|
}
|
|
|
|
// menu (no title bar)
|
|
if (ImGui::BeginMenuBar())
|
|
{
|
|
// Close and widget menu
|
|
if (ImGuiToolkit::IconButton(4,16))
|
|
Settings::application.widget.help = false;
|
|
if (ImGui::BeginMenu(IMGUI_TITLE_HELP))
|
|
{
|
|
// Enable/Disable Ableton Link
|
|
if ( ImGui::MenuItem( ICON_FA_EXTERNAL_LINK_ALT " Online user manual") ) {
|
|
SystemToolkit::open("https://github.com/brunoherbelin/vimix/wiki/User-manual");
|
|
}
|
|
|
|
// Enable/Disable tooltips
|
|
if ( ImGui::MenuItem( ICON_FA_QUESTION_CIRCLE " Show tooltips", nullptr, &Settings::application.show_tooptips) ) {
|
|
ImGuiToolkit::setToolTipsEnabled( Settings::application.show_tooptips );
|
|
}
|
|
|
|
// output manager menu
|
|
ImGui::Separator();
|
|
if ( ImGui::MenuItem( ICON_FA_TIMES " Close") )
|
|
Settings::application.widget.help = false;
|
|
|
|
ImGui::EndMenu();
|
|
}
|
|
|
|
ImGui::EndMenuBar();
|
|
}
|
|
|
|
const float width_window = ImGui::GetWindowSize().x - ImGui::GetFontSize();
|
|
const float width_column0 = ImGui::GetFontSize() * 6;
|
|
|
|
if (ImGui::CollapsingHeader("Views"))
|
|
{
|
|
|
|
ImGui::Columns(2, "viewscolumn", false); // 4-ways, with border
|
|
ImGui::SetColumnWidth(0, width_column0);
|
|
ImGui::PushTextWrapPos(width_window );
|
|
|
|
ImGui::Text(ICON_FA_BULLSEYE " Mixing"); ImGui::NextColumn();
|
|
ImGui::Text ("Adjust opacity of sources, visible in the center and transparent on the side. Sources are de-activated outside of darker circle.");
|
|
ImGui::NextColumn();
|
|
ImGui::Text(ICON_FA_OBJECT_UNGROUP " Geometry"); ImGui::NextColumn();
|
|
ImGui::Text ("Move, scale, rotate or crop sources to place them in the output frame.");
|
|
ImGui::NextColumn();
|
|
ImGui::Text(ICON_FA_LAYER_GROUP " Layers"); ImGui::NextColumn();
|
|
ImGui::Text ("Organize the rendering order of sources, from background to foreground.");
|
|
ImGui::NextColumn();
|
|
ImGui::Text(ICON_FA_CHESS_BOARD " Texturing"); ImGui::NextColumn();
|
|
ImGui::Text ("Apply masks or freely paint the texture on the source surface. Repeat or crop the graphics.");
|
|
ImGui::NextColumn();
|
|
|
|
ImGui::Columns(1);
|
|
ImGui::PopTextWrapPos();
|
|
}
|
|
|
|
if (ImGui::CollapsingHeader("Windows"))
|
|
{
|
|
ImGui::Columns(2, "windowcolumn", false); // 4-ways, with border
|
|
ImGui::SetColumnWidth(0, width_column0);
|
|
ImGui::PushTextWrapPos(width_window );
|
|
|
|
ImGui::Text(ICON_FA_STICKY_NOTE " Notes"); ImGui::NextColumn();
|
|
ImGui::Text ("Place sticky notes into your session.");
|
|
ImGui::NextColumn();
|
|
ImGui::Text(ICON_FA_TACHOMETER_ALT " Metrics"); ImGui::NextColumn();
|
|
ImGui::Text ("Utility monitoring of metrics on the system (FPS, RAM), the runtime (session duration), or the current source.");
|
|
ImGui::NextColumn();
|
|
ImGui::Text(ICON_FA_PLAY_CIRCLE " Player"); ImGui::NextColumn();
|
|
ImGui::Text ("Play, pause, rewind videos or dynamic sources. Control play duration, speed and synchronize multiple videos.");
|
|
ImGui::NextColumn();
|
|
ImGui::Text(ICON_FA_DESKTOP " Output"); ImGui::NextColumn();
|
|
ImGui::Text ("Preview the output displayed in the rendering window. Control video recording and sharing to other vimix in local network.");
|
|
ImGui::NextColumn();
|
|
ImGui::Text(ICON_FA_CLOCK " Timer"); ImGui::NextColumn();
|
|
ImGui::Text ("Keep track of time with a stopwatch or a metronome (Ableton Link).");
|
|
ImGui::NextColumn();
|
|
|
|
ImGui::Columns(1);
|
|
ImGui::PopTextWrapPos();
|
|
}
|
|
|
|
if (ImGui::CollapsingHeader("Sources"))
|
|
{
|
|
ImGui::Columns(2, "windowcolumn", false); // 4-ways, with border
|
|
ImGui::SetColumnWidth(0, width_column0);
|
|
ImGui::PushTextWrapPos(width_window );
|
|
|
|
ImGui::Text(ICON_FA_PHOTO_VIDEO); ImGui::NextColumn();
|
|
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_BOLD); ImGui::Text("File");ImGui::PopFont();
|
|
ImGui::NextColumn();
|
|
ImGuiToolkit::Icon(ICON_SOURCE_VIDEO); ImGui::SameLine(0, IMGUI_SAME_LINE);ImGui::Text("Video"); ImGui::NextColumn();
|
|
ImGui::Text ("Video file (*.mpg, *mov, *.avi, etc.).");
|
|
ImGui::NextColumn();
|
|
ImGuiToolkit::Icon(ICON_SOURCE_IMAGE); ImGui::SameLine(0, IMGUI_SAME_LINE);ImGui::Text("Image"); ImGui::NextColumn();
|
|
ImGui::Text ("Image file (*.jpg, *.png, etc.) or vector graphics (*.svg).");
|
|
ImGui::NextColumn();
|
|
ImGuiToolkit::Icon(ICON_SOURCE_SESSION); ImGui::SameLine(0, IMGUI_SAME_LINE);ImGui::Text("Session"); ImGui::NextColumn();
|
|
ImGui::Text ("Render a session (*.mix) as a source.");
|
|
ImGui::NextColumn();
|
|
ImGui::Separator();
|
|
ImGui::Text(ICON_FA_SORT_NUMERIC_DOWN); ImGui::NextColumn();
|
|
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_BOLD); ImGui::Text("Sequence");ImGui::PopFont();
|
|
ImGui::NextColumn();
|
|
ImGuiToolkit::Icon(ICON_SOURCE_SEQUENCE); ImGui::SameLine(0, IMGUI_SAME_LINE);ImGui::Text("Sequence"); ImGui::NextColumn();
|
|
ImGui::Text ("Set of images numbered sequentially (*.jpg, *.png, etc.).");
|
|
ImGui::NextColumn();
|
|
ImGui::Separator();
|
|
ImGui::Text(ICON_FA_PLUG); ImGui::NextColumn();
|
|
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_BOLD); ImGui::Text("Connected");ImGui::PopFont();
|
|
ImGui::NextColumn();
|
|
ImGuiToolkit::Icon(ICON_SOURCE_DEVICE); ImGui::SameLine(0, IMGUI_SAME_LINE);ImGui::Text("Device"); ImGui::NextColumn();
|
|
ImGui::Text ("Connected webcam or frame grabber.");
|
|
ImGui::NextColumn();
|
|
ImGuiToolkit::Icon(ICON_SOURCE_DEVICE_SCREEN); ImGui::SameLine(0, IMGUI_SAME_LINE);ImGui::Text("Screen"); ImGui::NextColumn();
|
|
ImGui::Text ("Screen capture.");
|
|
ImGui::NextColumn();
|
|
ImGuiToolkit::Icon(ICON_SOURCE_NETWORK); ImGui::SameLine(0, IMGUI_SAME_LINE);ImGui::Text("Shared"); ImGui::NextColumn();
|
|
ImGui::Text ("Connected stream from another vimix in the local network (shared output stream).");
|
|
ImGui::NextColumn();
|
|
ImGuiToolkit::Icon(ICON_SOURCE_SRT); ImGui::SameLine(0, IMGUI_SAME_LINE);ImGui::Text("SRT"); ImGui::NextColumn();
|
|
ImGui::Text ("Connected Secure Reliable Transport (SRT) stream emmitted on the network (e.g. broadcasted by vimix).");
|
|
ImGui::NextColumn();
|
|
ImGui::Separator();
|
|
ImGui::Text(ICON_FA_COG); ImGui::NextColumn();
|
|
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_BOLD); ImGui::Text("Generated");ImGui::PopFont();
|
|
ImGui::NextColumn();
|
|
ImGuiToolkit::Icon(ICON_SOURCE_PATTERN); ImGui::SameLine(0, IMGUI_SAME_LINE);ImGui::Text("Pattern"); ImGui::NextColumn();
|
|
ImGui::Text ("Algorithmically generated source; colors, grids, test patterns, timers...");
|
|
ImGui::NextColumn();
|
|
ImGuiToolkit::Icon(ICON_SOURCE_GSTREAMER); ImGui::SameLine(0, IMGUI_SAME_LINE);ImGui::Text("GStreamer"); ImGui::NextColumn();
|
|
ImGui::Text ("Custom gstreamer pipeline, as described in command line for gst-launch-1.0 (without the target sink).");
|
|
ImGui::NextColumn();
|
|
ImGui::Separator();
|
|
ImGui::Text(ICON_FA_SYNC); ImGui::NextColumn();
|
|
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_BOLD); ImGui::Text("Internal");ImGui::PopFont();
|
|
ImGui::NextColumn();
|
|
ImGuiToolkit::Icon(ICON_SOURCE_RENDER); ImGui::SameLine(0, IMGUI_SAME_LINE);ImGui::Text("Rendering"); ImGui::NextColumn();
|
|
ImGui::Text ("Displays the rendering output as a source, with or without loopback.");
|
|
ImGui::NextColumn();
|
|
ImGuiToolkit::Icon(ICON_SOURCE_CLONE); ImGui::SameLine(0, IMGUI_SAME_LINE);ImGui::Text("Clone"); ImGui::NextColumn();
|
|
ImGui::Text ("Clone a source into another source, possibly with a short delay.");
|
|
ImGui::NextColumn();
|
|
ImGuiToolkit::Icon(ICON_SOURCE_GROUP); ImGui::SameLine(0, IMGUI_SAME_LINE);ImGui::Text("Group"); ImGui::NextColumn();
|
|
ImGui::Text ("Group of sources rendered together after flattenning them in Layers view.");
|
|
|
|
ImGui::Columns(1);
|
|
ImGui::PopTextWrapPos();
|
|
}
|
|
|
|
if (ImGui::CollapsingHeader("Keyboard shortcuts", ImGuiTreeNodeFlags_DefaultOpen))
|
|
{
|
|
ImGui::Columns(2, "keyscolumns", false); // 4-ways, with border
|
|
ImGui::SetColumnWidth(0, width_column0);
|
|
|
|
ImGui::Text("HOME"); ImGui::NextColumn();
|
|
ImGui::Text(ICON_FA_BARS " Main menu"); ImGui::NextColumn();
|
|
ImGui::Text("INS"); ImGui::NextColumn();
|
|
ImGui::Text(ICON_FA_PLUS " New source"); ImGui::NextColumn();
|
|
ImGui::Text("F1"); ImGui::NextColumn();
|
|
ImGui::Text(ICON_FA_BULLSEYE " Mixing view"); ImGui::NextColumn();
|
|
ImGui::Text("F2"); ImGui::NextColumn();
|
|
ImGui::Text(ICON_FA_OBJECT_UNGROUP " Geometry view"); ImGui::NextColumn();
|
|
ImGui::Text("F3"); ImGui::NextColumn();
|
|
ImGui::Text(ICON_FA_LAYER_GROUP " Layers view"); ImGui::NextColumn();
|
|
ImGui::Text("F4"); ImGui::NextColumn();
|
|
ImGui::Text(ICON_FA_CHESS_BOARD " Texturing view"); ImGui::NextColumn();
|
|
ImGui::Text(ICON_FA_BACKSPACE " DEL"); ImGui::NextColumn();
|
|
ImGui::Text("Delete Current source"); ImGui::NextColumn();
|
|
ImGui::Text(ICON_FA_EXCHANGE_ALT " TAB"); ImGui::NextColumn();
|
|
ImGui::Text("Switch Current source"); ImGui::NextColumn();
|
|
ImGui::Text(CTRL_MOD "TAB"); ImGui::NextColumn();
|
|
ImGui::Text("Switch view"); ImGui::NextColumn();
|
|
ImGui::Separator();
|
|
ImGui::Text(SHORTCUT_FULLSCREEN); ImGui::NextColumn();
|
|
ImGui::Text(ICON_FA_EXPAND_ALT TOOLTIP_FULLSCREEN "main window"); ImGui::NextColumn();
|
|
ImGui::Text(SHORTCUT_NOTE); ImGui::NextColumn();
|
|
ImGui::Text(ICON_FA_STICKY_NOTE TOOLTIP_NOTE); ImGui::NextColumn();
|
|
ImGui::Text(SHORTCUT_PLAYER); ImGui::NextColumn();
|
|
ImGui::Text(ICON_FA_PLAY_CIRCLE TOOLTIP_PLAYER "window" ); ImGui::NextColumn();
|
|
ImGui::Text(SHORTCUT_OUTPUT); ImGui::NextColumn();
|
|
ImGui::Text(ICON_FA_DESKTOP TOOLTIP_OUTPUT "window"); ImGui::NextColumn();
|
|
ImGui::Text(SHORTCUT_TIMER); ImGui::NextColumn();
|
|
ImGui::Text(ICON_FA_CLOCK TOOLTIP_TIMER "window"); ImGui::NextColumn();
|
|
ImGui::Text("ESC"); ImGui::NextColumn();
|
|
ImGui::Text("Hide / Show windows"); ImGui::NextColumn();
|
|
ImGui::Separator();
|
|
ImGui::Text(SHORTCUT_NEW_FILE); ImGui::NextColumn();
|
|
ImGui::Text(MENU_NEW_FILE " session"); ImGui::NextColumn();
|
|
ImGui::Text(SHORTCUT_OPEN_FILE); ImGui::NextColumn();
|
|
ImGui::Text(MENU_OPEN_FILE " session"); ImGui::NextColumn();
|
|
ImGui::Text(SHORTCUT_REOPEN_FILE); ImGui::NextColumn();
|
|
ImGui::Text(MENU_REOPEN_FILE " session"); ImGui::NextColumn();
|
|
ImGui::Text(SHORTCUT_SAVE_FILE); ImGui::NextColumn();
|
|
ImGui::Text(MENU_SAVE_FILE " session"); ImGui::NextColumn();
|
|
ImGui::Text(SHORTCUT_SAVEAS_FILE); ImGui::NextColumn();
|
|
ImGui::Text(MENU_SAVEAS_FILE " session"); ImGui::NextColumn();
|
|
ImGui::Separator();
|
|
ImGui::Text(SHORTCUT_CUT); ImGui::NextColumn();
|
|
ImGui::Text(MENU_CUT " source"); ImGui::NextColumn();
|
|
ImGui::Text(SHORTCUT_COPY); ImGui::NextColumn();
|
|
ImGui::Text(MENU_COPY " source"); ImGui::NextColumn();
|
|
ImGui::Text(SHORTCUT_PASTE); ImGui::NextColumn();
|
|
ImGui::Text(MENU_PASTE); ImGui::NextColumn();
|
|
ImGui::Text(SHORTCUT_SELECTALL); ImGui::NextColumn();
|
|
ImGui::Text(MENU_SELECTALL " source"); ImGui::NextColumn();
|
|
ImGui::Text(SHORTCUT_UNDO); ImGui::NextColumn();
|
|
ImGui::Text(MENU_UNDO); ImGui::NextColumn();
|
|
ImGui::Text(SHORTCUT_REDO); ImGui::NextColumn();
|
|
ImGui::Text(MENU_REDO); ImGui::NextColumn();
|
|
ImGui::Separator();
|
|
ImGui::Text(SHORTCUT_CAPTUREFRAME); ImGui::NextColumn();
|
|
ImGui::Text(MENU_CAPTUREFRAME " Output"); ImGui::NextColumn();
|
|
ImGui::Text(SHORTCUT_RECORD); ImGui::NextColumn();
|
|
ImGui::Text(MENU_RECORD " Output"); ImGui::NextColumn();
|
|
ImGui::Text(SHORTCUT_RECORDCONT); ImGui::NextColumn();
|
|
ImGui::Text(MENU_RECORDCONT " recording"); ImGui::NextColumn();
|
|
ImGui::Text(SHORTCUT_OUTPUTFULLSCREEN); ImGui::NextColumn();
|
|
ImGui::Text(MENU_OUTPUTFULLSCREEN " output window"); ImGui::NextColumn();
|
|
ImGui::Text(SHORTCUT_OUTPUTDISABLE); ImGui::NextColumn();
|
|
ImGui::Text(MENU_OUTPUTDISABLE " output window"); ImGui::NextColumn();
|
|
ImGui::Separator();
|
|
ImGui::Text("Space"); ImGui::NextColumn();
|
|
ImGui::Text("Toggle Play/Pause selected videos"); ImGui::NextColumn();
|
|
ImGui::Text(CTRL_MOD "Space"); ImGui::NextColumn();
|
|
ImGui::Text("Back restart selected videos"); ImGui::NextColumn();
|
|
ImGui::Text(ICON_FA_ARROW_DOWN " " ICON_FA_ARROW_UP " " ICON_FA_ARROW_DOWN " " ICON_FA_ARROW_RIGHT ); ImGui::NextColumn();
|
|
ImGui::Text("Move the selection in the canvas"); ImGui::NextColumn();
|
|
ImGui::Separator();
|
|
ImGui::Text(SHORTCUT_LOGS); ImGui::NextColumn();
|
|
ImGui::Text(IMGUI_TITLE_LOGS); ImGui::NextColumn();
|
|
ImGui::Text(SHORTCUT_HELP); ImGui::NextColumn();
|
|
ImGui::Text(IMGUI_TITLE_HELP " window"); ImGui::NextColumn();
|
|
ImGui::Text(SHORTCUT_QUIT); ImGui::NextColumn();
|
|
ImGui::Text(MENU_QUIT); ImGui::NextColumn();
|
|
|
|
ImGui::Columns(1);
|
|
}
|
|
|
|
ImGui::End();
|
|
|
|
}
|
|
|
|
///
|
|
/// SMART WINDOW
|
|
///
|
|
|
|
bool WorkspaceWindow::clear_workspace_enabled = false;
|
|
std::list<WorkspaceWindow *> WorkspaceWindow::windows_;
|
|
|
|
struct ImGuiProperties
|
|
{
|
|
ImGuiWindow *ptr;
|
|
ImVec2 user_pos;
|
|
ImVec2 outside_pos;
|
|
float progress; // [0 1]
|
|
float target; // 1 go to outside, 0 go to user pos
|
|
bool animation; // need animation
|
|
bool resizing_workspace;
|
|
ImVec2 resized_pos;
|
|
|
|
ImGuiProperties ()
|
|
{
|
|
ptr = nullptr;
|
|
progress = 0.f;
|
|
target = 0.f;
|
|
animation = false;
|
|
resizing_workspace = false;
|
|
}
|
|
};
|
|
|
|
WorkspaceWindow::WorkspaceWindow(const char* name): name_(name), impl_(nullptr)
|
|
{
|
|
WorkspaceWindow::windows_.push_back(this);
|
|
}
|
|
|
|
void WorkspaceWindow::toggleClearRestoreWorkspace()
|
|
{
|
|
if (clear_workspace_enabled)
|
|
restoreWorkspace();
|
|
else
|
|
clearWorkspace();
|
|
}
|
|
|
|
void WorkspaceWindow::restoreWorkspace(bool instantaneous)
|
|
{
|
|
if (clear_workspace_enabled) {
|
|
const ImVec2 display_size = ImGui::GetIO().DisplaySize;
|
|
for(auto it = windows_.cbegin(); it != windows_.cend(); ++it) {
|
|
ImGuiProperties *impl = (*it)->impl_;
|
|
if (impl && impl->ptr)
|
|
{
|
|
float margin = (impl->ptr->MenuBarHeight() + impl->ptr->TitleBarHeight()) * 3.f;
|
|
impl->user_pos.x = CLAMP(impl->user_pos.x, -impl->ptr->SizeFull.x +margin, display_size.x -margin);
|
|
impl->user_pos.y = CLAMP(impl->user_pos.y, -impl->ptr->SizeFull.y +margin, display_size.y -margin);
|
|
|
|
if (instantaneous) {
|
|
impl->animation = false;
|
|
ImGui::SetWindowPos(impl->ptr, impl->user_pos);
|
|
}
|
|
else {
|
|
// remember outside position before move
|
|
impl->outside_pos = impl->ptr->Pos;
|
|
|
|
// initialize animation
|
|
impl->progress = 1.f;
|
|
impl->target = 0.f;
|
|
impl->animation = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
clear_workspace_enabled = false;
|
|
}
|
|
|
|
void WorkspaceWindow::clearWorkspace()
|
|
{
|
|
if (!clear_workspace_enabled) {
|
|
const ImVec2 display_size = ImGui::GetIO().DisplaySize;
|
|
for(auto it = windows_.cbegin(); it != windows_.cend(); ++it) {
|
|
ImGuiProperties *impl = (*it)->impl_;
|
|
if (impl && impl->ptr)
|
|
{
|
|
// remember user position before move
|
|
impl->user_pos = impl->ptr->Pos;
|
|
|
|
// init before decision
|
|
impl->outside_pos = impl->ptr->Pos;
|
|
|
|
// distance to right side, top & bottom
|
|
float right = display_size.x - (impl->ptr->Pos.x + impl->ptr->SizeFull.x * 0.7);
|
|
float top = impl->ptr->Pos.y;
|
|
float bottom = display_size.y - (impl->ptr->Pos.y + impl->ptr->SizeFull.y);
|
|
|
|
// move to closest border, with a margin to keep visible
|
|
float margin = (impl->ptr->MenuBarHeight() + impl->ptr->TitleBarHeight()) * 1.5f;
|
|
if (top < bottom && top < right)
|
|
impl->outside_pos.y = margin - impl->ptr->SizeFull.y;
|
|
else if (right < top && right < bottom)
|
|
impl->outside_pos.x = display_size.x - margin;
|
|
else
|
|
impl->outside_pos.y = display_size.y - margin;
|
|
|
|
// initialize animation
|
|
impl->progress = 0.f;
|
|
impl->target = 1.f;
|
|
impl->animation = true;
|
|
}
|
|
}
|
|
}
|
|
clear_workspace_enabled = true;
|
|
}
|
|
|
|
void WorkspaceWindow::notifyWorkspaceSizeChanged(int prev_width, int prev_height, int curr_width, int curr_height)
|
|
{
|
|
// restore windows pos before rescale
|
|
restoreWorkspace(true);
|
|
|
|
for(auto it = windows_.cbegin(); it != windows_.cend(); ++it) {
|
|
ImGuiProperties *impl = (*it)->impl_;
|
|
if ( impl && impl->ptr)
|
|
{
|
|
ImVec2 distance_to_corner = ImVec2(prev_width, prev_height) - impl->ptr->Pos - impl->ptr->SizeFull;
|
|
|
|
impl->resized_pos = impl->ptr->Pos;
|
|
|
|
if ( ABS(distance_to_corner.x) < 100.f ) {
|
|
impl->resized_pos.x += curr_width - prev_width;
|
|
impl->resizing_workspace = true;
|
|
}
|
|
|
|
if ( ABS(distance_to_corner.y) < 100.f ) {
|
|
impl->resized_pos.y += curr_height -prev_height;
|
|
impl->resizing_workspace = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void WorkspaceWindow::Update()
|
|
{
|
|
// get ImGui pointer to window (available only after first run)
|
|
if (!impl_) {
|
|
ImGuiWindow *w = ImGui::FindWindowByName(name_);
|
|
if (w != NULL) {
|
|
impl_ = new ImGuiProperties;
|
|
impl_->ptr = w;
|
|
impl_->user_pos = w->Pos;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( Visible() ) {
|
|
// perform animation for clear/restore workspace
|
|
if (impl_->animation) {
|
|
// increment animation progress by small steps
|
|
impl_->progress += SIGN(impl_->target -impl_->progress) * 0.1f;
|
|
// finished animation : apply full target position
|
|
if (ABS_DIFF(impl_->target, impl_->progress) < 0.05f) {
|
|
impl_->animation = false;
|
|
ImVec2 pos = impl_->user_pos * (1.f -impl_->target) + impl_->outside_pos * impl_->target;
|
|
ImGui::SetWindowPos(impl_->ptr, pos);
|
|
}
|
|
// move window by interpolation between user position and outside target position
|
|
else {
|
|
ImVec2 pos = impl_->user_pos * (1.f -impl_->progress) + impl_->outside_pos * impl_->progress;
|
|
ImGui::SetWindowPos(impl_->ptr, pos);
|
|
}
|
|
}
|
|
// Restore if clic on overlay
|
|
if (clear_workspace_enabled)
|
|
{
|
|
// draw another window on top of the WorkspaceWindow, at exact same position and size
|
|
ImGuiWindow* window = impl_->ptr;
|
|
ImGui::SetNextWindowPos(window->Pos, ImGuiCond_Always);
|
|
ImGui::SetNextWindowSize(window->Size, ImGuiCond_Always);
|
|
char nameoverlay[64];
|
|
sprintf(nameoverlay, "%sOverlay", name_);
|
|
if (ImGui::Begin(nameoverlay, NULL, ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings ))
|
|
{
|
|
// exit workspace clear mode if user clics on the window
|
|
ImGui::InvisibleButton("##dummy", window->Size);
|
|
if (ImGui::IsItemClicked())
|
|
WorkspaceWindow::restoreWorkspace();
|
|
ImGui::End();
|
|
}
|
|
}
|
|
}
|
|
// move windows because workspace was resized
|
|
if (impl_->resizing_workspace) {
|
|
// how far from the target ?
|
|
ImVec2 delta = impl_->resized_pos - impl_->ptr->Pos;
|
|
// got close enough to stop workspace resize
|
|
if (ABS(delta.x) < 2.f && ABS(delta.y) < 2.f)
|
|
impl_->resizing_workspace = false;
|
|
// dichotomic reach of target position
|
|
ImVec2 pos = impl_->ptr->Pos + delta * 0.5f;
|
|
ImGui::SetWindowPos(impl_->ptr, pos);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
///
|
|
/// SOURCE CONTROLLER
|
|
///
|
|
SourceController::SourceController() : 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_AUTO_MEDIA_PLAYER), 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_mode_(false), mediaplayer_slider_pressed_(false), mediaplayer_timeline_zoom_(1.f)
|
|
{
|
|
info_.setExtendedStringMode();
|
|
}
|
|
|
|
|
|
void SourceController::resetActiveSelection()
|
|
{
|
|
info_.reset();
|
|
active_selection_ = -1;
|
|
active_label_ = LABEL_AUTO_MEDIA_PLAYER;
|
|
}
|
|
|
|
|
|
|
|
void SourceController::setVisible(bool on)
|
|
{
|
|
// 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 (on && Settings::application.widget.media_player_view != Settings::application.current_view)
|
|
Settings::application.widget.media_player_view = -1;
|
|
|
|
Settings::application.widget.media_player = on;
|
|
}
|
|
|
|
bool SourceController::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 SourceController::Update()
|
|
{
|
|
WorkspaceWindow::Update();
|
|
|
|
SourceList selectedsources;
|
|
|
|
// get new selection or validate previous list if selection was not updated
|
|
selectedsources = Mixer::manager().validate(selection_);
|
|
if (selectedsources.empty() && !Mixer::selection().empty())
|
|
selectedsources = playable_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 );
|
|
|
|
play_toggle_request_ = false;
|
|
}
|
|
|
|
// Replay / rewind button or keyboard [B] was pressed
|
|
if ( replay_request_ ) {
|
|
|
|
for (auto source = selectedsources.begin(); source != selectedsources.end(); ++source)
|
|
(*source)->replay();
|
|
|
|
replay_request_ = false;
|
|
}
|
|
|
|
// reset on session change
|
|
static Session *__session = nullptr;
|
|
if ( Mixer::manager().session() != __session ) {
|
|
__session = Mixer::manager().session();
|
|
resetActiveSelection();
|
|
}
|
|
}
|
|
|
|
void SourceController::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 * 7.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;
|
|
selection_.clear();
|
|
}
|
|
if (ImGui::BeginMenu(IMGUI_TITLE_MEDIAPLAYER))
|
|
{
|
|
// Menu section for play control
|
|
if (ImGui::MenuItem( ICON_FA_FAST_BACKWARD " Restart", CTRL_MOD"Space"))
|
|
replay_request_ = true;
|
|
if (ImGui::MenuItem( ICON_FA_PLAY " Play | Pause", "Space"))
|
|
play_toggle_request_ = true;
|
|
|
|
// Menu section for list
|
|
ImGui::Separator();
|
|
if (ImGui::MenuItem( ICON_FA_TH " List all")) {
|
|
selection_.clear();
|
|
resetActiveSelection();
|
|
Mixer::manager().unsetCurrentSource();
|
|
Mixer::selection().clear();
|
|
selection_ = playable_only( Mixer::manager().session()->getDepthSortedList() );
|
|
}
|
|
if (ImGui::MenuItem( ICON_FA_WIND " 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()->numPlayGroups();
|
|
bool enabled = !selection_.empty() && active_selection_ < 0;
|
|
|
|
// Menu : Dynamic selection
|
|
if (ImGui::MenuItem(LABEL_AUTO_MEDIA_PLAYER))
|
|
resetActiveSelection();
|
|
// Menu : store selection
|
|
if (ImGui::MenuItem(ICON_FA_PLUS_SQUARE LABEL_STORE_SELECTION, NULL, false, enabled))
|
|
{
|
|
active_selection_ = N;
|
|
active_label_ = std::string(ICON_FA_CHECK_SQUARE " Selection #") + std::to_string(active_selection_);
|
|
Mixer::manager().session()->addPlayGroup( ids(playable_only(selection_)) );
|
|
info_.reset();
|
|
}
|
|
// Menu : list of selections
|
|
if (N>0) {
|
|
ImGui::Separator();
|
|
for (size_t i = 0 ; i < N; ++i)
|
|
{
|
|
std::string label = std::string(ICON_FA_CHECK_SQUARE " Selection #") + std::to_string(i);
|
|
if (ImGui::MenuItem( label.c_str() ))
|
|
{
|
|
active_selection_ = i;
|
|
active_label_ = label;
|
|
info_.reset();
|
|
}
|
|
}
|
|
}
|
|
|
|
ImGui::EndMenu();
|
|
}
|
|
|
|
if (mediaplayer_active_) {
|
|
if (ImGui::BeginMenu(ICON_FA_FILM " Video") )
|
|
{
|
|
if (ImGui::MenuItem(ICON_FA_WINDOW_CLOSE " Reset timeline")){
|
|
mediaplayer_timeline_zoom_ = 1.f;
|
|
mediaplayer_active_->timeline()->clearFading();
|
|
mediaplayer_active_->timeline()->clearGaps();
|
|
std::ostringstream oss;
|
|
oss << SystemToolkit::base_filename( mediaplayer_active_->filename() );
|
|
oss << ": Reset timeline";
|
|
Action::manager().store(oss.str());
|
|
}
|
|
|
|
if (ImGui::MenuItem(LABEL_EDIT_FADING))
|
|
mediaplayer_edit_fading_ = true;
|
|
|
|
if (ImGui::BeginMenu(ICON_FA_CLOCK " Metronome"))
|
|
{
|
|
Metronome::Synchronicity sync = mediaplayer_active_->syncToMetronome();
|
|
bool active = sync == Metronome::SYNC_NONE;
|
|
if (ImGuiToolkit::MenuItemIcon(5, 13, " Not synchronized", active ))
|
|
mediaplayer_active_->setSyncToMetronome(Metronome::SYNC_NONE);
|
|
active = sync == Metronome::SYNC_BEAT;
|
|
if (ImGuiToolkit::MenuItemIcon(6, 13, " Sync to beat", active ))
|
|
mediaplayer_active_->setSyncToMetronome(Metronome::SYNC_BEAT);
|
|
active = sync == Metronome::SYNC_PHASE;
|
|
if (ImGuiToolkit::MenuItemIcon(7, 13, " Sync to phase", active ))
|
|
mediaplayer_active_->setSyncToMetronome(Metronome::SYNC_PHASE);
|
|
ImGui::EndMenu();
|
|
}
|
|
|
|
if (ImGui::BeginMenu(ICON_FA_SNOWFLAKE " Deactivation"))
|
|
{
|
|
bool option = !mediaplayer_active_->rewindOnDisabled();
|
|
if (ImGui::MenuItem(ICON_FA_STOP " Stop", NULL, &option ))
|
|
mediaplayer_active_->setRewindOnDisabled(false);
|
|
option = mediaplayer_active_->rewindOnDisabled();
|
|
if (ImGui::MenuItem(ICON_FA_FAST_BACKWARD " Rewind & Stop", NULL, &option ))
|
|
mediaplayer_active_->setRewindOnDisabled(true);
|
|
ImGui::EndMenu();
|
|
}
|
|
|
|
// if (ImGui::BeginMenu(ICON_FA_CUT " Auto cut" ))
|
|
// {
|
|
// if (ImGuiToolkit::MenuItemIcon(14, 12, "Cut faded areas" ))
|
|
// if (mediaplayer_active_->timeline()->autoCut()){
|
|
// std::ostringstream oss;
|
|
// oss << SystemToolkit::base_filename( mediaplayer_active_->filename() );
|
|
// oss << ": Cut faded areas";
|
|
// Action::manager().store(oss.str());
|
|
// }
|
|
// ImGui::EndMenu();
|
|
// }
|
|
if (Settings::application.render.gpu_decoding)
|
|
{
|
|
ImGui::Separator();
|
|
if (ImGui::BeginMenu(ICON_FA_MICROCHIP " Hardware decoding"))
|
|
{
|
|
bool hwdec = !mediaplayer_active_->softwareDecodingForced();
|
|
if (ImGui::MenuItem("Auto", "", &hwdec ))
|
|
mediaplayer_active_->setSoftwareDecodingForced(false);
|
|
hwdec = mediaplayer_active_->softwareDecodingForced();
|
|
if (ImGui::MenuItem("Disabled", "", &hwdec ))
|
|
mediaplayer_active_->setSoftwareDecodingForced(true);
|
|
ImGui::EndMenu();
|
|
}
|
|
}
|
|
|
|
ImGui::EndMenu();
|
|
}
|
|
}
|
|
else {
|
|
ImGui::SameLine(0, 2.f * g.Style.ItemSpacing.x );
|
|
ImGui::TextDisabled(ICON_FA_FILM " Video");
|
|
}
|
|
|
|
ImGui::EndMenuBar();
|
|
}
|
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
std::list< std::pair<float, guint64> > DrawTimeline(const char* label, Timeline *timeline, guint64 time,
|
|
double width_ratio, float height, double tempo = 0, double quantum = 0)
|
|
{
|
|
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 - 2.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
|
|
if (tempo > 0 && quantum > 0)
|
|
ImGuiToolkit::RenderTimelineBPM(section_bbox_min, section_bbox_max, tempo, quantum, section->begin, section->end, timeline->step());
|
|
else
|
|
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 SourceController::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()->playGroup(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)
|
|
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);
|
|
{
|
|
|
|
// 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
|
|
for (auto source = selection_.begin(); source != selection_.end(); ++source) {
|
|
|
|
ImVec2 framesize(1.5f * timeline_height_ * (*source)->frame()->aspectRatio(), 1.5f * timeline_height_);
|
|
ImVec2 image_top = ImGui::GetCursorPos();
|
|
|
|
// Thumbnail of source (clic to open)
|
|
if (SourceButton(*source, framesize))
|
|
UserInterface::manager().showSourceEditor(*source);
|
|
|
|
// text below thumbnail to show status
|
|
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO);
|
|
ImGui::Text("%s %s", SourcePlayIcon(*source), GstToolkit::time_to_string((*source)->playtime()).c_str() );
|
|
ImGui::PopFont();
|
|
|
|
// get media source
|
|
MediaSource *ms = dynamic_cast<MediaSource *>(*source);
|
|
if (ms != nullptr) {
|
|
// ok, get the media player of the media source
|
|
MediaPlayer *mp = ms->mediaplayer();
|
|
|
|
// 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
|
|
|
|
if (mp->syncToMetronome() > Metronome::SYNC_NONE)
|
|
DrawTimeline("##timeline_mediaplayer_bpm", mp->timeline(), mp->position(),
|
|
width_ratio / fabs(mp->playSpeed()), framesize.y,
|
|
Metronome::manager().tempo(), Metronome::manager().quantum());
|
|
else
|
|
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(9, 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_));
|
|
}
|
|
|
|
}
|
|
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_SQUARE " ") + 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)->playable() ) {
|
|
if (std::find(selection_.begin(),selection_.end(),*s) == selection_.end()) {
|
|
if (ImGui::MenuItem( (*s)->name().c_str() , 0, false ))
|
|
Mixer::manager().session()->addToPlayGroup(i, *s);
|
|
}
|
|
else {
|
|
if (ImGui::MenuItem( (*s)->name().c_str(), 0, true ))
|
|
Mixer::manager().session()->removeFromPlayGroup(i, *s);
|
|
}
|
|
}
|
|
}
|
|
ImGui::EndCombo();
|
|
}
|
|
}
|
|
|
|
ImGui::SameLine();
|
|
ImGui::SetCursorPosX(rendersize.x - buttons_height_ / 1.3f);
|
|
if (ImGui::Button(ICON_FA_MINUS_SQUARE)) {
|
|
resetActiveSelection();
|
|
Mixer::manager().session()->deletePlayGroup(i);
|
|
}
|
|
|
|
ImGui::PopStyleColor(4);
|
|
}
|
|
|
|
void SourceController::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" , 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 , 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();
|
|
}
|
|
|
|
}
|
|
|
|
|
|
bool SourceController::SourceButton(Source *s, ImVec2 framesize)
|
|
{
|
|
bool ret = false;
|
|
|
|
ImVec2 frame_top = ImGui::GetCursorScreenPos();
|
|
ImGui::Image((void*)(uintptr_t) s->texture(), framesize);
|
|
frame_top.x += 1.f;
|
|
ImGui::SetCursorScreenPos(frame_top);
|
|
|
|
ImGui::PushID(s->id());
|
|
ImGui::InvisibleButton("##sourcebutton", framesize);
|
|
if (ImGui::IsItemClicked()) {
|
|
ret = true;
|
|
}
|
|
if (ImGui::IsItemHovered()){
|
|
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
|
draw_list->AddRect(frame_top, frame_top + framesize - ImVec2(1.f, 0.f), ImGui::GetColorU32(ImGuiCol_Text), 0, 0, 3.f);
|
|
frame_top.x += (framesize.x - ImGui::GetTextLineHeight()) / 2.f;
|
|
frame_top.y += (framesize.y - ImGui::GetTextLineHeight()) / 2.f;
|
|
draw_list->AddText(frame_top, ImGui::GetColorU32(ImGuiCol_Text), ICON_FA_CARET_SQUARE_RIGHT);
|
|
}
|
|
ImGui::PopID();
|
|
|
|
return ret;
|
|
}
|
|
|
|
void SourceController::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_ = Mixer::manager().validate(selection_);
|
|
else
|
|
selection_ = playable_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 pack
|
|
int numcolumns = CLAMP( int(ceil(1.0f * 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());
|
|
if (SourceButton(*source, framesize))
|
|
UserInterface::manager().showSourceEditor(*source);
|
|
|
|
// Play 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 - h));
|
|
ImGui::Text("%s %s", SourcePlayIcon(*source), 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_SQUARE);
|
|
if (space > buttons_width_) { // enough space to show full button with label text
|
|
label += LABEL_STORE_SELECTION;
|
|
width = buttons_width_;
|
|
}
|
|
ImGui::SameLine(0, space -width);
|
|
ImGui::SetNextItemWidth(width);
|
|
if (ImGui::Button( label.c_str() )) {
|
|
active_selection_ = Mixer::manager().session()->numPlayGroups();
|
|
active_label_ = std::string("Selection #") + std::to_string(active_selection_);
|
|
Mixer::manager().session()->addPlayGroup( ids(playable_only(selection_)) );
|
|
}
|
|
if (space < buttons_width_ && ImGui::IsItemHovered())
|
|
ImGuiToolkit::ToolTip(LABEL_STORE_SELECTION);
|
|
|
|
ImGui::PopStyleColor(2);
|
|
}
|
|
|
|
}
|
|
|
|
void SourceController::RenderSingleSource(Source *s)
|
|
{
|
|
if ( s == nullptr)
|
|
return;
|
|
|
|
// in case of a MediaSource
|
|
MediaSource *ms = dynamic_cast<MediaSource *>(s);
|
|
if ( ms != nullptr ) {
|
|
RenderMediaPlayer( ms->mediaplayer() );
|
|
}
|
|
else
|
|
{
|
|
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_);
|
|
|
|
///
|
|
/// 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) {
|
|
corner.y = (framesize.y - tmp.y) / 2.f;
|
|
framesize.y = tmp.y;
|
|
}
|
|
else {
|
|
corner.x = (framesize.x - tmp.x) / 2.f;
|
|
framesize.x = tmp.x;
|
|
}
|
|
|
|
///
|
|
/// Image
|
|
///
|
|
top += corner;
|
|
ImGui::SetCursorScreenPos(top);
|
|
ImGui::Image((void*)(uintptr_t) s->texture(), framesize);
|
|
|
|
///
|
|
/// Info overlays
|
|
///
|
|
ImGui::SetCursorScreenPos(top + ImVec2(framesize.x - ImGui::GetTextLineHeightWithSpacing(), v_space_));
|
|
ImGui::Text(ICON_FA_INFO_CIRCLE);
|
|
if (ImGui::IsItemHovered()){
|
|
// fill info string
|
|
s->accept(info_);
|
|
// draw overlay frame and text
|
|
float tooltip_height = 3.f * ImGui::GetTextLineHeightWithSpacing();
|
|
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
|
draw_list->AddRectFilled(top, top + ImVec2(framesize.x, tooltip_height), IMGUI_COLOR_OVERLAY);
|
|
ImGui::SetCursorScreenPos(top + 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(top + ImVec2( framesize.x - 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();
|
|
|
|
///
|
|
/// Play icon lower left corner
|
|
///
|
|
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
|
|
ImGui::SetCursorScreenPos(bottom + ImVec2(h_space_, -ImGui::GetTextLineHeightWithSpacing()));
|
|
ImGui::Text("%s %s", SourcePlayIcon(s), GstToolkit::time_to_string(s->playtime()).c_str() );
|
|
ImGui::PopFont();
|
|
|
|
///
|
|
/// Play button bar
|
|
///
|
|
DrawButtonBar(bottom, rendersize.x);
|
|
}
|
|
}
|
|
|
|
void SourceController::RenderMediaPlayer(MediaPlayer *mp)
|
|
{
|
|
mediaplayer_active_ = mp;
|
|
|
|
// 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();
|
|
|
|
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_);
|
|
|
|
///
|
|
/// Centered frame
|
|
///
|
|
ImVec2 framesize = rendersize;
|
|
ImVec2 corner(0.f, 0.f);
|
|
ImVec2 tmp = ImVec2(framesize.y * mediaplayer_active_->aspectRatio(), framesize.x / mediaplayer_active_->aspectRatio());
|
|
if (tmp.x > framesize.x) {
|
|
corner.y = (framesize.y - tmp.y) / 2.f;
|
|
framesize.y = tmp.y;
|
|
}
|
|
else {
|
|
corner.x = (framesize.x - tmp.x) / 2.f;
|
|
framesize.x = tmp.x;
|
|
}
|
|
|
|
///
|
|
/// Image
|
|
///
|
|
const ImVec2 top_image = top + corner;
|
|
ImGui::SetCursorScreenPos(top_image);
|
|
ImGui::Image((void*)(uintptr_t) mediaplayer_active_->texture(), framesize);
|
|
|
|
///
|
|
/// Info overlays
|
|
///
|
|
ImGui::SetCursorScreenPos(top_image + ImVec2(framesize.x - ImGui::GetTextLineHeightWithSpacing(), v_space_));
|
|
ImGui::Text(ICON_FA_INFO_CIRCLE);
|
|
if (ImGui::IsItemHovered()){
|
|
// information visitor
|
|
mediaplayer_active_->accept(info_);
|
|
float tooltip_height = 3.f * ImGui::GetTextLineHeightWithSpacing();
|
|
draw_list->AddRectFilled(top_image, top_image + ImVec2(framesize.x, tooltip_height), IMGUI_COLOR_OVERLAY);
|
|
ImGui::SetCursorScreenPos(top_image + ImVec2(h_space_, v_space_));
|
|
ImGui::Text("%s", info_.str().c_str());
|
|
|
|
// Icon to inform hardware decoding
|
|
if ( mediaplayer_active_->decoderName().compare("software") != 0) {
|
|
ImGui::SetCursorScreenPos(top_image + ImVec2( framesize.x - ImGui::GetTextLineHeightWithSpacing(), 0.35f * tooltip_height));
|
|
ImGui::Text(ICON_FA_MICROCHIP);
|
|
}
|
|
|
|
// refresh frequency
|
|
if ( mediaplayer_active_->isPlaying()) {
|
|
ImGui::SetCursorScreenPos(top_image + ImVec2( framesize.x - 1.5f * buttons_height_, 0.667f * tooltip_height));
|
|
ImGui::Text("%.1f Hz", mediaplayer_active_->updateFrameRate());
|
|
}
|
|
}
|
|
|
|
// Play icon lower left corner
|
|
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
|
|
ImGui::SetCursorScreenPos(bottom + ImVec2(h_space_, -ImGui::GetTextLineHeightWithSpacing()));
|
|
if (mediaplayer_active_->isEnabled())
|
|
ImGui::Text("%s %s", mediaplayer_active_->isPlaying() ? ICON_FA_PLAY : ICON_FA_PAUSE, GstToolkit::time_to_string(mediaplayer_active_->position()).c_str() );
|
|
else
|
|
ImGui::Text( ICON_FA_SNOWFLAKE " %s", GstToolkit::time_to_string(mediaplayer_active_->position()).c_str() );
|
|
ImGui::PopFont();
|
|
|
|
|
|
const ImVec2 scrollwindow = ImVec2(ImGui::GetContentRegionAvail().x - slider_zoom_width - 3.0,
|
|
2.f * timeline_height_ + scrollbar_ );
|
|
|
|
///
|
|
/// media player timelines
|
|
///
|
|
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();
|
|
|
|
// 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::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 ( ImGuiToolkit::EditPlotHistoLines("##TimelineArray", tl->gapsArray(), tl->fadingArray(),
|
|
MAX_TIMELINE_ARRAY, 0.f, 1.f, tl->begin(), tl->end(),
|
|
Settings::application.widget.media_player_timeline_editmode, &released, size) ) {
|
|
tl->update();
|
|
}
|
|
else if (released)
|
|
{
|
|
tl->refresh();
|
|
if (Settings::application.widget.media_player_timeline_editmode)
|
|
oss << ": Timeline cut";
|
|
else
|
|
oss << ": Timeline opacity";
|
|
Action::manager().store(oss.str());
|
|
}
|
|
|
|
// custom timeline slider
|
|
if (mediaplayer_active_->syncToMetronome() > Metronome::SYNC_NONE)
|
|
mediaplayer_slider_pressed_ = ImGuiToolkit::TimelineSlider("##timeline", &seek_t, tl->begin(),
|
|
tl->first(), tl->end(), tl->step(), size.x,
|
|
Metronome::manager().tempo(), Metronome::manager().quantum());
|
|
else
|
|
mediaplayer_slider_pressed_ = ImGuiToolkit::TimelineSlider("##timeline", &seek_t, tl->begin(),
|
|
tl->first(), tl->end(), tl->step(), 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));
|
|
const char *tooltip[2] = {"Draw opacity tool", "Cut tool"};
|
|
ImGuiToolkit::IconToggle(7,4,8,3, &Settings::application.widget.media_player_timeline_editmode, tooltip);
|
|
|
|
ImGui::SetCursorScreenPos(bottom + ImVec2(1.f, 0.5f * timeline_height_));
|
|
if (Settings::application.widget.media_player_timeline_editmode) {
|
|
// action cut
|
|
if (mediaplayer_active_->isPlaying()) {
|
|
ImGuiToolkit::Indication("Pause video to enable cut options", 9, 3);
|
|
}
|
|
else if (ImGuiToolkit::IconButton(9, 3, "Cut at cursor")) {
|
|
ImGui::OpenPopup("timeline_cut_context_menu");
|
|
}
|
|
if (ImGui::BeginPopup("timeline_cut_context_menu")){
|
|
if (ImGuiToolkit::MenuItemIcon(1,0,"Cut left")){
|
|
if (mediaplayer_active_->timeline()->cut(mediaplayer_active_->position(), true, false)) {
|
|
oss << ": Timeline cut";
|
|
Action::manager().store(oss.str());
|
|
}
|
|
}
|
|
if (ImGuiToolkit::MenuItemIcon(2,0,"Cut right")){
|
|
if (mediaplayer_active_->timeline()->cut(mediaplayer_active_->position(), false, false)){
|
|
oss << ": Timeline cut";
|
|
Action::manager().store(oss.str());
|
|
}
|
|
}
|
|
ImGui::EndPopup();
|
|
}
|
|
}
|
|
else {
|
|
static int _actionsmooth = 0;
|
|
|
|
// action smooth
|
|
ImGui::PushButtonRepeat(true);
|
|
if (ImGuiToolkit::IconButton(13, 12, "Smooth")){
|
|
mediaplayer_active_->timeline()->smoothFading( 5 );
|
|
++_actionsmooth;
|
|
}
|
|
ImGui::PopButtonRepeat();
|
|
|
|
if (_actionsmooth > 0 && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
|
|
oss << ": Timeline opacity smooth";
|
|
Action::manager().store(oss.str());
|
|
_actionsmooth = 0;
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
ImGui::SameLine(0, h_space_);
|
|
|
|
ImGui::PushButtonRepeat(true);
|
|
if (ImGui::Button( mediaplayer_active_->playSpeed() < 0 ? ICON_FA_BACKWARD :ICON_FA_FORWARD))
|
|
mediaplayer_active_->jump ();
|
|
ImGui::PopButtonRepeat();
|
|
}
|
|
else {
|
|
if (ImGui::Button(ICON_FA_PLAY))
|
|
mediaplayer_mode_ = true;
|
|
ImGui::SameLine(0, h_space_);
|
|
|
|
ImGui::PushButtonRepeat(true);
|
|
if (ImGui::Button( mediaplayer_active_->playSpeed() < 0 ? ICON_FA_STEP_BACKWARD : ICON_FA_STEP_FORWARD))
|
|
mediaplayer_active_->step();
|
|
ImGui::PopButtonRepeat();
|
|
}
|
|
|
|
// loop modes button
|
|
ImGui::SameLine(0, h_space_);
|
|
static int current_loop = 0;
|
|
static std::vector< std::pair<int, int> > iconsloop = { {0,15}, {1,15}, {19,14} };
|
|
current_loop = (int) mediaplayer_active_->loop();
|
|
if ( ImGuiToolkit::ButtonIconMultistate(iconsloop, ¤t_loop, "Loop mode") )
|
|
mediaplayer_active_->setLoop( (MediaPlayer::LoopMode) current_loop );
|
|
|
|
// speed slider (if enough space)
|
|
if ( rendersize.x > min_width_ * 1.4f ) {
|
|
ImGui::SameLine(0, MAX(h_space_ * 2.f, rendersize.x - min_width_ * 1.6f) );
|
|
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - buttons_height_ );
|
|
// speed slider
|
|
float speed = static_cast<float>(mediaplayer_active_->playSpeed());
|
|
if (ImGui::DragFloat( "##Speed", &speed, 0.01f, -10.f, 10.f, "Speed " UNICODE_MULTIPLY " %.1f", 2.f))
|
|
mediaplayer_active_->setPlaySpeed( static_cast<double>(speed) );
|
|
// store action on mouse release
|
|
if (ImGui::IsItemDeactivatedAfterEdit()){
|
|
oss << ": Speed x" << std::setprecision(3) << speed;
|
|
Action::manager().store(oss.str());
|
|
}
|
|
}
|
|
|
|
ImGui::SameLine();
|
|
ImGui::SetCursorPosX(rendersize.x - buttons_height_ / 1.4f);
|
|
if (ImGuiToolkit::ButtonIcon(12,14,"Reset speed" )) {
|
|
mediaplayer_active_->setPlaySpeed( 1.0 );
|
|
oss << ": Speed x1";
|
|
Action::manager().store(oss.str());
|
|
}
|
|
|
|
// 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_);
|
|
|
|
// apply play action to media only if status should change
|
|
if ( mediaplayer_active_->isPlaying() != media_play ) {
|
|
mediaplayer_active_->play( media_play );
|
|
}
|
|
|
|
}
|
|
else {
|
|
|
|
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_);
|
|
|
|
///
|
|
/// Play button bar
|
|
///
|
|
bottom.y += 2.f * timeline_height_ + scrollbar_;
|
|
DrawButtonBar(bottom, rendersize.x);
|
|
}
|
|
|
|
|
|
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::pair<int, int> > icons_loc = { {19,7}, {18,7}, {0,8} };
|
|
static std::vector< std::string > labels_loc = { "Fade in", "Fade out", "Auto fade in & out" };
|
|
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
|
ImGuiToolkit::ComboIcon("Fading", icons_loc, labels_loc, &l);
|
|
|
|
static int c = 0;
|
|
static std::vector< std::pair<int, int> > icons_curve = { {18,3}, {19,3}, {17,3} };
|
|
static std::vector< std::string > labels_curve = { "Linear", "Progressive", "Abrupt" };
|
|
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
|
ImGuiToolkit::ComboIcon("Curve", icons_curve, labels_curve, &c);
|
|
|
|
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("Cancel", ImVec2(area.x * 0.3f, 0)))
|
|
close = true;
|
|
ImGui::SetCursorPos(pos + ImVec2(area.x * 0.7f, area.y - buttons_height_));
|
|
if (ImGui::Button("Apply", ImVec2(area.x * 0.3f, 0))) {
|
|
close = true;
|
|
// timeline to edit
|
|
Timeline *tl = mediaplayer_active_->timeline();
|
|
switch (l) {
|
|
case 0:
|
|
tl->fadeIn(d, (Timeline::FadingCurve) c);
|
|
oss << ": Timeline Fade in " << d;
|
|
break;
|
|
case 1:
|
|
tl->fadeOut(d, (Timeline::FadingCurve) c);
|
|
oss << ": Timeline Fade out " << d;
|
|
break;
|
|
case 2:
|
|
tl->autoFading(d, (Timeline::FadingCurve) c);
|
|
oss << ": Timeline Fade in&out " << d;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
tl->smoothFading( 2 );
|
|
Action::manager().store(oss.str());
|
|
}
|
|
|
|
if (close)
|
|
ImGui::CloseCurrentPopup();
|
|
ImGui::EndPopup();
|
|
}
|
|
}
|
|
|
|
const char *SourceController::SourcePlayIcon(Source *s)
|
|
{
|
|
if (s->active()) {
|
|
if ( s->playing() )
|
|
return ICON_FA_PLAY;
|
|
else
|
|
return ICON_FA_PAUSE;
|
|
}
|
|
else
|
|
return ICON_FA_SNOWFLAKE;
|
|
}
|
|
|
|
void SourceController::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() )
|
|
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);
|
|
}
|
|
}
|
|
else {
|
|
if (ImGui::Button(ICON_FA_PLAY) && enabled){
|
|
for (auto source = selection_.begin(); source != selection_.end(); ++source)
|
|
(*source)->play(true);
|
|
}
|
|
}
|
|
}
|
|
// 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);
|
|
}
|
|
ImGui::SameLine(0, h_space_);
|
|
if (ImGui::Button(ICON_FA_PAUSE) && enabled) {
|
|
for (auto source = selection_.begin(); source != selection_.end(); ++source)
|
|
(*source)->play(false);
|
|
}
|
|
}
|
|
ImGui::SameLine(0, h_space_);
|
|
|
|
// restore style
|
|
ImGui::PopStyleColor(3);
|
|
}
|
|
|
|
///
|
|
/// OUTPUT PREVIEW
|
|
///
|
|
|
|
OutputPreview::OutputPreview() : WorkspaceWindow("OutputPreview"),
|
|
video_recorder_(nullptr), video_broadcaster_(nullptr)
|
|
{
|
|
#if defined(LINUX)
|
|
webcam_emulator_ = nullptr;
|
|
#endif
|
|
|
|
recordFolderDialog = new DialogToolkit::OpenFolderDialog("Recording Location");
|
|
}
|
|
|
|
void OutputPreview::setVisible(bool on)
|
|
{
|
|
// 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 (on && Settings::application.widget.preview_view != Settings::application.current_view)
|
|
Settings::application.widget.preview_view = -1;
|
|
|
|
Settings::application.widget.preview = on;
|
|
}
|
|
|
|
bool OutputPreview::Visible() const
|
|
{
|
|
return ( Settings::application.widget.preview
|
|
&& (Settings::application.widget.preview_view < 0 || Settings::application.widget.preview_view == Settings::application.current_view )
|
|
);
|
|
}
|
|
|
|
void OutputPreview::Update()
|
|
{
|
|
WorkspaceWindow::Update();
|
|
|
|
// management of video_recorders
|
|
if ( !_video_recorders.empty() ) {
|
|
// check that file dialog thread finished
|
|
if (_video_recorders.back().wait_for(std::chrono::milliseconds(4)) == std::future_status::ready ) {
|
|
video_recorder_ = _video_recorders.back().get();
|
|
FrameGrabbing::manager().add(video_recorder_);
|
|
_video_recorders.pop_back();
|
|
}
|
|
}
|
|
// verify the video recorder is valid (change to nullptr if invalid)
|
|
FrameGrabbing::manager().verify( (FrameGrabber**) &video_recorder_);
|
|
if (video_recorder_ // if there is an ongoing recorder
|
|
&& Settings::application.record.timeout < RECORD_MAX_TIMEOUT // and if the timeout is valid
|
|
&& video_recorder_->duration() > Settings::application.record.timeout ) // and the timeout is reached
|
|
{
|
|
video_recorder_->stop();
|
|
}
|
|
|
|
// verify the video broadcaster is valid (change to nullptr if invalid)
|
|
FrameGrabbing::manager().verify( (FrameGrabber**) &video_broadcaster_);
|
|
|
|
#if defined(LINUX)
|
|
// verify the frame grabber for webcam emulator is valid
|
|
FrameGrabbing::manager().verify(&webcam_emulator_);
|
|
#endif
|
|
}
|
|
|
|
VideoRecorder *delayTrigger(VideoRecorder *g, std::chrono::milliseconds delay) {
|
|
std::this_thread::sleep_for (delay);
|
|
return g;
|
|
}
|
|
|
|
void OutputPreview::ToggleRecord(bool save_and_continue)
|
|
{
|
|
if (video_recorder_) {
|
|
// prepare for next time user open new source panel to show the recording
|
|
if (Settings::application.recentRecordings.load_at_start)
|
|
UserInterface::manager().navigator.setNewMedia(Navigator::MEDIA_RECORDING);
|
|
// 'save & continue'
|
|
if ( save_and_continue) {
|
|
VideoRecorder *rec = new VideoRecorder;
|
|
FrameGrabbing::manager().chain(video_recorder_, rec);
|
|
video_recorder_ = rec;
|
|
}
|
|
// normal case: Ctrl+R stop recording
|
|
else
|
|
// stop recording
|
|
video_recorder_->stop();
|
|
}
|
|
else {
|
|
_video_recorders.emplace_back( std::async(std::launch::async, delayTrigger, new VideoRecorder,
|
|
std::chrono::seconds(Settings::application.record.delay)) );
|
|
}
|
|
}
|
|
|
|
void OutputPreview::ToggleBroadcast()
|
|
{
|
|
if (video_broadcaster_) {
|
|
video_broadcaster_->stop();
|
|
}
|
|
else {
|
|
video_broadcaster_ = new VideoBroadcast;
|
|
FrameGrabbing::manager().add(video_broadcaster_);
|
|
}
|
|
}
|
|
|
|
void OutputPreview::Render()
|
|
{
|
|
|
|
#if defined(LINUX)
|
|
bool openInitializeSystemLoopback = false;
|
|
#endif
|
|
|
|
FrameBuffer *output = Mixer::manager().session()->frame();
|
|
if (output)
|
|
{
|
|
// constraint aspect ratio resizing
|
|
float ar = output->aspectRatio();
|
|
ImGui::SetNextWindowSizeConstraints(ImVec2(300, 200), ImVec2(FLT_MAX, FLT_MAX), CustomConstraints::AspectRatio, &ar);
|
|
ImGui::SetNextWindowPos(ImVec2(1180, 20), ImGuiCond_FirstUseEver);
|
|
ImGui::SetNextWindowSize(ImVec2(400, 260), ImGuiCond_FirstUseEver);
|
|
|
|
// Window named "OutputPreview" at instanciation
|
|
if ( !ImGui::Begin(name_, &Settings::application.widget.preview,
|
|
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse ) )
|
|
{
|
|
ImGui::End();
|
|
return;
|
|
}
|
|
|
|
// return from thread for folder openning
|
|
if (recordFolderDialog->closed() && !recordFolderDialog->path().empty())
|
|
// get the folder from this file dialog
|
|
Settings::application.record.path = recordFolderDialog->path();
|
|
|
|
// menu (no title bar)
|
|
if (ImGui::BeginMenuBar())
|
|
{
|
|
if (ImGuiToolkit::IconButton(4,16))
|
|
Settings::application.widget.preview = false;
|
|
if (ImGui::BeginMenu(IMGUI_TITLE_PREVIEW))
|
|
{
|
|
// Output window menu
|
|
if ( ImGui::MenuItem( ICON_FA_WINDOW_RESTORE " Show window") )
|
|
Rendering::manager().outputWindow().show();
|
|
|
|
bool isfullscreen = Rendering::manager().outputWindow().isFullscreen();
|
|
if ( ImGui::MenuItem( MENU_OUTPUTFULLSCREEN, SHORTCUT_OUTPUTFULLSCREEN, &isfullscreen) ) {
|
|
Rendering::manager().outputWindow().show();
|
|
Rendering::manager().outputWindow().toggleFullscreen();
|
|
}
|
|
|
|
ImGui::MenuItem( MENU_OUTPUTDISABLE, SHORTCUT_OUTPUTDISABLE, &Settings::application.render.disabled);
|
|
|
|
// output manager menu
|
|
ImGui::Separator();
|
|
bool pinned = Settings::application.widget.preview_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.preview_view = Settings::application.current_view;
|
|
else
|
|
Settings::application.widget.preview_view = -1;
|
|
}
|
|
if ( ImGui::MenuItem( MENU_CLOSE, SHORTCUT_OUTPUT) )
|
|
Settings::application.widget.preview = false;
|
|
|
|
ImGui::EndMenu();
|
|
}
|
|
if (ImGui::BeginMenu("Record"))
|
|
{
|
|
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_CAPTURE, 0.8f));
|
|
if ( ImGui::MenuItem( MENU_CAPTUREFRAME, SHORTCUT_CAPTUREFRAME) )
|
|
FrameGrabbing::manager().add(new PNGRecorder);
|
|
ImGui::PopStyleColor(1);
|
|
|
|
// temporary disabled
|
|
if (!_video_recorders.empty()) {
|
|
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_RECORD, 0.8f));
|
|
ImGui::MenuItem( MENU_RECORD, SHORTCUT_RECORD, false, false);
|
|
ImGui::MenuItem( MENU_RECORDCONT, SHORTCUT_RECORDCONT, false, false);
|
|
ImGui::PopStyleColor(1);
|
|
}
|
|
// Stop recording menu (recorder already exists)
|
|
else if (video_recorder_) {
|
|
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_RECORD, 0.8f));
|
|
if ( ImGui::MenuItem( ICON_FA_SQUARE " Stop Record", SHORTCUT_RECORD) ) {
|
|
// prepare for next time user open new source panel to show the recording
|
|
if (Settings::application.recentRecordings.load_at_start)
|
|
UserInterface::manager().navigator.setNewMedia(Navigator::MEDIA_RECORDING);
|
|
// stop recorder
|
|
video_recorder_->stop();
|
|
}
|
|
// offer the 'save & continue' recording
|
|
if ( ImGui::MenuItem( MENU_RECORDCONT, SHORTCUT_RECORDCONT) ) {
|
|
// prepare for next time user open new source panel to show the recording
|
|
if (Settings::application.recentRecordings.load_at_start)
|
|
UserInterface::manager().navigator.setNewMedia(Navigator::MEDIA_RECORDING);
|
|
// create a new recorder chainned to the current one
|
|
VideoRecorder *rec = new VideoRecorder;
|
|
FrameGrabbing::manager().chain(video_recorder_, rec);
|
|
// swap recorder
|
|
video_recorder_ = rec;
|
|
}
|
|
ImGui::PopStyleColor(1);
|
|
}
|
|
// start recording
|
|
else {
|
|
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_RECORD, 0.9f));
|
|
if ( ImGui::MenuItem( MENU_RECORD, SHORTCUT_RECORD) ) {
|
|
_video_recorders.emplace_back( std::async(std::launch::async, delayTrigger, new VideoRecorder,
|
|
std::chrono::seconds(Settings::application.record.delay)) );
|
|
}
|
|
ImGui::MenuItem( MENU_RECORDCONT, SHORTCUT_RECORDCONT, false, false);
|
|
ImGui::PopStyleColor(1);
|
|
}
|
|
// Options menu
|
|
ImGui::Separator();
|
|
ImGui::MenuItem("Options", nullptr, false, false);
|
|
// offer to open config panel from here for more options
|
|
ImGui::SameLine(ImGui::GetContentRegionAvailWidth() + 1.2f * IMGUI_RIGHT_ALIGN);
|
|
if (ImGuiToolkit::IconButton(13, 5))
|
|
UserInterface::manager().navigator.showConfig();
|
|
ImGui::SameLine(0);
|
|
ImGui::Text("Settings");
|
|
// BASIC OPTIONS
|
|
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));
|
|
sprintf( name_path[1], "%s", ICON_FA_HOME " Home");
|
|
sprintf( name_path[2], "%s", ICON_FA_FOLDER " Session location");
|
|
sprintf( name_path[3], "%s", ICON_FA_FOLDER_PLUS " Select");
|
|
}
|
|
if (Settings::application.record.path.empty())
|
|
Settings::application.record.path = SystemToolkit::home_path();
|
|
sprintf( name_path[0], "%s", Settings::application.record.path.c_str());
|
|
|
|
int selected_path = 0;
|
|
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
|
ImGui::Combo("Path", &selected_path, name_path, 4);
|
|
if (selected_path > 2)
|
|
recordFolderDialog->open();
|
|
else if (selected_path > 1)
|
|
Settings::application.record.path = SystemToolkit::path_filename( Mixer::manager().session()->filename() );
|
|
else if (selected_path > 0)
|
|
Settings::application.record.path = SystemToolkit::home_path();
|
|
|
|
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
|
ImGuiToolkit::SliderTiming ("Duration", &Settings::application.record.timeout, 1000, RECORD_MAX_TIMEOUT, 1000, "Until stopped");
|
|
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
|
ImGui::SliderInt("Trigger", &Settings::application.record.delay, 0, 5,
|
|
Settings::application.record.delay < 1 ? "Immediate" : "After %d s");
|
|
|
|
ImGui::EndMenu();
|
|
}
|
|
if (ImGui::BeginMenu("Stream"))
|
|
{
|
|
|
|
#if defined(LINUX_NOT_YET_WORKING)
|
|
bool on = webcam_emulator_ != nullptr;
|
|
if ( ImGui::MenuItem( ICON_FA_CAMERA " Emulate video camera", NULL, &on) ) {
|
|
if (on) {
|
|
if (Loopback::systemLoopbackInitialized()) {
|
|
webcam_emulator_ = new Loopback;
|
|
FrameGrabbing::manager().add(webcam_emulator_);
|
|
}
|
|
else
|
|
openInitializeSystemLoopback = true;
|
|
}
|
|
else if (webcam_emulator_ != nullptr) {
|
|
webcam_emulator_->stop();
|
|
webcam_emulator_ = nullptr;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (VideoBroadcast::available()) {
|
|
// Broadcasting menu
|
|
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_BROADCAST, 0.8f));
|
|
// Stop broadcast menu (broadcaster already exists)
|
|
if (video_broadcaster_) {
|
|
if ( ImGui::MenuItem( ICON_FA_SQUARE " Stop Broadcast") )
|
|
video_broadcaster_->stop();
|
|
}
|
|
// start broadcast (broadcaster does not exists)
|
|
else {
|
|
if ( ImGui::MenuItem( ICON_FA_PODCAST " Broadcast") ) {
|
|
video_broadcaster_ = new VideoBroadcast(Settings::application.broadcast_port);
|
|
FrameGrabbing::manager().add(video_broadcaster_);
|
|
}
|
|
}
|
|
ImGui::PopStyleColor(1);
|
|
}
|
|
|
|
// Stream sharing menu
|
|
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_STREAM, 0.9f));
|
|
if ( ImGui::MenuItem( ICON_FA_SHARE_ALT_SQUARE " Share on local network", NULL, &Settings::application.accept_connections) ) {
|
|
Streaming::manager().enable(Settings::application.accept_connections);
|
|
}
|
|
ImGui::PopStyleColor(1);
|
|
if (Settings::application.accept_connections)
|
|
{
|
|
std::vector<std::string> ls = Streaming::manager().listStreams();
|
|
if (ls.size()>0) {
|
|
ImGui::Separator();
|
|
ImGui::MenuItem("Connected vimix", nullptr, false, false);
|
|
for (auto it = ls.begin(); it != ls.end(); ++it)
|
|
ImGui::Text(" %s", (*it).c_str() );
|
|
}
|
|
|
|
}
|
|
ImGui::EndMenu();
|
|
}
|
|
ImGui::EndMenuBar();
|
|
}
|
|
|
|
float width = ImGui::GetContentRegionAvail().x;
|
|
|
|
ImVec2 imagesize ( width, width / ar);
|
|
// virtual button to show the output window when clic on the preview
|
|
ImVec2 draw_pos = ImGui::GetCursorScreenPos();
|
|
// preview image
|
|
ImGui::Image((void*)(intptr_t)output->texture(), imagesize);
|
|
// raise window on double clic
|
|
if (ImGui::IsMouseDoubleClicked(0) )
|
|
Rendering::manager().outputWindow().show();
|
|
|
|
///
|
|
/// Icons overlays
|
|
///
|
|
const float r = ImGui::GetTextLineHeightWithSpacing();
|
|
|
|
// info indicator
|
|
ImGui::SetCursorScreenPos(draw_pos + ImVec2(imagesize.x - r, 6));
|
|
ImGui::Text(ICON_FA_INFO_CIRCLE);
|
|
bool drawoverlay = ImGui::IsItemHovered();
|
|
// recording indicator
|
|
if (video_recorder_)
|
|
{
|
|
ImGui::SetCursorScreenPos(ImVec2(draw_pos.x + r, draw_pos.y + r));
|
|
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
|
|
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_RECORD, 0.8f));
|
|
ImGui::Text(ICON_FA_CIRCLE " %s", video_recorder_->info().c_str() );
|
|
ImGui::PopStyleColor(1);
|
|
ImGui::PopFont();
|
|
}
|
|
else if (!_video_recorders.empty())
|
|
{
|
|
ImGui::SetCursorScreenPos(ImVec2(draw_pos.x + r, draw_pos.y + r));
|
|
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
|
|
static double anim = 0.f;
|
|
double a = sin(anim+=0.104); // 2 * pi / 60fps
|
|
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_RECORD, a));
|
|
ImGui::Text(ICON_FA_CIRCLE);
|
|
ImGui::PopStyleColor(1);
|
|
ImGui::PopFont();
|
|
}
|
|
// broadcast indicator
|
|
if (video_broadcaster_)
|
|
{
|
|
ImGui::SetCursorScreenPos(ImVec2(draw_pos.x + imagesize.x - 2.5f * r, draw_pos.y + r));
|
|
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
|
|
if (video_broadcaster_->busy())
|
|
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_BROADCAST, 0.8f));
|
|
else
|
|
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_BROADCAST, 0.4f));
|
|
ImGui::Text(ICON_FA_PODCAST);
|
|
ImGui::PopStyleColor(1);
|
|
ImGui::PopFont();
|
|
}
|
|
// streaming indicator
|
|
if (Settings::application.accept_connections)
|
|
{
|
|
ImGui::SetCursorScreenPos(ImVec2(draw_pos.x + imagesize.x - 2.4f * r, draw_pos.y + imagesize.y - 2.f*r));
|
|
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
|
|
if ( Streaming::manager().busy())
|
|
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_STREAM, 0.8f));
|
|
else
|
|
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_STREAM, 0.4f));
|
|
ImGui::Text(ICON_FA_SHARE_ALT_SQUARE);
|
|
ImGui::PopStyleColor(1);
|
|
ImGui::PopFont();
|
|
}
|
|
// OUTPUT DISABLED indicator
|
|
if (Settings::application.render.disabled)
|
|
{
|
|
ImGui::SetCursorScreenPos(ImVec2(draw_pos.x + r, draw_pos.y + imagesize.y - 2.f*r));
|
|
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
|
|
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_RECORD, 0.8f));
|
|
ImGui::Text(ICON_FA_EYE_SLASH);
|
|
ImGui::PopStyleColor(1);
|
|
ImGui::PopFont();
|
|
}
|
|
#if defined(LINUX)
|
|
// streaming indicator
|
|
if (webcam_emulator_)
|
|
{
|
|
ImGui::SetCursorScreenPos(ImVec2(draw_pos.x + imagesize.x - 2.5f * r, draw_pos.y + 2.f * r));
|
|
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
|
|
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.8f, 0.8f, 0.8f, 0.8f));
|
|
ImGui::Text(ICON_FA_CAMERA);
|
|
ImGui::PopStyleColor(1);
|
|
ImGui::PopFont();
|
|
}
|
|
#endif
|
|
|
|
///
|
|
/// Info overlay over image
|
|
///
|
|
if (drawoverlay)
|
|
{
|
|
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
|
float h = 1.f;
|
|
h += (Settings::application.accept_connections ? 1.f : 0.f);
|
|
h += (video_broadcaster_ ? 1.f : 0.f);
|
|
draw_list->AddRectFilled(draw_pos, ImVec2(draw_pos.x + imagesize.x, draw_pos.y + h * r), IMGUI_COLOR_OVERLAY);
|
|
ImGui::SetCursorScreenPos(draw_pos);
|
|
ImGui::Text(" " ICON_FA_TV " %d x %d px, %.d fps", output->width(), output->height(), int(Mixer::manager().fps()) );
|
|
if (Settings::application.accept_connections)
|
|
ImGui::Text( " " ICON_FA_SHARE_ALT_SQUARE " %s (%d peer)",
|
|
Connection::manager().info().name.c_str(),
|
|
Streaming::manager().listStreams().size() );
|
|
if (video_broadcaster_)
|
|
ImGui::Text( " " ICON_FA_PODCAST " %s", video_broadcaster_->info().c_str() );
|
|
}
|
|
|
|
ImGui::End();
|
|
}
|
|
|
|
#if defined(LINUX)
|
|
if (openInitializeSystemLoopback && !ImGui::IsPopupOpen("Initialize System Loopback"))
|
|
ImGui::OpenPopup("Initialize System Loopback");
|
|
if (ImGui::BeginPopupModal("Initialize System Loopback", NULL, ImGuiWindowFlags_AlwaysAutoResize))
|
|
{
|
|
int w = 600;
|
|
ImGui::Text("In order to enable the video4linux camera loopback,\n"
|
|
"'v4l2loopack' has to be installed and initialized on your machine\n\n"
|
|
"To do so, the following commands should be executed (admin rights):\n");
|
|
|
|
static char dummy_str[512];
|
|
sprintf(dummy_str, "sudo apt install v4l2loopback-dkms");
|
|
ImGui::Text("Install v4l2loopack (once):");
|
|
ImGui::SetNextItemWidth(600-40);
|
|
ImGui::InputText("##cmd1", dummy_str, IM_ARRAYSIZE(dummy_str), ImGuiInputTextFlags_ReadOnly);
|
|
ImGui::SameLine();
|
|
ImGui::PushID(358794);
|
|
if ( ImGuiToolkit::ButtonIcon(11,2, "Copy to clipboard") )
|
|
ImGui::SetClipboardText(dummy_str);
|
|
ImGui::PopID();
|
|
|
|
sprintf(dummy_str, "sudo modprobe v4l2loopback exclusive_caps=1 video_nr=10 card_label=\"vimix loopback\"");
|
|
ImGui::Text("Initialize v4l2loopack (after reboot):");
|
|
ImGui::SetNextItemWidth(600-40);
|
|
ImGui::InputText("##cmd2", dummy_str, IM_ARRAYSIZE(dummy_str), ImGuiInputTextFlags_ReadOnly);
|
|
ImGui::SameLine();
|
|
ImGui::PushID(899872);
|
|
if ( ImGuiToolkit::ButtonIcon(11,2, "Copy to clipboard") )
|
|
ImGui::SetClipboardText(dummy_str);
|
|
ImGui::PopID();
|
|
|
|
ImGui::Separator();
|
|
ImGui::SetItemDefaultFocus();
|
|
if (ImGui::Button("Ok, I'll do this in a terminal and try again later.", ImVec2(w, 0)) ) {
|
|
ImGui::CloseCurrentPopup();
|
|
}
|
|
|
|
ImGui::EndPopup();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
///
|
|
/// TIMER & METRONOME
|
|
///
|
|
|
|
TimerMetronome::TimerMetronome() : WorkspaceWindow("Timer")
|
|
{
|
|
// timer modes : 0 Metronome, 1 Stopwatch
|
|
timer_menu = { "Metronome", "Stopwatch" };
|
|
// clock times
|
|
start_time_ = gst_util_get_timestamp ();
|
|
start_time_hand_ = gst_util_get_timestamp ();
|
|
duration_hand_ = Settings::application.timer.stopwatch_duration * GST_SECOND;
|
|
}
|
|
|
|
|
|
void TimerMetronome::setVisible(bool on)
|
|
{
|
|
// 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 (on && Settings::application.widget.timer_view != Settings::application.current_view)
|
|
Settings::application.widget.timer_view = -1;
|
|
|
|
Settings::application.widget.timer = on;
|
|
}
|
|
|
|
bool TimerMetronome::Visible() const
|
|
{
|
|
return ( Settings::application.widget.timer
|
|
&& (Settings::application.widget.timer_view < 0 || Settings::application.widget.timer_view == Settings::application.current_view )
|
|
);
|
|
}
|
|
|
|
void TimerMetronome::Render()
|
|
{
|
|
// constraint square resizing
|
|
static ImVec2 timer_window_size_min = ImVec2(11.f * ImGui::GetTextLineHeight(), 11.f * ImGui::GetTextLineHeight());
|
|
ImGui::SetNextWindowSizeConstraints(timer_window_size_min, timer_window_size_min * 1.5f, CustomConstraints::Square);
|
|
ImGui::SetNextWindowPos(ImVec2(600, 20), ImGuiCond_FirstUseEver);
|
|
ImGui::SetNextWindowSize(timer_window_size_min, ImGuiCond_FirstUseEver);
|
|
|
|
if ( !ImGui::Begin(name_, &Settings::application.widget.timer,
|
|
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse ))
|
|
{
|
|
ImGui::End();
|
|
return;
|
|
}
|
|
|
|
// menu (no title bar)
|
|
if (ImGui::BeginMenuBar())
|
|
{
|
|
// Close and widget menu
|
|
if (ImGuiToolkit::IconButton(4,16))
|
|
Settings::application.widget.timer = false;
|
|
if (ImGui::BeginMenu(IMGUI_TITLE_TIMER))
|
|
{
|
|
// Enable/Disable Ableton Link
|
|
if ( ImGui::MenuItem( ICON_FA_USER_CLOCK " Ableton Link", nullptr, &Settings::application.timer.link_enabled) ) {
|
|
Metronome::manager().setEnabled(Settings::application.timer.link_enabled);
|
|
}
|
|
|
|
// output manager menu
|
|
ImGui::Separator();
|
|
bool pinned = Settings::application.widget.timer_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.timer_view = Settings::application.current_view;
|
|
else
|
|
Settings::application.widget.timer_view = -1;
|
|
}
|
|
if ( ImGui::MenuItem( MENU_CLOSE, SHORTCUT_TIMER) )
|
|
Settings::application.widget.timer = false;
|
|
|
|
ImGui::EndMenu();
|
|
}
|
|
// Selection of the timer mode
|
|
if (ImGui::BeginMenu( timer_menu[Settings::application.timer.mode].c_str() ))
|
|
{
|
|
for (size_t i = 0; i < timer_menu.size(); ++i) {
|
|
if (ImGui::MenuItem(timer_menu[i].c_str(), NULL, Settings::application.timer.mode==i))
|
|
Settings::application.timer.mode = i;
|
|
}
|
|
ImGui::EndMenu();
|
|
}
|
|
|
|
ImGui::EndMenuBar();
|
|
}
|
|
|
|
// current windowdraw parameters
|
|
ImGuiWindow* window = ImGui::GetCurrentWindow();
|
|
ImDrawList* draw_list = window->DrawList;
|
|
// positions and size of GUI elements
|
|
const float margin = window->MenuBarHeight();
|
|
const float h = 0.4f * ImGui::GetFrameHeight();
|
|
const ImVec2 circle_top_left = window->Pos + ImVec2(margin + h, margin + h);
|
|
const ImVec2 circle_top_right = window->Pos + ImVec2(window->Size.y - margin - h, margin + h);
|
|
const ImVec2 circle_botom_right = window->Pos + ImVec2(window->Size.y - margin - h, window->Size.x - margin - h);
|
|
const ImVec2 circle_center = window->Pos + (window->Size + ImVec2(margin, margin) )/ 2.f;
|
|
const float circle_radius = (window->Size.y - 2.f * margin) / 2.f;
|
|
|
|
// color palette
|
|
static ImU32 colorbg = ImGui::GetColorU32(ImGuiCol_FrameBgActive, 0.6f);
|
|
static ImU32 colorfg = ImGui::GetColorU32(ImGuiCol_FrameBg, 2.5f);
|
|
static ImU32 colorline = ImGui::GetColorU32(ImGuiCol_PlotHistogram);
|
|
|
|
//
|
|
// METRONOME
|
|
//
|
|
if (Settings::application.timer.mode < 1) {
|
|
|
|
// Metronome info
|
|
double t = Metronome::manager().tempo();
|
|
double p = Metronome::manager().phase();
|
|
double q = Metronome::manager().quantum();
|
|
uint np = (int) Metronome::manager().peers();
|
|
|
|
// draw background ring
|
|
draw_list->AddCircleFilled(circle_center, circle_radius, colorbg, PLOT_CIRCLE_SEGMENTS);
|
|
|
|
// draw quarter
|
|
static const float resolution = PLOT_CIRCLE_SEGMENTS / (2.f * M_PI);
|
|
static ImVec2 buffer[PLOT_CIRCLE_SEGMENTS];
|
|
float a0 = -M_PI_2 + (floor(p)/floor(q)) * (2.f * M_PI);
|
|
float a1 = a0 + (1.f / floor(q)) * (2.f * M_PI);
|
|
int n = ImMax(3, (int)((a1 - a0) * resolution));
|
|
double da = (a1 - a0) / (n - 1);
|
|
int index = 0;
|
|
buffer[index++] = circle_center;
|
|
for (int i = 0; i < n; ++i) {
|
|
double a = a0 + i * da;
|
|
buffer[index++] = ImVec2(circle_center.x + circle_radius * cos(a), circle_center.y + circle_radius * sin(a));
|
|
}
|
|
draw_list->AddConvexPolyFilled(buffer, index, colorfg);
|
|
|
|
// draw clock hand
|
|
a0 = -M_PI_2 + (p/q) * (2.f * M_PI);
|
|
draw_list->AddLine(ImVec2(circle_center.x + margin * cos(a0), circle_center.y + margin * sin(a0)),
|
|
ImVec2(circle_center.x + circle_radius * cos(a0), circle_center.y + circle_radius * sin(a0)), colorline, 2.f);
|
|
|
|
// centered indicator 'x / N'
|
|
draw_list->AddCircleFilled(circle_center, margin, colorfg, PLOT_CIRCLE_SEGMENTS);
|
|
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO);
|
|
char text_buf[24];
|
|
sprintf(text_buf, "%d/%d", (int)(p)+1, (int)(q) );
|
|
ImVec2 label_size = ImGui::CalcTextSize(text_buf, NULL);
|
|
ImGui::SetCursorScreenPos(circle_center - label_size/2);
|
|
ImGui::Text("%s", text_buf);
|
|
ImGui::PopFont();
|
|
|
|
// left slider : quantum
|
|
float float_value = ceil(Metronome::manager().quantum());
|
|
ImGui::SetCursorPos(ImVec2(0.5f * margin, 1.5f * margin));
|
|
if ( ImGui::VSliderFloat("##quantum", ImVec2(0.5f * margin, 2.f * circle_radius ), &float_value, 2, 200, "", 2.f) ){
|
|
Metronome::manager().setQuantum( ceil(float_value) );
|
|
}
|
|
if (ImGui::IsItemHovered() || ImGui::IsItemActive() ) {
|
|
ImGui::BeginTooltip();
|
|
guint64 time_phase = GST_SECOND * (60.0 * q / t) ;
|
|
ImGui::Text("%d beats per phase\n= %s at %d BPM", (int) ceil(float_value),
|
|
GstToolkit::time_to_string(time_phase, GstToolkit::TIME_STRING_READABLE).c_str(), (int) t);
|
|
ImGui::EndTooltip();
|
|
}
|
|
|
|
// Controls NOT operational if peer connected
|
|
if (np >0 ) {
|
|
// Tempo
|
|
ImGui::SetCursorScreenPos(circle_top_right);
|
|
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_BOLD);
|
|
ImGui::PushStyleColor(ImGuiCol_Text, colorfg);
|
|
sprintf(text_buf, "%d", (int) ceil(t) );
|
|
ImGui::Text("%s", text_buf);
|
|
ImGui::PopStyleColor();
|
|
ImGui::PopFont();
|
|
if (ImGui::IsItemHovered()){
|
|
sprintf(text_buf, "%d BPM\n(set by peer)", (int) ceil(t));
|
|
ImGuiToolkit::ToolTip(text_buf);
|
|
}
|
|
}
|
|
// Controls operational only if no peer
|
|
else {
|
|
// Tempo
|
|
ImGui::SetCursorScreenPos(circle_top_right);
|
|
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_BOLD);
|
|
sprintf(text_buf, "%d", (int) ceil(t) );
|
|
ImGui::Text("%s", text_buf);
|
|
ImGui::PopFont();
|
|
if (ImGui::IsItemClicked())
|
|
ImGui::OpenPopup("bpm_popup");
|
|
else if (ImGui::IsItemHovered()){
|
|
sprintf(text_buf, "%d BPM\n(clic to edit)", (int) ceil(t));
|
|
ImGuiToolkit::ToolTip(text_buf);
|
|
}
|
|
if (ImGui::BeginPopup("bpm_popup", ImGuiWindowFlags_NoMove))
|
|
{
|
|
ImGui::SetNextItemWidth(80);
|
|
ImGui::InputText("BPM", text_buf, 8, ImGuiInputTextFlags_CharsDecimal);
|
|
if (ImGui::IsItemDeactivatedAfterEdit()) {
|
|
int t = 0;
|
|
sscanf(text_buf, "%d", &t);
|
|
t = CLAMP(t, 20, 2000);
|
|
Metronome::manager().setTempo((double) t);
|
|
ImGui::CloseCurrentPopup();
|
|
}
|
|
|
|
ImGui::EndPopup();
|
|
}
|
|
// restart icon
|
|
ImGui::SetCursorScreenPos(circle_top_left);
|
|
if (ImGuiToolkit::IconButton(9, 13)) {
|
|
Metronome::manager().restart();
|
|
}
|
|
}
|
|
|
|
// Network indicator, if link enabled
|
|
if (Settings::application.timer.link_enabled) {
|
|
ImGui::SetCursorScreenPos(circle_botom_right);
|
|
ImGuiToolkit::Icon(16, 5, np > 0);
|
|
if (ImGui::IsItemHovered()){
|
|
sprintf(text_buf, np < 1 ? "Ableton Link\nNo peer" : "Ableton Link\n%d peer%c", np, np < 2 ? ' ' : 's' );
|
|
ImGuiToolkit::ToolTip(text_buf);
|
|
}
|
|
}
|
|
|
|
}
|
|
//
|
|
// STOPWATCH
|
|
//
|
|
else {
|
|
guint64 time_ = gst_util_get_timestamp ();
|
|
|
|
// draw ring
|
|
draw_list->AddCircle(circle_center, circle_radius, colorbg, PLOT_CIRCLE_SEGMENTS, 12 );
|
|
draw_list->AddCircleFilled(ImVec2(circle_center.x, circle_center.y - circle_radius), 7, colorfg, PLOT_CIRCLE_SEGMENTS);
|
|
// draw indicator time hand
|
|
double da = -M_PI_2 + ( (double) (time_-start_time_hand_) / (double) duration_hand_) * (2.f * M_PI);
|
|
draw_list->AddCircleFilled(ImVec2(circle_center.x + circle_radius * cos(da), circle_center.y + circle_radius * sin(da)), 7, colorline, PLOT_CIRCLE_SEGMENTS);
|
|
|
|
// left slider : countdown
|
|
float float_value = (float) Settings::application.timer.stopwatch_duration;
|
|
ImGui::SetCursorPos(ImVec2(0.5f * margin, 1.5f * margin));
|
|
if ( ImGui::VSliderFloat("##duration", ImVec2(0.5f * margin, 2.f * circle_radius ), &float_value, 1, 3600, "", 3.f) ){
|
|
Settings::application.timer.stopwatch_duration = (uint64_t) float_value;
|
|
duration_hand_ = Settings::application.timer.stopwatch_duration * GST_SECOND;
|
|
}
|
|
if (ImGui::IsItemHovered() || ImGui::IsItemActive()) {
|
|
ImGui::BeginTooltip();
|
|
ImGui::Text("%s\ncountdown", GstToolkit::time_to_string(duration_hand_, GstToolkit::TIME_STRING_READABLE).c_str() );
|
|
ImGui::EndTooltip();
|
|
}
|
|
|
|
// main text: elapsed time
|
|
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
|
|
char text_buf[24];
|
|
sprintf(text_buf, "%s", GstToolkit::time_to_string(time_-start_time_, GstToolkit::TIME_STRING_FIXED).c_str() );
|
|
ImVec2 label_size = ImGui::CalcTextSize(text_buf, NULL);
|
|
ImGui::SetCursorScreenPos(circle_center - label_size/2);
|
|
ImGui::Text("%s", text_buf);
|
|
ImGui::PopFont();
|
|
|
|
// small text: remaining time
|
|
ImGui::PushStyleColor(ImGuiCol_Text, colorfg);
|
|
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_BOLD);
|
|
sprintf(text_buf, "%s", GstToolkit::time_to_string(duration_hand_-(time_-start_time_hand_)%duration_hand_, GstToolkit::TIME_STRING_READABLE).c_str() );
|
|
label_size = ImGui::CalcTextSize(text_buf, NULL);
|
|
ImGui::SetCursorScreenPos(circle_center + ImVec2(0.f, circle_radius * -0.7f) - label_size/2);
|
|
ImGui::Text("%s", text_buf);
|
|
ImGui::PopFont();
|
|
ImGui::PopStyleColor();
|
|
|
|
// reset icon
|
|
ImGui::SetCursorScreenPos(circle_top_left);
|
|
if (ImGuiToolkit::IconButton(8, 13))
|
|
start_time_ = start_time_hand_ = time_; // reset timers
|
|
|
|
// TODO : pause ?
|
|
}
|
|
|
|
ImGui::End();
|
|
}
|
|
|
|
|
|
///
|
|
/// KEYBOARDS
|
|
///
|
|
|
|
InputMappingInterface::InputMappingInterface() : WorkspaceWindow("InputMappingInterface")
|
|
{
|
|
input_mode = { ICON_FA_KEYBOARD " Keyboard", ICON_FA_CALCULATOR " Numpad" };
|
|
current_input_for_mode = { INPUT_KEYBOARD_FIRST, INPUT_NUMPAD_FIRST };
|
|
current_input_ = current_input_for_mode[Settings::application.mapping.mode];
|
|
}
|
|
|
|
void InputMappingInterface::setVisible(bool on)
|
|
{
|
|
// 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 (on && Settings::application.widget.inputs_view != Settings::application.current_view)
|
|
Settings::application.widget.inputs_view = -1;
|
|
|
|
Settings::application.widget.inputs = on;
|
|
}
|
|
|
|
bool InputMappingInterface::Visible() const
|
|
{
|
|
return ( Settings::application.widget.inputs
|
|
&& (Settings::application.widget.inputs_view < 0 || Settings::application.widget.inputs_view == Settings::application.current_view )
|
|
);
|
|
}
|
|
|
|
|
|
Source *InputMappingInterface::ComboSelectSource(Source *current)
|
|
{
|
|
Source *selected = nullptr;
|
|
std::string label = "Select";
|
|
if (current)
|
|
label = current->name();
|
|
|
|
if (ImGui::BeginCombo("##ComboSelectSource", label.c_str()) )
|
|
{
|
|
Session *ses = Mixer::manager().session();
|
|
for (auto sit = ses->begin(); sit != ses->end(); ++sit) {
|
|
if (ImGui::Selectable( (*sit)->name().c_str() )) {
|
|
selected = *sit;
|
|
}
|
|
}
|
|
ImGui::EndCombo();
|
|
}
|
|
|
|
return selected;
|
|
}
|
|
|
|
uint InputMappingInterface::ComboSelectCallback(uint current)
|
|
{
|
|
uint selected = SourceCallback::CALLBACK_GENERIC;
|
|
|
|
const char* callback_names[9] = { "Select",
|
|
ICON_FA_BULLSEYE " Alpha",
|
|
ICON_FA_BULLSEYE " Loom",
|
|
ICON_FA_OBJECT_UNGROUP " Grab",
|
|
ICON_FA_OBJECT_UNGROUP " Resize",
|
|
ICON_FA_OBJECT_UNGROUP " Turn",
|
|
ICON_FA_LAYER_GROUP " Depth",
|
|
ICON_FA_PLAY_CIRCLE " Play",
|
|
ICON_FA_PLAY_CIRCLE " Replay"
|
|
};
|
|
|
|
ImGui::SetNextItemWidth(-1.1f * ImGui::GetTextLineHeightWithSpacing());
|
|
if (ImGui::BeginCombo("##ComboSelectCallback", callback_names[current]) ) {
|
|
for (uint i = SourceCallback::CALLBACK_ALPHA; i < SourceCallback::CALLBACK_REPLAY; ++i){
|
|
if ( ImGui::Selectable( callback_names[i]) ) {
|
|
selected = i;
|
|
}
|
|
}
|
|
ImGui::EndCombo();
|
|
}
|
|
ImGui::SameLine();
|
|
if (current == 1 || current == 6 || current == 7)
|
|
ImGuiToolkit::Indication("* On Press *\nApply target value on key press,\nrevert on key up.", 2, 13);
|
|
else
|
|
ImGuiToolkit::Indication("* Repeat *\nMaintain key down to loop action and animate.", 18, 5);
|
|
|
|
return selected;
|
|
}
|
|
|
|
void InputMappingInterface::SliderParametersCallback(SourceCallback *callback)
|
|
{
|
|
const float right_align = -1.1f * ImGui::GetTextLineHeightWithSpacing();
|
|
|
|
switch ( callback->type() ) {
|
|
case SourceCallback::CALLBACK_ALPHA:
|
|
{
|
|
SetAlpha *edited = static_cast<SetAlpha*>(callback);
|
|
float val = edited->value();
|
|
ImGui::SetNextItemWidth(right_align);
|
|
if (ImGui::SliderFloat("##CALLBACK_ALPHA", &val, 0.f, 1.f, "%.2f"))
|
|
edited->setValue(val);
|
|
ImGui::SameLine(0, 6);
|
|
ImGuiToolkit::Indication("Target alpha to make the source\nvisible (1.0) or transparent (0.0)", 18, 12);
|
|
}
|
|
break;
|
|
case SourceCallback::CALLBACK_LOOM:
|
|
{
|
|
Loom *edited = static_cast<Loom*>(callback);
|
|
float val = edited->value();
|
|
ImGui::SetNextItemWidth(right_align);
|
|
if (ImGui::SliderFloat("##CALLBACK_LOOM", &val, -1.f, 1.f, "%.2f", 2.f))
|
|
edited->setValue(val);
|
|
ImGui::SameLine(0, 6);
|
|
ImGuiToolkit::Indication("Change alpha to make source more visible (<0) or more transparent (>0)", 19, 12);
|
|
}
|
|
break;
|
|
case SourceCallback::CALLBACK_GRAB:
|
|
{
|
|
Grab *edited = static_cast<Grab*>(callback);
|
|
float val[2] = {edited->value().x, edited->value().y};
|
|
ImGui::SetNextItemWidth(right_align);
|
|
if (ImGui::SliderFloat2("##CALLBACK_GRAB", val, -2.f, 2.f, "%.2f"))
|
|
edited->setValue( glm::vec2(val[0], val[1]));
|
|
ImGui::SameLine(0, 6);
|
|
ImGuiToolkit::Indication("Vector (x,y) to move the source horizontally and vertically.", 6, 15);
|
|
}
|
|
break;
|
|
case SourceCallback::CALLBACK_RESIZE:
|
|
{
|
|
Resize *edited = static_cast<Resize*>(callback);
|
|
float val[2] = {edited->value().x, edited->value().y};
|
|
ImGui::SetNextItemWidth(right_align);
|
|
if (ImGui::SliderFloat2("##CALLBACK_RESIZE", val, -2.f, 2.f, "%.2f"))
|
|
edited->setValue( glm::vec2(val[0], val[1]));
|
|
ImGui::SameLine(0, 6);
|
|
ImGuiToolkit::Indication("Vector (x,y) to scale the source horizontally and vertically.", 2, 15);
|
|
|
|
}
|
|
break;
|
|
case SourceCallback::CALLBACK_TURN:
|
|
{
|
|
Turn *edited = static_cast<Turn*>(callback);
|
|
float val = edited->value();
|
|
ImGui::SetNextItemWidth(right_align);
|
|
if (ImGui::SliderFloat("##CALLBACK_TURN", &val, -2.f, 2.f, "%.2f")) // 18.9
|
|
edited->setValue(val);
|
|
ImGui::SameLine(0, 6);
|
|
ImGuiToolkit::Indication("Angle of rotation of the source, clockwise (>0) or counter-clockwise (<0).", 18, 9);
|
|
}
|
|
break;
|
|
case SourceCallback::CALLBACK_DEPTH:
|
|
{
|
|
SetDepth *edited = static_cast<SetDepth*>(callback);
|
|
float val = edited->value();
|
|
ImGui::SetNextItemWidth(right_align);
|
|
if (ImGui::SliderFloat("##CALLBACK_DEPTH", &val, 11.9f, 0.1f, "%.1f"))
|
|
edited->setValue(val);
|
|
ImGui::SameLine(0, 6);
|
|
ImGuiToolkit::Indication("Target depth to bring the source\nfront (12) or back (0).", 6, 6);
|
|
}
|
|
case SourceCallback::CALLBACK_PLAY:
|
|
{
|
|
Play *edited = static_cast<Play*>(callback);
|
|
int val = edited->value() ? 1 : 0;
|
|
ImGui::SetNextItemWidth(right_align);
|
|
if (ImGui::SliderInt("##CALLBACK_PLAY", &val, 0, 1, val ? "Play" : "Pause"))
|
|
edited->setValue(val>0);
|
|
ImGui::SameLine(0, 6);
|
|
ImGuiToolkit::Indication("Play or pause the source.", 16, 7);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void InputMappingInterface::Render()
|
|
{
|
|
const ImGuiContext& g = *GImGui;
|
|
static ImVec2 keyLetterIconSize = ImVec2(60, 60);
|
|
static ImVec2 keyLetterItemSize = keyLetterIconSize + g.Style.ItemSpacing;
|
|
static ImVec2 keyNumpadIconSize = ImVec2(75, 75);
|
|
static ImVec2 keyNumpadItemSize = keyNumpadIconSize + g.Style.ItemSpacing;
|
|
static float fixed_height = keyLetterItemSize.y * 5.f + g.Style.WindowBorderSize + g.FontSize + g.Style.FramePadding.y * 2.0f + g.Style.ItemSpacing.y * 2.0f ;
|
|
static float inputarea_width = keyLetterItemSize.x * 5.f;
|
|
|
|
ImGui::SetNextWindowPos(ImVec2(600, 400), ImGuiCond_FirstUseEver);
|
|
ImGui::SetNextWindowSize(ImVec2(1000, fixed_height), ImGuiCond_FirstUseEver);
|
|
ImGui::SetNextWindowSizeConstraints(ImVec2(900, fixed_height), ImVec2(FLT_MAX, fixed_height));
|
|
|
|
if ( !ImGui::Begin(name_, &Settings::application.widget.inputs,
|
|
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse ))
|
|
{
|
|
ImGui::End();
|
|
return;
|
|
}
|
|
|
|
// menu (no title bar)
|
|
if (ImGui::BeginMenuBar())
|
|
{
|
|
// Close and widget menu
|
|
if (ImGuiToolkit::IconButton(4,16))
|
|
Settings::application.widget.inputs = false;
|
|
if (ImGui::BeginMenu(IMGUI_TITLE_INPUT_MAPPING))
|
|
{
|
|
// Enable/Disable
|
|
ImGui::MenuItem( ICON_FA_BAN " Disable all", nullptr, &Settings::application.mapping.disabled);
|
|
|
|
// output manager menu
|
|
ImGui::Separator();
|
|
bool pinned = Settings::application.widget.inputs_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.inputs_view = Settings::application.current_view;
|
|
else
|
|
Settings::application.widget.inputs_view = -1;
|
|
}
|
|
if ( ImGui::MenuItem( MENU_CLOSE, SHORTCUT_INPUTS) )
|
|
Settings::application.widget.inputs = false;
|
|
|
|
ImGui::EndMenu();
|
|
}
|
|
|
|
// Selection of the keyboard mode
|
|
if (ImGui::BeginMenu( input_mode[Settings::application.mapping.mode].c_str() ))
|
|
{
|
|
for (size_t i = 0; i < input_mode.size(); ++i) {
|
|
if (ImGui::MenuItem(input_mode[i].c_str(), NULL, Settings::application.mapping.mode==i)) {
|
|
current_input_for_mode[Settings::application.mapping.mode] = current_input_;
|
|
Settings::application.mapping.mode = i;
|
|
current_input_ = current_input_for_mode[i];
|
|
}
|
|
}
|
|
ImGui::EndMenu();
|
|
}
|
|
|
|
ImGui::EndMenuBar();
|
|
}
|
|
|
|
// current windowdraw parameters
|
|
const ImGuiWindow* window = ImGui::GetCurrentWindow();
|
|
ImDrawList* draw_list = window->DrawList;
|
|
ImVec2 frame_top = ImGui::GetCursorScreenPos();
|
|
Session *ses = Mixer::manager().session();
|
|
|
|
|
|
// create data structures more adapted for display
|
|
std::multimap< uint, std::pair<Source *, SourceCallback*> > input_sources_callbacks;
|
|
bool input_assigned[INPUT_MAX]{};
|
|
// loop over sources of the session
|
|
for (auto sit = ses->begin(); sit != ses->end(); ++sit) {
|
|
// loop over registered keys
|
|
std::list<uint> inputs = (*sit)->callbackInputs();
|
|
for (auto k = inputs.begin(); k != inputs.end(); ++k) {
|
|
// add entry in input map
|
|
std::list<SourceCallback *> callbacks = (*sit)->inputCallbacks(*k);
|
|
for (auto c = callbacks.begin(); c != callbacks.end(); ++c )
|
|
input_sources_callbacks.emplace( *k, std::pair<Source *, SourceCallback*>(*sit, *c) );
|
|
input_assigned[*k] = true;
|
|
}
|
|
}
|
|
|
|
//
|
|
// KEYBOARD
|
|
//
|
|
if ( Settings::application.mapping.mode == 0 ) {
|
|
|
|
// Draw table of letter keys [A] to [Y]
|
|
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
|
|
ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, ImVec2(0.5f, 0.50f));
|
|
ImVec4 color = ImGui::GetStyle().Colors[ImGuiCol_Header];
|
|
color.w /= Settings::application.mapping.disabled ? 2.f : 0.9f;
|
|
ImGui::PushStyleColor(ImGuiCol_Header, color);
|
|
|
|
for (uint ik = INPUT_KEYBOARD_FIRST; ik < INPUT_KEYBOARD_LAST; ++ik){
|
|
int i = ik - INPUT_KEYBOARD_FIRST;
|
|
// draw overlay on active keys
|
|
if ( Control::inputActive(ik) ) {
|
|
ImVec2 pos = frame_top + keyLetterItemSize * ImVec2( i % 5, i / 5);
|
|
draw_list->AddRectFilled(pos, pos + keyLetterIconSize, ImGui::GetColorU32(ImGuiCol_Border), 6.f);
|
|
// set current
|
|
current_input_ = ik;
|
|
}
|
|
// draw key button
|
|
ImGui::PushID(i);
|
|
if (ImGui::Selectable(Control::manager().inputLabel(ik).c_str(), input_assigned[ik], 0, keyLetterIconSize)) {
|
|
|
|
current_input_ = ik;
|
|
}
|
|
ImGui::PopID();
|
|
if ((i % 5) < 4) ImGui::SameLine();
|
|
|
|
// Draw frame around current keyboard letter
|
|
if (current_input_ == ik) {
|
|
ImVec2 pos = frame_top + keyLetterItemSize * ImVec2( i % 5, i / 5);
|
|
draw_list->AddRect(pos, pos + keyLetterIconSize, ImGui::GetColorU32(ImGuiCol_Text), 6.f, ImDrawCornerFlags_All, 3.f);
|
|
}
|
|
}
|
|
ImGui::PopStyleColor();
|
|
ImGui::PopStyleVar();
|
|
ImGui::PopFont();
|
|
|
|
}
|
|
//
|
|
// NUMPAD
|
|
//
|
|
else if ( Settings::application.mapping.mode == 1 ) {
|
|
// custom layout of numerical keypad
|
|
std::vector<uint> numpad_inputs = { INPUT_NUMPAD_FIRST+7, INPUT_NUMPAD_FIRST+8, INPUT_NUMPAD_FIRST+9, INPUT_NUMPAD_FIRST+11,
|
|
INPUT_NUMPAD_FIRST+4, INPUT_NUMPAD_FIRST+5, INPUT_NUMPAD_FIRST+6, INPUT_NUMPAD_FIRST+12,
|
|
INPUT_NUMPAD_FIRST+1, INPUT_NUMPAD_FIRST+2, INPUT_NUMPAD_FIRST+3, INPUT_NUMPAD_FIRST+13,
|
|
INPUT_NUMPAD_FIRST+0, INPUT_NUMPAD_FIRST+10, INPUT_NUMPAD_FIRST+14 };
|
|
|
|
// Draw table of keypad keys
|
|
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
|
|
ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, ImVec2(0.5f, 0.50f));
|
|
ImVec4 color = ImGui::GetStyle().Colors[ImGuiCol_Header];
|
|
color.w /= Settings::application.mapping.disabled ? 2.f : 0.9f;
|
|
ImGui::PushStyleColor(ImGuiCol_Header, color);
|
|
|
|
for (size_t p = 0; p < numpad_inputs.size(); ++p){
|
|
uint ik = numpad_inputs[p];
|
|
ImVec2 iconsize = p == 12 ? keyNumpadIconSize + ImVec2(keyNumpadIconSize.x+ g.Style.ItemSpacing.x, 0.f) : keyNumpadIconSize;
|
|
ImVec2 itemsize = p == 12 ? keyNumpadItemSize + ImVec2(keyNumpadItemSize.x+ g.Style.ItemSpacing.x, 0.f) : keyNumpadItemSize;
|
|
// draw overlay on active keys
|
|
if ( Control::inputActive(ik) ) {
|
|
ImVec2 pos = frame_top + itemsize * ImVec2( p % 4, p / 4);
|
|
pos += p > 12 ? ImVec2(keyNumpadIconSize.x+ g.Style.ItemSpacing.x, 0.f) : ImVec2(0,0);
|
|
draw_list->AddRectFilled(pos, pos + iconsize, ImGui::GetColorU32(ImGuiCol_Border), 6.f);
|
|
// set current
|
|
current_input_ = ik;
|
|
}
|
|
// draw key button
|
|
ImGui::PushID(p);
|
|
if (ImGui::Selectable(Control::manager().inputLabel(ik).c_str(), input_assigned[ik], 0, iconsize)) {
|
|
current_input_ = ik;
|
|
}
|
|
ImGui::PopID();
|
|
if ((p % 4) < 3) ImGui::SameLine();
|
|
|
|
// Draw frame around current
|
|
if (ik == current_input_){
|
|
ImVec2 pos = frame_top + itemsize * ImVec2( p % 4, p / 4);
|
|
pos += p > 12 ? ImVec2(keyNumpadIconSize.x + g.Style.ItemSpacing.x, 0.f) : ImVec2(0,0);
|
|
draw_list->AddRect(pos, pos + iconsize, ImGui::GetColorU32(ImGuiCol_Text), 6.f, ImDrawCornerFlags_All, 3.f);
|
|
}
|
|
}
|
|
ImGui::PopStyleColor();
|
|
ImGui::PopStyleVar();
|
|
ImGui::PopFont();
|
|
|
|
}
|
|
|
|
// Draw Indicator for current input
|
|
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO);
|
|
ImGui::SetCursorScreenPos(frame_top + ImVec2(inputarea_width, 0) + g.Style.FramePadding);
|
|
ImGui::Text( ICON_FA_CUBE " %s", Control::manager().inputLabel(current_input_).c_str() );
|
|
ImGui::PopFont();
|
|
|
|
// Draw child Window (rounded border) to list reactions to input
|
|
ImGui::SetCursorScreenPos(frame_top + ImVec2(inputarea_width, g.FontSize + g.Style.FramePadding.y * 2.0f + g.Style.ItemSpacing.y * 2.0f));
|
|
{
|
|
ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 5.0f);
|
|
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(2.f, g.Style.ItemSpacing.y * 2.f) );
|
|
ImGui::BeginChild("InputsMappingInterfacePanel", ImVec2(0, 0), true);
|
|
|
|
if (input_sources_callbacks.count(current_input_) < 1) {
|
|
|
|
ImGui::Text("No action associated to this input. Create a new one with '+'.");
|
|
}
|
|
else {
|
|
|
|
ImGui::Columns(3, "InputMapping", false);
|
|
auto result = input_sources_callbacks.equal_range(current_input_);
|
|
for (auto kit = result.first; kit != result.second; ++kit) {
|
|
|
|
Source *source = kit->second.first;
|
|
SourceCallback *callback = kit->second.second;
|
|
|
|
// push ID because we repeat the same UI
|
|
ImGui::PushID( (void*) callback);
|
|
|
|
// Delete interface
|
|
if (ImGuiToolkit::IconButton(ICON_FA_MINUS, "Delete") ){
|
|
source->removeInputCallback(callback);
|
|
// reload
|
|
ImGui::PopID();
|
|
break;
|
|
}
|
|
|
|
// Select Source
|
|
ImGui::SameLine(0, IMGUI_SAME_LINE);
|
|
ImGui::SetNextItemWidth( -FLT_MIN);
|
|
Source *select = ComboSelectSource(source);
|
|
if (select != nullptr) {
|
|
// copy callback to other source
|
|
select->addInputCallback(current_input_, callback->clone());
|
|
// remove previous callback
|
|
source->removeInputCallback(callback);
|
|
// reload
|
|
ImGui::PopID();
|
|
break;
|
|
}
|
|
|
|
// Select Reaction
|
|
ImGui::NextColumn();
|
|
ImGui::SetNextItemWidth( -FLT_MIN);
|
|
uint type = ComboSelectCallback( callback->type() );
|
|
if (type > 0) {
|
|
// remove previous callback
|
|
source->removeInputCallback(callback);
|
|
// add callback
|
|
source->addInputCallback(current_input_, SourceCallback::create((SourceCallback::CallbackType)type) );
|
|
// reload
|
|
ImGui::PopID();
|
|
break;
|
|
}
|
|
|
|
// Adjust parameters
|
|
ImGui::NextColumn();
|
|
if (callback)
|
|
SliderParametersCallback( callback );
|
|
|
|
ImGui::NextColumn();
|
|
ImGui::PopID();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Add a new interface
|
|
static bool temp_new_input = false;
|
|
static Source *temp_new_source = nullptr;
|
|
static uint temp_new_callback = 0;
|
|
|
|
ImGui::Columns(3, "NewInputMapping", false);
|
|
// step 1 : press '+'
|
|
if (ImGuiToolkit::IconButton(ICON_FA_PLUS, "New") )
|
|
temp_new_input = true;
|
|
if (temp_new_input) {
|
|
// step 2 : Get input for source
|
|
ImGui::SameLine(0, IMGUI_SAME_LINE);
|
|
ImGui::SetNextItemWidth( -FLT_MIN);
|
|
Source *s = ComboSelectSource(temp_new_source);
|
|
// user selected a source (or changed selection)
|
|
if (s != nullptr) {
|
|
temp_new_source = s;
|
|
temp_new_callback = 0;
|
|
}
|
|
// possible new source
|
|
if (temp_new_source != nullptr) {
|
|
// step 3: Get input for callback type
|
|
ImGui::NextColumn();
|
|
ImGui::SetNextItemWidth( -FLT_MIN);
|
|
temp_new_callback = ComboSelectCallback( temp_new_callback );
|
|
// user selected a callback type
|
|
if (temp_new_callback > 0) {
|
|
// step 4 : create new callback and add it to source
|
|
temp_new_source->addInputCallback(current_input_, SourceCallback::create((SourceCallback::CallbackType)temp_new_callback) );
|
|
// done
|
|
temp_new_source = nullptr;
|
|
temp_new_callback = 0;
|
|
temp_new_input = false;
|
|
}
|
|
}
|
|
}
|
|
ImGui::Columns(1);
|
|
|
|
ImGui::EndChild();
|
|
ImGui::PopStyleVar(2);
|
|
}
|
|
|
|
// Clear all button
|
|
if (input_sources_callbacks.count(current_input_) > 0) {
|
|
ImGui::SetCursorScreenPos(frame_top + ImVec2(window->Size.x - g.FontSize - g.Style.FramePadding.x * 2.0f - g.Style.WindowPadding.x, g.Style.FramePadding.y));
|
|
if (ImGuiToolkit::IconButton(ICON_FA_BACKSPACE, "Remove all") ){
|
|
auto result = input_sources_callbacks.equal_range(current_input_);
|
|
for (auto kit = result.first; kit != result.second; ++kit) {
|
|
kit->second.first->removeInputCallback(kit->second.second);
|
|
}
|
|
}
|
|
}
|
|
|
|
ImGui::End();
|
|
}
|
|
|
|
|
|
///
|
|
/// NAVIGATOR
|
|
///
|
|
Navigator::Navigator()
|
|
{
|
|
// default geometry
|
|
width_ = 100;
|
|
pannel_width_ = 5.f * width_;
|
|
height_ = 100;
|
|
padding_width_ = 100;
|
|
|
|
// clean start
|
|
show_config_ = false;
|
|
pannel_visible_ = false;
|
|
view_pannel_visible = false;
|
|
clearButtonSelection();
|
|
|
|
// restore media mode as saved
|
|
if (Settings::application.recentImportFolders.path.compare(IMGUI_LABEL_RECENT_FILES) == 0)
|
|
new_media_mode = MEDIA_RECENT;
|
|
else if (Settings::application.recentImportFolders.path.compare(IMGUI_LABEL_RECENT_RECORDS) == 0)
|
|
new_media_mode = MEDIA_RECORDING;
|
|
else
|
|
new_media_mode = MEDIA_FOLDER;
|
|
new_media_mode_changed = true;
|
|
}
|
|
|
|
void Navigator::applyButtonSelection(int index)
|
|
{
|
|
// ensure only one button is active at a time
|
|
bool status = selected_button[index];
|
|
clearButtonSelection();
|
|
selected_button[index] = status;
|
|
|
|
// set visible if button is active
|
|
pannel_visible_ = status;
|
|
|
|
show_config_ = false;
|
|
}
|
|
|
|
void Navigator::clearButtonSelection()
|
|
{
|
|
// clear all buttons
|
|
for(int i=0; i<NAV_COUNT; ++i)
|
|
selected_button[i] = false;
|
|
|
|
// clear new source pannel
|
|
new_source_preview_.setSource();
|
|
pattern_type = -1;
|
|
custom_pipeline = false;
|
|
sourceSequenceFiles.clear();
|
|
sourceMediaFileCurrent.clear();
|
|
new_media_mode_changed = true;
|
|
}
|
|
|
|
void Navigator::showPannelSource(int index)
|
|
{
|
|
// invalid index given
|
|
if ( index < 0)
|
|
hidePannel();
|
|
else {
|
|
selected_button[index] = true;
|
|
applyButtonSelection(index);
|
|
}
|
|
}
|
|
|
|
void Navigator::showConfig()
|
|
{
|
|
selected_button[NAV_MENU] = true;
|
|
applyButtonSelection(NAV_MENU);
|
|
show_config_ = true;
|
|
}
|
|
|
|
void Navigator::togglePannelMenu()
|
|
{
|
|
selected_button[NAV_MENU] = !selected_button[NAV_MENU];
|
|
applyButtonSelection(NAV_MENU);
|
|
}
|
|
|
|
void Navigator::togglePannelNew()
|
|
{
|
|
selected_button[NAV_NEW] = !selected_button[NAV_NEW];
|
|
applyButtonSelection(NAV_NEW);
|
|
new_media_mode_changed = true;
|
|
}
|
|
|
|
void Navigator::hidePannel()
|
|
{
|
|
clearButtonSelection();
|
|
pannel_visible_ = false;
|
|
view_pannel_visible = false;
|
|
show_config_ = false;
|
|
}
|
|
|
|
void Navigator::Render()
|
|
{
|
|
std::pair<std::string, std::string> tooltip = {"", ""};
|
|
static uint _timeout_tooltip = 0;
|
|
|
|
const ImGuiStyle& style = ImGui::GetStyle();
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0);
|
|
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(COLOR_NAVIGATOR, 1.f));
|
|
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(COLOR_NAVIGATOR, 1.f));
|
|
|
|
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
|
|
ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, ImVec2(0.50f, 0.50f));
|
|
|
|
// calculate size of items based on text size and display dimensions
|
|
width_ = 2.f * ImGui::GetTextLineHeightWithSpacing(); // dimension of left bar depends on FONT_LARGE
|
|
pannel_width_ = 5.f * width_; // pannel is 5x the bar
|
|
padding_width_ = 2.f * style.WindowPadding.x; // panning for alighment
|
|
height_ = ImGui::GetIO().DisplaySize.y; // cover vertically
|
|
const float icon_width = width_ - 2.f * style.WindowPadding.x; // icons keep padding
|
|
const ImVec2 iconsize(icon_width, icon_width);
|
|
const float sourcelist_height = height_ - 4.5f * icon_width - 5.f * style.WindowPadding.y; // space for 4 icons of view
|
|
|
|
// hack to show more sources if not enough space; make source icons smaller...
|
|
ImVec2 sourceiconsize(icon_width, icon_width);
|
|
if (sourcelist_height - 2.f * icon_width < Mixer::manager().session()->numSource() * icon_width )
|
|
sourceiconsize.y *= 0.75f;
|
|
|
|
// Left bar top
|
|
ImGui::SetNextWindowPos( ImVec2(0, 0), ImGuiCond_Always );
|
|
ImGui::SetNextWindowSize( ImVec2(width_, sourcelist_height), ImGuiCond_Always );
|
|
ImGui::SetNextWindowBgAlpha(0.95f); // Transparent background
|
|
if (ImGui::Begin( ICON_FA_BARS " Navigator", NULL, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing))
|
|
{
|
|
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
|
|
|
if (Settings::application.current_view < View::TRANSITION) {
|
|
|
|
// the "=" icon for menu
|
|
if (ImGui::Selectable( ICON_FA_BARS, &selected_button[NAV_MENU], 0, iconsize))
|
|
applyButtonSelection(NAV_MENU);
|
|
if (ImGui::IsItemHovered())
|
|
tooltip = {TOOLTIP_MAIN, SHORTCUT_MAIN};
|
|
|
|
// the list of INITIALS for sources
|
|
int index = 0;
|
|
SourceList::iterator iter;
|
|
for (iter = Mixer::manager().session()->begin(); iter != Mixer::manager().session()->end(); ++iter, ++index)
|
|
{
|
|
Source *s = (*iter);
|
|
// draw an indicator for current source
|
|
if ( s->mode() >= Source::SELECTED ){
|
|
ImVec2 p1 = ImGui::GetCursorScreenPos() + ImVec2(icon_width, 0.5f * sourceiconsize.y);
|
|
ImVec2 p2 = ImVec2(p1.x + 2.f, p1.y + 2.f);
|
|
const ImU32 color = ImGui::GetColorU32(ImGuiCol_Text);
|
|
if ((*iter)->mode() == Source::CURRENT) {
|
|
p1 = ImGui::GetCursorScreenPos() + ImVec2(icon_width, 0);
|
|
p2 = ImVec2(p1.x + 2.f, p1.y + sourceiconsize.y);
|
|
}
|
|
draw_list->AddRect(p1, p2, color, 0.0f, 0, 3.f);
|
|
}
|
|
// draw select box
|
|
ImGui::PushID(std::to_string(s->group(View::RENDERING)->id()).c_str());
|
|
if (ImGui::Selectable(s->initials(), &selected_button[index], 0, sourceiconsize))
|
|
{
|
|
applyButtonSelection(index);
|
|
if (selected_button[index])
|
|
Mixer::manager().setCurrentIndex(index);
|
|
}
|
|
|
|
if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None))
|
|
{
|
|
ImGui::SetDragDropPayload("DND_SOURCE", &index, sizeof(int));
|
|
ImGui::Text( ICON_FA_SORT " %s ", s->initials());
|
|
ImGui::EndDragDropSource();
|
|
}
|
|
if (ImGui::BeginDragDropTarget())
|
|
{
|
|
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("DND_SOURCE"))
|
|
{
|
|
if ( payload->DataSize == sizeof(int) ) {
|
|
bool status_current_index = selected_button[Mixer::manager().indexCurrentSource()];
|
|
// drop means move index and reorder
|
|
int payload_index = *(const int*)payload->Data;
|
|
Mixer::manager().moveIndex(payload_index, index);
|
|
// index of current source changed
|
|
selected_button[Mixer::manager().indexCurrentSource()] = status_current_index;
|
|
applyButtonSelection(Mixer::manager().indexCurrentSource());
|
|
}
|
|
}
|
|
ImGui::EndDragDropTarget();
|
|
}
|
|
|
|
ImGui::PopID();
|
|
}
|
|
|
|
// the "+" icon for action of creating new source
|
|
if (ImGui::Selectable( ICON_FA_PLUS, &selected_button[NAV_NEW], 0, iconsize))
|
|
applyButtonSelection(NAV_NEW);
|
|
if (ImGui::IsItemHovered())
|
|
tooltip = {TOOLTIP_NEW_SOURCE, SHORTCUT_NEW_SOURCE};
|
|
}
|
|
else {
|
|
// the ">" icon for transition menu
|
|
if (ImGui::Selectable( ICON_FA_ARROW_CIRCLE_RIGHT, &selected_button[NAV_TRANS], 0, iconsize))
|
|
{
|
|
Mixer::manager().unsetCurrentSource();
|
|
applyButtonSelection(NAV_TRANS);
|
|
}
|
|
}
|
|
ImGui::End();
|
|
}
|
|
|
|
// Left bar bottom
|
|
ImGui::SetNextWindowPos( ImVec2(0, sourcelist_height), ImGuiCond_Always );
|
|
ImGui::SetNextWindowSize( ImVec2(width_, height_ - sourcelist_height), ImGuiCond_Always );
|
|
ImGui::SetNextWindowBgAlpha(0.95f); // Transparent background
|
|
if (ImGui::Begin("##navigatorViews", NULL, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration |
|
|
ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoScrollWithMouse))
|
|
{
|
|
bool selected_view[View::INVALID] = { };
|
|
selected_view[ Settings::application.current_view ] = true;
|
|
int previous_view = Settings::application.current_view;
|
|
if (ImGui::Selectable( ICON_FA_BULLSEYE, &selected_view[1], 0, iconsize))
|
|
{
|
|
Mixer::manager().setView(View::MIXING);
|
|
view_pannel_visible = previous_view == Settings::application.current_view;
|
|
}
|
|
if (ImGui::IsItemHovered())
|
|
tooltip = {"Mixing ", "F1"};
|
|
if (ImGui::Selectable( ICON_FA_OBJECT_UNGROUP , &selected_view[2], 0, iconsize))
|
|
{
|
|
Mixer::manager().setView(View::GEOMETRY);
|
|
view_pannel_visible = previous_view == Settings::application.current_view;
|
|
}
|
|
if (ImGui::IsItemHovered())
|
|
tooltip = {"Geometry ", "F2"};
|
|
if (ImGui::Selectable( ICON_FA_LAYER_GROUP, &selected_view[3], 0, iconsize))
|
|
{
|
|
Mixer::manager().setView(View::LAYER);
|
|
view_pannel_visible = previous_view == Settings::application.current_view;
|
|
}
|
|
if (ImGui::IsItemHovered())
|
|
tooltip = {"Layers ", "F3"};
|
|
if (ImGui::Selectable( ICON_FA_CHESS_BOARD, &selected_view[4], 0, iconsize))
|
|
{
|
|
Mixer::manager().setView(View::TEXTURE);
|
|
view_pannel_visible = previous_view == Settings::application.current_view;
|
|
}
|
|
if (ImGui::IsItemHovered())
|
|
tooltip = {"Texturing ", "F4"};
|
|
|
|
ImVec2 pos = ImGui::GetCursorPos();
|
|
ImGui::SetCursorPos(pos + ImVec2(0.f, style.WindowPadding.y));
|
|
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO);
|
|
if ( ImGuiToolkit::IconButton( Rendering::manager().mainWindow().isFullscreen() ? ICON_FA_COMPRESS_ALT : ICON_FA_EXPAND_ALT ) )
|
|
Rendering::manager().mainWindow().toggleFullscreen();
|
|
if (ImGui::IsItemHovered())
|
|
tooltip = {TOOLTIP_FULLSCREEN, SHORTCUT_FULLSCREEN};
|
|
|
|
ImGui::SetCursorPos(pos + ImVec2(width_ * 0.5f, style.WindowPadding.y));
|
|
if ( ImGuiToolkit::IconButton( WorkspaceWindow::clear() ? ICON_FA_WINDOW_MAXIMIZE : ICON_FA_WINDOW_MINIMIZE ) )
|
|
WorkspaceWindow::toggleClearRestoreWorkspace();
|
|
if (ImGui::IsItemHovered())
|
|
tooltip = { WorkspaceWindow::clear() ? TOOLTIP_SHOW : TOOLTIP_HIDE, SHORTCUT_HIDE};
|
|
|
|
ImGui::PopFont();
|
|
|
|
ImGui::End();
|
|
}
|
|
|
|
// show tooltip
|
|
if (!tooltip.first.empty()) {
|
|
// pseudo timeout for showing tooltip
|
|
if (_timeout_tooltip > IMGUI_TOOLTIP_TIMEOUT)
|
|
ImGuiToolkit::ToolTip(tooltip.first.c_str(), tooltip.second.c_str());
|
|
else
|
|
++_timeout_tooltip;
|
|
}
|
|
else
|
|
_timeout_tooltip = 0;
|
|
|
|
if ( view_pannel_visible && !pannel_visible_ )
|
|
RenderViewPannel( ImVec2(width_, sourcelist_height), ImVec2(width_*0.8f, height_ - sourcelist_height) );
|
|
|
|
ImGui::PopStyleVar();
|
|
ImGui::PopFont();
|
|
|
|
if ( pannel_visible_){
|
|
// pannel menu
|
|
if (selected_button[NAV_MENU])
|
|
{
|
|
RenderMainPannel();
|
|
}
|
|
// pannel to manage transition
|
|
else if (selected_button[NAV_TRANS])
|
|
{
|
|
RenderTransitionPannel();
|
|
}
|
|
// pannel to create a source
|
|
else if (selected_button[NAV_NEW])
|
|
{
|
|
RenderNewPannel();
|
|
}
|
|
// pannel to configure a selected source
|
|
else
|
|
{
|
|
RenderSourcePannel(Mixer::manager().currentSource());
|
|
}
|
|
view_pannel_visible = false;
|
|
}
|
|
ImGui::PopStyleColor(2);
|
|
ImGui::PopStyleVar();
|
|
|
|
}
|
|
|
|
void Navigator::RenderViewPannel(ImVec2 draw_pos , ImVec2 draw_size)
|
|
{
|
|
ImGui::SetNextWindowPos( draw_pos, ImGuiCond_Always );
|
|
ImGui::SetNextWindowSize( draw_size, ImGuiCond_Always );
|
|
ImGui::SetNextWindowBgAlpha(0.95f); // Transparent background
|
|
if (ImGui::Begin("##ViewPannel", NULL, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav))
|
|
{
|
|
ImGui::SetCursorPosX(10.f);
|
|
ImGui::SetCursorPosY(10.f);
|
|
if (ImGuiToolkit::IconButton(5,7)) {
|
|
// reset zoom
|
|
Mixer::manager().view((View::Mode)Settings::application.current_view)->recenter();
|
|
}
|
|
|
|
draw_size.x *= 0.5;
|
|
ImGui::SetCursorPosX( 10.f);
|
|
draw_size.y -= ImGui::GetCursorPosY() + 10.f;
|
|
int percent_zoom = Mixer::manager().view((View::Mode)Settings::application.current_view)->size();
|
|
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.1, 0.1, 0.1, 0.95));
|
|
ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(0.14, 0.14, 0.14, 0.95));
|
|
ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(0.14, 0.14, 0.14, 0.95));
|
|
ImGui::PushStyleColor(ImGuiCol_SliderGrab, ImVec4(0.9, 0.9, 0.9, 0.95));
|
|
if (ImGui::VSliderInt("##z", draw_size, &percent_zoom, 0, 100, "") )
|
|
{
|
|
Mixer::manager().view((View::Mode)Settings::application.current_view)->resize(percent_zoom);
|
|
}
|
|
ImGui::PopStyleColor(4);
|
|
if (ImGui::IsItemActive() || ImGui::IsItemHovered())
|
|
ImGui::SetTooltip("Zoom %d %%", percent_zoom);
|
|
ImGui::End();
|
|
}
|
|
|
|
}
|
|
|
|
// Source pannel : *s was checked before
|
|
void Navigator::RenderSourcePannel(Source *s)
|
|
{
|
|
if (s == nullptr || Settings::application.current_view >= View::TRANSITION)
|
|
return;
|
|
|
|
// Next window is a side pannel
|
|
ImGui::SetNextWindowPos( ImVec2(width_, 0), ImGuiCond_Always );
|
|
ImGui::SetNextWindowSize( ImVec2(pannel_width_, height_), ImGuiCond_Always );
|
|
ImGui::SetNextWindowBgAlpha(0.85f); // Transparent background
|
|
if (ImGui::Begin("##navigatorSource", NULL, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav))
|
|
{
|
|
// TITLE
|
|
ImGui::SetCursorPosY(IMGUI_TOP_ALIGN);
|
|
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
|
|
ImGui::Text("Source");
|
|
ImGui::PopFont();
|
|
|
|
// index indicator
|
|
ImGui::SetCursorPos(ImVec2(pannel_width_ - 35.f, 15.f));
|
|
ImGui::TextDisabled("#%d", Mixer::manager().indexCurrentSource());
|
|
|
|
// name
|
|
std::string sname = s->name();
|
|
ImGui::SetCursorPosY(width_);
|
|
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
|
if (ImGuiToolkit::InputText("Name", &sname) ){
|
|
Mixer::manager().renameSource(s, sname);
|
|
}
|
|
// Source pannel
|
|
static ImGuiVisitor v;
|
|
s->accept(v);
|
|
// clone & delete buttons
|
|
ImGui::Text(" ");
|
|
// Action on source
|
|
if ( ImGui::Button( ICON_FA_SHARE_SQUARE " Clone", ImVec2(ImGui::GetContentRegionAvail().x, 0)) )
|
|
Mixer::manager().addSource ( Mixer::manager().createSourceClone() );
|
|
if ( ImGui::Button( ICON_FA_BACKSPACE " Delete", ImVec2(ImGui::GetContentRegionAvail().x, 0)) ) {
|
|
Mixer::manager().deleteSource(s);
|
|
Action::manager().store(sname + std::string(": deleted"));
|
|
}
|
|
ImGui::End();
|
|
}
|
|
}
|
|
|
|
|
|
void Navigator::setNewMedia(MediaCreateMode mode, std::string path)
|
|
{
|
|
Settings::application.source.new_type = Navigator::SOURCE_FILE;
|
|
|
|
// change mode
|
|
new_media_mode = mode;
|
|
new_media_mode_changed = true;
|
|
|
|
// mode dependent actions
|
|
switch (new_media_mode) {
|
|
case MEDIA_RECENT:
|
|
// set filename
|
|
sourceMediaFileCurrent = path;
|
|
// set combo to 'recent files'
|
|
Settings::application.recentImportFolders.path = IMGUI_LABEL_RECENT_FILES;
|
|
break;
|
|
case MEDIA_RECORDING:
|
|
// set filename
|
|
sourceMediaFileCurrent = path;
|
|
// set combo to 'recent recordings'
|
|
Settings::application.recentImportFolders.path = IMGUI_LABEL_RECENT_RECORDS;
|
|
break;
|
|
default:
|
|
case MEDIA_FOLDER:
|
|
// reset filename
|
|
sourceMediaFileCurrent.clear();
|
|
// set combo: a path was selected
|
|
if (!path.empty())
|
|
Settings::application.recentImportFolders.path.assign(path);
|
|
break;
|
|
}
|
|
|
|
// clear preview
|
|
new_source_preview_.setSource();
|
|
}
|
|
|
|
void Navigator::RenderNewPannel()
|
|
{
|
|
// Next window is a side pannel
|
|
ImGui::SetNextWindowPos( ImVec2(width_, 0), ImGuiCond_Always );
|
|
ImGui::SetNextWindowSize( ImVec2(pannel_width_, height_), ImGuiCond_Always );
|
|
ImGui::SetNextWindowBgAlpha(0.85f); // Transparent background
|
|
if (ImGui::Begin("##navigatorNewSource", NULL, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav))
|
|
{
|
|
// TITLE
|
|
ImGui::SetCursorPosY(10);
|
|
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
|
|
ImGui::Text("Insert");
|
|
ImGui::PopFont();
|
|
|
|
// Edit menu
|
|
ImGui::SetCursorPosY(width_);
|
|
ImGui::Text("Source");
|
|
|
|
//
|
|
// News Source selection pannel
|
|
//
|
|
static const char* origin_names[SOURCE_TYPES] = { ICON_FA_PHOTO_VIDEO " File",
|
|
ICON_FA_SORT_NUMERIC_DOWN " Sequence",
|
|
ICON_FA_PLUG " Connected",
|
|
ICON_FA_COG " Generated",
|
|
ICON_FA_SYNC " Internal"
|
|
};
|
|
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
|
if (ImGui::Combo("##Origin", &Settings::application.source.new_type, origin_names, IM_ARRAYSIZE(origin_names)) )
|
|
new_source_preview_.setSource();
|
|
|
|
ImGui::SetCursorPosY(2.f * width_);
|
|
|
|
// File Source creation
|
|
if (Settings::application.source.new_type == SOURCE_FILE) {
|
|
|
|
static DialogToolkit::OpenMediaDialog fileimportdialog("Open Media");
|
|
static DialogToolkit::OpenFolderDialog folderimportdialog("Select Folder");
|
|
|
|
// clic button to load file
|
|
if ( ImGui::Button( ICON_FA_FILE_EXPORT " Open File", ImVec2(ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN, 0)) )
|
|
fileimportdialog.open();
|
|
// Indication
|
|
ImGui::SameLine();
|
|
ImGuiToolkit::HelpToolTip("Create a source from a file:\n"
|
|
ICON_FA_CARET_RIGHT " Video (*.mpg, *mov, *.avi, etc.)\n"
|
|
ICON_FA_CARET_RIGHT " Image (*.jpg, *.png, etc.)\n"
|
|
ICON_FA_CARET_RIGHT " Vector graphics (*.svg)\n"
|
|
ICON_FA_CARET_RIGHT " vimix session (*.mix)\n"
|
|
"\nNB: Equivalent to dropping the file in the workspace");
|
|
|
|
// get media file if dialog finished
|
|
if (fileimportdialog.closed()){
|
|
// get the filename from this file dialog
|
|
std::string importpath = fileimportdialog.path();
|
|
// switch to recent files
|
|
setNewMedia(MEDIA_RECENT, importpath);
|
|
// open file
|
|
if (!importpath.empty()) {
|
|
std::string label = BaseToolkit::transliterate( sourceMediaFileCurrent );
|
|
new_source_preview_.setSource( Mixer::manager().createSourceFile(sourceMediaFileCurrent), label);
|
|
}
|
|
}
|
|
|
|
// combo to offer lists
|
|
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
|
if (ImGui::BeginCombo("##SelectionNewMedia", BaseToolkit::truncated(Settings::application.recentImportFolders.path, 25).c_str() ))
|
|
{
|
|
// Mode MEDIA_RECENT : recent files
|
|
if (ImGui::Selectable( ICON_FA_LIST_OL IMGUI_LABEL_RECENT_FILES) ) {
|
|
setNewMedia(MEDIA_RECENT);
|
|
}
|
|
// Mode MEDIA_RECORDING : recent recordings
|
|
if (ImGui::Selectable( ICON_FA_LIST IMGUI_LABEL_RECENT_RECORDS) ) {
|
|
setNewMedia(MEDIA_RECORDING);
|
|
}
|
|
// Mode MEDIA_FOLDER : known folders
|
|
for(auto foldername = Settings::application.recentImportFolders.filenames.begin();
|
|
foldername != Settings::application.recentImportFolders.filenames.end(); foldername++) {
|
|
std::string f = std::string(ICON_FA_FOLDER) + " " + BaseToolkit::truncated( *foldername, 40);
|
|
if (ImGui::Selectable( f.c_str() )) {
|
|
setNewMedia(MEDIA_FOLDER, *foldername);
|
|
}
|
|
}
|
|
// Add a folder for MEDIA_FOLDER
|
|
if (ImGui::Selectable( ICON_FA_FOLDER_PLUS " Add Folder") ) {
|
|
folderimportdialog.open();
|
|
}
|
|
ImGui::EndCombo();
|
|
}
|
|
|
|
// return from thread for folder openning
|
|
if (folderimportdialog.closed() && !folderimportdialog.path().empty()) {
|
|
Settings::application.recentImportFolders.push(folderimportdialog.path());
|
|
setNewMedia(MEDIA_FOLDER, folderimportdialog.path());
|
|
}
|
|
|
|
// icons to clear lists or discarc folder
|
|
ImVec2 pos_top = ImGui::GetCursorPos();
|
|
ImGui::SameLine();
|
|
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.7);
|
|
if ( new_media_mode == MEDIA_FOLDER ) {
|
|
if (ImGuiToolkit::IconButton( ICON_FA_FOLDER_MINUS, "Discard folder")) {
|
|
Settings::application.recentImportFolders.filenames.remove(Settings::application.recentImportFolders.path);
|
|
if (Settings::application.recentImportFolders.filenames.empty())
|
|
// revert mode RECENT
|
|
setNewMedia(MEDIA_RECENT);
|
|
else
|
|
setNewMedia(MEDIA_FOLDER, Settings::application.recentImportFolders.filenames.front());
|
|
}
|
|
}
|
|
else if ( new_media_mode == MEDIA_RECORDING ) {
|
|
if (ImGuiToolkit::IconButton( ICON_FA_BACKSPACE, "Clear list")) {
|
|
Settings::application.recentRecordings.filenames.clear();
|
|
Settings::application.recentRecordings.front_is_valid = false;
|
|
setNewMedia(MEDIA_RECORDING);
|
|
}
|
|
}
|
|
else if ( new_media_mode == MEDIA_RECENT ) {
|
|
if (ImGuiToolkit::IconButton( ICON_FA_BACKSPACE, "Clear list")) {
|
|
Settings::application.recentImport.filenames.clear();
|
|
Settings::application.recentImport.front_is_valid = false;
|
|
setNewMedia(MEDIA_RECENT);
|
|
}
|
|
}
|
|
ImGui::PopStyleVar();
|
|
ImGui::SetCursorPos(pos_top);
|
|
|
|
// change session list if changed
|
|
if (new_media_mode_changed || Settings::application.recentImport.changed || Settings::application.recentRecordings.changed) {
|
|
|
|
// MODE RECENT
|
|
if ( new_media_mode == MEDIA_RECENT) {
|
|
// show list of recent imports
|
|
Settings::application.recentImport.validate();
|
|
sourceMediaFiles = Settings::application.recentImport.filenames;
|
|
// done changed
|
|
Settings::application.recentImport.changed = false;
|
|
}
|
|
// MODE RECORDINGS
|
|
else if ( new_media_mode == MEDIA_RECORDING) {
|
|
// show list of recent records
|
|
Settings::application.recentRecordings.validate();
|
|
sourceMediaFiles = Settings::application.recentRecordings.filenames;
|
|
// in auto
|
|
if (Settings::application.recentRecordings.load_at_start
|
|
&& Settings::application.recentRecordings.changed
|
|
&& Settings::application.recentRecordings.filenames.size() > 0){
|
|
sourceMediaFileCurrent = sourceMediaFiles.front();
|
|
std::string label = BaseToolkit::transliterate( sourceMediaFileCurrent );
|
|
new_source_preview_.setSource( Mixer::manager().createSourceFile(sourceMediaFileCurrent), label);
|
|
}
|
|
// done changed
|
|
Settings::application.recentRecordings.changed = false;
|
|
}
|
|
// MODE LIST FOLDER
|
|
else if ( new_media_mode == MEDIA_FOLDER) {
|
|
// show list of media files in folder
|
|
sourceMediaFiles = SystemToolkit::list_directory( Settings::application.recentImportFolders.path, { MEDIA_FILES_PATTERN });
|
|
}
|
|
// indicate the list changed (do not change at every frame)
|
|
new_media_mode_changed = false;
|
|
}
|
|
|
|
// different labels for each mode
|
|
static const char *listboxname[3] = { "##NewSourceMediaRecent", "##NewSourceMediaRecording", "##NewSourceMediafolder"};
|
|
// display the import-list and detect if one was selected
|
|
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
|
if (ImGui::ListBoxHeader(listboxname[new_media_mode], sourceMediaFiles.size(), CLAMP(sourceMediaFiles.size(), 4, 6)) ) {
|
|
static int tooltip = 0;
|
|
static std::string filenametooltip;
|
|
// loop over list of files
|
|
for(auto it = sourceMediaFiles.begin(); it != sourceMediaFiles.end(); ++it) {
|
|
// build displayed file name
|
|
std::string filename = BaseToolkit::transliterate(*it);
|
|
std::string label = BaseToolkit::truncated(SystemToolkit::filename(filename), 25);
|
|
// add selectable item to ListBox; open if clickec
|
|
if (ImGui::Selectable( label.c_str(), sourceMediaFileCurrent.compare(*it) == 0 )) {
|
|
// set new source preview
|
|
new_source_preview_.setSource( Mixer::manager().createSourceFile(*it), filename);
|
|
// remember current list item
|
|
sourceMediaFileCurrent = *it;
|
|
}
|
|
// smart tooltip : displays only after timout when item changed
|
|
if (ImGui::IsItemHovered()){
|
|
if (filenametooltip.compare(filename)==0){
|
|
++tooltip;
|
|
if (tooltip>30) {
|
|
ImGui::BeginTooltip();
|
|
ImGui::Text(filenametooltip.c_str());
|
|
ImGui::EndTooltip();
|
|
}
|
|
}
|
|
else {
|
|
filenametooltip.assign(filename);
|
|
tooltip = 0;
|
|
}
|
|
}
|
|
}
|
|
ImGui::ListBoxFooter();
|
|
}
|
|
|
|
if (new_media_mode == MEDIA_RECORDING) {
|
|
// Bottom Right side of the list: helper and options
|
|
ImVec2 pos_bot = ImGui::GetCursorPos();
|
|
ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_bot.y - 2.f * ImGui::GetFrameHeightWithSpacing()));
|
|
ImGuiToolkit::HelpToolTip("Recently recorded videos (lastest on top). Clic on a filename to open.\n\n"
|
|
ICON_FA_CHEVRON_CIRCLE_RIGHT " Auto-preload prepares this panel with the "
|
|
"most recent recording after 'Stop Record' or 'Save & continue'.");
|
|
ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_bot.y - ImGui::GetFrameHeightWithSpacing()) );
|
|
if (ImGuiToolkit::ButtonToggle( ICON_FA_CHEVRON_CIRCLE_RIGHT, &Settings::application.recentRecordings.load_at_start, "Auto-preload" ) ){
|
|
// demonstrate action
|
|
if (Settings::application.recentRecordings.load_at_start
|
|
&& Settings::application.recentRecordings.filenames.size() > 0) {
|
|
sourceMediaFileCurrent = sourceMediaFiles.front();
|
|
std::string label = BaseToolkit::transliterate( sourceMediaFileCurrent );
|
|
new_source_preview_.setSource( Mixer::manager().createSourceFile(sourceMediaFileCurrent), label);
|
|
}
|
|
}
|
|
// come back...
|
|
ImGui::SetCursorPos(pos_bot);
|
|
}
|
|
|
|
}
|
|
// Folder Source creator
|
|
else if (Settings::application.source.new_type == SOURCE_SEQUENCE){
|
|
|
|
bool update_new_source = false;
|
|
static DialogToolkit::MultipleImagesDialog _selectImagesDialog("Select Images");
|
|
|
|
// clic button to load file
|
|
if ( ImGui::Button( ICON_FA_IMAGES " Open images", ImVec2(ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN, 0)) ) {
|
|
sourceSequenceFiles.clear();
|
|
_selectImagesDialog.open();
|
|
}
|
|
|
|
// Indication
|
|
ImGui::SameLine();
|
|
ImGuiToolkit::HelpToolTip("Create a source from a sequence of numbered images.");
|
|
|
|
// return from thread for folder openning
|
|
if (_selectImagesDialog.closed()) {
|
|
sourceSequenceFiles = _selectImagesDialog.images();
|
|
if (sourceSequenceFiles.empty())
|
|
Log::Notify("No file selected.");
|
|
// ask to reload the preview
|
|
update_new_source = true;
|
|
}
|
|
|
|
// multiple files selected
|
|
if (sourceSequenceFiles.size() > 1) {
|
|
|
|
// set framerate
|
|
static int _fps = 30;
|
|
static bool _fps_changed = false;
|
|
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
|
if ( ImGui::SliderInt("Framerate", &_fps, 1, 30, "%d fps") ) {
|
|
_fps_changed = true;
|
|
}
|
|
// only call for new source after mouse release to avoid repeating call to re-open the stream
|
|
else if (_fps_changed && ImGui::IsMouseReleased(ImGuiMouseButton_Left)){
|
|
update_new_source = true;
|
|
_fps_changed = false;
|
|
}
|
|
|
|
if (update_new_source) {
|
|
std::string label = BaseToolkit::transliterate( BaseToolkit::common_pattern(sourceSequenceFiles) );
|
|
new_source_preview_.setSource( Mixer::manager().createSourceMultifile(sourceSequenceFiles, _fps), label);
|
|
}
|
|
}
|
|
// single file selected
|
|
else if (sourceSequenceFiles.size() > 0) {
|
|
|
|
ImGui::Text("Single file selected");
|
|
|
|
if (update_new_source) {
|
|
std::string label = BaseToolkit::transliterate( sourceSequenceFiles.front() );
|
|
new_source_preview_.setSource( Mixer::manager().createSourceFile(sourceSequenceFiles.front()), label);
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
// Internal Source creator
|
|
else if (Settings::application.source.new_type == SOURCE_INTERNAL){
|
|
|
|
// fill new_source_preview with a new source
|
|
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
|
if (ImGui::BeginCombo("##Source", "Select object"))
|
|
{
|
|
std::string label = "Rendering output";
|
|
if (ImGui::Selectable( label.c_str() )) {
|
|
new_source_preview_.setSource( Mixer::manager().createSourceRender(), label);
|
|
}
|
|
SourceList::iterator iter;
|
|
for (iter = Mixer::manager().session()->begin(); iter != Mixer::manager().session()->end(); ++iter)
|
|
{
|
|
label = std::string("Source ") + (*iter)->name();
|
|
if (ImGui::Selectable( label.c_str() )) {
|
|
label = std::string("Clone of ") + label;
|
|
new_source_preview_.setSource( Mixer::manager().createSourceClone((*iter)->name()),label);
|
|
}
|
|
}
|
|
ImGui::EndCombo();
|
|
}
|
|
|
|
// Indication
|
|
ImGui::SameLine();
|
|
ImGuiToolkit::HelpToolTip("Create a source replicating internal vimix objects.\n"
|
|
ICON_FA_CARET_RIGHT " Loopback from output\n"
|
|
ICON_FA_CARET_RIGHT " Clone other sources");
|
|
}
|
|
// Generated Source creator
|
|
else if (Settings::application.source.new_type == SOURCE_GENERATED){
|
|
|
|
bool update_new_source = false;
|
|
|
|
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
|
if (ImGui::BeginCombo("##Pattern", "Select generator", ImGuiComboFlags_HeightLarge))
|
|
{
|
|
if ( ImGui::Selectable("Custom") ) {
|
|
update_new_source = true;
|
|
custom_pipeline = true;
|
|
pattern_type = -1;
|
|
}
|
|
for (int p = 0; p < (int) Pattern::count(); ++p){
|
|
if (Pattern::get(p).available && ImGui::Selectable( Pattern::get(p).label.c_str(), p == pattern_type )) {
|
|
update_new_source = true;
|
|
custom_pipeline = false;
|
|
pattern_type = p;
|
|
}
|
|
}
|
|
ImGui::EndCombo();
|
|
}
|
|
|
|
// Indication
|
|
ImGui::SameLine();
|
|
ImGuiToolkit::HelpToolTip("Create a source with graphics generated algorithmically.");
|
|
|
|
if (custom_pipeline) {
|
|
static std::vector< std::pair< std::string, std::string> > _examples = { {"Videotest", "videotestsrc horizontal-speed=1 " },
|
|
{"Checker", "videotestsrc pattern=checkers-8 ! video/x-raw, width=64, height=64 "},
|
|
{"Color", "videotestsrc pattern=gradient foreground-color= 0xff55f54f background-color= 0x000000 "},
|
|
{"Text", "videotestsrc pattern=black ! textoverlay text=\"vimix\" halignment=center valignment=center font-desc=\"Sans,72\" "},
|
|
{"GStreamer Webcam", "udpsrc port=5000 buffer-size=200000 ! h264parse ! avdec_h264 "},
|
|
{"SRT listener", "srtsrc uri=\"srt://:5000?mode=listener\" ! decodebin "}
|
|
};
|
|
static std::string _description = _examples[0].second;
|
|
static ImVec2 fieldsize(ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN, 100);
|
|
static int numlines = 0;
|
|
const ImGuiContext& g = *GImGui;
|
|
fieldsize.y = MAX(3, numlines) * g.FontSize + g.Style.ItemSpacing.y + g.Style.FramePadding.y;
|
|
|
|
// Editor
|
|
if ( ImGuiToolkit::InputCodeMultiline("Pipeline", &_description, fieldsize, &numlines) )
|
|
update_new_source = true;
|
|
|
|
// 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("##Examples", "Examples", ImGuiComboFlags_NoPreview)) {
|
|
ImGui::TextDisabled("Gstreamer examples");
|
|
ImGui::Separator();
|
|
for (auto it = _examples.begin(); it != _examples.end(); ++it) {
|
|
if (ImGui::Selectable( it->first.c_str() ) ) {
|
|
_description = it->second;
|
|
update_new_source = true;
|
|
}
|
|
}
|
|
ImGui::EndCombo();
|
|
}
|
|
ImGui::SetCursorPos(pos_bot);
|
|
// take action
|
|
if (update_new_source)
|
|
new_source_preview_.setSource( Mixer::manager().createSourceStream(_description), "Custom");
|
|
|
|
}
|
|
// if pattern selected
|
|
else {
|
|
// resolution
|
|
if (pattern_type >= 0) {
|
|
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
|
if (ImGui::Combo("Ratio", &Settings::application.source.ratio,
|
|
GlmToolkit::aspect_ratio_names, IM_ARRAYSIZE(GlmToolkit::aspect_ratio_names) ) )
|
|
update_new_source = true;
|
|
|
|
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
|
if (ImGui::Combo("Height", &Settings::application.source.res,
|
|
GlmToolkit::height_names, IM_ARRAYSIZE(GlmToolkit::height_names) ) )
|
|
update_new_source = true;
|
|
}
|
|
// create preview
|
|
if (update_new_source) {
|
|
glm::ivec2 res = GlmToolkit::resolutionFromDescription(Settings::application.source.ratio, Settings::application.source.res);
|
|
new_source_preview_.setSource( Mixer::manager().createSourcePattern(pattern_type, res),
|
|
Pattern::get(pattern_type).label);
|
|
}
|
|
}
|
|
}
|
|
// External source creator
|
|
else if (Settings::application.source.new_type == SOURCE_CONNECTED){
|
|
|
|
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
|
if (ImGui::BeginCombo("##External", "Select device"))
|
|
{
|
|
for (int d = 0; d < Device::manager().numDevices(); ++d){
|
|
std::string namedev = Device::manager().name(d);
|
|
if (ImGui::Selectable( namedev.c_str() )) {
|
|
custom_connected = false;
|
|
new_source_preview_.setSource( Mixer::manager().createSourceDevice(namedev), namedev);
|
|
}
|
|
}
|
|
for (int d = 1; d < Connection::manager().numHosts(); ++d){
|
|
std::string namehost = Connection::manager().info(d).name;
|
|
if (ImGui::Selectable( namehost.c_str() )) {
|
|
custom_connected = false;
|
|
new_source_preview_.setSource( Mixer::manager().createSourceNetwork(namehost), namehost);
|
|
}
|
|
}
|
|
|
|
if ( ImGui::Selectable("SRT Broadcaster") ) {
|
|
new_source_preview_.setSource();
|
|
custom_connected = true;
|
|
}
|
|
|
|
ImGui::EndCombo();
|
|
}
|
|
|
|
// Indication
|
|
ImGui::SameLine();
|
|
ImGuiToolkit::HelpToolTip("Create a source getting images from connected devices or machines;\n"
|
|
ICON_FA_CARET_RIGHT " webcams or frame grabbers\n"
|
|
ICON_FA_CARET_RIGHT " screen capture\n"
|
|
ICON_FA_CARET_RIGHT " stream shared by vimix on local network\n"
|
|
ICON_FA_CARET_RIGHT " SRT stream (e.g. broadcasted by vimix)");
|
|
|
|
if (custom_connected) {
|
|
|
|
ImGui::Text("\nConnect to SRT broadcaster:");
|
|
|
|
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
|
ImGuiToolkit::InputText("IP", &Settings::application.custom_connect_ip, ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsDecimal);
|
|
|
|
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
|
ImGuiToolkit::InputText("Port", &Settings::application.custom_connect_port, ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsDecimal);
|
|
|
|
static char bufurl[32];
|
|
ImFormatString(bufurl, IM_ARRAYSIZE(bufurl), "srt://%s:%s",
|
|
Settings::application.custom_connect_ip.c_str(),
|
|
Settings::application.custom_connect_port.c_str() );
|
|
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.14f, 0.14f, 0.14f, 0.8f));
|
|
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
|
ImGui::InputText("##url", bufurl, IM_ARRAYSIZE(bufurl), ImGuiInputTextFlags_ReadOnly);
|
|
ImGui::PopStyleColor(1);
|
|
|
|
ImGui::SameLine(0); ImGuiToolkit::Indication("URL for connecting to a stream on Secure Reliable Transport (SRT) protocol", ICON_SOURCE_SRT);
|
|
|
|
if ( ImGui::Button("Try to connect", ImVec2(IMGUI_RIGHT_ALIGN, 0)) ) {
|
|
new_source_preview_.setSource( Mixer::manager().createSourceSrt(Settings::application.custom_connect_ip, Settings::application.custom_connect_port), bufurl);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
ImGui::NewLine();
|
|
|
|
// if a new source was added
|
|
if (new_source_preview_.filled()) {
|
|
// show preview
|
|
new_source_preview_.Render(ImGui::GetContentRegionAvail().x IMGUI_RIGHT_ALIGN);
|
|
// ask to import the source in the mixer
|
|
ImGui::NewLine();
|
|
if (new_source_preview_.ready() && ImGui::Button( ICON_FA_CHECK " Create", ImVec2(pannel_width_ - padding_width_, 0)) ) {
|
|
// take out the source from the preview
|
|
Source *s = new_source_preview_.getSource();
|
|
// restart and add the source.
|
|
Mixer::manager().addSource(s);
|
|
s->replay();
|
|
// close NEW pannel
|
|
togglePannelNew();
|
|
|
|
/// BHBN TEST SOURCE CALLBACKS KEYaa
|
|
// s->setKeyCallback(GLFW_KEY_A, new GotoAlpha(0.9));
|
|
//// s->setKeyCallback(GLFW_KEY_A, new GotoDepth(10.0));
|
|
// s->setKeyCallback(GLFW_KEY_L, new Loom(-0.1));
|
|
|
|
}
|
|
}
|
|
|
|
ImGui::End();
|
|
}
|
|
}
|
|
|
|
void Navigator::RenderMainPannelVimix()
|
|
{
|
|
// TITLE
|
|
ImGui::SetCursorPosY(IMGUI_TOP_ALIGN);
|
|
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
|
|
ImGui::Text(APP_NAME);
|
|
ImGui::PopFont();
|
|
|
|
// MENU
|
|
ImGui::SameLine();
|
|
ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, IMGUI_TOP_ALIGN) );
|
|
if (ImGui::BeginMenu("File"))
|
|
{
|
|
UserInterface::manager().showMenuFile();
|
|
ImGui::EndMenu();
|
|
}
|
|
ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, IMGUI_TOP_ALIGN + ImGui::GetTextLineHeightWithSpacing()) );
|
|
if (ImGui::BeginMenu("Edit"))
|
|
{
|
|
UserInterface::manager().showMenuEdit();
|
|
ImGui::EndMenu();
|
|
}
|
|
|
|
ImGui::SetCursorPosY(width_);
|
|
|
|
//
|
|
// SESSION panel
|
|
//
|
|
ImGui::Text("Sessions");
|
|
static bool selection_session_mode_changed = true;
|
|
static int selection_session_mode = (Settings::application.recentFolders.path == IMGUI_LABEL_RECENT_FILES) ? 0 : 1;
|
|
static DialogToolkit::OpenFolderDialog customFolder("Open Folder");
|
|
|
|
// Show combo box of quick selection modes
|
|
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
|
if (ImGui::BeginCombo("##SelectionSession", BaseToolkit::truncated(Settings::application.recentFolders.path, 25).c_str() )) {
|
|
|
|
// Mode 0 : recent files
|
|
if (ImGui::Selectable( ICON_FA_LIST_OL IMGUI_LABEL_RECENT_FILES) ) {
|
|
Settings::application.recentFolders.path = IMGUI_LABEL_RECENT_FILES;
|
|
selection_session_mode = 0;
|
|
selection_session_mode_changed = true;
|
|
}
|
|
// Mode 1 : known folders
|
|
for(auto foldername = Settings::application.recentFolders.filenames.begin();
|
|
foldername != Settings::application.recentFolders.filenames.end(); foldername++) {
|
|
std::string f = std::string(ICON_FA_FOLDER) + " " + BaseToolkit::truncated( *foldername, 40);
|
|
if (ImGui::Selectable( f.c_str() )) {
|
|
// remember which path was selected
|
|
Settings::application.recentFolders.path.assign(*foldername);
|
|
// set mode
|
|
selection_session_mode = 1;
|
|
selection_session_mode_changed = true;
|
|
}
|
|
}
|
|
// Add a folder
|
|
if (ImGui::Selectable( ICON_FA_FOLDER_PLUS " Add Folder") )
|
|
customFolder.open();
|
|
ImGui::EndCombo();
|
|
}
|
|
|
|
// return from thread for folder openning
|
|
if (customFolder.closed() && !customFolder.path().empty()) {
|
|
Settings::application.recentFolders.push(customFolder.path());
|
|
Settings::application.recentFolders.path.assign(customFolder.path());
|
|
selection_session_mode = 1;
|
|
selection_session_mode_changed = true;
|
|
}
|
|
|
|
// icon to clear list
|
|
ImVec2 pos_top = ImGui::GetCursorPos();
|
|
ImGui::SameLine();
|
|
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.7);
|
|
if ( selection_session_mode == 1) {
|
|
if (ImGuiToolkit::IconButton( ICON_FA_FOLDER_MINUS, "Discard folder")) {
|
|
Settings::application.recentFolders.filenames.remove(Settings::application.recentFolders.path);
|
|
if (Settings::application.recentFolders.filenames.empty()) {
|
|
Settings::application.recentFolders.path.assign(IMGUI_LABEL_RECENT_FILES);
|
|
selection_session_mode = 0;
|
|
}
|
|
else
|
|
Settings::application.recentFolders.path = Settings::application.recentFolders.filenames.front();
|
|
// reload the list next time
|
|
selection_session_mode_changed = true;
|
|
}
|
|
}
|
|
else {
|
|
if (ImGuiToolkit::IconButton( ICON_FA_BACKSPACE, "Clear list")) {
|
|
Settings::application.recentSessions.filenames.clear();
|
|
Settings::application.recentSessions.front_is_valid = false;
|
|
// reload the list next time
|
|
selection_session_mode_changed = true;
|
|
}
|
|
}
|
|
ImGui::PopStyleVar();
|
|
ImGui::SetCursorPos(pos_top);
|
|
|
|
// fill the session list depending on the mode
|
|
static std::list<std::string> sessions_list;
|
|
static std::list<std::string>::iterator _file_over = sessions_list.end();
|
|
static std::list<std::string>::iterator _displayed_over = sessions_list.end();
|
|
|
|
// change session list if changed
|
|
if (selection_session_mode_changed || Settings::application.recentSessions.changed || Settings::application.recentFolders.changed) {
|
|
|
|
// selection MODE 0 ; RECENT sessions
|
|
if ( selection_session_mode == 0) {
|
|
// show list of recent sessions
|
|
Settings::application.recentSessions.validate();
|
|
sessions_list = Settings::application.recentSessions.filenames;
|
|
Settings::application.recentSessions.changed = false;
|
|
}
|
|
// selection MODE 1 : LIST FOLDER
|
|
else if ( selection_session_mode == 1) {
|
|
// show list of vimix files in folder
|
|
sessions_list = SystemToolkit::list_directory( Settings::application.recentFolders.path, { VIMIX_FILE_PATTERN });
|
|
}
|
|
// indicate the list changed (do not change at every frame)
|
|
selection_session_mode_changed = false;
|
|
_file_over = sessions_list.end();
|
|
_displayed_over = sessions_list.end();
|
|
}
|
|
|
|
{
|
|
static bool _tooltip = 0;
|
|
|
|
// display the sessions list and detect if one was selected (double clic)
|
|
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
|
if (ImGui::ListBoxHeader("##Sessions", sessions_list.size(), CLAMP(sessions_list.size(), 4, 8)) ) {
|
|
|
|
bool done = false;
|
|
int count_over = 0;
|
|
ImVec2 size = ImVec2( ImGui::GetContentRegionAvailWidth(), ImGui::GetTextLineHeight() );
|
|
|
|
for(auto it = sessions_list.begin(); it != sessions_list.end(); ++it) {
|
|
|
|
if (it->empty())
|
|
continue;
|
|
|
|
std::string shortname = SystemToolkit::filename(*it);
|
|
if (ImGui::Selectable( shortname.c_str(), false, ImGuiSelectableFlags_AllowDoubleClick, size )) {
|
|
// open on double clic
|
|
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left) /*|| file_selected == it*/) {
|
|
Mixer::manager().open( *it, Settings::application.smooth_transition );
|
|
if (Settings::application.smooth_transition)
|
|
WorkspaceWindow::clearWorkspace();
|
|
done = true;
|
|
}
|
|
else
|
|
// show tooltip on clic
|
|
_tooltip = true;
|
|
|
|
}
|
|
if (ImGui::IsItemHovered())
|
|
_file_over = it;
|
|
|
|
if (_tooltip && _file_over != sessions_list.end() && count_over < 1) {
|
|
|
|
static std::string _file_info = "";
|
|
static Thumbnail _file_thumbnail;
|
|
static bool with_tag_ = false;
|
|
|
|
// load info only if changed from the one already displayed
|
|
if (_displayed_over != _file_over) {
|
|
_displayed_over = _file_over;
|
|
SessionInformation info = SessionCreator::info(*_displayed_over);
|
|
_file_info = info.description;
|
|
if (info.thumbnail) {
|
|
// set image content to thumbnail display
|
|
_file_thumbnail.fill( info.thumbnail );
|
|
with_tag_ = info.user_thumbnail_;
|
|
delete info.thumbnail;
|
|
} else
|
|
_file_thumbnail.reset();
|
|
}
|
|
|
|
if ( !_file_info.empty()) {
|
|
|
|
ImGui::BeginTooltip();
|
|
ImVec2 p_ = ImGui::GetCursorScreenPos();
|
|
_file_thumbnail.Render(size.x);
|
|
ImGui::Text("%s", _file_info.c_str());
|
|
if (with_tag_) {
|
|
ImGui::SetCursorScreenPos(p_ + ImVec2(6, 6));
|
|
ImGui::Text(ICON_FA_TAG);
|
|
}
|
|
ImGui::EndTooltip();
|
|
}
|
|
else
|
|
selection_session_mode_changed = true;
|
|
|
|
++count_over; // prevents display twice on item overlap
|
|
}
|
|
}
|
|
ImGui::ListBoxFooter();
|
|
|
|
// done the selection !
|
|
if (done) {
|
|
hidePannel();
|
|
_tooltip = false;
|
|
_displayed_over = _file_over = sessions_list.end();
|
|
// reload the list next time
|
|
selection_session_mode_changed = true;
|
|
}
|
|
}
|
|
// cancel tooltip and mouse over on mouse exit
|
|
if ( !ImGui::IsItemHovered()) {
|
|
_tooltip = false;
|
|
_displayed_over = _file_over = sessions_list.end();
|
|
}
|
|
}
|
|
|
|
ImVec2 pos_bot = ImGui::GetCursorPos();
|
|
|
|
// Right side of the list: helper and options
|
|
ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_top.y));
|
|
if ( ImGuiToolkit::IconButton( ICON_FA_FILE " +" )) {
|
|
Mixer::manager().close(Settings::application.smooth_transition );
|
|
if (Settings::application.smooth_transition)
|
|
WorkspaceWindow::clearWorkspace();
|
|
hidePannel();
|
|
}
|
|
if (ImGui::IsItemHovered())
|
|
ImGuiToolkit::ToolTip("New session", SHORTCUT_NEW_FILE);
|
|
|
|
ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_bot.y - 2.f * ImGui::GetFrameHeightWithSpacing()));
|
|
ImGuiToolkit::HelpToolTip("Select the history of recently opened files or a folder. "
|
|
"Double-clic on a filename to open the session.\n\n"
|
|
ICON_FA_ARROW_CIRCLE_RIGHT " Smooth transition "
|
|
"performs cross fading to the openned session.");
|
|
// toggle button for smooth transition
|
|
ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_bot.y - ImGui::GetFrameHeightWithSpacing()) );
|
|
ImGuiToolkit::ButtonToggle(ICON_FA_ARROW_CIRCLE_RIGHT, &Settings::application.smooth_transition, "Smooth transition");
|
|
// come back...
|
|
ImGui::SetCursorPos(pos_bot);
|
|
|
|
//
|
|
// Status
|
|
//
|
|
ImGuiToolkit::Spacing();
|
|
ImGui::Text("Current session");
|
|
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
|
ImGui::Combo("##Selectpanelsession", &Settings::application.pannel_current_session_mode,
|
|
ICON_FA_CODE_BRANCH " Versions\0" ICON_FA_HISTORY " Undo history\0" ICON_FA_FILE_ALT " Properties\0");
|
|
pos_bot = ImGui::GetCursorPos();
|
|
|
|
//
|
|
// Current 2. PROPERTIES
|
|
//
|
|
if (Settings::application.pannel_current_session_mode > 1) {
|
|
|
|
std::string sessionfilename = Mixer::manager().session()->filename();
|
|
|
|
// Information and resolution
|
|
const FrameBuffer *output = Mixer::manager().session()->frame();
|
|
if (output)
|
|
{
|
|
// Show info text bloc (dark background)
|
|
ImGuiTextBuffer info;
|
|
if (sessionfilename.empty())
|
|
info.appendf("<unsaved>");
|
|
else
|
|
info.appendf("%s", SystemToolkit::filename(sessionfilename).c_str());
|
|
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.14f, 0.14f, 0.14f, 0.9f));
|
|
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
|
ImGui::InputText("##Info", (char *)info.c_str(), info.size(), ImGuiInputTextFlags_ReadOnly);
|
|
ImGui::PopStyleColor(1);
|
|
|
|
// Kept for later? Larger info box with more details on the session file...
|
|
// ImGuiTextBuffer info;
|
|
// if (!sessionfilename.empty())
|
|
// info.appendf("%s\n", SystemToolkit::filename(sessionfilename).c_str());
|
|
// else
|
|
// info.append("<unsaved>\n");
|
|
// info.appendf("%dx%dpx", output->width(), output->height());
|
|
// if (p.x > -1)
|
|
// info.appendf(", %s", FrameBuffer::aspect_ratio_name[p.x]);
|
|
// // content info
|
|
// const uint N = Mixer::manager().session()->numSource();
|
|
// if (N > 1)
|
|
// info.appendf("\n%d sources", N);
|
|
// else if (N > 0)
|
|
// info.append("\n1 source");
|
|
// const size_t M = MediaPlayer::registered().size();
|
|
// if (M > 0) {
|
|
// info.appendf("\n%d media files:", M);
|
|
// for (auto mit = MediaPlayer::begin(); mit != MediaPlayer::end(); ++mit)
|
|
// info.appendf("\n- %s", SystemToolkit::filename((*mit)->filename()).c_str());
|
|
// }
|
|
// // Show info text bloc (multi line, dark background)
|
|
// ImGuiToolkit::PushFont( ImGuiToolkit::FONT_MONO );
|
|
// ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.14f, 0.14f, 0.14f, 0.9f));
|
|
// ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
|
// ImGui::InputTextMultiline("##Info", (char *)info.c_str(), info.size(), ImVec2(IMGUI_RIGHT_ALIGN, 2*ImGui::GetTextLineHeightWithSpacing()), ImGuiInputTextFlags_ReadOnly);
|
|
// ImGui::PopStyleColor(1);
|
|
// ImGui::PopFont();
|
|
|
|
// change resolution (height only)
|
|
// get parameters to edit resolution
|
|
glm::ivec2 p = FrameBuffer::getParametersFromResolution(output->resolution());
|
|
if (p.y > -1) {
|
|
// cannot change resolution when recording
|
|
if ( UserInterface::manager().outputcontrol.isRecording() ) {
|
|
// show static info (same size than combo)
|
|
static char dummy_str[512];
|
|
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.14f, 0.14f, 0.14f, 0.9f));
|
|
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
|
sprintf(dummy_str, "%s", FrameBuffer::aspect_ratio_name[p.x]);
|
|
ImGui::InputText("Ratio", dummy_str, IM_ARRAYSIZE(dummy_str), ImGuiInputTextFlags_ReadOnly);
|
|
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
|
sprintf(dummy_str, "%s", FrameBuffer::resolution_name[p.y]);
|
|
ImGui::InputText("Height", dummy_str, IM_ARRAYSIZE(dummy_str), ImGuiInputTextFlags_ReadOnly);
|
|
ImGui::PopStyleColor(1);
|
|
}
|
|
// offer to change filename, ratio and resolution
|
|
else {
|
|
ImVec2 draw_pos = ImGui::GetCursorScreenPos();
|
|
ImGui::SameLine();
|
|
if ( ImGuiToolkit::IconButton(ICON_FA_FILE_DOWNLOAD, "Save as" )) {
|
|
UserInterface::manager().selectSaveFilename();
|
|
}
|
|
ImGui::SetCursorScreenPos(draw_pos);
|
|
// combo boxes to select Rario and Height
|
|
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
|
if (ImGui::Combo("Ratio", &p.x, FrameBuffer::aspect_ratio_name, IM_ARRAYSIZE(FrameBuffer::aspect_ratio_name) ) )
|
|
{
|
|
glm::vec3 res = FrameBuffer::getResolutionFromParameters(p.x, p.y);
|
|
Mixer::manager().setResolution(res);
|
|
}
|
|
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
|
if (ImGui::Combo("Height", &p.y, FrameBuffer::resolution_name, IM_ARRAYSIZE(FrameBuffer::resolution_name) ) )
|
|
{
|
|
glm::vec3 res = FrameBuffer::getResolutionFromParameters(p.x, p.y);
|
|
Mixer::manager().setResolution(res);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// the session file exists
|
|
if (!sessionfilename.empty())
|
|
{
|
|
// Folder
|
|
std::string path = SystemToolkit::path_filename(sessionfilename);
|
|
std::string label = BaseToolkit::truncated(path, 23);
|
|
label = BaseToolkit::transliterate(label);
|
|
ImGuiToolkit::ButtonOpenUrl( label.c_str(), path.c_str(), ImVec2(IMGUI_RIGHT_ALIGN, 0) );
|
|
ImGui::SameLine();
|
|
ImGui::Text("Folder");
|
|
|
|
// Thumbnail
|
|
static Thumbnail _file_thumbnail;
|
|
static FrameBufferImage *thumbnail = nullptr;
|
|
if ( ImGui::Button( ICON_FA_TAG " New thumbnail", ImVec2(IMGUI_RIGHT_ALIGN, 0)) ) {
|
|
Mixer::manager().session()->setThumbnail();
|
|
thumbnail = nullptr;
|
|
}
|
|
pos_bot = ImGui::GetCursorPos();
|
|
if (ImGui::IsItemHovered()){
|
|
// thumbnail changed
|
|
if (thumbnail != Mixer::manager().session()->thumbnail()) {
|
|
_file_thumbnail.reset();
|
|
thumbnail = Mixer::manager().session()->thumbnail();
|
|
if (thumbnail != nullptr)
|
|
_file_thumbnail.fill( thumbnail );
|
|
}
|
|
if (_file_thumbnail.filled()) {
|
|
ImGui::BeginTooltip();
|
|
_file_thumbnail.Render(230);
|
|
ImGui::Text("Thumbnail used in the\nlist of Sessions above.");
|
|
ImGui::EndTooltip();
|
|
}
|
|
}
|
|
if (Mixer::manager().session()->thumbnail()) {
|
|
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.7);
|
|
ImGui::SameLine();
|
|
if (ImGuiToolkit::IconButton(ICON_FA_BACKSPACE, "Remove thumbnail")) {
|
|
Mixer::manager().session()->resetThumbnail();
|
|
_file_thumbnail.reset();
|
|
thumbnail = nullptr;
|
|
}
|
|
ImGui::PopStyleVar();
|
|
}
|
|
ImGui::SetCursorPos( pos_bot );
|
|
}
|
|
|
|
}
|
|
//
|
|
// Current 1. UNDO History
|
|
//
|
|
else if (Settings::application.pannel_current_session_mode > 0) {
|
|
|
|
static uint _over = 0;
|
|
static uint64_t _displayed_over = 0;
|
|
static bool _tooltip = 0;
|
|
|
|
ImGui::SameLine();
|
|
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.7);
|
|
if (ImGuiToolkit::IconButton( ICON_FA_BACKSPACE, "Clear history")) {
|
|
Action::manager().init();
|
|
}
|
|
ImGui::PopStyleVar();
|
|
// come back...
|
|
ImGui::SetCursorPos(pos_bot);
|
|
|
|
pos_top = ImGui::GetCursorPos();
|
|
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
|
if ( ImGui::ListBoxHeader("##UndoHistory", Action::manager().max(), CLAMP(Action::manager().max(), 4, 8)) ) {
|
|
|
|
int count_over = 0;
|
|
ImVec2 size = ImVec2( ImGui::GetContentRegionAvailWidth(), ImGui::GetTextLineHeight() );
|
|
|
|
for (uint i = Action::manager().max(); i > 0; --i) {
|
|
|
|
if (ImGui::Selectable( Action::manager().label(i).c_str(), i == Action::manager().current(), ImGuiSelectableFlags_AllowDoubleClick, size )) {
|
|
// go to on double clic
|
|
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left))
|
|
Action::manager().stepTo(i);
|
|
else
|
|
// show tooltip on clic
|
|
_tooltip = true;
|
|
}
|
|
// mouse over
|
|
if (ImGui::IsItemHovered())
|
|
_over = i;
|
|
|
|
// if mouse over (only once)
|
|
if (_tooltip && _over > 0 && count_over < 1) {
|
|
static std::string text = "";
|
|
static Thumbnail _undo_thumbnail;
|
|
// load label and thumbnail only if current changed
|
|
if (_displayed_over != _over) {
|
|
_displayed_over = _over;
|
|
text = Action::manager().label(_over);
|
|
if (text.find_first_of(':') < text.size())
|
|
text = text.insert( text.find_first_of(':') + 1, 1, '\n');
|
|
FrameBufferImage *im = Action::manager().thumbnail(_over);
|
|
if (im) {
|
|
// set image content to thumbnail display
|
|
_undo_thumbnail.fill( im );
|
|
delete im;
|
|
}
|
|
else
|
|
_undo_thumbnail.reset();
|
|
}
|
|
// draw thumbnail in tooltip
|
|
ImGui::BeginTooltip();
|
|
_undo_thumbnail.Render(size.x);
|
|
ImGui::Text("%s", text.c_str());
|
|
ImGui::EndTooltip();
|
|
++count_over; // prevents display twice on item overlap
|
|
}
|
|
|
|
}
|
|
ImGui::ListBoxFooter();
|
|
}
|
|
// cancel tooltip and mouse over on mouse exit
|
|
if ( !ImGui::IsItemHovered()) {
|
|
_tooltip = false;
|
|
_displayed_over = _over = 0;
|
|
}
|
|
|
|
pos_bot = ImGui::GetCursorPos();
|
|
|
|
// right buttons
|
|
ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_top.y ));
|
|
if ( Action::manager().current() > 1 ) {
|
|
if ( ImGuiToolkit::IconButton( ICON_FA_UNDO ) )
|
|
Action::manager().undo();
|
|
} else
|
|
ImGui::TextDisabled( ICON_FA_UNDO );
|
|
|
|
ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_top.y + ImGui::GetTextLineHeightWithSpacing() + 4));
|
|
if ( Action::manager().current() < Action::manager().max() ) {
|
|
if ( ImGuiToolkit::IconButton( ICON_FA_REDO ))
|
|
Action::manager().redo();
|
|
} else
|
|
ImGui::TextDisabled( ICON_FA_REDO );
|
|
|
|
ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_bot.y - ImGui::GetFrameHeightWithSpacing()) );
|
|
ImGuiToolkit::ButtonToggle(ICON_FA_MAP_MARKED_ALT, &Settings::application.action_history_follow_view, "Show in view");
|
|
}
|
|
//
|
|
// Current 0. VERSIONS
|
|
//
|
|
else {
|
|
static uint64_t _over = 0;
|
|
static bool _tooltip = 0;
|
|
|
|
ImGui::SameLine();
|
|
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.7);
|
|
if (ImGuiToolkit::IconButton( ICON_FA_BACKSPACE, "Clear versions")) {
|
|
Action::manager().clearSnapshots();
|
|
}
|
|
ImGui::PopStyleVar();
|
|
// come back...
|
|
ImGui::SetCursorPos(pos_bot);
|
|
|
|
// list snapshots
|
|
std::list<uint64_t> snapshots = Action::manager().snapshots();
|
|
pos_top = ImGui::GetCursorPos();
|
|
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
|
if ( ImGui::ListBoxHeader("##Snapshots", snapshots.size(), CLAMP(snapshots.size(), 4, 8)) ) {
|
|
|
|
static uint64_t _selected = 0;
|
|
static Thumbnail _snap_thumbnail;
|
|
static std::string _snap_label = "";
|
|
static std::string _snap_date = "";
|
|
|
|
int count_over = 0;
|
|
ImVec2 size = ImVec2( ImGui::GetContentRegionAvailWidth(), ImGui::GetTextLineHeight() );
|
|
for (auto snapit = snapshots.rbegin(); snapit != snapshots.rend(); ++snapit)
|
|
{
|
|
// entry
|
|
ImVec2 pos = ImGui::GetCursorPos();
|
|
|
|
// context menu icon on currently hovered item
|
|
if ( _over == *snapit ) {
|
|
// open context menu
|
|
ImGui::SetCursorPos(ImVec2(size.x-ImGui::GetTextLineHeight()/2.f, pos.y));
|
|
if ( ImGuiToolkit::IconButton( ICON_FA_CHEVRON_DOWN ) ) {
|
|
// current list item
|
|
Action::manager().open(*snapit);
|
|
// open menu
|
|
ImGui::OpenPopup( "MenuSnapshot" );
|
|
}
|
|
// show tooltip and select on mouse over menu icon
|
|
if (ImGui::IsItemHovered()) {
|
|
_selected = *snapit;
|
|
_tooltip = true;
|
|
}
|
|
ImGui::SetCursorPos(pos);
|
|
}
|
|
|
|
// snapshot item
|
|
if (ImGui::Selectable( Action::manager().label(*snapit).c_str(), (*snapit == _selected), ImGuiSelectableFlags_AllowDoubleClick, size )) {
|
|
// shot tooltip on clic
|
|
_tooltip = true;
|
|
// trigger snapshot on double clic
|
|
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left))
|
|
Action::manager().restore(*snapit);
|
|
}
|
|
// mouse over
|
|
if (ImGui::IsItemHovered()) {
|
|
_over = *snapit;
|
|
_selected = 0;
|
|
}
|
|
|
|
// if mouse over (only once)
|
|
if (_tooltip && _over > 0 && count_over < 1) {
|
|
static uint64_t current_over = 0;
|
|
// load label and thumbnail only if current changed
|
|
if (current_over != _over) {
|
|
_snap_label = Action::manager().label(_over);
|
|
_snap_date = "Version of " + readable_date_time_string(Action::manager().date(_over));
|
|
FrameBufferImage *im = Action::manager().thumbnail(_over);
|
|
if (im) {
|
|
// set image content to thumbnail display
|
|
_snap_thumbnail.fill( im );
|
|
delete im;
|
|
}
|
|
else
|
|
_snap_thumbnail.reset();
|
|
current_over = _over;
|
|
}
|
|
// draw thumbnail in tooltip
|
|
ImGui::BeginTooltip();
|
|
_snap_thumbnail.Render(size.x);
|
|
ImGui::Text(_snap_date.c_str());
|
|
ImGui::EndTooltip();
|
|
++count_over; // prevents display twice on item overlap
|
|
}
|
|
}
|
|
|
|
// context menu on currently open snapshot
|
|
uint64_t current = Action::manager().currentSnapshot();
|
|
if (ImGui::BeginPopup( "MenuSnapshot" ) && current > 0 )
|
|
{
|
|
_selected = current;
|
|
// snapshot thumbnail
|
|
_snap_thumbnail.Render(size.x);
|
|
// snapshot editable label
|
|
ImGui::SetNextItemWidth(size.x);
|
|
if ( ImGuiToolkit::InputText("##Rename", &_snap_label ) )
|
|
Action::manager().setLabel( current, _snap_label);
|
|
// snapshot actions
|
|
if (ImGui::Selectable( ICON_FA_ANGLE_DOUBLE_RIGHT " Restore", false, 0, size ))
|
|
Action::manager().restore();
|
|
if (ImGui::Selectable( ICON_FA_CODE_BRANCH "- Remove", false, 0, size ))
|
|
Action::manager().remove();
|
|
// export option if possible
|
|
std::string filename = Mixer::manager().session()->filename();
|
|
if (filename.size()>0) {
|
|
if (ImGui::Selectable( ICON_FA_FILE_DOWNLOAD " Export", false, 0, size )) {
|
|
Action::manager().saveas(filename);
|
|
}
|
|
}
|
|
ImGui::EndPopup();
|
|
}
|
|
else
|
|
_selected = 0;
|
|
|
|
// end list snapshots
|
|
ImGui::ListBoxFooter();
|
|
}
|
|
// cancel tooltip and mouse over on mouse exit
|
|
if ( !ImGui::IsItemHovered()) {
|
|
_tooltip = false;
|
|
_over = 0;
|
|
}
|
|
|
|
// Right panel buton
|
|
pos_bot = ImGui::GetCursorPos();
|
|
|
|
// right buttons
|
|
ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_top.y ));
|
|
if ( ImGuiToolkit::IconButton( ICON_FA_FILE_DOWNLOAD " +")) {
|
|
UserInterface::manager().saveOrSaveAs(true);
|
|
}
|
|
if (ImGui::IsItemHovered())
|
|
ImGuiToolkit::ToolTip("Save & Keep version");
|
|
|
|
ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_bot.y - 2.f * ImGui::GetFrameHeightWithSpacing()));
|
|
ImGuiToolkit::HelpToolTip("Previous versions of the session (latest on top). "
|
|
"Double-clic on a version to restore it.\n\n"
|
|
ICON_FA_CODE_BRANCH " Iterative saving automatically "
|
|
"keeps a version each time a session is saved.");
|
|
// toggle button for versioning
|
|
ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_bot.y - ImGui::GetFrameHeightWithSpacing()) );
|
|
ImGuiToolkit::ButtonToggle(" " ICON_FA_CODE_BRANCH " ", &Settings::application.save_version_snapshot,"Iterative saving");
|
|
|
|
ImGui::SetCursorPos( pos_bot );
|
|
}
|
|
|
|
//
|
|
// Buttons to show WINDOWS
|
|
//
|
|
ImGuiToolkit::Spacing();
|
|
ImGui::Text("Windows");
|
|
ImGui::Spacing();
|
|
|
|
std::pair<std::string, std::string> tooltip_;
|
|
|
|
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
|
|
ImGui::SameLine(0, ImGui::GetTextLineHeight());
|
|
if ( ImGuiToolkit::IconButton( ICON_FA_PLAY_CIRCLE ) )
|
|
UserInterface::manager().sourcecontrol.setVisible(!Settings::application.widget.media_player);
|
|
if (ImGui::IsItemHovered())
|
|
tooltip_ = { TOOLTIP_PLAYER, SHORTCUT_PLAYER};
|
|
|
|
ImGui::SameLine(0, ImGui::GetTextLineHeight());
|
|
if ( ImGuiToolkit::IconButton( ICON_FA_DESKTOP ) )
|
|
UserInterface::manager().outputcontrol.setVisible(!Settings::application.widget.preview);
|
|
if (ImGui::IsItemHovered())
|
|
tooltip_ = { TOOLTIP_OUTPUT, SHORTCUT_OUTPUT};
|
|
|
|
ImGui::SameLine(0, ImGui::GetTextLineHeight());
|
|
if ( ImGuiToolkit::IconButton( ICON_FA_CLOCK ) )
|
|
UserInterface::manager().timercontrol.setVisible(!Settings::application.widget.timer);
|
|
if (ImGui::IsItemHovered())
|
|
tooltip_ = { TOOLTIP_TIMER, SHORTCUT_TIMER};
|
|
|
|
ImGui::SameLine(0, ImGui::GetTextLineHeight());
|
|
if ( ImGuiToolkit::IconButton( ICON_FA_CUBES ) )
|
|
UserInterface::manager().inputscontrol.setVisible(!Settings::application.widget.inputs);
|
|
if (ImGui::IsItemHovered())
|
|
tooltip_ = { TOOLTIP_INPUTS, SHORTCUT_INPUTS};
|
|
ImGui::PopFont();
|
|
|
|
ImGui::Spacing();
|
|
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO);
|
|
ImGui::SameLine(0, ImGui::GetTextLineHeight());
|
|
if ( ImGuiToolkit::IconButton( ICON_FA_STICKY_NOTE ) )
|
|
Mixer::manager().session()->addNote();
|
|
if (ImGui::IsItemHovered())
|
|
tooltip_ = { TOOLTIP_NOTE, SHORTCUT_NOTE};
|
|
|
|
ImGui::SameLine(0, ImGui::GetTextLineHeight());
|
|
if ( ImGuiToolkit::IconButton( ICON_FA_TACHOMETER_ALT ) )
|
|
Settings::application.widget.stats = !Settings::application.widget.stats;
|
|
if (ImGui::IsItemHovered())
|
|
tooltip_ = { TOOLTIP_METRICS, SHORTCUT_METRICS};
|
|
ImGui::PopFont();
|
|
|
|
if (!tooltip_.first.empty()) {
|
|
ImGuiToolkit::ToolTip(tooltip_.first.c_str(), tooltip_.second.c_str());
|
|
}
|
|
|
|
}
|
|
|
|
void Navigator::RenderMainPannelSettings()
|
|
{
|
|
// TITLE
|
|
ImGui::SetCursorPosY(IMGUI_TOP_ALIGN);
|
|
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
|
|
ImGui::Text("Settings");
|
|
ImGui::PopFont();
|
|
ImGui::SetCursorPosY(width_);
|
|
|
|
//
|
|
// Appearance
|
|
//
|
|
ImGui::Text("Interface");
|
|
int v = Settings::application.accent_color;
|
|
if (ImGui::RadioButton("##Color", &v, v)){
|
|
Settings::application.accent_color = (v+1)%3;
|
|
ImGuiToolkit::SetAccentColor(static_cast<ImGuiToolkit::accent_color>(Settings::application.accent_color));
|
|
}
|
|
if (ImGui::IsItemHovered())
|
|
ImGuiToolkit::ToolTip("Change accent color");
|
|
ImGui::SameLine();
|
|
ImGui::SetCursorPosX(-1.f * IMGUI_RIGHT_ALIGN);
|
|
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
|
if ( ImGui::DragFloat("Scale", &Settings::application.scale, 0.01f, 0.5f, 2.0f, "%.1f"))
|
|
ImGui::GetIO().FontGlobalScale = Settings::application.scale;
|
|
|
|
ImGuiToolkit::ButtonSwitch( ICON_FA_MOUSE_POINTER " Smooth cursor", &Settings::application.smooth_cursor);
|
|
|
|
//
|
|
// Recording preferences
|
|
//
|
|
ImGui::Text("Record");
|
|
|
|
// select CODEC and FPS
|
|
ImGui::SetCursorPosX(-1.f * IMGUI_RIGHT_ALIGN);
|
|
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
|
ImGui::Combo("Codec", &Settings::application.record.profile, VideoRecorder::profile_name, IM_ARRAYSIZE(VideoRecorder::profile_name) );
|
|
|
|
ImGui::SetCursorPosX(-1.f * IMGUI_RIGHT_ALIGN);
|
|
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
|
ImGui::Combo("Framerate", &Settings::application.record.framerate_mode, VideoRecorder::framerate_preset_name, IM_ARRAYSIZE(VideoRecorder::framerate_preset_name) );
|
|
|
|
// compute number of frames in buffer and show warning sign if too low
|
|
const FrameBuffer *output = Mixer::manager().session()->frame();
|
|
if (output) {
|
|
guint64 nb = 0;
|
|
nb = VideoRecorder::buffering_preset_value[Settings::application.record.buffering_mode] / (output->width() * output->height() * 4);
|
|
char buf[256]; sprintf(buf, "Buffer can contain %ld frames (%dx%d), %.1f sec", (unsigned long)nb, output->width(), output->height(),
|
|
(float)nb / (float) VideoRecorder::framerate_preset_value[Settings::application.record.framerate_mode] );
|
|
ImGuiToolkit::Indication(buf, ICON_FA_MEMORY);
|
|
ImGui::SameLine(0);
|
|
}
|
|
|
|
ImGui::SetCursorPosX(-1.f * IMGUI_RIGHT_ALIGN);
|
|
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
|
ImGui::SliderInt("Buffer", &Settings::application.record.buffering_mode, 0, IM_ARRAYSIZE(VideoRecorder::buffering_preset_name)-1,
|
|
VideoRecorder::buffering_preset_name[Settings::application.record.buffering_mode]);
|
|
|
|
ImGuiToolkit::HelpToolTip("Priority when buffer is full and recorder has to skip frames;\n"
|
|
ICON_FA_CARET_RIGHT " Duration:\n Variable framerate, correct duration.\n"
|
|
ICON_FA_CARET_RIGHT " Framerate:\n Correct framerate, shorter duration.");
|
|
ImGui::SameLine(0);
|
|
ImGui::SetCursorPosX(-1.f * IMGUI_RIGHT_ALIGN);
|
|
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
|
ImGui::Combo("Priority", &Settings::application.record.priority_mode, "Duration\0Framerate\0");
|
|
|
|
//
|
|
// Networking preferences
|
|
//
|
|
ImGui::Text("Stream");
|
|
|
|
char msg[256];
|
|
ImFormatString(msg, IM_ARRAYSIZE(msg),"Port for broadcasting on Secure Reliable Transport (SRT) protocol\n"
|
|
"You can e.g. connect to:\n srt://%s:%d",
|
|
NetworkToolkit::host_ips()[1].c_str(), Settings::application.broadcast_port);
|
|
ImGuiToolkit::Indication(msg, ICON_FA_PODCAST);
|
|
ImGui::SameLine(0);
|
|
ImGui::SetCursorPosX(-1.f * IMGUI_RIGHT_ALIGN);
|
|
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
|
char bufport[7] = "";
|
|
sprintf(bufport, "%d", Settings::application.broadcast_port);
|
|
ImGui::InputTextWithHint("Broadcast", "7070", bufport, 6, ImGuiInputTextFlags_CharsDecimal);
|
|
if (ImGui::IsItemDeactivatedAfterEdit()){
|
|
if ( BaseToolkit::is_a_number(bufport, &Settings::application.broadcast_port))
|
|
Settings::application.broadcast_port = CLAMP(Settings::application.broadcast_port, 1029, 49150);
|
|
}
|
|
|
|
ImGuiToolkit::Indication("Sharing H264 stream requires less bandwidth but more resources for encoding.", ICON_FA_SHARE_ALT_SQUARE);
|
|
ImGui::SameLine(0);
|
|
ImGui::SetCursorPosX(-1.f * IMGUI_RIGHT_ALIGN);
|
|
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
|
ImGui::Combo("Share", &Settings::application.stream_protocol, "JPEG\0H264\0");
|
|
|
|
//
|
|
// OSC preferences
|
|
//
|
|
ImGuiToolkit::Spacing();
|
|
ImGui::Text("OSC");
|
|
|
|
sprintf(msg, "You can send OSC messages via UDP to the local IP address %s on Port %d",
|
|
NetworkToolkit::host_ips()[1].c_str(), Settings::application.control.osc_port_receive);
|
|
ImGuiToolkit::Indication(msg, ICON_FA_NETWORK_WIRED);
|
|
ImGui::SameLine(0);
|
|
|
|
ImGui::SetCursorPosX(-1.f * IMGUI_RIGHT_ALIGN);
|
|
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
|
char bufreceive[7] = "";
|
|
sprintf(bufreceive, "%d", Settings::application.control.osc_port_receive);
|
|
ImGui::InputTextWithHint("Port in", "7000", bufreceive, 7, ImGuiInputTextFlags_CharsDecimal);
|
|
if (ImGui::IsItemDeactivatedAfterEdit()){
|
|
if ( BaseToolkit::is_a_number(bufreceive, &Settings::application.control.osc_port_receive)){
|
|
Settings::application.control.osc_port_receive = CLAMP(Settings::application.control.osc_port_receive, 1029, 49150);
|
|
Control::manager().init();
|
|
}
|
|
}
|
|
|
|
ImGui::SetCursorPosX(-1.f * IMGUI_RIGHT_ALIGN);
|
|
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
|
char bufsend[7] = "";
|
|
sprintf(bufsend, "%d", Settings::application.control.osc_port_send);
|
|
ImGui::InputTextWithHint("Port out", "7001", bufsend, 7, ImGuiInputTextFlags_CharsDecimal);
|
|
if (ImGui::IsItemDeactivatedAfterEdit()){
|
|
if ( BaseToolkit::is_a_number(bufsend, &Settings::application.control.osc_port_send)){
|
|
Settings::application.control.osc_port_send = CLAMP(Settings::application.control.osc_port_send, 1029, 49150);
|
|
Control::manager().init();
|
|
}
|
|
}
|
|
|
|
ImGui::SetCursorPosX(-1.f * IMGUI_RIGHT_ALIGN);
|
|
const float w = IMGUI_RIGHT_ALIGN - ImGui::GetFrameHeightWithSpacing();
|
|
ImGuiToolkit::ButtonOpenUrl( "Edit", Settings::application.control.osc_filename.c_str(), ImVec2(w, 0) );
|
|
ImGui::SameLine(0, 6);
|
|
if ( ImGuiToolkit::IconButton(15, 12, "Reload") )
|
|
Control::manager().init();
|
|
ImGui::SameLine();
|
|
ImGui::Text("Translator");
|
|
|
|
//
|
|
// System preferences
|
|
//
|
|
ImGuiToolkit::Spacing();
|
|
// ImGuiToolkit::HelpMarker("If you encounter some rendering issues on your machine, "
|
|
// "you can try to disable some of the OpenGL optimizations below.");
|
|
// ImGui::SameLine();
|
|
ImGui::Text("System");
|
|
|
|
static bool need_restart = false;
|
|
static bool vsync = (Settings::application.render.vsync > 0);
|
|
static bool blit = Settings::application.render.blit;
|
|
static bool multi = (Settings::application.render.multisampling > 0);
|
|
static bool gpu = Settings::application.render.gpu_decoding;
|
|
bool change = false;
|
|
change |= ImGuiToolkit::ButtonSwitch( "Vertical synchronization", &vsync);
|
|
change |= ImGuiToolkit::ButtonSwitch( "Blit framebuffer", &blit);
|
|
#ifndef NDEBUG
|
|
change |= ImGuiToolkit::ButtonSwitch( "Antialiasing framebuffer", &multi);
|
|
#endif
|
|
// hardware support deserves more explanation
|
|
ImGuiToolkit::Indication("If enabled, tries to find a platform adapted hardware-accelerated "
|
|
"driver to decode (read) or encode (record) videos.", ICON_FA_MICROCHIP);
|
|
ImGui::SameLine(0);
|
|
change |= ImGuiToolkit::ButtonSwitch( "Hardware video en/decoding", &gpu);
|
|
|
|
if (change) {
|
|
need_restart = ( vsync != (Settings::application.render.vsync > 0) ||
|
|
blit != Settings::application.render.blit ||
|
|
multi != (Settings::application.render.multisampling > 0) ||
|
|
gpu != Settings::application.render.gpu_decoding );
|
|
}
|
|
if (need_restart) {
|
|
ImGuiToolkit::Spacing();
|
|
if (ImGui::Button( ICON_FA_POWER_OFF " Restart to apply", ImVec2(ImGui::GetContentRegionAvail().x - 50, 0))) {
|
|
Settings::application.render.vsync = vsync ? 1 : 0;
|
|
Settings::application.render.blit = blit;
|
|
Settings::application.render.multisampling = multi ? 3 : 0;
|
|
Settings::application.render.gpu_decoding = gpu;
|
|
if (UserInterface::manager().TryClose())
|
|
Rendering::manager().close();
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void Navigator::RenderTransitionPannel()
|
|
{
|
|
if (Settings::application.current_view < View::TRANSITION) {
|
|
hidePannel();
|
|
return;
|
|
}
|
|
|
|
// Next window is a side pannel
|
|
ImGui::SetNextWindowPos( ImVec2(width_, 0), ImGuiCond_Always );
|
|
ImGui::SetNextWindowSize( ImVec2(pannel_width_, height_), ImGuiCond_Always );
|
|
ImGui::SetNextWindowBgAlpha(0.85f); // Transparent background
|
|
if (ImGui::Begin("##navigatorTrans", NULL, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav))
|
|
{
|
|
// TITLE
|
|
ImGui::SetCursorPosY(IMGUI_TOP_ALIGN);
|
|
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
|
|
ImGui::Text("Transition");
|
|
ImGui::PopFont();
|
|
|
|
// Transition options
|
|
ImGuiToolkit::Spacing();
|
|
ImGui::Text("Transition");
|
|
if (ImGuiToolkit::IconButton(0, 8)) Settings::application.transition.cross_fade = true;
|
|
ImGui::SameLine(0, IMGUI_SAME_LINE);
|
|
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
|
int mode = Settings::application.transition.cross_fade ? 0 : 1;
|
|
if (ImGui::Combo("Fading", &mode, "Cross fading\0Fade to black\0") )
|
|
Settings::application.transition.cross_fade = mode < 1;
|
|
if (ImGuiToolkit::IconButton(4, 13)) Settings::application.transition.duration = 1.f;
|
|
ImGui::SameLine(0, IMGUI_SAME_LINE);
|
|
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
|
ImGui::SliderFloat("Duration", &Settings::application.transition.duration, TRANSITION_MIN_DURATION, TRANSITION_MAX_DURATION, "%.1f s");
|
|
if (ImGuiToolkit::IconButton(9, 1)) Settings::application.transition.profile = 0;
|
|
ImGui::SameLine(0, IMGUI_SAME_LINE);
|
|
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
|
|
ImGui::Combo("Curve", &Settings::application.transition.profile, "Linear\0Quadratic\0");
|
|
|
|
// specific transition actions
|
|
ImGuiToolkit::Spacing();
|
|
ImGui::Text("Actions");
|
|
if ( ImGui::Button( ICON_FA_TIMES " Cancel ", ImVec2(IMGUI_RIGHT_ALIGN, 0)) ){
|
|
TransitionView *tv = static_cast<TransitionView *>(Mixer::manager().view(View::TRANSITION));
|
|
if (tv) tv->cancel();
|
|
}
|
|
if ( ImGui::Button( ICON_FA_PLAY " Play ", ImVec2(IMGUI_RIGHT_ALIGN, 0)) ){
|
|
TransitionView *tv = static_cast<TransitionView *>(Mixer::manager().view(View::TRANSITION));
|
|
if (tv) tv->play(false);
|
|
}
|
|
ImGui::SameLine();
|
|
ImGui::Text("Animation");
|
|
if ( ImGui::Button( ICON_FA_FILE_UPLOAD " Open ", ImVec2(IMGUI_RIGHT_ALIGN, 0)) ){
|
|
TransitionView *tv = static_cast<TransitionView *>(Mixer::manager().view(View::TRANSITION));
|
|
if (tv) tv->open();
|
|
}
|
|
ImGui::SameLine();
|
|
ImGui::Text("Session");
|
|
|
|
// General transition actions
|
|
ImGui::Text(" ");
|
|
if ( ImGui::Button( ICON_FA_PLAY " Play & " ICON_FA_FILE_UPLOAD " Open ", ImVec2(ImGui::GetContentRegionAvail().x, 0)) ){
|
|
TransitionView *tv = static_cast<TransitionView *>(Mixer::manager().view(View::TRANSITION));
|
|
if (tv) tv->play(true);
|
|
}
|
|
if ( ImGui::Button( ICON_FA_DOOR_OPEN " Exit", ImVec2(ImGui::GetContentRegionAvail().x, 0)) )
|
|
Mixer::manager().setView(View::MIXING);
|
|
|
|
ImGui::End();
|
|
}
|
|
}
|
|
|
|
void Navigator::RenderMainPannel()
|
|
{
|
|
// Next window is a side pannel
|
|
ImGui::SetNextWindowPos( ImVec2(width_, 0), ImGuiCond_Always );
|
|
ImGui::SetNextWindowSize( ImVec2(pannel_width_, height_), ImGuiCond_Always );
|
|
ImGui::SetNextWindowBgAlpha(0.85f); // Transparent background
|
|
if (ImGui::Begin("##navigatorMain", NULL, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav))
|
|
{
|
|
//
|
|
// Panel content depends on show_config_
|
|
//
|
|
if (show_config_)
|
|
RenderMainPannelSettings();
|
|
else
|
|
RenderMainPannelVimix();
|
|
|
|
//
|
|
// Icon and About vimix
|
|
//
|
|
ImGuiContext& g = *GImGui;
|
|
const ImVec2 rightcorner(pannel_width_ + width_, height_);
|
|
const float remaining_height = height_ - ImGui::GetCursorPosY();
|
|
const float button_height = g.FontSize + g.Style.FramePadding.y * 2.0f + g.Style.ItemSpacing.y;
|
|
const float icon_height = 128;
|
|
// About vimix button (if enough room)
|
|
if (remaining_height > button_height + g.Style.ItemSpacing.y) {
|
|
int index_label = 0;
|
|
const char *button_label[2] = {ICON_FA_CROW " About vimix", "About vimix"};
|
|
// Logo (if enougth room)
|
|
if (remaining_height > icon_height + button_height + g.Style.ItemSpacing.y) {
|
|
static unsigned int vimixicon = Resource::getTextureImage("images/vimix_256x256.png");
|
|
ImGui::SetCursorScreenPos( rightcorner - ImVec2( (icon_height + pannel_width_) * 0.5f, icon_height + button_height + g.Style.ItemSpacing.y) );
|
|
ImGui::Image((void*)(intptr_t)vimixicon, ImVec2(icon_height, icon_height));
|
|
index_label = 1;
|
|
}
|
|
// Button About
|
|
ImGui::SetCursorScreenPos( rightcorner - ImVec2(pannel_width_ * 0.75f, button_height) );
|
|
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4());
|
|
if ( ImGui::Button( button_label[index_label], ImVec2(pannel_width_ * 0.5f, 0)) ) {
|
|
UserInterface::manager().show_vimix_about = true;
|
|
WorkspaceWindow::restoreWorkspace(true);
|
|
}
|
|
ImGui::PopStyleColor();
|
|
}
|
|
|
|
//
|
|
// Settings icon (non scollable) in Bottom-right corner
|
|
//
|
|
ImGui::SetCursorScreenPos( rightcorner - ImVec2(button_height, button_height));
|
|
const char *tooltip[2] = {"Settings", "Settings"};
|
|
ImGuiToolkit::IconToggle(13,5,12,5, &show_config_, tooltip);
|
|
|
|
ImGui::End();
|
|
}
|
|
}
|
|
|
|
///
|
|
/// SOURCE PREVIEW
|
|
///
|
|
|
|
SourcePreview::SourcePreview() : source_(nullptr), label_(""), reset_(0)
|
|
{
|
|
|
|
}
|
|
|
|
void SourcePreview::setSource(Source *s, const string &label)
|
|
{
|
|
if(source_)
|
|
delete source_;
|
|
|
|
source_ = s;
|
|
label_ = BaseToolkit::truncated(label, 35);
|
|
reset_ = true;
|
|
}
|
|
|
|
Source * SourcePreview::getSource()
|
|
{
|
|
Source *s = source_;
|
|
source_ = nullptr;
|
|
return s;
|
|
}
|
|
|
|
void SourcePreview::Render(float width)
|
|
{
|
|
static InfoVisitor _info;
|
|
|
|
if(source_) {
|
|
// cancel if failed
|
|
if (source_->failed()) {
|
|
// remove from list of recent import files if relevant
|
|
MediaSource *failedFile = dynamic_cast<MediaSource *>(source_);
|
|
if (failedFile != nullptr) {
|
|
Settings::application.recentImport.remove( failedFile->path() );
|
|
}
|
|
setSource();
|
|
}
|
|
else
|
|
{
|
|
// render framebuffer
|
|
if ( reset_ && source_->ready() ) {
|
|
// trick to ensure a minimum of 2 frames are rendered actively
|
|
source_->setActive(true);
|
|
source_->update( Mixer::manager().dt() );
|
|
source_->render();
|
|
source_->setActive(false);
|
|
reset_ = false;
|
|
}
|
|
else {
|
|
// update source
|
|
source_->update( Mixer::manager().dt() );
|
|
source_->render();
|
|
}
|
|
|
|
// draw preview
|
|
FrameBuffer *frame = source_->frame();
|
|
ImVec2 preview_size(width, width / frame->aspectRatio());
|
|
ImGui::Image((void*)(uintptr_t) frame->texture(), preview_size);
|
|
// if the source is playable and once its ready
|
|
if (source_->playable() && source_->ready()) {
|
|
// activate the source on mouse over
|
|
bool activate = ImGui::IsItemHovered();
|
|
if (source_->active() != activate)
|
|
source_->setActive(activate);
|
|
// show icon '>' to indicate if we can activate it
|
|
if (!activate) {
|
|
ImVec2 pos = ImGui::GetCursorPos();
|
|
ImGui::SetCursorPos(pos + preview_size * ImVec2(0.5f, -0.6f));
|
|
ImGuiToolkit::Icon(12,7);
|
|
ImGui::SetCursorPos(pos);
|
|
}
|
|
}
|
|
// information text
|
|
ImGuiToolkit::Icon(source_->icon().x, source_->icon().y);
|
|
ImGui::SameLine(0, IMGUI_SAME_LINE);
|
|
ImGui::Text("%s", label_.c_str());
|
|
if (source_->ready()) {
|
|
source_->accept(_info);
|
|
ImGui::Text("%s", _info.str().c_str());
|
|
}
|
|
else
|
|
ImGui::Text("loading...");
|
|
}
|
|
}
|
|
}
|
|
|
|
bool SourcePreview::ready() const
|
|
{
|
|
return source_ != nullptr && source_->ready();
|
|
}
|
|
|
|
///
|
|
/// THUMBNAIL
|
|
///
|
|
|
|
Thumbnail::Thumbnail() : aspect_ratio_(-1.f), texture_(0)
|
|
{
|
|
}
|
|
|
|
Thumbnail::~Thumbnail()
|
|
{
|
|
if (texture_)
|
|
glDeleteTextures(1, &texture_);
|
|
}
|
|
|
|
bool Thumbnail::filled()
|
|
{
|
|
return aspect_ratio_ > 0.f;
|
|
}
|
|
|
|
void Thumbnail::reset()
|
|
{
|
|
aspect_ratio_ = -1.f;
|
|
}
|
|
|
|
void Thumbnail::fill(const FrameBufferImage *image)
|
|
{
|
|
if (!texture_) {
|
|
glGenTextures(1, &texture_);
|
|
glBindTexture( GL_TEXTURE_2D, texture_);
|
|
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGB8, SESSION_THUMBNAIL_HEIGHT * 2, SESSION_THUMBNAIL_HEIGHT);
|
|
}
|
|
|
|
aspect_ratio_ = static_cast<float>(image->width) / static_cast<float>(image->height);
|
|
glBindTexture( GL_TEXTURE_2D, texture_);
|
|
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, image->width, image->height, GL_RGB, GL_UNSIGNED_BYTE, image->rgb);
|
|
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
|
}
|
|
|
|
void Thumbnail::Render(float width)
|
|
{
|
|
if (filled())
|
|
ImGui::Image((void*)(intptr_t)texture_, ImVec2(width, width/aspect_ratio_), ImVec2(0,0), ImVec2(0.5f*aspect_ratio_, 1.f));
|
|
}
|
|
|
|
///
|
|
/// UTILITY
|
|
///
|
|
|
|
#define SEGMENT_ARRAY_MAX 1000
|
|
#define MAXSIZE 65535
|
|
|
|
|
|
void ShowSandbox(bool* p_open)
|
|
{
|
|
ImGui::SetNextWindowPos(ImVec2(100, 100), ImGuiCond_FirstUseEver);
|
|
ImGui::SetNextWindowSize(ImVec2(400, 260), ImGuiCond_FirstUseEver);
|
|
if (!ImGui::Begin( ICON_FA_BABY_CARRIAGE " Sandbox", p_open))
|
|
{
|
|
ImGui::End();
|
|
return;
|
|
}
|
|
|
|
ImGui::Text("Testing sandox");
|
|
ImGui::Separator();
|
|
|
|
ImGui::Text("IMAGE of Font");
|
|
|
|
ImGuiToolkit::ImageGlyph(ImGuiToolkit::FONT_DEFAULT, 'v');
|
|
ImGui::SameLine();
|
|
ImGuiToolkit::ImageGlyph(ImGuiToolkit::FONT_BOLD, 'i');
|
|
ImGui::SameLine();
|
|
ImGuiToolkit::ImageGlyph(ImGuiToolkit::FONT_ITALIC, 'm');
|
|
ImGui::SameLine();
|
|
ImGuiToolkit::ImageGlyph(ImGuiToolkit::FONT_MONO, 'i');
|
|
ImGui::SameLine();
|
|
ImGuiToolkit::ImageGlyph(ImGuiToolkit::FONT_LARGE, 'x');
|
|
|
|
ImGui::Separator();
|
|
ImGui::Text("Source list");
|
|
Session *se = Mixer::manager().session();
|
|
for (auto sit = se->begin(); sit != se->end(); ++sit) {
|
|
|
|
ImGui::Text("[%s] %s ", std::to_string((*sit)->id()).c_str(), (*sit)->name().c_str());
|
|
}
|
|
|
|
ImGui::Separator();
|
|
static char str[128] = "";
|
|
ImGui::InputText("Command", str, IM_ARRAYSIZE(str));
|
|
if ( ImGui::Button("Execute") )
|
|
SystemToolkit::execute(str);
|
|
|
|
static char str0[128] = "àöäüèáû вторая строчка";
|
|
ImGui::InputText("##inputtext", str0, IM_ARRAYSIZE(str0));
|
|
std::string tra = BaseToolkit::transliterate(std::string(str0));
|
|
ImGui::Text("Transliteration: '%s'", tra.c_str());
|
|
|
|
ImGui::Separator();
|
|
|
|
static bool selected[25] = { true, false, false, false, false,
|
|
true, false, false, false, false,
|
|
true, false, false, true, false,
|
|
false, false, false, true, false,
|
|
false, false, false, true, false };
|
|
|
|
ImVec2 keyIconSize = ImVec2(60,60);
|
|
|
|
ImGuiContext& g = *GImGui;
|
|
ImVec2 itemsize = keyIconSize + g.Style.ItemSpacing;
|
|
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
|
|
ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, ImVec2(0.5f, 0.50f));
|
|
ImVec2 frame_top = ImGui::GetCursorScreenPos();
|
|
|
|
|
|
static int key = 0;
|
|
static ImVec2 current(-1.f, -1.f);
|
|
|
|
for (int i = 0; i < 25; ++i) {
|
|
ImGui::PushID(i);
|
|
char letter[2];
|
|
sprintf(letter, "%c", 'A' + i);
|
|
if (ImGui::Selectable(letter, selected[i], 0, keyIconSize))
|
|
{
|
|
current = ImVec2(i % 5, i / 5);
|
|
key = GLFW_KEY_A + i;
|
|
}
|
|
ImGui::PopID();
|
|
if ((i % 5) < 4) ImGui::SameLine();
|
|
}
|
|
ImGui::PopStyleVar();
|
|
ImGui::PopFont();
|
|
|
|
if (current.x > -1 && current.y > -1) {
|
|
ImVec2 pos = frame_top + itemsize * current;
|
|
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
|
draw_list->AddRect(pos, pos + keyIconSize, ImGui::GetColorU32(ImGuiCol_Text), 6.f, ImDrawCornerFlags_All, 3.f);
|
|
|
|
}
|
|
|
|
|
|
|
|
ImGui::End();
|
|
}
|
|
|
|
void ShowAboutOpengl(bool* p_open)
|
|
{
|
|
ImGui::SetNextWindowPos(ImVec2(520, 320), ImGuiCond_FirstUseEver);
|
|
if (!ImGui::Begin("About OpenGL", p_open, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_AlwaysAutoResize))
|
|
{
|
|
ImGui::End();
|
|
return;
|
|
}
|
|
|
|
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_BOLD);
|
|
ImGui::Text("OpenGL %s", glGetString(GL_VERSION) );
|
|
ImGui::PopFont();
|
|
ImGui::Separator();
|
|
ImGui::Text("OpenGL is the premier environment for developing portable, \ninteractive 2D and 3D graphics applications.");
|
|
ImGuiToolkit::ButtonOpenUrl("Visit website", "https://www.opengl.org");
|
|
ImGui::SameLine();
|
|
|
|
static bool show_opengl_info = false;
|
|
ImGui::SetNextItemWidth(-100.f);
|
|
ImGui::Text(" Details");
|
|
ImGui::SameLine();
|
|
|
|
ImGuiToolkit::IconToggle(10,0,11,0,&show_opengl_info);
|
|
if (show_opengl_info)
|
|
{
|
|
ImGui::Separator();
|
|
bool copy_to_clipboard = ImGui::Button(MENU_COPY);
|
|
ImGui::SameLine(0.f, 60.f);
|
|
static char _openglfilter[64] = "";
|
|
ImGui::InputText("Filter", _openglfilter, 64);
|
|
ImGui::SameLine();
|
|
if ( ImGuiToolkit::ButtonIcon( 12, 14 ) )
|
|
_openglfilter[0] = '\0';
|
|
std::string filter(_openglfilter);
|
|
|
|
ImGui::BeginChildFrame(ImGui::GetID("gstinfos"), ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * 18), ImGuiWindowFlags_NoMove);
|
|
if (copy_to_clipboard)
|
|
{
|
|
ImGui::LogToClipboard();
|
|
ImGui::LogText("```\n");
|
|
}
|
|
|
|
ImGui::Text("OpenGL %s", glGetString(GL_VERSION) );
|
|
ImGui::Text("%s %s", glGetString(GL_RENDERER), glGetString(GL_VENDOR));
|
|
ImGui::Text("Extensions (runtime) :");
|
|
|
|
GLint numExtensions = 0;
|
|
glGetIntegerv( GL_NUM_EXTENSIONS, &numExtensions );
|
|
for (int i = 0; i < numExtensions; ++i){
|
|
std::string ext( (char*) glGetStringi(GL_EXTENSIONS, i) );
|
|
if ( filter.empty() || ext.find(filter) != std::string::npos )
|
|
ImGui::Text("%s", ext.c_str());
|
|
}
|
|
|
|
|
|
if (copy_to_clipboard)
|
|
{
|
|
ImGui::LogText("\n```\n");
|
|
ImGui::LogFinish();
|
|
}
|
|
|
|
ImGui::EndChildFrame();
|
|
}
|
|
ImGui::End();
|
|
}
|
|
|
|
void ShowAboutGStreamer(bool* p_open)
|
|
{
|
|
ImGui::SetNextWindowPos(ImVec2(430, 20), ImGuiCond_Appearing);
|
|
ImGui::SetNextWindowSize(ImVec2(600, 200), ImGuiCond_Appearing);
|
|
if (ImGui::Begin("About Gstreamer", p_open, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings))
|
|
{
|
|
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_BOLD);
|
|
ImGui::Text("GStreamer %s", GstToolkit::gst_version().c_str());
|
|
ImGui::PopFont();
|
|
ImGui::Separator();
|
|
ImGui::Text("A flexible, fast and multiplatform multimedia framework.");
|
|
ImGui::Text("GStreamer is licensed under the LGPL License.");
|
|
ImGuiToolkit::ButtonOpenUrl("Visit website", "https://gstreamer.freedesktop.org/");
|
|
ImGui::SameLine();
|
|
|
|
static bool show_config_info = false;
|
|
ImGui::SetNextItemWidth(-100.f);
|
|
ImGui::Text(" Details");
|
|
ImGui::SameLine();
|
|
ImGuiToolkit::IconToggle(10,0,11,0,&show_config_info);
|
|
if (show_config_info)
|
|
{
|
|
ImGui::Separator();
|
|
bool copy_to_clipboard = ImGui::Button(MENU_COPY);
|
|
ImGui::SameLine(0.f, 60.f);
|
|
static char _filter[64] = ""; ImGui::InputText("Filter", _filter, 64);
|
|
ImGui::SameLine();
|
|
if ( ImGuiToolkit::ButtonIcon( 12, 14 ) )
|
|
_filter[0] = '\0';
|
|
std::string filter(_filter);
|
|
|
|
ImGui::BeginChildFrame(ImGui::GetID("gstinfos"), ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * 18), ImGuiWindowFlags_NoMove);
|
|
if (copy_to_clipboard)
|
|
{
|
|
ImGui::LogToClipboard();
|
|
ImGui::LogText("```\n");
|
|
}
|
|
|
|
ImGui::Text("GStreamer %s", GstToolkit::gst_version().c_str());
|
|
ImGui::Text("Plugins & features (runtime) :");
|
|
|
|
std::list<std::string> filteredlist;
|
|
static std::list<std::string> pluginslist;
|
|
static std::map<std::string, std::list<std::string> > featureslist;
|
|
if (pluginslist.empty()) {
|
|
pluginslist = GstToolkit::all_plugins();
|
|
for (auto const& i: pluginslist) {
|
|
// list features
|
|
featureslist[i] = GstToolkit::all_plugin_features(i);
|
|
}
|
|
}
|
|
|
|
// filter list
|
|
if ( filter.empty() )
|
|
filteredlist = pluginslist;
|
|
else {
|
|
for (auto const& i: pluginslist) {
|
|
// add plugin if plugin name matches
|
|
if ( i.find(filter) != std::string::npos )
|
|
filteredlist.push_back( i );
|
|
// check in features
|
|
for (auto const& j: featureslist[i]) {
|
|
// add plugin if feature name matches
|
|
if ( j.find(filter) != std::string::npos )
|
|
filteredlist.push_back( i );
|
|
}
|
|
}
|
|
filteredlist.unique();
|
|
}
|
|
|
|
// display list
|
|
for (auto const& t: filteredlist) {
|
|
ImGui::Text("> %s", t.c_str());
|
|
for (auto const& j: featureslist[t]) {
|
|
if ( j.find(filter) != std::string::npos )
|
|
{
|
|
ImGui::Text(" - %s", j.c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (copy_to_clipboard)
|
|
{
|
|
ImGui::LogText("\n```\n");
|
|
ImGui::LogFinish();
|
|
}
|
|
|
|
ImGui::EndChildFrame();
|
|
}
|
|
ImGui::End();
|
|
}
|
|
}
|
|
|
|
void SetMouseCursor(ImVec2 mousepos, View::Cursor c)
|
|
{
|
|
// Hack if GLFW does not have all cursors, ask IMGUI to redraw cursor
|
|
#if GLFW_HAS_NEW_CURSORS == 0
|
|
ImGui::GetIO().MouseDrawCursor = (c.type > 0); // only redraw non-arrow cursor
|
|
#endif
|
|
ImGui::SetMouseCursor(c.type);
|
|
|
|
if ( !c.info.empty()) {
|
|
float d = 0.5f * ImGui::GetFrameHeight() ;
|
|
ImVec2 window_pos = ImVec2( mousepos.x - d, mousepos.y - d );
|
|
ImGui::SetNextWindowPos(window_pos, ImGuiCond_Always);
|
|
ImGui::SetNextWindowBgAlpha(0.75f); // Transparent background
|
|
if (ImGui::Begin("MouseInfoContext", NULL, ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav))
|
|
{
|
|
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO);
|
|
ImGui::Text(" %s", c.info.c_str());
|
|
ImGui::PopFont();
|
|
ImGui::End();
|
|
}
|
|
}
|
|
}
|