Code cleanup. Split code for each workspace windows

Renamed class and split code for compiling the multiple workspace windows as separate files (h and cpp).
This commit is contained in:
Bruno Herbelin
2023-08-03 16:18:00 +02:00
parent 49605f9c23
commit 1b658e9b40
17 changed files with 5201 additions and 4990 deletions

View File

@@ -65,6 +65,12 @@ set(VMIX_SRCS
DelayFilter.cpp DelayFilter.cpp
RenderingManager.cpp RenderingManager.cpp
UserInterfaceManager.cpp UserInterfaceManager.cpp
WorkspaceWindow.cpp
SourceControlWindow.cpp
OutputPreviewWindow.cpp
TimerMetronomeWindow.cpp
InputMappingWindow.cpp
ShaderEditWindow.cpp
PickingVisitor.cpp PickingVisitor.cpp
BoundingBoxVisitor.cpp BoundingBoxVisitor.cpp
DrawVisitor.cpp DrawVisitor.cpp

View File

@@ -94,6 +94,17 @@ namespace ImGuiToolkit
bool WindowButton(const char* window_name, ImVec2 window_pos, const char* text); bool WindowButton(const char* window_name, ImVec2 window_pos, const char* text);
void WindowDragFloat(const char* window_name, ImVec2 window_pos, float* v, float v_speed, float v_min, float v_max, const char* format); void WindowDragFloat(const char* window_name, ImVec2 window_pos, float* v, float v_speed, float v_min, float v_max, const char* format);
// 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);
}
};
} }
#endif // __IMGUI_TOOLKIT_H_ #endif // __IMGUI_TOOLKIT_H_

1303
src/InputMappingWindow.cpp Normal file

File diff suppressed because it is too large Load Diff

32
src/InputMappingWindow.h Normal file
View File

@@ -0,0 +1,32 @@
#ifndef INPUTMAPPINGWINDOW_H
#define INPUTMAPPINGWINDOW_H
#include <string>
#include <array>
#include "SourceList.h"
#include "WorkspaceWindow.h"
class SourceCallback;
class InputMappingWindow : public WorkspaceWindow
{
std::array< std::string, 4 > input_mode;
std::array< uint, 4 > current_input_for_mode;
uint current_input_;
Target ComboSelectTarget(const Target &current);
uint ComboSelectCallback(uint current, bool imageprocessing);
void SliderParametersCallback(SourceCallback *callback, const Target &target);
public:
InputMappingWindow();
void Render();
void setVisible(bool on);
// from WorkspaceWindow
bool Visible() const override;
};
#endif // INPUTMAPPINGWINDOW_H

634
src/OutputPreviewWindow.cpp Normal file
View File

@@ -0,0 +1,634 @@
/*
* This file is part of vimix - video live mixer
*
* **Copyright** (C) 2019-2023 Bruno Herbelin <bruno.herbelin@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/
#include <iostream>
#include <thread>
#include <algorithm>
// ImGui
#include "ImGuiToolkit.h"
#include "imgui_internal.h"
#include "defines.h"
#include "Log.h"
#include "SystemToolkit.h"
#include "NetworkToolkit.h"
#include "Settings.h"
#include "Mixer.h"
#include "Recorder.h"
#include "Connection.h"
#include "Streamer.h"
#include "Loopback.h"
#include "VideoBroadcast.h"
#include "ShmdataBroadcast.h"
#include "UserInterfaceManager.h"
#include "OutputPreviewWindow.h"
OutputPreviewWindow::OutputPreviewWindow() : WorkspaceWindow("OutputPreview"),
video_recorder_(nullptr), video_broadcaster_(nullptr), loopback_broadcaster_(nullptr),
magnifying_glass(false)
{
recordFolderDialog = new DialogToolkit::OpenFolderDialog("Recording Location");
}
void OutputPreviewWindow::setVisible(bool on)
{
magnifying_glass = false;
// restore workspace to show the window
if (WorkspaceWindow::clear_workspace_enabled) {
WorkspaceWindow::restoreWorkspace(on);
// do not change status if ask to hide (consider user asked to toggle because the window was not visible)
if (!on) return;
}
if (Settings::application.widget.preview_view > 0 && Settings::application.widget.preview_view != Settings::application.current_view) {
Settings::application.widget.preview_view = -1;
on = true;
}
Settings::application.widget.preview = on;
}
bool OutputPreviewWindow::Visible() const
{
return ( Settings::application.widget.preview
&& (Settings::application.widget.preview_view < 0 || Settings::application.widget.preview_view == Settings::application.current_view )
);
}
void OutputPreviewWindow::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 frame grabbers are valid (change to nullptr if invalid)
FrameGrabbing::manager().verify( (FrameGrabber**) &video_broadcaster_);
FrameGrabbing::manager().verify( (FrameGrabber**) &shm_broadcaster_);
FrameGrabbing::manager().verify( (FrameGrabber**) &loopback_broadcaster_);
}
VideoRecorder *delayTrigger(VideoRecorder *g, std::chrono::milliseconds delay) {
std::this_thread::sleep_for (delay);
return g;
}
void OutputPreviewWindow::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(SystemToolkit::base_filename( Mixer::manager().session()->filename()));
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(SystemToolkit::base_filename( Mixer::manager().session()->filename())),
std::chrono::seconds(Settings::application.record.delay)) );
}
}
void OutputPreviewWindow::ToggleVideoBroadcast()
{
if (video_broadcaster_) {
video_broadcaster_->stop();
}
else {
if (Settings::application.broadcast_port<1000)
Settings::application.broadcast_port = BROADCAST_DEFAULT_PORT;
video_broadcaster_ = new VideoBroadcast(Settings::application.broadcast_port);
FrameGrabbing::manager().add(video_broadcaster_);
}
}
void OutputPreviewWindow::ToggleSharedMemory()
{
if (shm_broadcaster_) {
shm_broadcaster_->stop();
}
else {
// find a folder to put the socket for shm
std::string _shm_socket_file = Settings::application.shm_socket_path;
if (_shm_socket_file.empty() || !SystemToolkit::file_exists(_shm_socket_file))
_shm_socket_file = SystemToolkit::home_path();
_shm_socket_file = SystemToolkit::full_filename(_shm_socket_file, ".shm_vimix" + std::to_string(Settings::application.instance_id));
// create shhmdata broadcaster with method
shm_broadcaster_ = new ShmdataBroadcast( (ShmdataBroadcast::Method) Settings::application.shm_method, _shm_socket_file);
FrameGrabbing::manager().add(shm_broadcaster_);
}
}
bool OutputPreviewWindow::ToggleLoopbackCamera()
{
bool need_initialization = false;
if (loopback_broadcaster_) {
loopback_broadcaster_->stop();
}
else {
if (Settings::application.loopback_camera < 1)
Settings::application.loopback_camera = LOOPBACK_DEFAULT_DEVICE;
Settings::application.loopback_camera += Settings::application.instance_id;
try {
loopback_broadcaster_ = new Loopback(Settings::application.loopback_camera);
FrameGrabbing::manager().add(loopback_broadcaster_);
}
catch (const std::runtime_error &e) {
need_initialization = true;
Log::Info("%s", e.what());
}
}
return need_initialization;
}
void OutputPreviewWindow::Render()
{
const ImGuiContext& g = *GImGui;
bool openInitializeSystemLoopback = false;
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), ImGuiToolkit::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))
{
// Preview and output menu
ImGui::MenuItem( MENU_LARGEPREVIEW, SHORTCUT_LARGEPREVIEW, &UserInterface::manager().show_output_fullview);
ImGui::MenuItem( MENU_OUTPUTDISABLE, SHORTCUT_OUTPUTDISABLE, &Settings::application.render.disabled);
// Display window 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( ICON_FA_ARROW_ALT_CIRCLE_DOWN " Capture"))
{
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_CAPTURE, 0.8f));
if ( ImGui::MenuItem( MENU_CAPTUREFRAME, SHORTCUT_CAPTURE_DISPLAY) ) {
FrameGrabbing::manager().add(new PNGRecorder(SystemToolkit::base_filename( Mixer::manager().session()->filename())));
}
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(SystemToolkit::base_filename( Mixer::manager().session()->filename()));
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(SystemToolkit::base_filename( Mixer::manager().session()->filename())),
std::chrono::seconds(Settings::application.record.delay)) );
}
ImGui::MenuItem( MENU_RECORDCONT, SHORTCUT_RECORDCONT, false, false);
ImGui::PopStyleColor(1);
}
// Options menu
ImGui::Separator();
ImGui::MenuItem("Settings", 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, "Advanced settings"))
UserInterface::manager().navigator.showConfig();
// 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();
// offer to open folder location
ImVec2 draw_pos = ImGui::GetCursorPos();
ImGui::SetCursorPos(draw_pos + ImVec2(ImGui::GetContentRegionAvailWidth() - 1.2 * ImGui::GetTextLineHeightWithSpacing(), -ImGui::GetFrameHeight()) );
if (ImGuiToolkit::IconButton( ICON_FA_FOLDER_OPEN, Settings::application.record.path.c_str()))
SystemToolkit::open(Settings::application.record.path);
ImGui::SetCursorPos(draw_pos);
// Naming menu selection
static const char* naming_style[2] = { ICON_FA_SORT_NUMERIC_DOWN " Sequential", ICON_FA_CALENDAR " Date prefix" };
ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN);
ImGui::Combo("Filename", &Settings::application.record.naming_mode, naming_style, IM_ARRAYSIZE(naming_style));
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(ICON_FA_WIFI " Stream"))
{
// Stream sharing menu
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_STREAM, 0.9f));
if ( ImGui::MenuItem( ICON_FA_SHARE_ALT_SQUARE " P2P Peer-to-peer sharing", NULL, &Settings::application.accept_connections) ) {
Streaming::manager().enable(Settings::application.accept_connections);
}
ImGui::PopStyleColor(1);
// list active streams:
std::vector<std::string> ls = Streaming::manager().listStreams();
// Broadcasting menu
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_BROADCAST, 0.9f));
if (VideoBroadcast::available()) {
if ( ImGui::MenuItem( ICON_FA_GLOBE " SRT Broadcast", NULL, videoBroadcastEnabled()) )
ToggleVideoBroadcast();
}
// Shared Memory menu
if (ShmdataBroadcast::available()) {
if ( ImGui::MenuItem( ICON_FA_MEMORY " SHM Shared Memory", NULL, sharedMemoryEnabled()) )
ToggleSharedMemory();
}
// Loopback camera menu
if (Loopback::available()) {
if ( ImGui::MenuItem( ICON_FA_VIDEO " Loopback Camera", NULL, loopbackCameraEnabled()) )
openInitializeSystemLoopback = ToggleLoopbackCamera();
}
ImGui::PopStyleColor(1);
// Display list of active stream
if (ls.size()>0 || videoBroadcastEnabled() || sharedMemoryEnabled() || loopbackCameraEnabled()) {
ImGui::Separator();
ImGui::MenuItem("Active streams:", nullptr, false, false);
// First the list of peer 2 peer
for (auto it = ls.begin(); it != ls.end(); ++it)
ImGui::Text(" %s ", (*it).c_str() );
// SRT broadcast description
if (videoBroadcastEnabled()) {
ImGui::Text(" %s ", video_broadcaster_->info().c_str());
// copy text icon to give user the srt link to connect to
ImVec2 draw_pos = ImGui::GetCursorPos();
ImGui::SetCursorPos(draw_pos + ImVec2(ImGui::GetContentRegionAvailWidth() - 1.2 * ImGui::GetTextLineHeightWithSpacing(), -0.8 * ImGui::GetFrameHeight()) );
char msg[256];
ImFormatString(msg, IM_ARRAYSIZE(msg), "srt//%s:%d", NetworkToolkit::host_ips()[1].c_str(), Settings::application.broadcast_port );
if (ImGuiToolkit::IconButton( ICON_FA_COPY, msg))
ImGui::SetClipboardText(msg);
ImGui::SetCursorPos(draw_pos);
}
// Shared memory broadcast description
if (sharedMemoryEnabled()) {
ImGui::Text(" %s ", shm_broadcaster_->info().c_str());
// copy text icon to give user the socket path to connect to
ImVec2 draw_pos = ImGui::GetCursorPos();
ImGui::SetCursorPos(draw_pos + ImVec2(ImGui::GetContentRegionAvailWidth() - 1.2 * ImGui::GetTextLineHeightWithSpacing(), -0.8 * ImGui::GetFrameHeight()) );
if (ImGuiToolkit::IconButton( ICON_FA_COPY, shm_broadcaster_->gst_pipeline().c_str()))
ImGui::SetClipboardText(shm_broadcaster_->gst_pipeline().c_str());
ImGui::SetCursorPos(draw_pos);
}
// Loopback camera description
if (loopbackCameraEnabled()) {
ImGui::Text(" %s ", loopback_broadcaster_->info().c_str());
// copy text icon to give user the device path to connect to
ImVec2 draw_pos = ImGui::GetCursorPos();
ImGui::SetCursorPos(draw_pos + ImVec2(ImGui::GetContentRegionAvailWidth() - 1.2 * ImGui::GetTextLineHeightWithSpacing(), -0.8 * ImGui::GetFrameHeight()) );
if (ImGuiToolkit::IconButton( ICON_FA_COPY, loopback_broadcaster_->device_name().c_str()))
ImGui::SetClipboardText(loopback_broadcaster_->device_name().c_str());
ImGui::SetCursorPos(draw_pos);
}
}
else {
ImGui::Separator();
ImGui::MenuItem("No active streams", nullptr, false, false);
}
ImGui::EndMenu();
}
// button to activate the magnifying glass at top right corner
ImVec2 p = g.CurrentWindow->Pos;
p.x += g.CurrentWindow->Size.x - 2.1f * g.FontSize;
if (g.CurrentWindow->DC.CursorPos.x < p.x)
{
ImGui::SetCursorScreenPos(p);
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.f, 0.f, 0.f, 0.f));
ImGuiToolkit::ButtonToggle( ICON_FA_SEARCH, &magnifying_glass);
ImGui::PopStyleColor();
}
ImGui::EndMenuBar();
}
// image takes the available window area
ImVec2 imagesize = ImGui::GetContentRegionAvail();
// image height respects original aspect ratio but is at most the available window height
imagesize.y = MIN( imagesize.x / ar, imagesize.y);
// image respects original aspect ratio
imagesize.x = imagesize.y * ar;
// virtual button to show the output window when clic on the preview
ImVec2 draw_pos = ImGui::GetCursorScreenPos();
// 100% opacity for the image (ensures true colors)
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 1.f);
ImGui::Image((void*)(intptr_t)output->texture(), imagesize);
ImGui::PopStyleVar();
// mouse over the image
if ( ImGui::IsItemHovered() ) {
// show magnifying glass if active
if (magnifying_glass)
DrawInspector(output->texture(), imagesize, imagesize, draw_pos);
}
// disable magnifying glass if window is deactivated
if ( g.NavWindow != g.CurrentWindow )
magnifying_glass = false;
///
/// Icons overlays
///
const float r = ImGui::GetTextLineHeightWithSpacing();
// info indicator
bool drawoverlay = false;
if (!magnifying_glass) {
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.f, 0.f, 0.f, 0.8f));
ImGui::SetCursorScreenPos(draw_pos + ImVec2(imagesize.x - r, 6));
ImGui::Text(ICON_FA_CIRCLE);
ImGui::PopStyleColor(1);
ImGui::SetCursorScreenPos(draw_pos + ImVec2(imagesize.x - r, 6));
ImGui::Text(ICON_FA_INFO_CIRCLE);
drawoverlay = ImGui::IsItemHovered();
}
// icon indicators
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE);
// recording indicator
if (video_recorder_)
{
ImGui::SetCursorScreenPos(ImVec2(draw_pos.x + r, draw_pos.y + r));
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_RECORD, 0.8f));
ImGui::Text(ICON_FA_CIRCLE " %s", video_recorder_->info().c_str() );
ImGui::PopStyleColor(1);
}
else if (!_video_recorders.empty())
{
ImGui::SetCursorScreenPos(ImVec2(draw_pos.x + r, draw_pos.y + r));
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);
}
// broadcast indicator
float vertical = r;
if (video_broadcaster_)
{
ImGui::SetCursorScreenPos(ImVec2(draw_pos.x + imagesize.x - 2.5f * r, draw_pos.y + vertical));
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_GLOBE);
ImGui::PopStyleColor(1);
vertical += 2.f * r;
}
// shmdata indicator
if (shm_broadcaster_)
{
ImGui::SetCursorScreenPos(ImVec2(draw_pos.x + imagesize.x - 2.5f * r, draw_pos.y + vertical));
if (shm_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_MEMORY);
ImGui::PopStyleColor(1);
vertical += 2.f * r;
}
// loopback camera indicator
if (loopback_broadcaster_)
{
ImGui::SetCursorScreenPos(ImVec2(draw_pos.x + imagesize.x - 2.5f * r, draw_pos.y + vertical));
if (loopback_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_VIDEO);
ImGui::PopStyleColor(1);
}
// 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));
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);
}
// OUTPUT DISABLED indicator
if (Settings::application.render.disabled)
{
ImGui::SetCursorScreenPos(ImVec2(draw_pos.x + r, draw_pos.y + imagesize.y - 2.f*r));
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(COLOR_WINDOW, 0.8f));
ImGui::Text(ICON_FA_EYE_SLASH);
ImGui::PopStyleColor(1);
}
ImGui::PopFont();
///
/// Info overlay over image
///
if (drawoverlay)
{
ImDrawList* draw_list = ImGui::GetWindowDrawList();
float h = 1.f;
h += (Settings::application.accept_connections ? 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_LAPTOP " %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 " Available as %s (%ld peer connected)",
Connection::manager().info().name.c_str(),
Streaming::manager().listStreams().size() );
}
ImGui::End();
}
// Dialog to explain to the user how to initialize the loopback on the system
if (openInitializeSystemLoopback && !ImGui::IsPopupOpen("Initialize System Loopback"))
ImGui::OpenPopup("Initialize System Loopback");
if (ImGui::BeginPopupModal("Initialize System Loopback", NULL, ImGuiWindowFlags_AlwaysAutoResize))
{
#if defined(LINUX)
int w = 600;
ImGui::Text("In order to enable the video4linux camera loopback,\n"
"'v4l2loopack' has to be installed and initialized on your machine");
ImGui::Spacing();
ImGuiToolkit::ButtonOpenUrl("More information online on v4l2loopback", "https://github.com/umlaeute/v4l2loopback");
ImGui::Spacing();
ImGui::Text("To do so, the following commands should be executed\n(with admin rights):");
static char dummy_str[512];
sprintf(dummy_str, "sudo apt install v4l2loopback-dkms");
ImGui::NewLine();
ImGui::Text("Install v4l2loopack (only once, and reboot):");
ImGui::SetNextItemWidth(600-40);
ImGui::InputText("##cmd1", dummy_str, IM_ARRAYSIZE(dummy_str), ImGuiInputTextFlags_ReadOnly);
ImGui::SameLine();
ImGui::PushID(358794);
if ( ImGuiToolkit::IconButton(ICON_FA_COPY, "Copy to clipboard") )
ImGui::SetClipboardText(dummy_str);
ImGui::PopID();
sprintf(dummy_str, "sudo modprobe v4l2loopback exclusive_caps=1 video_nr=%d"
" card_label=\"vimix loopback\"" , Settings::application.loopback_camera);
ImGui::NewLine();
ImGui::Text("Initialize v4l2loopack:");
ImGui::SetNextItemWidth(600-40);
ImGui::InputText("##cmd2", dummy_str, IM_ARRAYSIZE(dummy_str), ImGuiInputTextFlags_ReadOnly);
ImGui::SameLine();
ImGui::PushID(899872);
if ( ImGuiToolkit::IconButton(ICON_FA_COPY, "Copy to clipboard") )
ImGui::SetClipboardText(dummy_str);
ImGui::PopID();
ImGui::NewLine();
ImGui::SetItemDefaultFocus();
if (ImGui::Button("Ok, I'll do this in a terminal and try again later.", ImVec2(w, 0))
|| ImGui::IsKeyPressedMap(ImGuiKey_Enter) || ImGui::IsKeyPressedMap(ImGuiKey_KeyPadEnter) ) {
ImGui::CloseCurrentPopup();
}
#endif
ImGui::EndPopup();
}
}

52
src/OutputPreviewWindow.h Normal file
View File

@@ -0,0 +1,52 @@
#ifndef OUTPUTPREVIEWWINDOW_H
#define OUTPUTPREVIEWWINDOW_H
#include "DialogToolkit.h"
#include "WorkspaceWindow.h"
class VideoRecorder;
class VideoBroadcast;
class ShmdataBroadcast;
class Loopback;
class OutputPreviewWindow : public WorkspaceWindow
{
// frame grabbers
VideoRecorder *video_recorder_;
VideoBroadcast *video_broadcaster_;
ShmdataBroadcast *shm_broadcaster_;
Loopback *loopback_broadcaster_;
// delayed trigger for recording
std::vector< std::future<VideoRecorder *> > _video_recorders;
// dialog to select record location
DialogToolkit::OpenFolderDialog *recordFolderDialog;
// magnifying glass
bool magnifying_glass;
public:
OutputPreviewWindow();
void ToggleRecord(bool save_and_continue = false);
inline bool isRecording() const { return video_recorder_ != nullptr; }
void ToggleVideoBroadcast();
inline bool videoBroadcastEnabled() const { return video_broadcaster_ != nullptr; }
void ToggleSharedMemory();
inline bool sharedMemoryEnabled() const { return shm_broadcaster_ != nullptr; }
bool ToggleLoopbackCamera();
inline bool loopbackCameraEnabled() const { return loopback_broadcaster_!= nullptr; }
void Render();
void setVisible(bool on);
// from WorkspaceWindow
void Update() override;
bool Visible() const override;
};
#endif // OUTPUTPREVIEWWINDOW_H

354
src/ShaderEditWindow.cpp Normal file
View File

@@ -0,0 +1,354 @@
// ImGui
#include "ImGuiToolkit.h"
#include "imgui_internal.h"
#include <glm/glm.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <GLFW/glfw3.h>
#include "TextEditor.h"
TextEditor _editor;
#include "defines.h"
#include "Settings.h"
#include "Mixer.h"
#include "SystemToolkit.h"
#include "CloneSource.h"
#include "ShaderEditWindow.h"
///
/// SHADER EDITOR
///
///
ShaderEditWindow::ShaderEditWindow() : WorkspaceWindow("Shader"), current_(nullptr), show_shader_inputs_(false)
{
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", "pow", "exp2", "log2", "sqrt", "inversesqrt",
"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 = "GLSL function";
lang.mIdentifiers.insert(std::make_pair(std::string(k), id));
}
static const char* const filter_keyword[] = {
"iResolution", "iTime", "iTimeDelta", "iFrame", "iChannelResolution", "iDate", "iMouse",
"iChannel0", "iChannel1", "iTransform", "FragColor", "vertexColor", "vertexUV"
};
for (auto& k : filter_keyword)
{
TextEditor::Identifier id;
id.mDeclaration = "Shader keyword";
lang.mPreprocIdentifiers.insert(std::make_pair(std::string(k), id));
}
// init editor
_editor.SetLanguageDefinition(lang);
_editor.SetHandleKeyboardInputs(true);
_editor.SetShowWhitespaces(false);
_editor.SetText("");
_editor.SetReadOnly(true);
// status
status_ = "-";
}
void ShaderEditWindow::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.shader_editor_view != Settings::application.current_view)
Settings::application.widget.shader_editor_view = -1;
Settings::application.widget.shader_editor = on;
}
bool ShaderEditWindow::Visible() const
{
return ( Settings::application.widget.shader_editor
&& (Settings::application.widget.shader_editor_view < 0 || Settings::application.widget.shader_editor_view == Settings::application.current_view )
);
}
void ShaderEditWindow::BuildShader()
{
// if the UI has a current clone, and ref to code for current clone is valid
if (current_ != nullptr && filters_.find(current_) != filters_.end()) {
// set the code of the current filter
filters_[current_].setCode( { _editor.GetText(), "" } );
// filter changed, cannot be named as before
filters_[current_].setName("Custom");
// change the filter of the current image filter
// => this triggers compilation of shader
compilation_ = new std::promise<std::string>();
current_->setProgram( filters_[current_], compilation_ );
compilation_return_ = compilation_->get_future();
// inform status
status_ = "Building...";
}
}
void ShaderEditWindow::Render()
{
ImGui::SetWindowSize(ImVec2(800, 600), ImGuiCond_FirstUseEver);
if ( !ImGui::Begin(name_, &Settings::application.widget.shader_editor,
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.shader_editor = false;
if (ImGui::BeginMenu(IMGUI_TITLE_SHADEREDITOR))
{
// reload code from GPU
if (ImGui::MenuItem( ICON_FA_SYNC " Reload", nullptr, nullptr, current_ != nullptr)) {
// force reload
if ( current_ != nullptr )
filters_.erase(current_);
current_ = nullptr;
}
// Menu section for presets
if (ImGui::BeginMenu( ICON_FA_SCROLL " Example code", current_ != nullptr))
{
for (auto p = FilteringProgram::presets.begin(); p != FilteringProgram::presets.end(); ++p){
if (current_ != nullptr && ImGui::MenuItem( p->name().c_str() )) {
// change the filter of the current image filter
// => this triggers compilation of shader
compilation_ = new std::promise<std::string>();
current_->setProgram( *p, compilation_ );
compilation_return_ = compilation_->get_future();
// inform status
status_ = "Building...";
// force reload
if ( current_ != nullptr )
filters_.erase(current_);
current_ = nullptr;
}
}
ImGui::EndMenu();
}
// Open browser to shadertoy website
if (ImGui::MenuItem( ICON_FA_EXTERNAL_LINK_ALT " Browse shadertoy.com"))
SystemToolkit::open("https://www.shadertoy.com/");
// Enable/Disable editor options
ImGui::Separator();
ImGui::MenuItem( ICON_FA_UNDERLINE " Show Shader Inputs", nullptr, &show_shader_inputs_);
bool ws = _editor.IsShowingWhitespaces();
if (ImGui::MenuItem( ICON_FA_LONG_ARROW_ALT_RIGHT " Show whitespace", nullptr, &ws))
_editor.SetShowWhitespaces(ws);
// output manager menu
ImGui::Separator();
bool pinned = Settings::application.widget.shader_editor_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.shader_editor_view = Settings::application.current_view;
else
Settings::application.widget.shader_editor_view = -1;
}
if ( ImGui::MenuItem( MENU_CLOSE, SHORTCUT_SHADEREDITOR) )
Settings::application.widget.shader_editor = false;
ImGui::EndMenu();
}
// Edit menu
bool ro = _editor.IsReadOnly();
if (ImGui::BeginMenu( "Edit", current_ != nullptr ) ) {
if (ImGui::MenuItem( MENU_UNDO, SHORTCUT_UNDO, nullptr, !ro && _editor.CanUndo()))
_editor.Undo();
if (ImGui::MenuItem( MENU_REDO, CTRL_MOD "Y", nullptr, !ro && _editor.CanRedo()))
_editor.Redo();
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();
if (ImGui::MenuItem( MENU_SELECTALL, SHORTCUT_SELECTALL, nullptr, _editor.GetText().size() > 1 ))
_editor.SetSelection(TextEditor::Coordinates(), TextEditor::Coordinates(_editor.GetTotalLines(), 0));
ImGui::EndMenu();
}
// Build action menu
if (ImGui::MenuItem( ICON_FA_HAMMER " Build", CTRL_MOD "B", nullptr, current_ != nullptr ))
BuildShader();
ImGui::EndMenuBar();
}
// garbage collection
if ( Mixer::manager().session()->numSources() < 1 )
{
filters_.clear();
current_ = nullptr;
_editor.SetText("");
}
// if compiling, cannot change source nor do anything else
static std::chrono::milliseconds timeout = std::chrono::milliseconds(4);
if (compilation_ != nullptr && compilation_return_.wait_for(timeout) == std::future_status::ready )
{
// get message returned from compilation
status_ = compilation_return_.get();
// end compilation promise
delete compilation_;
compilation_ = nullptr;
}
// not compiling
else {
ImageFilter *i = nullptr;
// get current clone source
Source *s = Mixer::manager().currentSource();
// if there is a current source
if (s != nullptr) {
CloneSource *c = dynamic_cast<CloneSource *>(s);
// if the current source is a clone
if ( c != nullptr ) {
FrameBufferFilter *f = c->filter();
// if the filter seems to be an Image Filter
if (f != nullptr && f->type() == FrameBufferFilter::FILTER_IMAGE ) {
i = dynamic_cast<ImageFilter *>(f);
// if we can access the code of the filter
if (i != nullptr) {
// if the current clone was not already registered
if ( filters_.find(i) == filters_.end() )
// remember code for this clone
filters_[i] = i->program();
}
else {
filters_.erase(i);
i = nullptr;
}
}
else
status_ = "-";
}
else
status_ = "-";
}
else
status_ = "-";
// change editor text only if current changed
if ( current_ != i)
{
// get the editor text and remove trailing '\n'
std::string code = _editor.GetText();
code = code.substr(0, code.size() -1);
// remember this code as buffered for the filter of this source
filters_[current_].setCode( { code, "" } );
// if switch to another shader code
if ( i != nullptr ) {
// change editor
_editor.SetText( filters_[i].code().first );
_editor.SetReadOnly(false);
status_ = "Ready.";
}
// cancel edit clone
else {
// cancel editor
_editor.SetText("");
_editor.SetReadOnly(true);
status_ = "-";
}
// current changed
current_ = i;
}
}
// render the window content in mono font
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO);
// render status message
ImGui::Text("Status: %s", status_.c_str());
// render shader input
if (show_shader_inputs_) {
ImGuiTextBuffer info;
info.append(FilteringProgram::getFilterCodeInputs().c_str());
// Show info text bloc (multi line, dark background)
ImGuiToolkit::PushFont( ImGuiToolkit::FONT_MONO );
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.6, 0.6, 0.6, 0.9f));
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(-1, 8*ImGui::GetTextLineHeightWithSpacing()), ImGuiInputTextFlags_ReadOnly);
ImGui::PopStyleColor(2);
ImGui::PopFont();
// sliders iMouse
ImGui::SliderFloat4("##iMouse", glm::value_ptr( FilteringProgram::iMouse ), 0.0, 1.0 );
}
else
ImGui::Spacing();
// special case for 'CTRL + B' keyboard shortcut
// the TextEditor captures keyboard focus from the main imgui context
// so UserInterface::handleKeyboard cannot capture this event:
// reading key press before render bypasses this problem
const ImGuiIO& io = ImGui::GetIO();
if (io.ConfigMacOSXBehaviors ? io.KeySuper : io.KeyCtrl && ImGui::IsKeyPressed(io.KeyMap[ImGuiKey_A]+1))
BuildShader();
// render main editor
_editor.Render("Shader Editor");
ImGui::PopFont();
ImGui::End();
}

35
src/ShaderEditWindow.h Normal file
View File

@@ -0,0 +1,35 @@
#ifndef SHADEREDITWINDOW_H
#define SHADEREDITWINDOW_H
#include <string>
#include <future>
#include <map>
#include "ImageFilter.h"
#include "WorkspaceWindow.h"
class ShaderEditWindow : public WorkspaceWindow
{
ImageFilter *current_;
std::map<ImageFilter *, FilteringProgram> filters_;
std::promise<std::string> *compilation_;
std::future<std::string> compilation_return_;
bool show_shader_inputs_;
std::string status_;
public:
ShaderEditWindow();
void Render();
void setVisible(bool on);
void BuildShader();
// from WorkspaceWindow
bool Visible() const override;
};
#endif // SHADEREDITWINDOW_H

1940
src/SourceControlWindow.cpp Normal file

File diff suppressed because it is too large Load Diff

81
src/SourceControlWindow.h Normal file
View File

@@ -0,0 +1,81 @@
#ifndef SOURCECONTROLWINDOW_H
#define SOURCECONTROLWINDOW_H
struct ImVec2;
#include "SourceList.h"
#include "InfoVisitor.h"
#include "DialogToolkit.h"
#include "Screenshot.h"
#include "WorkspaceWindow.h"
class SourceControlWindow : public WorkspaceWindow
{
float min_width_;
float h_space_;
float v_space_;
float scrollbar_;
float timeline_height_;
float mediaplayer_height_;
float buttons_width_;
float buttons_height_;
bool play_toggle_request_, replay_request_, capture_request_;
bool pending_;
std::string active_label_;
int active_selection_;
InfoVisitor info_;
SourceList selection_;
// re-usable ui parts
void DrawButtonBar(ImVec2 bottom, float width);
int SourceButton(Source *s, ImVec2 framesize);
// Render the sources dynamically selected
void RenderSelectedSources();
// Render a stored selection
bool selection_context_menu_;
MediaPlayer *selection_mediaplayer_;
double selection_target_slower_;
double selection_target_faster_;
void RenderSelectionContextMenu();
void RenderSelection(size_t i);
// Render a single source
void RenderSingleSource(Source *s);
// Render a single media player
MediaPlayer *mediaplayer_active_;
bool mediaplayer_edit_fading_;
bool mediaplayer_edit_pipeline_;
bool mediaplayer_mode_;
bool mediaplayer_slider_pressed_;
float mediaplayer_timeline_zoom_;
void RenderMediaPlayer(MediaSource *ms);
// magnifying glass
bool magnifying_glass;
// dialog to select frame capture location
DialogToolkit::OpenFolderDialog *captureFolderDialog;
Screenshot capture;
public:
SourceControlWindow();
inline void Play() { play_toggle_request_ = true; }
inline void Replay() { replay_request_= true; }
inline void Capture(){ capture_request_= true; }
void resetActiveSelection();
void setVisible(bool on);
void Render();
// from WorkspaceWindow
void Update() override;
bool Visible() const override;
};
#endif // SOURCECONTROLWINDOW_H

View File

@@ -0,0 +1,323 @@
/*
* This file is part of vimix - video live mixer
*
* **Copyright** (C) 2019-2023 Bruno Herbelin <bruno.herbelin@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/
// ImGui
#include "ImGuiToolkit.h"
#include "imgui_internal.h"
#include "defines.h"
#include "Settings.h"
#include "GstToolkit.h"
#include "Metronome.h"
#include "TimerMetronomeWindow.h"
#define PLOT_CIRCLE_SEGMENTS 64
TimerMetronomeWindow::TimerMetronomeWindow() : 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 TimerMetronomeWindow::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 (Settings::application.widget.timer_view > 0 && Settings::application.widget.timer_view != Settings::application.current_view){
Settings::application.widget.timer_view = -1;
on = true;
}
Settings::application.widget.timer = on;
}
bool TimerMetronomeWindow::Visible() const
{
return ( Settings::application.widget.timer
&& (Settings::application.widget.timer_view < 0 || Settings::application.widget.timer_view == Settings::application.current_view )
);
}
void TimerMetronomeWindow::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, ImGuiToolkit::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);
guint64 lap = (time_-start_time_hand_)/duration_hand_;
sprintf(text_buf, "%ld turn%s", lap, lap > 1 ? "s" : " " );
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);
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();
}

View File

@@ -0,0 +1,31 @@
#ifndef TIMERMETRONOMEWINDOW_H
#define TIMERMETRONOMEWINDOW_H
#include <string>
#include <array>
#include <gst/gstutils.h>
#include "WorkspaceWindow.h"
class TimerMetronomeWindow : public WorkspaceWindow
{
std::array< std::string, 2 > timer_menu;
// clock times
guint64 start_time_;
guint64 start_time_hand_;
guint64 duration_hand_;
public:
TimerMetronomeWindow();
void Render();
void setVisible(bool on);
// from WorkspaceWindow
bool Visible() const override;
};
#endif // TIMERMETRONOMEWINDOW_H

File diff suppressed because it is too large Load Diff

View File

@@ -1,136 +1,28 @@
#ifndef __UI_MANAGER_H_ #ifndef __UI_MANAGER_H_
#define __UI_MANAGER_H_ #define __UI_MANAGER_H_
#include "Session.h"
#include <string>
#include <array>
#include <list>
#include <future>
#include <variant>
#include <gst/gstutils.h>
#define NAV_COUNT 68 #define NAV_COUNT 68
#define NAV_MAX 64 #define NAV_MAX 64
#define NAV_NEW 65 #define NAV_NEW 65
#define NAV_MENU 66 #define NAV_MENU 66
#define NAV_TRANS 67 #define NAV_TRANS 67
#ifdef APPLE #include <string>
#define CTRL_MOD "Cmd+"
#define ALT_MOD "Option+"
#else
#define CTRL_MOD "Ctrl+"
#define ALT_MOD "Alt+"
#endif
#define IMGUI_TITLE_MEDIAPLAYER ICON_FA_PLAY_CIRCLE " Player" #include "View.h"
#define IMGUI_TITLE_TIMER ICON_FA_CLOCK " Timer"
#define IMGUI_TITLE_INPUT_MAPPING ICON_FA_HAND_PAPER " Input Mapping"
#define IMGUI_TITLE_HELP ICON_FA_LIFE_RING " Help"
#define IMGUI_TITLE_TOOLBOX ICON_FA_HAMSA " Guru Toolbox"
#define IMGUI_TITLE_SHADEREDITOR ICON_FA_CODE " Shader Editor"
#define IMGUI_TITLE_PREVIEW ICON_FA_LAPTOP " Display"
#define MENU_NEW_FILE ICON_FA_FILE " New"
#define SHORTCUT_NEW_FILE CTRL_MOD "W"
#define MENU_OPEN_FILE ICON_FA_FILE_UPLOAD " Open"
#define SHORTCUT_OPEN_FILE CTRL_MOD "O"
#define MENU_REOPEN_FILE ICON_FA_FILE_UPLOAD " Re-open"
#define SHORTCUT_REOPEN_FILE CTRL_MOD "Shift+O"
#define MENU_SAVE_FILE ICON_FA_FILE_DOWNLOAD " Save"
#define SHORTCUT_SAVE_FILE CTRL_MOD "S"
#define MENU_SAVEAS_FILE ICON_FA_FILE_DOWNLOAD " Save as"
#define MENU_SAVE_ON_EXIT ICON_FA_LEVEL_DOWN_ALT " Save on exit"
#define MENU_OPEN_ON_START ICON_FA_LEVEL_UP_ALT " Restore on start"
#define SHORTCUT_SAVEAS_FILE CTRL_MOD "Shift+S"
#define MENU_QUIT ICON_FA_POWER_OFF " Quit"
#define SHORTCUT_QUIT CTRL_MOD "Q"
#define MENU_CUT ICON_FA_CUT " Cut"
#define SHORTCUT_CUT CTRL_MOD "X"
#define MENU_COPY ICON_FA_COPY " Copy"
#define SHORTCUT_COPY CTRL_MOD "C"
#define MENU_DELETE ICON_FA_ERASER " Delete"
#define SHORTCUT_DELETE "Del"
#define ACTION_DELETE ICON_FA_BACKSPACE " Delete"
#define MENU_PASTE ICON_FA_PASTE " Paste"
#define SHORTCUT_PASTE CTRL_MOD "V"
#define MENU_SELECTALL ICON_FA_TH_LIST " Select all"
#define SHORTCUT_SELECTALL CTRL_MOD "A"
#define MENU_UNDO ICON_FA_UNDO " Undo"
#define SHORTCUT_UNDO CTRL_MOD "Z"
#define MENU_REDO ICON_FA_REDO " Redo"
#define SHORTCUT_REDO CTRL_MOD "Shift+Z"
#define MENU_RECORD ICON_FA_CIRCLE " Record"
#define SHORTCUT_RECORD CTRL_MOD "R"
#define MENU_RECORDCONT ICON_FA_STOP_CIRCLE " Save & continue"
#define SHORTCUT_RECORDCONT CTRL_MOD "Alt+R"
#define MENU_CAPTUREFRAME ICON_FA_CAMERA_RETRO " Capture frame"
#define SHORTCUT_CAPTURE_DISPLAY "F11"
#define SHORTCUT_CAPTURE_PLAYER "F10"
#define MENU_CAPTUREGUI ICON_FA_CAMERA " Screenshot vimix"
#define SHORTCUT_CAPTURE_GUI "F9"
#define MENU_OUTPUTDISABLE ICON_FA_EYE_SLASH " Disable"
#define SHORTCUT_OUTPUTDISABLE "F12"
#define MENU_LARGEPREVIEW ICON_FA_EXPAND_ARROWS_ALT " Large preview"
#define SHORTCUT_LARGEPREVIEW "F6"
#define MENU_CLOSE ICON_FA_TIMES " Close"
#define DIALOG_FAILED_SOURCE ICON_FA_EXCLAMATION_TRIANGLE " Source failure"
#define MENU_NOTE ICON_FA_STICKY_NOTE " Add sticky note"
#define MENU_METRICS ICON_FA_TACHOMETER_ALT " Metrics"
#define MENU_SOURCE_TOOL ICON_FA_WRENCH " Source toolbar"
#define MENU_HELP ICON_FA_LIFE_RING " Help"
#define SHORTCUT_HELP CTRL_MOD "H"
#define MENU_LOGS ICON_FA_LIST_UL " Logs"
#define SHORTCUT_LOGS CTRL_MOD "L"
#define TOOLTIP_PLAYER "Player "
#define SHORTCUT_PLAYER CTRL_MOD "P"
#define TOOLTIP_OUTPUT "Display "
#define SHORTCUT_OUTPUT CTRL_MOD "D"
#define TOOLTIP_TIMER "Timer "
#define SHORTCUT_TIMER CTRL_MOD "T"
#define TOOLTIP_INPUTS "Inputs mapping "
#define SHORTCUT_INPUTS CTRL_MOD "I"
#define TOOLTIP_SHADEREDITOR "Shader Editor "
#define SHORTCUT_SHADEREDITOR CTRL_MOD "E"
#define TOOLTIP_FULLSCREEN "Fullscreen "
#define SHORTCUT_FULLSCREEN CTRL_MOD "F"
#define TOOLTIP_MAIN "Main menu "
#define SHORTCUT_MAIN "HOME"
#define TOOLTIP_NEW_SOURCE "New source "
#define SHORTCUT_NEW_SOURCE "INS"
#define TOOLTIP_HIDE "Hide windows "
#define TOOLTIP_SHOW "Show windows "
#define SHORTCUT_HIDE "ESC"
#define TOOLTIP_PANEL_VISIBLE "Panel always visible "
#define TOOLTIP_PANEL_AUTO "Panel auto hide "
#define SHORTCUT_PANEL_MODE "HOME"
#define LABEL_AUTO_MEDIA_PLAYER ICON_FA_USER_CIRCLE " User selection"
#define LABEL_STORE_SELECTION " Create batch"
#define LABEL_EDIT_FADING ICON_FA_RANDOM " Fade in & out"
#define LABEL_VIDEO_SEQUENCE " Encode an image sequence"
#include "SourceList.h"
#include "InfoVisitor.h"
#include "DialogToolkit.h" #include "DialogToolkit.h"
#include "SessionParser.h" #include "SourceControlWindow.h"
#include "ImageFilter.h" #include "OutputPreviewWindow.h"
#include "Screenshot.h" #include "TimerMetronomeWindow.h"
#include "InputMappingWindow.h"
#include "ShaderEditWindow.h"
struct ImVec2; struct ImVec2;
class MediaPlayer; void DrawInspector(uint texture, ImVec2 texturesize, ImVec2 cropsize, ImVec2 origin);
class FrameBufferImage;
class FrameGrabber;
class VideoRecorder;
class VideoBroadcast;
class ShmdataBroadcast;
class Loopback;
class SourcePreview {
class SourcePreview
{
Source *source_; Source *source_;
std::string label_; std::string label_;
bool reset_; bool reset_;
@@ -146,20 +38,6 @@ public:
inline bool filled() const { return source_ != nullptr; } inline bool filled() const { return source_ != nullptr; }
}; };
class Thumbnail
{
float aspect_ratio_;
uint texture_;
public:
Thumbnail();
~Thumbnail();
void reset();
void fill (const FrameBufferImage *image);
bool filled();
void Render(float width);
};
class Navigator class Navigator
{ {
@@ -246,217 +124,11 @@ public:
void Render(); void Render();
}; };
class HelperToolbox
{
SessionParser parser_;
public:
HelperToolbox();
void Render();
};
class WorkspaceWindow
{
static std::list<WorkspaceWindow *> windows_;
public:
WorkspaceWindow(const char* name);
// global access to Workspace control
static bool clear() { return clear_workspace_enabled; }
static void toggleClearRestoreWorkspace();
static void clearWorkspace();
static void restoreWorkspace(bool instantaneous = false);
static void notifyWorkspaceSizeChanged(int prev_width, int prev_height, int curr_width, int curr_height);
// for inherited classes
virtual void Update();
virtual bool Visible() const { return true; }
protected:
static bool clear_workspace_enabled;
const char *name_;
struct ImGuiProperties *impl_;
};
class SourceController : public WorkspaceWindow
{
float min_width_;
float h_space_;
float v_space_;
float scrollbar_;
float timeline_height_;
float mediaplayer_height_;
float buttons_width_;
float buttons_height_;
bool play_toggle_request_, replay_request_, capture_request_;
bool pending_;
std::string active_label_;
int active_selection_;
InfoVisitor info_;
SourceList selection_;
// re-usable ui parts
void DrawButtonBar(ImVec2 bottom, float width);
int SourceButton(Source *s, ImVec2 framesize);
// Render the sources dynamically selected
void RenderSelectedSources();
// Render a stored selection
bool selection_context_menu_;
MediaPlayer *selection_mediaplayer_;
double selection_target_slower_;
double selection_target_faster_;
void RenderSelectionContextMenu();
void RenderSelection(size_t i);
// Render a single source
void RenderSingleSource(Source *s);
// Render a single media player
MediaPlayer *mediaplayer_active_;
bool mediaplayer_edit_fading_;
bool mediaplayer_edit_pipeline_;
bool mediaplayer_mode_;
bool mediaplayer_slider_pressed_;
float mediaplayer_timeline_zoom_;
void RenderMediaPlayer(MediaSource *ms);
// magnifying glass
bool magnifying_glass;
// dialog to select frame capture location
DialogToolkit::OpenFolderDialog *captureFolderDialog;
Screenshot capture;
public:
SourceController();
inline void Play() { play_toggle_request_ = true; }
inline void Replay() { replay_request_= true; }
inline void Capture(){ capture_request_= true; }
void resetActiveSelection();
void setVisible(bool on);
void Render();
// from WorkspaceWindow
void Update() override;
bool Visible() const override;
};
class OutputPreview : public WorkspaceWindow
{
// frame grabbers
VideoRecorder *video_recorder_;
VideoBroadcast *video_broadcaster_;
ShmdataBroadcast *shm_broadcaster_;
Loopback *loopback_broadcaster_;
// delayed trigger for recording
std::vector< std::future<VideoRecorder *> > _video_recorders;
// dialog to select record location
DialogToolkit::OpenFolderDialog *recordFolderDialog;
// magnifying glass
bool magnifying_glass;
public:
OutputPreview();
void ToggleRecord(bool save_and_continue = false);
inline bool isRecording() const { return video_recorder_ != nullptr; }
void ToggleVideoBroadcast();
inline bool videoBroadcastEnabled() const { return video_broadcaster_ != nullptr; }
void ToggleSharedMemory();
inline bool sharedMemoryEnabled() const { return shm_broadcaster_ != nullptr; }
bool ToggleLoopbackCamera();
inline bool loopbackCameraEnabled() const { return loopback_broadcaster_!= nullptr; }
void Render();
void setVisible(bool on);
// from WorkspaceWindow
void Update() override;
bool Visible() const override;
};
class TimerMetronome : public WorkspaceWindow
{
std::array< std::string, 2 > timer_menu;
// clock times
guint64 start_time_;
guint64 start_time_hand_;
guint64 duration_hand_;
public:
TimerMetronome();
void Render();
void setVisible(bool on);
// from WorkspaceWindow
bool Visible() const override;
};
class InputMappingInterface : public WorkspaceWindow
{
std::array< std::string, 4 > input_mode;
std::array< uint, 4 > current_input_for_mode;
uint current_input_;
Target ComboSelectTarget(const Target &current);
uint ComboSelectCallback(uint current, bool imageprocessing);
void SliderParametersCallback(SourceCallback *callback, const Target &target);
public:
InputMappingInterface();
void Render();
void setVisible(bool on);
// from WorkspaceWindow
bool Visible() const override;
};
class ShaderEditor : public WorkspaceWindow
{
ImageFilter *current_;
std::map<ImageFilter *, FilteringProgram> filters_;
std::promise<std::string> *compilation_;
std::future<std::string> compilation_return_;
bool show_shader_inputs_;
std::string status_;
public:
ShaderEditor();
void Render();
void setVisible(bool on);
void BuildShader();
// from WorkspaceWindow
bool Visible() const override;
};
class UserInterface class UserInterface
{ {
friend class ImGuiVisitor; friend class ImGuiVisitor;
friend class Navigator; friend class Navigator;
friend class OutputPreview; friend class OutputPreviewWindow;
// Private Constructor // Private Constructor
UserInterface(); UserInterface();
@@ -525,12 +197,11 @@ protected:
// objects and windows // objects and windows
Navigator navigator; Navigator navigator;
ToolBox toolbox; ToolBox toolbox;
SourceController sourcecontrol; SourceControlWindow sourcecontrol;
OutputPreview outputcontrol; OutputPreviewWindow outputcontrol;
TimerMetronome timercontrol; TimerMetronomeWindow timercontrol;
InputMappingInterface inputscontrol; InputMappingWindow inputscontrol;
ShaderEditor shadercontrol; ShaderEditWindow shadercontrol;
HelperToolbox helpwindow;
void showMenuFile(); void showMenuFile();
void showMenuEdit(); void showMenuEdit();
@@ -544,6 +215,7 @@ protected:
void RenderOutputView(); void RenderOutputView();
void RenderAbout(bool* p_open); void RenderAbout(bool* p_open);
void RenderNotes(); void RenderNotes();
void RenderHelp();
void setView(View::Mode mode); void setView(View::Mode mode);
void handleKeyboard(); void handleKeyboard();

228
src/WorkspaceWindow.cpp Normal file
View File

@@ -0,0 +1,228 @@
/*
* This file is part of vimix - video live mixer
*
* **Copyright** (C) 2019-2023 Bruno Herbelin <bruno.herbelin@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/
#include "imgui.h"
#define IMGUI_DEFINE_MATH_OPERATORS
#include "imgui_internal.h"
#include "defines.h"
#include "WorkspaceWindow.h"
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()
{
// stop animations that are ongoing
for(auto it = windows_.cbegin(); it != windows_.cend(); ++it) {
if ( (*it)->impl_ && (*it)->impl_->animation )
(*it)->impl_->animation = false;
}
// toggle
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);
}
}
}

32
src/WorkspaceWindow.h Normal file
View File

@@ -0,0 +1,32 @@
#ifndef WORKSPACEWINDOW_H
#define WORKSPACEWINDOW_H
#include <list>
class WorkspaceWindow
{
static std::list<WorkspaceWindow *> windows_;
public:
WorkspaceWindow(const char* name);
// global access to Workspace control
static bool clear() { return clear_workspace_enabled; }
static void toggleClearRestoreWorkspace();
static void clearWorkspace();
static void restoreWorkspace(bool instantaneous = false);
static void notifyWorkspaceSizeChanged(int prev_width, int prev_height, int curr_width, int curr_height);
// for inherited classes
virtual void Update();
virtual bool Visible() const { return true; }
protected:
static bool clear_workspace_enabled;
const char *name_;
struct ImGuiProperties *impl_;
};
#endif // WORKSPACEWINDOW_H

View File

@@ -122,4 +122,100 @@
#define OSC_PORT_SEND_DEFAULT 7001 #define OSC_PORT_SEND_DEFAULT 7001
#define OSC_CONFIG_FILE "osc.xml" #define OSC_CONFIG_FILE "osc.xml"
#define IMGUI_TITLE_MEDIAPLAYER ICON_FA_PLAY_CIRCLE " Player"
#define IMGUI_TITLE_TIMER ICON_FA_CLOCK " Timer"
#define IMGUI_TITLE_INPUT_MAPPING ICON_FA_HAND_PAPER " Input Mapping"
#define IMGUI_TITLE_HELP ICON_FA_LIFE_RING " Help"
#define IMGUI_TITLE_TOOLBOX ICON_FA_HAMSA " Guru Toolbox"
#define IMGUI_TITLE_SHADEREDITOR ICON_FA_CODE " Shader Editor"
#define IMGUI_TITLE_PREVIEW ICON_FA_LAPTOP " Display"
#ifdef APPLE
#define CTRL_MOD "Cmd+"
#define ALT_MOD "Option+"
#else
#define CTRL_MOD "Ctrl+"
#define ALT_MOD "Alt+"
#endif
#define MENU_NEW_FILE ICON_FA_FILE " New"
#define SHORTCUT_NEW_FILE CTRL_MOD "W"
#define MENU_OPEN_FILE ICON_FA_FILE_UPLOAD " Open"
#define SHORTCUT_OPEN_FILE CTRL_MOD "O"
#define MENU_REOPEN_FILE ICON_FA_FILE_UPLOAD " Re-open"
#define SHORTCUT_REOPEN_FILE CTRL_MOD "Shift+O"
#define MENU_SAVE_FILE ICON_FA_FILE_DOWNLOAD " Save"
#define SHORTCUT_SAVE_FILE CTRL_MOD "S"
#define MENU_SAVEAS_FILE ICON_FA_FILE_DOWNLOAD " Save as"
#define MENU_SAVE_ON_EXIT ICON_FA_LEVEL_DOWN_ALT " Save on exit"
#define MENU_OPEN_ON_START ICON_FA_LEVEL_UP_ALT " Restore on start"
#define SHORTCUT_SAVEAS_FILE CTRL_MOD "Shift+S"
#define MENU_QUIT ICON_FA_POWER_OFF " Quit"
#define SHORTCUT_QUIT CTRL_MOD "Q"
#define MENU_CUT ICON_FA_CUT " Cut"
#define SHORTCUT_CUT CTRL_MOD "X"
#define MENU_COPY ICON_FA_COPY " Copy"
#define SHORTCUT_COPY CTRL_MOD "C"
#define MENU_DELETE ICON_FA_ERASER " Delete"
#define SHORTCUT_DELETE "Del"
#define ACTION_DELETE ICON_FA_BACKSPACE " Delete"
#define MENU_PASTE ICON_FA_PASTE " Paste"
#define SHORTCUT_PASTE CTRL_MOD "V"
#define MENU_SELECTALL ICON_FA_TH_LIST " Select all"
#define SHORTCUT_SELECTALL CTRL_MOD "A"
#define MENU_UNDO ICON_FA_UNDO " Undo"
#define SHORTCUT_UNDO CTRL_MOD "Z"
#define MENU_REDO ICON_FA_REDO " Redo"
#define SHORTCUT_REDO CTRL_MOD "Shift+Z"
#define MENU_RECORD ICON_FA_CIRCLE " Record"
#define SHORTCUT_RECORD CTRL_MOD "R"
#define MENU_RECORDCONT ICON_FA_STOP_CIRCLE " Save & continue"
#define SHORTCUT_RECORDCONT CTRL_MOD "Alt+R"
#define MENU_CAPTUREFRAME ICON_FA_CAMERA_RETRO " Capture frame"
#define SHORTCUT_CAPTURE_DISPLAY "F11"
#define SHORTCUT_CAPTURE_PLAYER "F10"
#define MENU_CAPTUREGUI ICON_FA_CAMERA " Screenshot vimix"
#define SHORTCUT_CAPTURE_GUI "F9"
#define MENU_OUTPUTDISABLE ICON_FA_EYE_SLASH " Disable"
#define SHORTCUT_OUTPUTDISABLE "F12"
#define MENU_LARGEPREVIEW ICON_FA_EXPAND_ARROWS_ALT " Large preview"
#define SHORTCUT_LARGEPREVIEW "F6"
#define MENU_CLOSE ICON_FA_TIMES " Close"
#define DIALOG_FAILED_SOURCE ICON_FA_EXCLAMATION_TRIANGLE " Source failure"
#define MENU_NOTE ICON_FA_STICKY_NOTE " Add sticky note"
#define MENU_METRICS ICON_FA_TACHOMETER_ALT " Metrics"
#define MENU_SOURCE_TOOL ICON_FA_WRENCH " Source toolbar"
#define MENU_HELP ICON_FA_LIFE_RING " Help"
#define SHORTCUT_HELP CTRL_MOD "H"
#define MENU_LOGS ICON_FA_LIST_UL " Logs"
#define SHORTCUT_LOGS CTRL_MOD "L"
#define TOOLTIP_PLAYER "Player "
#define SHORTCUT_PLAYER CTRL_MOD "P"
#define TOOLTIP_OUTPUT "Display "
#define SHORTCUT_OUTPUT CTRL_MOD "D"
#define TOOLTIP_TIMER "Timer "
#define SHORTCUT_TIMER CTRL_MOD "T"
#define TOOLTIP_INPUTS "Inputs mapping "
#define SHORTCUT_INPUTS CTRL_MOD "I"
#define TOOLTIP_SHADEREDITOR "Shader Editor "
#define SHORTCUT_SHADEREDITOR CTRL_MOD "E"
#define TOOLTIP_FULLSCREEN "Fullscreen "
#define SHORTCUT_FULLSCREEN CTRL_MOD "F"
#define TOOLTIP_MAIN "Main menu "
#define SHORTCUT_MAIN "HOME"
#define TOOLTIP_NEW_SOURCE "New source "
#define SHORTCUT_NEW_SOURCE "INS"
#define TOOLTIP_HIDE "Hide windows "
#define TOOLTIP_SHOW "Show windows "
#define SHORTCUT_HIDE "ESC"
#define TOOLTIP_PANEL_VISIBLE "Panel always visible "
#define TOOLTIP_PANEL_AUTO "Panel auto hide "
#define SHORTCUT_PANEL_MODE "HOME"
#define LABEL_AUTO_MEDIA_PLAYER ICON_FA_USER_CIRCLE " User selection"
#define LABEL_STORE_SELECTION " Create batch"
#define LABEL_EDIT_FADING ICON_FA_RANDOM " Fade in & out"
#define LABEL_VIDEO_SEQUENCE " Encode an image sequence"
#endif // VMIX_DEFINES_H #endif // VMIX_DEFINES_H