diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3ccb494..aa3a2c2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -65,6 +65,12 @@ set(VMIX_SRCS DelayFilter.cpp RenderingManager.cpp UserInterfaceManager.cpp + WorkspaceWindow.cpp + SourceControlWindow.cpp + OutputPreviewWindow.cpp + TimerMetronomeWindow.cpp + InputMappingWindow.cpp + ShaderEditWindow.cpp PickingVisitor.cpp BoundingBoxVisitor.cpp DrawVisitor.cpp diff --git a/src/ImGuiToolkit.h b/src/ImGuiToolkit.h index 648a48f..df41451 100644 --- a/src/ImGuiToolkit.h +++ b/src/ImGuiToolkit.h @@ -94,6 +94,17 @@ namespace ImGuiToolkit 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); + // 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_ diff --git a/src/InputMappingWindow.cpp b/src/InputMappingWindow.cpp new file mode 100644 index 0000000..d3155a4 --- /dev/null +++ b/src/InputMappingWindow.cpp @@ -0,0 +1,1303 @@ +/* + * This file is part of vimix - video live mixer + * + * **Copyright** (C) 2019-2023 Bruno Herbelin + * + * 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 . +**/ + +#include +#include + +#include +#include + +// ImGui +#include "ImGuiToolkit.h" +#include "imgui_internal.h" + +#include "defines.h" +#include "BaseToolkit.h" +#include "Settings.h" +#include "Source.h" +#include "Mixer.h" +#include "SourceCallback.h" +#include "ControlManager.h" +#include "Metronome.h" + +#include "InputMappingWindow.h" + +InputMappingWindow::InputMappingWindow() : WorkspaceWindow("InputMappingInterface") +{ + input_mode = { ICON_FA_KEYBOARD " Keyboard", + ICON_FA_CALCULATOR " Numpad" , + ICON_FA_TABLET_ALT " TouchOSC" , + ICON_FA_GAMEPAD " Gamepad" }; + current_input_for_mode = { INPUT_KEYBOARD_FIRST, INPUT_NUMPAD_FIRST, INPUT_MULTITOUCH_FIRST, INPUT_JOYSTICK_FIRST }; + current_input_ = current_input_for_mode[Settings::application.mapping.mode]; +} + +void InputMappingWindow::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.inputs_view > 0 && Settings::application.widget.inputs_view != Settings::application.current_view) { + Settings::application.widget.inputs_view = -1; + on = true; + } + + Settings::application.widget.inputs = on; +} + +bool InputMappingWindow::Visible() const +{ + return ( Settings::application.widget.inputs + && (Settings::application.widget.inputs_view < 0 || Settings::application.widget.inputs_view == Settings::application.current_view ) + ); +} + +/// +/// Draw a combo box listing all sources and all batch of the current session +/// Returns a Target variant : non-assigned by default (std::monostate), or a Source, or a Batch +/// If current element is indicated, it is displayed first +/// +Target InputMappingWindow::ComboSelectTarget(const Target ¤t) +{ + Target selected; + std::string label = "Select"; + + // depending on variant, fill the label of combo + if (current.index() > 0) { + if (Source * const* v = std::get_if(¤t)) { + label = std::string((*v)->initials()) + " - " + (*v)->name(); + } + else if ( const size_t* v = std::get_if(¤t)) { + label = std::string("Batch #") + std::to_string(*v); + } + } + + if (ImGui::BeginCombo("##ComboSelectSource", label.c_str()) ) + { + Session *ses = Mixer::manager().session(); + for (auto sit = ses->begin(); sit != ses->end(); ++sit) { + label = std::string((*sit)->initials()) + " - " + (*sit)->name(); + if (ImGui::Selectable( label.c_str() )) { + selected = *sit; + } + } + for (size_t b = 0; b < Mixer::manager().session()->numBatch(); ++b){ + label = std::string("Batch #") + std::to_string(b); + if (ImGui::Selectable( label.c_str() )) { + selected = b; + } + } + + ImGui::EndCombo(); + } + + return selected; +} + +uint InputMappingWindow::ComboSelectCallback(uint current, bool imageprocessing) +{ + const char* callback_names[23] = { "Select", + ICON_FA_BULLSEYE " Alpha", + ICON_FA_BULLSEYE " Loom", + ICON_FA_OBJECT_UNGROUP " Geometry", + 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 " Speed", + ICON_FA_PLAY_CIRCLE " Fast forward", + ICON_FA_PLAY_CIRCLE " Seek", + " None", + " None", + " None", + ICON_FA_PALETTE " Gamma", + ICON_FA_PALETTE " Brightness", + ICON_FA_PALETTE " Contrast", + ICON_FA_PALETTE " Saturation", + ICON_FA_PALETTE " Hue", + ICON_FA_PALETTE " Threshold", + ICON_FA_PALETTE " Invert", + " None" + }; + + uint selected = 0; + if (ImGui::BeginCombo("##ComboSelectCallback", callback_names[current]) ) { + for (uint i = SourceCallback::CALLBACK_ALPHA; i <= SourceCallback::CALLBACK_SEEK; ++i){ + if ( ImGui::Selectable( callback_names[i]) ) { + selected = i; + } + } + if (imageprocessing) { + for (uint i = SourceCallback::CALLBACK_GAMMA; i <= SourceCallback::CALLBACK_INVERT; ++i){ + if ( ImGui::Selectable( callback_names[i]) ) { + selected = i; + } + } + } + ImGui::EndCombo(); + } + + return selected; +} + +struct ClosestIndex +{ + int index; + float val; + ClosestIndex (float v) { val = v; index = 0; } + void operator()(float v) { if (v < val) ++index; } +}; + +void InputMappingWindow::SliderParametersCallback(SourceCallback *callback, const Target &target) +{ + const float right_align = -1.05f * ImGui::GetTextLineHeightWithSpacing(); + static const char *press_tooltip[3] = {"Key Press\nApply value on key press", + "Key Down\nApply value on key down,\nrevert on key up", + "Repeat\nMaintain key down to repeat and iterate" }; + static std::vector< std::pair > speed_icon = { {18,15}, {17,15}, {16,15}, {15,15}, {14,15} }; + static std::vector< std::string > speed_tooltip = { "Fastest\n0 ms", "Fast\n60 ms", "Smooth\n120 ms", "Slow\n240 ms", "Slowest\n500 ms" }; + static std::vector< float > speed_values = { 0.f, 60.f, 120.f, 240.f, 500.f }; + + switch ( callback->type() ) { + case SourceCallback::CALLBACK_ALPHA: + { + SetAlpha *edited = static_cast(callback); + + bool bd = edited->bidirectional(); + if ( ImGuiToolkit::IconToggle(2, 13, 3, 13, &bd, press_tooltip ) ) + edited->setBidirectional(bd); + + ClosestIndex d = std::for_each(speed_values.begin(), speed_values.end(), ClosestIndex(edited->duration())); + int speed_index = d.index; + ImGui::SameLine(0, IMGUI_SAME_LINE / 2); + if (ImGuiToolkit::IconMultistate(speed_icon, &speed_index, speed_tooltip )) + edited->setDuration(speed_values[speed_index]); + + float val = edited->value(); + ImGui::SameLine(0, IMGUI_SAME_LINE / 2); + ImGui::SetNextItemWidth(right_align); + if (ImGui::SliderFloat("##CALLBACK_ALPHA", &val, -1.f, 1.f, "%.2f")) + edited->setValue(val); + + ImGui::SameLine(0, IMGUI_SAME_LINE / 2); + ImGuiToolkit::Indication("Alpha value to set if the source is\nvisible (1.0), transparent (0.0),\nor innactive (-1.0)", 18, 12); + + } + break; + case SourceCallback::CALLBACK_LOOM: + { + ImGuiToolkit::Indication(press_tooltip[2], 18, 5); + ImGui::SameLine(0, IMGUI_SAME_LINE / 2); + Loom *edited = static_cast(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, IMGUI_SAME_LINE / 2); + ImGuiToolkit::Indication("Increment alpha to make the source more visible (>0) or more transparent (<0)", 19, 12); + } + break; + case SourceCallback::CALLBACK_GEOMETRY: + { + SetGeometry *edited = static_cast(callback); + + bool bd = edited->bidirectional(); + if ( ImGuiToolkit::IconToggle(2, 13, 3, 13, &bd, press_tooltip ) ) + edited->setBidirectional(bd); + + ClosestIndex d = std::for_each(speed_values.begin(), speed_values.end(), ClosestIndex(edited->duration())); + int speed_index = d.index; + ImGui::SameLine(0, IMGUI_SAME_LINE / 2); + if (ImGuiToolkit::IconMultistate(speed_icon, &speed_index, speed_tooltip )) + edited->setDuration(speed_values[speed_index]); + + if (target.index() > 0) { + // 1. Case of variant as Source pointer + if (Source * const* v = std::get_if(&target)) { + // Button to capture the source current geometry + ImGui::SameLine(0, IMGUI_SAME_LINE / 2); + if (ImGui::Button("Capture", ImVec2(right_align, 0))) { + edited->setTarget( (*v)->group(View::GEOMETRY) ); + } + } + // 2. Case of variant as index of batch + else if ( const size_t* v = std::get_if(&target)) { + + std::vector _batch = Mixer::manager().session()->getAllBatch(); + + std::string label = "Capture"; + // Combo box to set which source to capture + if (ImGui::BeginCombo("##ComboSelectGeometryCapture", label.c_str()) ) + { + if ( *v < _batch.size() ) + { + for (auto sid = _batch[*v].begin(); sid != _batch[*v].end(); ++sid){ + SourceList::iterator sit = Mixer::manager().session()->find(*sid); + if ( sit != Mixer::manager().session()->end() ) { + + label = std::string((*sit)->initials()) + " - " + (*sit)->name(); + // C to capture the source current geometry + if (ImGui::Selectable( label.c_str() )) { + edited->setTarget( (*sit)->group(View::GEOMETRY) ); + } + } + } + } + + ImGui::EndCombo(); + } + } + + ImGui::SameLine(0, IMGUI_SAME_LINE / 2); + ImGuiToolkit::Indication("Capture source geometry to restore it later (position, scale and rotation).", 1, 16); + } + else { + + ImGui::SameLine(0, IMGUI_SAME_LINE / 2); + ImGui::TextDisabled("Invalid"); + } + } + break; + + case SourceCallback::CALLBACK_GRAB: + { + ImGuiToolkit::Indication(press_tooltip[2], 18, 5); + ImGui::SameLine(0, IMGUI_SAME_LINE / 2); + Grab *edited = static_cast(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, IMGUI_SAME_LINE / 2); + ImGuiToolkit::Indication("Increment vector (x,y) to move the source horizontally and vertically.", 6, 15); + } + break; + + case SourceCallback::CALLBACK_RESIZE: + { + ImGuiToolkit::Indication(press_tooltip[2], 18, 5); + ImGui::SameLine(0, IMGUI_SAME_LINE / 2); + Resize *edited = static_cast(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, IMGUI_SAME_LINE / 2); + ImGuiToolkit::Indication("Increment vector (x,y) to scale the source horizontally and vertically.", 2, 15); + + } + break; + + case SourceCallback::CALLBACK_TURN: + { + ImGuiToolkit::Indication(press_tooltip[2], 18, 5); + ImGui::SameLine(0, IMGUI_SAME_LINE / 2); + Turn *edited = static_cast(callback); + float val = edited->value(); + ImGui::SetNextItemWidth(right_align); + if ( ImGui::SliderAngle("##CALLBACK_TURN", &val, -180.f, 180.f) ) + edited->setValue(val ); + + ImGui::SameLine(0, IMGUI_SAME_LINE / 3); + ImGuiToolkit::Indication("Rotation speed (\u00B0/s) to turn the source clockwise (>0) or counterclockwise (<0)", 18, 9); + } + break; + + case SourceCallback::CALLBACK_DEPTH: + { + SetDepth *edited = static_cast(callback); + + bool bd = edited->bidirectional(); + if ( ImGuiToolkit::IconToggle(2, 13, 3, 13, &bd, press_tooltip ) ) + edited->setBidirectional(bd); + + ClosestIndex d = std::for_each(speed_values.begin(), speed_values.end(), ClosestIndex(edited->duration())); + int speed_index = d.index; + ImGui::SameLine(0, IMGUI_SAME_LINE / 2); + if (ImGuiToolkit::IconMultistate(speed_icon, &speed_index, speed_tooltip )) + edited->setDuration(speed_values[speed_index]); + + float val = edited->value(); + ImGui::SetNextItemWidth(right_align); + ImGui::SameLine(0, IMGUI_SAME_LINE / 2); + if (ImGui::SliderFloat("##CALLBACK_DEPTH", &val, 11.9f, 0.1f, "%.1f")) + edited->setValue(val); + + ImGui::SameLine(0, IMGUI_SAME_LINE / 2); + ImGuiToolkit::Indication("Depth value to place the source front (12) or back (0) in the scene.", 6, 6); + } + break; + + case SourceCallback::CALLBACK_PLAY: + { + Play *edited = static_cast(callback); + + bool bd = edited->bidirectional(); + if ( ImGuiToolkit::IconToggle(2, 13, 3, 13, &bd, press_tooltip ) ) + edited->setBidirectional(bd); + ImGui::SameLine(0, IMGUI_SAME_LINE / 2); + + int val = edited->value() ? 1 : 0; + ImGui::SetNextItemWidth(right_align); + if (ImGui::SliderInt("##CALLBACK_PLAY", &val, 0, 1, "Pause | Play ")) + edited->setValue(val>0); + ImGui::SameLine(0, IMGUI_SAME_LINE / 2); + ImGuiToolkit::Indication("Play or pause the source.", 12, 7); + } + break; + + case SourceCallback::CALLBACK_PLAYSPEED: + { + PlaySpeed *edited = static_cast(callback); + + bool bd = edited->bidirectional(); + if ( ImGuiToolkit::IconToggle(2, 13, 3, 13, &bd, press_tooltip ) ) + edited->setBidirectional(bd); + + ClosestIndex d = std::for_each(speed_values.begin(), speed_values.end(), ClosestIndex(edited->duration())); + int speed_index = d.index; + ImGui::SameLine(0, IMGUI_SAME_LINE / 2); + if (ImGuiToolkit::IconMultistate(speed_icon, &speed_index, speed_tooltip )) + edited->setDuration(speed_values[speed_index]); + + float val = edited->value(); + ImGui::SetNextItemWidth(right_align); + ImGui::SameLine(0, IMGUI_SAME_LINE / 2); + if (ImGui::SliderFloat("##CALLBACK_PLAYSPEED", &val, -10.f, 10.f, UNICODE_MULTIPLY " %.2f")) + edited->setValue(val); + + ImGui::SameLine(0, IMGUI_SAME_LINE / 2); + ImGuiToolkit::Indication("Factor to multiply the playback speed of a video source.", 16, 7); + } + break; + + case SourceCallback::CALLBACK_PLAYFFWD: + { + PlayFastForward *edited = static_cast(callback); + + ImGuiToolkit::Indication(press_tooltip[2], 18, 5); + ImGui::SameLine(0, IMGUI_SAME_LINE / 2); + + int val = (int) edited->value(); + ImGui::SetNextItemWidth(right_align); + ImGui::SameLine(0, IMGUI_SAME_LINE / 2); + if (ImGui::SliderInt("##CALLBACK_PLAYFFWD", &val, 30, 1000, "%d ms")) + edited->setValue( MAX(1, val) ); + + ImGui::SameLine(0, IMGUI_SAME_LINE / 2); + ImGuiToolkit::Indication("Step increment (in miliseconds) to jump fast-forward in a video source.", 13, 7); + } + break; + + case SourceCallback::CALLBACK_SEEK: + { + Seek *edited = static_cast(callback); + + bool bd = edited->bidirectional(); + if ( ImGuiToolkit::IconToggle(2, 13, 3, 13, &bd, press_tooltip ) ) + edited->setBidirectional(bd); + + // get value (seconds) and convert to minutes and seconds + float val = edited->value(); + int min = (int) floor(val / 60.f); + float sec = val - 60.f * (float) min; + + // filtering for reading MM:SS.MS text entry + static bool valid = true; + static std::regex RegExTime("([0-9]+\\:)*([0-5][0-9]|[0-9])(\\.[0-9]+)*"); + struct TextFilters { static int FilterTime(ImGuiInputTextCallbackData* data) { if (data->EventChar < 256 && strchr("0123456789.:", (char)data->EventChar)) return 0; return 1; } }; + char buf6[64] = ""; + sprintf(buf6, "%d:%.2f", min, sec ); + + // Text input field for MM:SS:MS seek target time + ImGui::SetNextItemWidth(right_align); + ImGui::SameLine(0, IMGUI_SAME_LINE / 2); + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, valid ? 1.0f : 0.2f, valid ? 1.0f : 0.2f, 1.f)); + ImGui::InputText("##CALLBACK_SEEK", buf6, 64, ImGuiInputTextFlags_CallbackCharFilter, TextFilters::FilterTime); + valid = std::regex_match(buf6, RegExTime); + if (ImGui::IsItemDeactivatedAfterEdit()) { + if (valid){ + // user confirmed the entry and the input is valid + min = 0; + std::string minutes = buf6; + std::string seconds = buf6; + const size_t min_idx = minutes.rfind(':'); + if (std::string::npos != min_idx) { + seconds = minutes.substr(min_idx+1); + minutes.erase(min_idx); + BaseToolkit::is_a_number(minutes, &min); + } + if (BaseToolkit::is_a_value(seconds, &sec)) + edited->setValue( sec + 60.f * (float) min ); + } + // force to test validity next frame + valid = false; + } + ImGui::PopStyleColor(); + + ImGui::SameLine(0, IMGUI_SAME_LINE / 3); + ImGuiToolkit::Indication("Target time (minutes and seconds, MM:SS.MS) to set where to jump to in a video source.", 15, 7); + } + break; + + case SourceCallback::CALLBACK_BRIGHTNESS: + { + SetBrightness *edited = static_cast(callback); + + bool bd = edited->bidirectional(); + if ( ImGuiToolkit::IconToggle(2, 13, 3, 13, &bd, press_tooltip ) ) + edited->setBidirectional(bd); + + ClosestIndex d = std::for_each(speed_values.begin(), speed_values.end(), ClosestIndex(edited->duration())); + int speed_index = d.index; + ImGui::SameLine(0, IMGUI_SAME_LINE / 2); + if (ImGuiToolkit::IconMultistate(speed_icon, &speed_index, speed_tooltip )) + edited->setDuration(speed_values[speed_index]); + + float val = edited->value(); + ImGui::SetNextItemWidth(right_align); + ImGui::SameLine(0, IMGUI_SAME_LINE / 2); + if (ImGui::SliderFloat("##CALLBACK_BRIGHTNESS", &val, -1.f, 1.f)) + edited->setValue(val); + + ImGui::SameLine(0, IMGUI_SAME_LINE / 2); + ImGuiToolkit::Indication("Brightness for color correction.", 5, 16); + } + break; + + case SourceCallback::CALLBACK_CONTRAST: + { + SetContrast *edited = static_cast(callback); + + bool bd = edited->bidirectional(); + if ( ImGuiToolkit::IconToggle(2, 13, 3, 13, &bd, press_tooltip ) ) + edited->setBidirectional(bd); + + ClosestIndex d = std::for_each(speed_values.begin(), speed_values.end(), ClosestIndex(edited->duration())); + int speed_index = d.index; + ImGui::SameLine(0, IMGUI_SAME_LINE / 2); + if (ImGuiToolkit::IconMultistate(speed_icon, &speed_index, speed_tooltip )) + edited->setDuration(speed_values[speed_index]); + + float val = edited->value(); + ImGui::SetNextItemWidth(right_align); + ImGui::SameLine(0, IMGUI_SAME_LINE / 2); + if (ImGui::SliderFloat("##CALLBACK_CONTRAST", &val, -1.f, 1.f)) + edited->setValue(val); + + ImGui::SameLine(0, IMGUI_SAME_LINE / 2); + ImGuiToolkit::Indication("Contrast for color correction.", 5, 16); + } + break; + + case SourceCallback::CALLBACK_SATURATION: + { + SetSaturation *edited = static_cast(callback); + + bool bd = edited->bidirectional(); + if ( ImGuiToolkit::IconToggle(2, 13, 3, 13, &bd, press_tooltip ) ) + edited->setBidirectional(bd); + + ClosestIndex d = std::for_each(speed_values.begin(), speed_values.end(), ClosestIndex(edited->duration())); + int speed_index = d.index; + ImGui::SameLine(0, IMGUI_SAME_LINE / 2); + if (ImGuiToolkit::IconMultistate(speed_icon, &speed_index, speed_tooltip )) + edited->setDuration(speed_values[speed_index]); + + float val = edited->value(); + ImGui::SetNextItemWidth(right_align); + ImGui::SameLine(0, IMGUI_SAME_LINE / 2); + if (ImGui::SliderFloat("##CALLBACK_SATURATION", &val, -1.f, 1.f)) + edited->setValue(val); + + ImGui::SameLine(0, IMGUI_SAME_LINE / 2); + ImGuiToolkit::Indication("Saturation for color correction.", 9, 16); + } + break; + + case SourceCallback::CALLBACK_HUE: + { + SetHue *edited = static_cast(callback); + + bool bd = edited->bidirectional(); + if ( ImGuiToolkit::IconToggle(2, 13, 3, 13, &bd, press_tooltip ) ) + edited->setBidirectional(bd); + + ClosestIndex d = std::for_each(speed_values.begin(), speed_values.end(), ClosestIndex(edited->duration())); + int speed_index = d.index; + ImGui::SameLine(0, IMGUI_SAME_LINE / 2); + if (ImGuiToolkit::IconMultistate(speed_icon, &speed_index, speed_tooltip )) + edited->setDuration(speed_values[speed_index]); + + float val = edited->value(); + ImGui::SetNextItemWidth(right_align); + ImGui::SameLine(0, IMGUI_SAME_LINE / 2); + if (ImGui::SliderFloat("##CALLBACK_HUE", &val, 0.f, 1.f)) + edited->setValue(val); + + ImGui::SameLine(0, IMGUI_SAME_LINE / 2); + ImGuiToolkit::Indication("Hue shift for color correction.", 12, 4); + } + break; + + case SourceCallback::CALLBACK_THRESHOLD: + { + SetThreshold *edited = static_cast(callback); + + bool bd = edited->bidirectional(); + if ( ImGuiToolkit::IconToggle(2, 13, 3, 13, &bd, press_tooltip ) ) + edited->setBidirectional(bd); + + ClosestIndex d = std::for_each(speed_values.begin(), speed_values.end(), ClosestIndex(edited->duration())); + int speed_index = d.index; + ImGui::SameLine(0, IMGUI_SAME_LINE / 2); + if (ImGuiToolkit::IconMultistate(speed_icon, &speed_index, speed_tooltip )) + edited->setDuration(speed_values[speed_index]); + + float val = edited->value(); + ImGui::SetNextItemWidth(right_align); + ImGui::SameLine(0, IMGUI_SAME_LINE / 2); + if (ImGui::SliderFloat("##CALLBACK_THRESHOLD", &val, 0.0, 1.0, val < 0.001 ? "None" : "%.2f")) + edited->setValue(val); + + ImGui::SameLine(0, IMGUI_SAME_LINE / 3); + ImGuiToolkit::Indication("Threshold for color correction.", 8, 1); + } + break; + + case SourceCallback::CALLBACK_GAMMA: + { + SetGamma *edited = static_cast(callback); + + bool bd = edited->bidirectional(); + if ( ImGuiToolkit::IconToggle(2, 13, 3, 13, &bd, press_tooltip ) ) + edited->setBidirectional(bd); + + ClosestIndex d = std::for_each(speed_values.begin(), speed_values.end(), ClosestIndex(edited->duration())); + int speed_index = d.index; + ImGui::SameLine(0, IMGUI_SAME_LINE / 2); + if (ImGuiToolkit::IconMultistate(speed_icon, &speed_index, speed_tooltip )) + edited->setDuration(speed_values[speed_index]); + + glm::vec4 val = edited->value(); + ImGui::SameLine(0, IMGUI_SAME_LINE / 2); + if (ImGui::ColorEdit3("##CALLBACK_GAMMA Color", glm::value_ptr(val), ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel) ) + edited->setValue(val); + ImGui::SetNextItemWidth(right_align); + ImGui::SameLine(0, IMGUI_SAME_LINE / 2); + if (ImGui::SliderFloat("##CALLBACK_GAMMA Gamma", &val.w, 0.5f, 10.f, "%.2f", 2.f) ) + edited->setValue(val); + + ImGui::SameLine(0, IMGUI_SAME_LINE / 2); + ImGuiToolkit::Indication("Set Gamma color correction.", 6, 4); + } + break; + + case SourceCallback::CALLBACK_INVERT: + { + SetInvert *edited = static_cast(callback); + + bool bd = edited->bidirectional(); + if ( ImGuiToolkit::IconToggle(2, 13, 3, 13, &bd, press_tooltip ) ) + edited->setBidirectional(bd); + ImGui::SameLine(0, IMGUI_SAME_LINE / 2); + + int val = (int) edited->value(); + ImGui::SetNextItemWidth(right_align); + if (ImGui::Combo("##CALLBACK_INVERT", &val, "None\0Color RGB\0Luminance\0")) + edited->setValue( (float) val); + ImGui::SameLine(0, IMGUI_SAME_LINE / 2); + ImGuiToolkit::Indication("Invert mode for color correction.", 6, 16); + } + break; + + default: + break; + } +} + + +void InputMappingWindow::Render() +{ + const ImGuiContext& g = *GImGui; + const ImVec2 keyItemSpacing = ImVec2(g.FontSize * 0.2f, g.FontSize * 0.2f); + const ImVec2 keyLetterIconSize = ImVec2(g.FontSize * 1.9f, g.FontSize * 1.9f); + const ImVec2 keyLetterItemSize = keyLetterIconSize + keyItemSpacing; + const ImVec2 keyNumpadIconSize = ImVec2(g.FontSize * 2.4f, g.FontSize * 2.4f); + const ImVec2 keyNumpadItemSize = keyNumpadIconSize + keyItemSpacing; + const float fixed_height = keyLetterItemSize.y * 5.f + g.Style.WindowBorderSize + g.FontSize + g.Style.FramePadding.y * 2.0f + keyItemSpacing.y; + const 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; + } + + // short variable to refer to session + Session *S = Mixer::manager().session(); + + // 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)) + { + // Disable + ImGui::MenuItem( ICON_FA_BAN " Disable", nullptr, &Settings::application.mapping.disabled); + + // Clear all + if ( ImGui::MenuItem( ICON_FA_BACKSPACE " Clear all" ) ) + S->clearInputCallbacks(); + + // 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(); + } + + // Options for current key + const std::string key = (current_input_ < INPUT_NUMPAD_LAST) ? " Key " : " "; + const std::string keymenu = ICON_FA_HAND_POINT_RIGHT + key + Control::manager().inputLabel(current_input_); + if (ImGui::BeginMenu(keymenu.c_str()) ) + { + if ( ImGui::MenuItem( ICON_FA_WINDOW_CLOSE " Reset mapping", NULL, false, S->inputAssigned(current_input_) ) ) + // remove all source callback of this input + S->deleteInputCallbacks(current_input_); + + if (ImGui::BeginMenu(ICON_FA_CLOCK " Metronome", S->inputAssigned(current_input_))) + { + Metronome::Synchronicity sync = S->inputSynchrony(current_input_); + bool active = sync == Metronome::SYNC_NONE; + if (ImGuiToolkit::MenuItemIcon(5, 13, " Not synchronized", NULL, active )){ + S->setInputSynchrony(current_input_, Metronome::SYNC_NONE); + } + active = sync == Metronome::SYNC_BEAT; + if (ImGuiToolkit::MenuItemIcon(6, 13, " Sync to beat", NULL, active )){ + S->setInputSynchrony(current_input_, Metronome::SYNC_BEAT); + } + active = sync == Metronome::SYNC_PHASE; + if (ImGuiToolkit::MenuItemIcon(7, 13, " Sync to phase", NULL, active )){ + S->setInputSynchrony(current_input_, Metronome::SYNC_PHASE); + } + ImGui::EndMenu(); + } + + std::list models = S->assignedInputs(); + if (ImGui::BeginMenu(ICON_FA_COPY " Duplicate", models.size() > 0) ) + { + for (auto m = models.cbegin(); m != models.cend(); ++m) { + if ( *m != current_input_ ) { + if ( ImGui::MenuItem( Control::inputLabel( *m ).c_str() ) ){ + S->copyInputCallback( *m, current_input_); + } + } + } + ImGui::EndMenu(); + } + + ImGui::EndMenu(); + } + ImGui::EndMenuBar(); + } + + // current window draw parameters + const ImGuiWindow* window = ImGui::GetCurrentWindow(); + ImDrawList* draw_list = window->DrawList; + ImVec2 frame_top = ImGui::GetCursorScreenPos(); + + // + // 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)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, keyItemSpacing); + ImVec4 color = ImGui::GetStyle().Colors[ImGuiCol_Header]; + color.w /= Settings::application.mapping.disabled ? 2.f : 0.9f; + ImGui::PushStyleColor(ImGuiCol_Header, color); + color = ImGui::GetStyle().Colors[ImGuiCol_Text]; + color.w /= Settings::application.mapping.disabled ? 2.f : 1.0f; + ImGui::PushStyleColor(ImGuiCol_Text, 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::manager().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(ik); + if (ImGui::Selectable(Control::manager().inputLabel(ik).c_str(), S->inputAssigned(ik), 0, keyLetterIconSize)) { + current_input_ = ik; + // TODO SET VAR current input assigned?? + } + ImGui::PopID(); + + // if user clics and drags an assigned key icon... + if (S->inputAssigned(ik) && ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) { + ImGui::SetDragDropPayload("DND_KEYBOARD", &ik, sizeof(uint)); + ImGui::Text( ICON_FA_HAND_POINT_RIGHT " %s ", Control::manager().inputLabel(ik).c_str()); + ImGui::EndDragDropSource(); + } + // ...and drops it onto another key icon + if (ImGui::BeginDragDropTarget()) { + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("DND_KEYBOARD")) { + if ( payload->DataSize == sizeof(uint) ) { + // drop means change key of input callbacks + uint previous_input_key = *(const int*)payload->Data; + // swap + S->swapInputCallback(previous_input_key, ik); + // switch to this key + current_input_ = ik; + } + } + ImGui::EndDragDropTarget(); + } + + // 5 elements in a row + if ((i % 5) < 4) ImGui::SameLine(); + + // Draw frame + ImVec2 pos = frame_top + keyLetterItemSize * ImVec2( i % 5, i / 5); + if (ik == current_input_) + draw_list->AddRect(pos, pos + keyLetterIconSize, ImGui::GetColorU32(ImGuiCol_Text), 6.f, ImDrawCornerFlags_All, 3.f); + else + draw_list->AddRect(pos, pos + keyLetterIconSize, ImGui::GetColorU32(ImGuiCol_Button), 6.f, ImDrawCornerFlags_All, 0.1f); + + } + ImGui::PopStyleColor(2); + ImGui::PopStyleVar(2); + ImGui::PopFont(); + + } + // + // NUMPAD + // + else if ( Settings::application.mapping.mode == 1 ) { + + // custom layout of numerical keypad + std::vector 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)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, keyItemSpacing); + ImVec4 color = ImGui::GetStyle().Colors[ImGuiCol_Header]; + color.w /= Settings::application.mapping.disabled ? 2.f : 0.9f; + ImGui::PushStyleColor(ImGuiCol_Header, color); + color = ImGui::GetStyle().Colors[ImGuiCol_Text]; + color.w /= Settings::application.mapping.disabled ? 2.f : 1.0f; + ImGui::PushStyleColor(ImGuiCol_Text, 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::manager().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(ik); + if (ImGui::Selectable(Control::manager().inputLabel(ik).c_str(), S->inputAssigned(ik), 0, iconsize)) { + current_input_ = ik; + } + ImGui::PopID(); + // if user clics and drags an assigned key icon... + if (S->inputAssigned(ik) && ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) { + ImGui::SetDragDropPayload("DND_NUMPAD", &ik, sizeof(uint)); + ImGui::Text( ICON_FA_HAND_POINT_RIGHT " %s ", Control::manager().inputLabel(ik).c_str()); + ImGui::EndDragDropSource(); + } + // ...and drops it onto another key icon + if (ImGui::BeginDragDropTarget()) { + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("DND_NUMPAD")) { + if ( payload->DataSize == sizeof(uint) ) { + // drop means change key of input callbacks + uint previous_input_key = *(const int*)payload->Data; + // swap + S->swapInputCallback(previous_input_key, ik); + // switch to this key + current_input_ = ik; + } + } + ImGui::EndDragDropTarget(); + } + + // 4 elements in a row + if ((p % 4) < 3) ImGui::SameLine(); + + // Draw frame + 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); + if (ik == current_input_) + draw_list->AddRect(pos, pos + iconsize, ImGui::GetColorU32(ImGuiCol_Text), 6.f, ImDrawCornerFlags_All, 3.f); + else + draw_list->AddRect(pos, pos + iconsize, ImGui::GetColorU32(ImGuiCol_Button), 6.f, ImDrawCornerFlags_All, 0.1f); + + } + ImGui::PopStyleColor(2); + ImGui::PopStyleVar(2); + ImGui::PopFont(); + + } + // + // MULTITOUCH OSC + // + else if ( Settings::application.mapping.mode == 2 ) { + + // Draw table of TouchOSC buttons + ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE); + ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, ImVec2(0.5f, 0.5f)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, keyItemSpacing); + ImVec4 color = ImGui::GetStyle().Colors[ImGuiCol_Header]; + color.w /= Settings::application.mapping.disabled ? 2.f : 0.9f; + ImGui::PushStyleColor(ImGuiCol_Header, color); + color = ImGui::GetStyle().Colors[ImGuiCol_Text]; + color.w /= Settings::application.mapping.disabled ? 2.f : 1.0f; + ImGui::PushStyleColor(ImGuiCol_Text, color); + + const ImVec2 touch_bar_size = keyNumpadItemSize * ImVec2(0.65f, 0.2f); + const ImVec2 touch_bar_pos = keyNumpadItemSize * ImVec2(0.125f, 0.6f); + + for (size_t t = 0; t < INPUT_MULTITOUCH_COUNT; ++t){ + uint it = INPUT_MULTITOUCH_FIRST + t; + ImVec2 pos = frame_top + keyNumpadItemSize * ImVec2( t % 4, t / 4); + + // draw overlay on active keys + if ( Control::manager().inputActive(it) ) { + draw_list->AddRectFilled(pos, pos + keyNumpadIconSize, ImGui::GetColorU32(ImGuiCol_Border), 6.f); + // set current + current_input_ = it; + } + + // draw key button + ImGui::PushID(it); + if (ImGui::Selectable(" ", S->inputAssigned(it), 0, keyNumpadIconSize)) + current_input_ = it; + ImGui::PopID(); + + // if user clics and drags an assigned key icon... + if (S->inputAssigned(it) && ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) { + ImGui::SetDragDropPayload("DND_MULTITOUCH", &it, sizeof(uint)); + ImGui::Text( ICON_FA_HAND_POINT_RIGHT " %s ", Control::manager().inputLabel(it).c_str()); + ImGui::EndDragDropSource(); + } + // ...and drops it onto another key icon + if (ImGui::BeginDragDropTarget()) { + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("DND_MULTITOUCH")) { + if ( payload->DataSize == sizeof(uint) ) { + // drop means change key of input callbacks + uint previous_input_key = *(const int*)payload->Data; + // swap + S->swapInputCallback(previous_input_key, it); + // switch to this key + current_input_ = it; + } + } + ImGui::EndDragDropTarget(); + } + + // 4 elements in a row + if ((t % 4) < 3) ImGui::SameLine(); + + // Draw frame + if (it == current_input_) + draw_list->AddRect(pos, pos + keyNumpadIconSize, ImGui::GetColorU32(ImGuiCol_Text), 6.f, ImDrawCornerFlags_All, 3.f); + else + draw_list->AddRect(pos, pos + keyNumpadIconSize, ImGui::GetColorU32(ImGuiCol_Button), 6.f, ImDrawCornerFlags_All, 0.1f); + + // Draw value bar + ImVec2 prev = ImGui::GetCursorScreenPos(); + ImGui::SetCursorScreenPos( pos + touch_bar_pos); + ImGui::ProgressBar(Control::manager().inputValue(it), touch_bar_size, ""); + ImGui::SetCursorScreenPos( prev ); + + } + + ImGui::PopStyleColor(2); + ImGui::PopStyleVar(2); + ImGui::PopFont(); + + } + // + // JOYSTICK + // + else if ( Settings::application.mapping.mode == 3 ) { + + // custom layout of gamepad buttons + std::vector gamepad_inputs = { INPUT_JOYSTICK_FIRST_BUTTON+11, INPUT_JOYSTICK_FIRST_BUTTON+13, + INPUT_JOYSTICK_FIRST_BUTTON+6, + INPUT_JOYSTICK_FIRST_BUTTON+2, INPUT_JOYSTICK_FIRST_BUTTON+3, + + INPUT_JOYSTICK_FIRST_BUTTON+14, INPUT_JOYSTICK_FIRST_BUTTON+12, + INPUT_JOYSTICK_FIRST_BUTTON+7, + INPUT_JOYSTICK_FIRST_BUTTON+0, INPUT_JOYSTICK_FIRST_BUTTON+1, + + INPUT_JOYSTICK_FIRST_BUTTON+4, INPUT_JOYSTICK_FIRST_BUTTON+9, + INPUT_JOYSTICK_FIRST_BUTTON+8, + INPUT_JOYSTICK_FIRST_BUTTON+10, INPUT_JOYSTICK_FIRST_BUTTON+5 }; + + std::vector< std::string > gamepad_labels = { ICON_FA_ARROW_UP, ICON_FA_ARROW_DOWN, + ICON_FA_CHEVRON_CIRCLE_LEFT, "X", "Y", + ICON_FA_ARROW_LEFT, ICON_FA_ARROW_RIGHT, + ICON_FA_CHEVRON_CIRCLE_RIGHT, "A", "B", + "L1", "LT", ICON_FA_DOT_CIRCLE, "RT", "R1" }; + + // Draw table of Gamepad Buttons + ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE); + ImVec4 color = ImGui::GetStyle().Colors[ImGuiCol_Header]; + color.w /= Settings::application.mapping.disabled ? 2.f : 0.9f; + ImGui::PushStyleColor(ImGuiCol_Header, color); + color = ImGui::GetStyle().Colors[ImGuiCol_Text]; + color.w /= Settings::application.mapping.disabled ? 2.f : 1.0f; + ImGui::PushStyleColor(ImGuiCol_Text, color); + + // CENTER text for button + ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, ImVec2(0.5f, 0.5f)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, keyItemSpacing); + for (size_t b = 0; b < gamepad_inputs.size(); ++b ){ + uint ig = gamepad_inputs[b]; + // draw overlay on active keys + if ( Control::manager().inputActive(ig) ) { + ImVec2 pos = frame_top + keyLetterItemSize * ImVec2( b % 5, b / 5); + draw_list->AddRectFilled(pos, pos + keyLetterIconSize, ImGui::GetColorU32(ImGuiCol_Border), 6.f); + // set current + current_input_ = ig; + } + // draw key button + ImGui::PushID(ig); + if (ImGui::Selectable(gamepad_labels[b].c_str(), S->inputAssigned(ig), 0, keyLetterIconSize)) + current_input_ = ig; + ImGui::PopID(); + + // if user clics and drags an assigned key icon... + if (S->inputAssigned(ig) && ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) { + ImGui::SetDragDropPayload("DND_GAMEPAD", &ig, sizeof(uint)); + ImGui::Text( ICON_FA_HAND_POINT_RIGHT " %s ", Control::manager().inputLabel(ig).c_str()); + ImGui::EndDragDropSource(); + } + // ...and drops it onto another key icon + if (ImGui::BeginDragDropTarget()) { + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("DND_GAMEPAD")) { + if ( payload->DataSize == sizeof(uint) ) { + // drop means change key of input callbacks + uint previous_input_key = *(const int*)payload->Data; + // swap + S->swapInputCallback(previous_input_key, ig); + // switch to this key + current_input_ = ig; + } + } + ImGui::EndDragDropTarget(); + } + + if ((b % 5) < 4) ImGui::SameLine(); + + // Draw frame + ImVec2 pos = frame_top + keyLetterItemSize * ImVec2( b % 5, b / 5); + if (ig == current_input_) + draw_list->AddRect(pos, pos + keyLetterIconSize, ImGui::GetColorU32(ImGuiCol_Text), 6.f, ImDrawCornerFlags_All, 3.f); + else if ( b!= 2 && b!= 7 && b!=12 ) + draw_list->AddRect(pos, pos + keyLetterIconSize, ImGui::GetColorU32(ImGuiCol_Button), 6.f, ImDrawCornerFlags_All, 0.1f); + + } + ImGui::PopStyleVar(); + ImGui::PopFont(); + + // Table of Gamepad Axis + const ImVec2 axis_top = frame_top + ImVec2(0.f, 3.f * keyLetterItemSize.y); + const ImVec2 axis_item_size(inputarea_width / 2.f, (2.f * keyLetterItemSize.y) / 3.f); + const ImVec2 axis_icon_size = axis_item_size - g.Style.ItemSpacing; + const ImVec2 axis_bar_size = axis_icon_size * ImVec2(0.4f, 0.4f); + ImVec2 axis_bar_pos(axis_icon_size.x * 0.5f, axis_icon_size.y *0.3f ); + + // LEFT align for 3 axis on the left + ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, ImVec2(0.09f, 0.5f)); + + // define top left screen cursor position + ImVec2 pos = axis_top; + // Draw a little bar showing activity on the gamepad axis + ImGui::SetCursorScreenPos( pos + axis_bar_pos); + ImGuiToolkit::ValueBar(Control::manager().inputValue(INPUT_JOYSTICK_FIRST_AXIS), axis_bar_size); + // Draw button to assign the axis to an action + ImGui::SetCursorScreenPos( pos ); + if (ImGui::Selectable("LX", S->inputAssigned(INPUT_JOYSTICK_FIRST_AXIS), 0, axis_icon_size)) + current_input_ = INPUT_JOYSTICK_FIRST_AXIS; + // Draw frame around current gamepad axis + if (current_input_ == INPUT_JOYSTICK_FIRST_AXIS) + draw_list->AddRect(pos, pos + axis_icon_size, ImGui::GetColorU32(ImGuiCol_Text), 6.f, ImDrawCornerFlags_All, 3.f); + + pos = axis_top + ImVec2( 0, axis_item_size.y); + ImGui::SetCursorScreenPos( pos + axis_bar_pos); + ImGuiToolkit::ValueBar(Control::manager().inputValue(INPUT_JOYSTICK_FIRST_AXIS+1), axis_bar_size); + ImGui::SetCursorScreenPos( pos ); + if (ImGui::Selectable("LY", S->inputAssigned(INPUT_JOYSTICK_FIRST_AXIS+1), 0, axis_icon_size)) + current_input_ = INPUT_JOYSTICK_FIRST_AXIS+1; + if (current_input_ == INPUT_JOYSTICK_FIRST_AXIS+1) + draw_list->AddRect(pos, pos + axis_icon_size, ImGui::GetColorU32(ImGuiCol_Text), 6.f, ImDrawCornerFlags_All, 3.f); + + pos = axis_top + ImVec2( 0, 2.f * axis_item_size.y); + ImGui::SetCursorScreenPos( pos + axis_bar_pos); + ImGuiToolkit::ValueBar(Control::manager().inputValue(INPUT_JOYSTICK_FIRST_AXIS+2), axis_bar_size); + ImGui::SetCursorScreenPos( pos ); + if (ImGui::Selectable("L2", S->inputAssigned(INPUT_JOYSTICK_FIRST_AXIS+2), 0, axis_icon_size)) + current_input_ = INPUT_JOYSTICK_FIRST_AXIS+2; + if (current_input_ == INPUT_JOYSTICK_FIRST_AXIS+2) + draw_list->AddRect(pos, pos + axis_icon_size, ImGui::GetColorU32(ImGuiCol_Text), 6.f, ImDrawCornerFlags_All, 3.f); + + ImGui::PopStyleVar(); + + // RIGHT align for 3 axis on the right + ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, ImVec2(0.91f, 0.5f)); + axis_bar_pos.x = g.Style.ItemSpacing.x; + + pos = axis_top + ImVec2( axis_item_size.x, 0.f); + ImGui::SetCursorScreenPos( pos + axis_bar_pos); + ImGuiToolkit::ValueBar(Control::manager().inputValue(INPUT_JOYSTICK_FIRST_AXIS+3), axis_bar_size); + ImGui::SetCursorScreenPos( pos ); + if (ImGui::Selectable("RX", S->inputAssigned(INPUT_JOYSTICK_FIRST_AXIS+3), 0, axis_icon_size)) + current_input_ = INPUT_JOYSTICK_FIRST_AXIS+3; + if (current_input_ == INPUT_JOYSTICK_FIRST_AXIS+3) + draw_list->AddRect(pos, pos + axis_icon_size, ImGui::GetColorU32(ImGuiCol_Text), 6.f, ImDrawCornerFlags_All, 3.f); + + pos = axis_top + ImVec2( axis_item_size.x, axis_item_size.y); + ImGui::SetCursorScreenPos( pos + axis_bar_pos); + ImGuiToolkit::ValueBar(Control::manager().inputValue(INPUT_JOYSTICK_FIRST_AXIS+4), axis_bar_size); + ImGui::SetCursorScreenPos( pos ); + if (ImGui::Selectable("RY", S->inputAssigned(INPUT_JOYSTICK_FIRST_AXIS+4), 0, axis_icon_size)) + current_input_ = INPUT_JOYSTICK_FIRST_AXIS+4; + if (current_input_ == INPUT_JOYSTICK_FIRST_AXIS+4) + draw_list->AddRect(pos, pos + axis_icon_size, ImGui::GetColorU32(ImGuiCol_Text), 6.f, ImDrawCornerFlags_All, 3.f); + + pos = axis_top + ImVec2( axis_item_size.x, 2.f * axis_item_size.y); + ImGui::SetCursorScreenPos( pos + axis_bar_pos); + ImGuiToolkit::ValueBar(Control::manager().inputValue(INPUT_JOYSTICK_FIRST_AXIS+5), axis_bar_size); + ImGui::SetCursorScreenPos( pos ); + if (ImGui::Selectable("R2", S->inputAssigned(INPUT_JOYSTICK_FIRST_AXIS+5), 0, axis_icon_size)) + current_input_ = INPUT_JOYSTICK_FIRST_AXIS+5; + if (current_input_ == INPUT_JOYSTICK_FIRST_AXIS+5) + draw_list->AddRect(pos, pos + axis_icon_size, ImGui::GetColorU32(ImGuiCol_Text), 6.f, ImDrawCornerFlags_All, 3.f); + + ImGui::PopStyleVar(2); + + // Done with color and font change + ImGui::PopStyleColor(2); + + } + + // Draw child Window (right) to list reactions to input + ImGui::SetCursorScreenPos(frame_top + g.Style.FramePadding + ImVec2(inputarea_width,0.f)); + { + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(2.f, g.Style.ItemSpacing.y * 2.f) ); + ImGui::BeginChild("InputsMappingInterfacePanel", ImVec2(0, 0), false); + + float w = ImGui::GetWindowWidth() *0.25f; + + /// + /// list of input callbacks for the current input + /// + if (S->inputAssigned(current_input_)) { + + auto result = S->getSourceCallbacks(current_input_); + for (auto kit = result.cbegin(); kit != result.cend(); ++kit) { + + Target target = kit->first; + SourceCallback *callback = kit->second; + + // push ID because we repeat the same UI + ImGui::PushID( (void*) callback); + + // Delete interface + if (ImGuiToolkit::IconButton(ICON_FA_MINUS, "Remove") ){ + S->deleteInputCallback(callback); + // reload + ImGui::PopID(); + break; + } + + // Select Target + ImGui::SameLine(0, IMGUI_SAME_LINE); + ImGui::SetNextItemWidth(w); + Target selected_target = ComboSelectTarget(target); + // if the selected target variant was filled with a value + if (selected_target.index() > 0) { + // reassign the callback to newly selected source + S->assignInputCallback(current_input_, selected_target, callback); + // reload + ImGui::PopID(); + break; + } + + // check if target is a Source with image processing enabled + bool withimageprocessing = false; + if ( target.index() == 1 ) { + if (Source * const* v = std::get_if(&target)) { + withimageprocessing = (*v)->imageProcessingEnabled(); + } + } + + // Select Reaction + ImGui::SameLine(0, IMGUI_SAME_LINE); + ImGui::SetNextItemWidth(w); + uint type = ComboSelectCallback( callback->type(), withimageprocessing ); + if (type > 0) { + // remove previous callback + S->deleteInputCallback(callback); + // assign callback + S->assignInputCallback(current_input_, target, SourceCallback::create((SourceCallback::CallbackType)type) ); + // reload + ImGui::PopID(); + break; + } + + // Adjust parameters + ImGui::SameLine(0, IMGUI_SAME_LINE); + if (callback) + SliderParametersCallback( callback, target ); + + ImGui::PopID(); + + } + + } + else { + ImGui::Text("No action mapped to this input. Add one with +."); + } + + /// + /// Add a new interface + /// + static bool temp_new_input = false; + static Target temp_new_target; + static uint temp_new_callback = 0; + + // step 1 : press '+' + if (temp_new_input) { + if (ImGuiToolkit::IconButton(ICON_FA_TIMES, "Cancel") ){ + temp_new_target = std::monostate(); + temp_new_callback = 0; + temp_new_input = false; + } + } + else if (ImGuiToolkit::IconButton(ICON_FA_PLUS, "Add mapping") ) + temp_new_input = true; + + if (temp_new_input) { + // step 2 : Get input for source + ImGui::SameLine(0, IMGUI_SAME_LINE); + ImGui::SetNextItemWidth(w); + + Target selected_target = ComboSelectTarget(temp_new_target); + // if the selected target variant was filled with a value + if (selected_target.index() > 0) { + temp_new_target = selected_target; + temp_new_callback = 0; + } + // possible new target + if (temp_new_target.index() > 0) { + // check if target is a Source with image processing enabled + bool withimageprocessing = false; + if ( temp_new_target.index() == 1 ) { + if (Source * const* v = std::get_if(&temp_new_target)) { + withimageprocessing = (*v)->imageProcessingEnabled(); + } + } + // step 3: Get input for callback type + ImGui::SameLine(0, IMGUI_SAME_LINE); + ImGui::SetNextItemWidth(w); + temp_new_callback = ComboSelectCallback( temp_new_callback, withimageprocessing ); + // user selected a callback type + if (temp_new_callback > 0) { + // step 4 : create new callback and add it to source + S->assignInputCallback(current_input_, temp_new_target, SourceCallback::create((SourceCallback::CallbackType)temp_new_callback) ); + // done + temp_new_target = std::monostate(); + temp_new_callback = 0; + temp_new_input = false; + } + } + } + + /// + /// Sync info lower right corner + /// + Metronome::Synchronicity sync = S->inputSynchrony(current_input_); + if ( sync > Metronome::SYNC_NONE) { + ImGui::SetCursorPos(ImGui::GetWindowSize() - ImVec2(50, 50)); + ImGuiToolkit::Icon( sync > Metronome::SYNC_BEAT ? 7 : 6, 13); + } + + // close child window + ImGui::EndChild(); + ImGui::PopStyleVar(); + } + + + ImGui::End(); +} + diff --git a/src/InputMappingWindow.h b/src/InputMappingWindow.h new file mode 100644 index 0000000..9ef49fd --- /dev/null +++ b/src/InputMappingWindow.h @@ -0,0 +1,32 @@ +#ifndef INPUTMAPPINGWINDOW_H +#define INPUTMAPPINGWINDOW_H + +#include +#include + +#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 ¤t); + 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 diff --git a/src/OutputPreviewWindow.cpp b/src/OutputPreviewWindow.cpp new file mode 100644 index 0000000..16c5dcb --- /dev/null +++ b/src/OutputPreviewWindow.cpp @@ -0,0 +1,634 @@ +/* + * This file is part of vimix - video live mixer + * + * **Copyright** (C) 2019-2023 Bruno Herbelin + * + * 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 . +**/ + +#include +#include +#include + +// 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 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(); + } +} + + diff --git a/src/OutputPreviewWindow.h b/src/OutputPreviewWindow.h new file mode 100644 index 0000000..02f3787 --- /dev/null +++ b/src/OutputPreviewWindow.h @@ -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 > _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 diff --git a/src/ShaderEditWindow.cpp b/src/ShaderEditWindow.cpp new file mode 100644 index 0000000..01a0f20 --- /dev/null +++ b/src/ShaderEditWindow.cpp @@ -0,0 +1,354 @@ + +// ImGui +#include "ImGuiToolkit.h" +#include "imgui_internal.h" + +#include +#include + +#include + +#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(); + 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(); + 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(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(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(); +} + diff --git a/src/ShaderEditWindow.h b/src/ShaderEditWindow.h new file mode 100644 index 0000000..3b17be5 --- /dev/null +++ b/src/ShaderEditWindow.h @@ -0,0 +1,35 @@ +#ifndef SHADEREDITWINDOW_H +#define SHADEREDITWINDOW_H + +#include +#include +#include + +#include "ImageFilter.h" +#include "WorkspaceWindow.h" + + +class ShaderEditWindow : public WorkspaceWindow +{ + ImageFilter *current_; + std::map filters_; + std::promise *compilation_; + std::future 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 diff --git a/src/SourceControlWindow.cpp b/src/SourceControlWindow.cpp new file mode 100644 index 0000000..f60aaa2 --- /dev/null +++ b/src/SourceControlWindow.cpp @@ -0,0 +1,1940 @@ +/* + * This file is part of vimix - video live mixer + * + * **Copyright** (C) 2019-2023 Bruno Herbelin + * + * 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 . +**/ + +#include +#include + +// ImGui +#include "ImGuiToolkit.h" +#include "imgui_internal.h" + +#include "defines.h" +#include "Log.h" +#include "Settings.h" +#include "SystemToolkit.h" +#include "DialogToolkit.h" +#include "GstToolkit.h" + +#include "Mixer.h" +#include "CloneSource.h" +#include "MediaSource.h" +#include "StreamSource.h" +#include "MediaPlayer.h" +#include "ActionManager.h" +#include "UserInterfaceManager.h" + +#include "SourceControlWindow.h" + +void DrawSource(Source *s, ImVec2 framesize, ImVec2 top_image, bool withslider = false, bool withinspector = false); +ImRect DrawSourceWithSlider(Source *s, ImVec2 top, ImVec2 rendersize, bool with_inspector); + + +SourceControlWindow::SourceControlWindow() : WorkspaceWindow("SourceController"), + min_width_(0.f), h_space_(0.f), v_space_(0.f), scrollbar_(0.f), + timeline_height_(0.f), mediaplayer_height_(0.f), buttons_width_(0.f), buttons_height_(0.f), + play_toggle_request_(false), replay_request_(false), pending_(false), + active_label_(LABEL_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_edit_pipeline_(false), mediaplayer_mode_(false), mediaplayer_slider_pressed_(false), mediaplayer_timeline_zoom_(1.f), + magnifying_glass(false) +{ + info_.setExtendedStringMode(); + + captureFolderDialog = new DialogToolkit::OpenFolderDialog("Capture frame Location"); +} + + +void SourceControlWindow::resetActiveSelection() +{ + info_.reset(); + active_selection_ = -1; + active_label_ = LABEL_AUTO_MEDIA_PLAYER; + play_toggle_request_ = false; + replay_request_ = false; + capture_request_ = false; +} + +void SourceControlWindow::setVisible(bool on) +{ + magnifying_glass = false; + + // restore workspace to show the window + if (WorkspaceWindow::clear_workspace_enabled) { + WorkspaceWindow::restoreWorkspace(on); + // do not change status if ask to hide (consider user asked to toggle because the window was not visible) + if (!on) return; + } + + if (Settings::application.widget.media_player_view > 0 && Settings::application.widget.media_player_view != Settings::application.current_view) { + Settings::application.widget.media_player_view = -1; + on = true; + } + + // if no selection in the player and in the source selection, show all sources + if (on && selection_.empty() && Mixer::selection().empty() ) + selection_ = valid_only( Mixer::manager().session()->getDepthSortedList() ); + + Settings::application.widget.media_player = on; +} + +bool SourceControlWindow::Visible() const +{ + return ( Settings::application.widget.media_player + && (Settings::application.widget.media_player_view < 0 || Settings::application.widget.media_player_view == Settings::application.current_view ) + ); +} + +void SourceControlWindow::Update() +{ + WorkspaceWindow::Update(); + + SourceList selectedsources; + + if (Settings::application.widget.media_player == false) + selection_.clear(); + + // get new selection or validate previous list if selection was not updated + selectedsources = Mixer::manager().validate(selection_); + if (selectedsources.empty() && !Mixer::selection().empty()) + selectedsources = valid_only(Mixer::selection().getCopy()); + + // compute number of source selected and playable + size_t n_source = selectedsources.size(); + size_t n_play = 0; + for (auto source = selectedsources.begin(); source != selectedsources.end(); ++source){ + if ( (*source)->active() && (*source)->playing() ) + n_play++; + } + + // + // Play button or keyboard [Space] was pressed + // + if ( play_toggle_request_ ) { + + for (auto source = selectedsources.begin(); source != selectedsources.end(); ++source) + (*source)->play( n_play < n_source ); + + play_toggle_request_ = false; + } + + // + // Replay / rewind button or keyboard [CTRL+Space] was pressed + // + if ( replay_request_ ) { + + for (auto source = selectedsources.begin(); source != selectedsources.end(); ++source) + (*source)->replay(); + + replay_request_ = false; + } + + // + // return from thread for selecting capture folder + // + if (captureFolderDialog->closed() && !captureFolderDialog->path().empty()) + // get the folder from this file dialog + Settings::application.source.capture_path = captureFolderDialog->path(); + + // + // Capture frame on current selection + // + Source *s = nullptr; + if ( selection_.size() == 1 ) + s = selection_.front(); + if ( s != nullptr) { + // back from capture of FBO: can save file + if ( capture.isFull() ){ + std::string filename; + // if sequencial naming of file is selected + if (Settings::application.source.capture_naming == 0 ) + filename = SystemToolkit::filename_sequential(Settings::application.source.capture_path, s->name(), "png"); + else + filename = SystemToolkit::filename_dateprefix(Settings::application.source.capture_path, s->name(), "png"); + // save capture and inform user + capture.save( filename ); + Log::Notify("Frame saved in %s", filename.c_str() ); + } + // request capture : initiate capture of FBO + if ( capture_request_ ) { + capture.captureFramebuffer( s->frame() ); + capture_request_ = false; + } + } + + // reset on session change + static Session *__session = nullptr; + if ( Mixer::manager().session() != __session ) { + __session = Mixer::manager().session(); + resetActiveSelection(); + } +} + +void SourceControlWindow::Render() +{ + // estimate window size + const ImGuiContext& g = *GImGui; + h_space_ = g.Style.ItemInnerSpacing.x; + v_space_ = g.Style.FramePadding.y; + buttons_height_ = g.FontSize + v_space_ * 4.0f ; + buttons_width_ = g.FontSize * 8.0f ; + min_width_ = 6.f * buttons_height_; + timeline_height_ = (g.FontSize + v_space_) * 2.0f ; // double line for each timeline + scrollbar_ = g.Style.ScrollbarSize; + // all together: 1 title bar + spacing + 1 toolbar + spacing + 2 timelines + scrollbar + mediaplayer_height_ = buttons_height_ + 2.f * timeline_height_ + 2.f * scrollbar_ + v_space_; + + // constraint size + ImGui::SetNextWindowSizeConstraints(ImVec2(min_width_, 2.f * mediaplayer_height_), ImVec2(FLT_MAX, FLT_MAX)); + ImGui::SetNextWindowPos(ImVec2(1180, 400), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowSize(ImVec2(400, 400), ImGuiCond_FirstUseEver); + + // Window named "SourceController" at instanciation + if ( !ImGui::Begin(name_, &Settings::application.widget.media_player, + ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse )) + { + ImGui::End(); + return; + } + + // menu (no title bar) + if (ImGui::BeginMenuBar()) + { + if (ImGuiToolkit::IconButton(4,16)){ + Settings::application.widget.media_player = false; + } + if (ImGui::BeginMenu(IMGUI_TITLE_MEDIAPLAYER)) + { + // + // Menu section for play control + // + if (ImGui::MenuItem( ICON_FA_FAST_BACKWARD " Restart", CTRL_MOD "Space", nullptr, !selection_.empty())) + replay_request_ = true; + if (ImGui::MenuItem( ICON_FA_PLAY " Play | Pause", "Space", nullptr, !selection_.empty())) + play_toggle_request_ = true; + + ImGui::Separator(); + // + // Menu section for display + // + if (ImGui::BeginMenu( ICON_FA_IMAGE " Displayed image")) + { + if (ImGuiToolkit::MenuItemIcon(8, 9, " Render")) + Settings::application.widget.media_player_slider = 0.0; + if (ImGuiToolkit::MenuItemIcon(6, 9, " Split")) + Settings::application.widget.media_player_slider = 0.5; + if (ImGuiToolkit::MenuItemIcon(7, 9, " Input")) + Settings::application.widget.media_player_slider = 1.0; + ImGui::EndMenu(); + } + if (ImGui::MenuItem( ICON_FA_TH " List all")) { + selection_.clear(); + resetActiveSelection(); + Mixer::manager().unsetCurrentSource(); + Mixer::selection().clear(); + selection_ = valid_only(Mixer::manager().session()->getDepthSortedList()); + } + if (ImGui::MenuItem( ICON_FA_MINUS " Clear")) { + selection_.clear(); + resetActiveSelection(); + Mixer::manager().unsetCurrentSource(); + Mixer::selection().clear(); + } + // + // Menu section for window management + // + ImGui::Separator(); + bool pinned = Settings::application.widget.media_player_view == Settings::application.current_view; + std::string menutext = std::string( ICON_FA_MAP_PIN " Stick to ") + Settings::application.views[Settings::application.current_view].name + " view"; + if ( ImGui::MenuItem( menutext.c_str(), nullptr, &pinned) ){ + if (pinned) + Settings::application.widget.media_player_view = Settings::application.current_view; + else + Settings::application.widget.media_player_view = -1; + } + if ( ImGui::MenuItem( MENU_CLOSE, SHORTCUT_PLAYER) ) { + Settings::application.widget.media_player = false; + selection_.clear(); + } + + ImGui::EndMenu(); + } + + if (ImGui::BeginMenu(active_label_.c_str())) + { + // info on selection status + size_t N = Mixer::manager().session()->numBatch(); + bool enabled = !selection_.empty() && active_selection_ < 0; + + // Menu : Dynamic selection + if (ImGui::MenuItem(LABEL_AUTO_MEDIA_PLAYER)) + resetActiveSelection(); + // Menu : store selection + if (ImGui::MenuItem(ICON_FA_PLUS_CIRCLE LABEL_STORE_SELECTION, NULL, false, enabled)) + { + active_selection_ = N; + active_label_ = std::string(ICON_FA_CHECK_CIRCLE " Batch #") + std::to_string(active_selection_); + Mixer::manager().session()->addBatch( ids(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_CIRCLE " Batch #") + std::to_string(i); + if (ImGui::MenuItem( label.c_str() )) + { + active_selection_ = i; + active_label_ = label; + info_.reset(); + } + } + } + + ImGui::EndMenu(); + } + + // + // Menu for capture frame + // + if ( ImGui::BeginMenu(ICON_FA_ARROW_ALT_CIRCLE_DOWN " Capture", selection_.size() == 1 ) ) + { + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_CAPTURE, 0.8f)); + if (ImGui::MenuItem( MENU_CAPTUREFRAME, "F10" )) + capture_request_ = true; + ImGui::PopStyleColor(1); + + // separator and hack for extending menu width + ImGui::Separator(); + ImGui::MenuItem("Settings ", nullptr, false, false); + + // path menu selection + static char* name_path[4] = { nullptr }; + if ( name_path[0] == nullptr ) { + for (int i = 0; i < 4; ++i) + name_path[i] = (char *) malloc( 1024 * sizeof(char)); + sprintf( name_path[1], "%s", ICON_FA_HOME " Home"); + sprintf( name_path[2], "%s", ICON_FA_FOLDER " File location"); + sprintf( name_path[3], "%s", ICON_FA_FOLDER_PLUS " Select"); + } + if (Settings::application.source.capture_path.empty()) + Settings::application.source.capture_path = SystemToolkit::home_path(); + sprintf( name_path[0], "%s", Settings::application.source.capture_path.c_str()); + int selected_path = 0; + ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); + ImGui::Combo("Path", &selected_path, name_path, 4); + if (selected_path > 2) + captureFolderDialog->open(); + else if (selected_path > 1) { + // file location of media player + if (mediaplayer_active_) + Settings::application.source.capture_path = SystemToolkit::path_filename( mediaplayer_active_->filename() ); + // else file location of session + else + Settings::application.source.capture_path = SystemToolkit::path_filename( Mixer::manager().session()->filename() ); + } + else if (selected_path > 0) + Settings::application.source.capture_path = SystemToolkit::home_path(); + + // 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.source.capture_path.c_str())) + SystemToolkit::open(Settings::application.source.capture_path); + ImGui::SetCursorPos(draw_pos); + + // Naming menu selection + static const char* naming_style[2] = { ICON_FA_SORT_NUMERIC_DOWN " Sequential", ICON_FA_CALENDAR " Date prefix" }; + ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); + ImGui::Combo("Filename", &Settings::application.source.capture_naming, naming_style, IM_ARRAYSIZE(naming_style)); + + ImGui::EndMenu(); + } + + // + // Menu for video Mediaplayer control + // + if (ImGui::BeginMenu(ICON_FA_FILM " Video", mediaplayer_active_) ) + { + 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", NULL, active )) + mediaplayer_active_->setSyncToMetronome(Metronome::SYNC_NONE); + active = sync == Metronome::SYNC_BEAT; + if (ImGuiToolkit::MenuItemIcon(6, 13, " Sync to beat", NULL, active )) + mediaplayer_active_->setSyncToMetronome(Metronome::SYNC_BEAT); + active = sync == Metronome::SYNC_PHASE; + if (ImGuiToolkit::MenuItemIcon(7, 13, " Sync to phase", NULL, active )) + mediaplayer_active_->setSyncToMetronome(Metronome::SYNC_PHASE); + ImGui::EndMenu(); + } + + ImGui::Separator(); + if (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(); + } + // always allow for hardware decoding to be disabled + 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(); + } + // TODO finalize pipeline editor +// if (ImGui::MenuItem(ICON_FA_HAT_WIZARD " gst pipeline")) +// mediaplayer_edit_pipeline_ = true; + + ImGui::EndMenu(); + } + + // button to activate the magnifying glass at top right corner + ImVec2 p = g.CurrentWindow->Pos; + p.x += g.CurrentWindow->Size.x - 2.1f * g.FontSize; + if (g.CurrentWindow->DC.CursorPos.x < p.x) + { + ImGui::SetCursorScreenPos(p); + if (selection_.size() == 1) { + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.f, 0.f, 0.f, 0.f)); + ImGuiToolkit::ButtonToggle( ICON_FA_SEARCH, &magnifying_glass); + ImGui::PopStyleColor(); + } + else + ImGui::TextDisabled(" " ICON_FA_SEARCH); + } + + ImGui::EndMenuBar(); + } + + // disable magnifying glass if window is deactivated + if ( g.NavWindow != g.CurrentWindow ) + magnifying_glass = false; + + // reset mediaplayer ptr + mediaplayer_active_ = nullptr; + + // render with appropriate method + if (active_selection_ > -1) + RenderSelection(active_selection_); + else + RenderSelectedSources(); + + ImGui::End(); + +} + +void DrawTimeScale(const char* label, guint64 duration, double width_ratio) +{ + // get window + ImGuiWindow* window = ImGui::GetCurrentWindow(); + if (window->SkipItems) + return; + + // get style & id + const ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; + const ImGuiID id = window->GetID(label); + + const ImVec2 timeline_size( static_cast( static_cast(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 > DrawTimeline(const char* label, Timeline *timeline, guint64 time, + double width_ratio, float height) +{ + std::list< std::pair > 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( static_cast(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(d) / static_cast(duration) ; + ImVec2 section_bbox_max = ImLerp(timeline_bbox.GetBL(), timeline_bbox.GetBR(), percent); + + // adjust bbox of section and render a timeline + ImRect section_bbox(section_bbox_min, section_bbox_max); + // render the timeline + ImGuiToolkit::RenderTimeline(section_bbox_min, section_bbox_max, section->begin, section->end, timeline->step()); + + // draw the cursor + float time_ = static_cast ( static_cast(time - section->begin) / static_cast(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(section_bbox_min.x,section->begin ) ); + ret.push_back( std::pair(section_bbox_max.x,section->end ) ); + + // iterate: next bbox of section starts at end of current + section_bbox_min.x = section_bbox_max.x; + } + + // detect if there is a gap after + if (e < timeline->duration()) + window->DrawList->AddRectFilled(ImVec2(section_bbox_min.x -2.f, plot_bbox.Min.y), ImVec2(section_bbox_min.x + 2.f, plot_bbox.Max.y), ImGui::GetColorU32(ImGuiCol_TitleBg)); + + return ret; +} + +void SourceControlWindow::RenderSelection(size_t i) +{ + ImVec2 top = ImGui::GetCursorScreenPos(); + ImVec2 rendersize = ImGui::GetContentRegionAvail() - ImVec2(0, buttons_height_ + scrollbar_ + v_space_); + ImVec2 bottom = ImVec2(top.x, top.y + rendersize.y + v_space_); + + selection_ = Mixer::manager().session()->getBatch(i); + int numsources = selection_.size(); + + // no source selected + if (numsources < 1) + { + /// + /// Centered text + /// + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.6, 0.6, 0.6, 0.5f)); + ImVec2 center = rendersize * ImVec2(0.5f, 0.5f); + ImGuiToolkit::PushFont(ImGuiToolkit::FONT_ITALIC); + center.x -= ImGui::GetTextLineHeight() * 2.f; + ImGui::SetCursorScreenPos(top + center); + ImGui::Text("Empty selection"); + ImGui::PopFont(); + ImGui::PopStyleColor(1); + } + else { + /// + /// Sources LIST + /// + /// + + // get max duration and max frame width + GstClockTime maxduration = 0; + std::list durations; + float maxframewidth = 0.f; + for (auto source = selection_.begin(); source != selection_.end(); ++source) { + // collect durations of all media sources + MediaSource *ms = dynamic_cast(*source); + if (ms != nullptr && !ms->mediaplayer()->isImage()) + durations.push_back(static_cast(static_cast(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(w) / static_cast(maxduration); + + // draw list in a scroll area + ImGui::BeginChild("##v_scroll2", rendersize, false); + { + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.f, v_space_)); + + // draw play time scale if a duration is set + if (maxduration > 0) { + ImGui::SetCursorPos(ImGui::GetCursorPos() + ImVec2( maxframewidth + h_space_, 0)); + DrawTimeScale("##timescale", maxduration, width_ratio); + } + + // loop over all sources + SourceList selection_sources = selection_; + /// + /// First pass: loop over media sources only (with timeline) + /// + for (auto source = selection_sources.begin(); source != selection_sources.end(); ) { + + // get media source + MediaSource *ms = dynamic_cast(*source); + if (ms == nullptr || ms->mediaplayer()->isImage()) { + // leave the source in the list and continue + ++source; + continue; + } + + // ok, get the media player of the media source + MediaPlayer *mp = ms->mediaplayer(); + + /// + /// Source Image Button + /// + ImVec2 image_top = ImGui::GetCursorPos(); + const ImVec2 framesize(1.5f * timeline_height_ * (*source)->frame()->aspectRatio(), 1.5f * timeline_height_); + int action = SourceButton(*source, framesize); + if (action > 1) + (*source)->play( ! (*source)->playing() ); + else if (action > 0) + UserInterface::manager().showSourceEditor(*source); + + // icon and text below thumbnail + ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO); + ImGuiToolkit::Icon( (*source)->icon().x, (*source)->icon().y); + if ((*source)->playable()) { + ImGui::SameLine(); + ImGui::Text(" %s", GstToolkit::time_to_string((*source)->playtime()).c_str() ); + } + ImGui::PopFont(); + + // start to draw timeline aligned at maximum frame width + horizontal space + ImVec2 pos = image_top + ImVec2( maxframewidth + h_space_, 0); + ImGui::SetCursorPos(pos); + + // draw the mediaplayer's timeline, with the indication of cursor position + // NB: use the same width/time ratio for all to ensure timing vertical correspondance + + // TODO : if (mp->syncToMetronome() > Metronome::SYNC_NONE) + DrawTimeline("##timeline_mediaplayer", mp->timeline(), mp->position(), + width_ratio / fabs(mp->playSpeed()), framesize.y); + + if ( w > maxframewidth ) { + + // next icon buttons are small + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(3.f, 3.f)); + + // next buttons sub id + ImGui::PushID( static_cast(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(*d)); + + // calculate position of icons + double x = static_cast(*d) * width_ratio; + ImGui::SetCursorPos(pos + ImVec2( static_cast(x) - 2.f, framesize.y + v_space_) ); + // depending on position relative to media play duration, offer corresponding action + double secdur = static_cast(mp->timeline()->sectionsDuration()); + guint64 playdur = static_cast( 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(*prev); + auto next = d; + next++; + selection_target_faster_ = SIGN(mp->playSpeed()) * secdur / static_cast(*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(*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(*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 " < 0) { + + // calculate position of icon + double x = static_cast(durations.front()) * width_ratio; + ImGui::SetCursorPos(pos + ImVec2( static_cast(x) - 2.f, framesize.y + v_space_) ); + + // offer only to adjust size by removing ending gap + if ( mp->timeline()->gapAt( mp->timeline()->end() ) ) { + if ( ImGuiToolkit::ButtonIcon(7, 0, "Remove end gap" )){ + if ( mp->timeline()->removeGaptAt(mp->timeline()->end()) ) { + std::ostringstream info; + info << SystemToolkit::base_filename( mp->filename() ) << ": Timeline Remove end gap"; + Action::manager().store(info.str()); + } + } + } + } + + ImGui::PopStyleVar(); + ImGui::PopID(); + } + + // next line position + ImGui::SetCursorPos(image_top + ImVec2(0, 2.0f * timeline_height_ + 2.f * v_space_)); + + // next source + source = selection_sources.erase(source); + } + + ImGui::Spacing(); + + /// + /// Second pass: loop over remaining sources (no timeline) + /// + ImGui::Columns( MAX( 1, MIN( int(ceil(w / 250.f)), (int)selection_sources.size()) ), "##selectioncolumns", false); + for (auto source = selection_sources.begin(); source != selection_sources.end(); ++source) { + /// + /// Source Image Button + /// + const ImVec2 framesize(1.5f * timeline_height_ * (*source)->frame()->aspectRatio(), 1.5f * timeline_height_); + int action = SourceButton(*source, framesize); + if (action > 1) + (*source)->play( ! (*source)->playing() ); + else if (action > 0) + UserInterface::manager().showSourceEditor(*source); + + // icon and text below thumbnail + ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO); + ImGuiToolkit::Icon( (*source)->icon().x, (*source)->icon().y); + if ((*source)->playable()) { + ImGui::SameLine(); + ImGui::Text(" %s", GstToolkit::time_to_string((*source)->playtime()).c_str() ); + } + ImGui::PopFont(); + + // next line position + ImGui::Spacing(); + ImGui::NextColumn(); + } + ImGui::Columns(1); + + ImGui::PopStyleVar(); + } + ImGui::EndChild(); + + } + + /// + /// context menu from actions above + /// + RenderSelectionContextMenu(); + + /// + /// Play button bar + /// + DrawButtonBar(bottom, rendersize.x); + + /// + /// Selection of sources + /// + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.00f, 0.00f, 0.00f, 0.00f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.14f, 0.14f, 0.14f, 0.5f)); + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.00f, 0.00f, 0.00f, 0.00f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(0.14f, 0.14f, 0.14f, 0.7f)); + + float width_combo = ImGui::GetContentRegionAvail().x - buttons_height_; + if (width_combo > buttons_width_) + { + ImGui::SameLine(0, width_combo -buttons_width_ ); + ImGui::SetNextItemWidth(buttons_width_); + std::string label = std::string(ICON_FA_CHECK_CIRCLE " ") + std::to_string(numsources) + ( numsources > 1 ? " sources" : " source"); + if (ImGui::BeginCombo("##SelectionImport", label.c_str())) + { + // select all playable sources + for (auto s = Mixer::manager().session()->begin(); s != Mixer::manager().session()->end(); ++s) { + if ( !(*s)->failed() ) { + std::string label = std::string((*s)->initials()) + " - " + (*s)->name(); + if (std::find(selection_.begin(),selection_.end(),*s) == selection_.end()) { + if (ImGui::MenuItem( label.c_str() , 0, false )) + Mixer::manager().session()->addSourceToBatch(i, *s); + } + else { + if (ImGui::MenuItem( label.c_str(), 0, true )) + Mixer::manager().session()->removeSourceFromBatch(i, *s); + } + } + } + ImGui::EndCombo(); + } + } + + ImGui::SameLine(); + ImGui::SetCursorPosX(rendersize.x - buttons_height_ / 1.3f); + if (ImGui::Button(ICON_FA_MINUS_CIRCLE)) { + resetActiveSelection(); + Mixer::manager().session()->deleteBatch(i); + } + if (ImGui::IsItemHovered()) + ImGuiToolkit::ToolTip("Delete batch"); + + ImGui::PopStyleColor(4); +} + +void SourceControlWindow::RenderSelectionContextMenu() +{ + if (selection_mediaplayer_ == nullptr) + return; + + if (selection_context_menu_) { + ImGui::OpenPopup("source_controller_selection_context_menu"); + selection_context_menu_ = false; + } + if (ImGui::BeginPopup("source_controller_selection_context_menu")) + { + std::ostringstream info; + info << SystemToolkit::base_filename( selection_mediaplayer_->filename() ); + + if ( ImGuiToolkit::MenuItemIcon(14, 16, ICON_FA_CARET_LEFT " Accelerate", NULL, false, fabs(selection_target_faster_) > 0 )){ + selection_mediaplayer_->setPlaySpeed( selection_target_faster_ ); + info << ": Speed x" << std::setprecision(3) << selection_target_faster_; + Action::manager().store(info.str()); + } + if ( ImGuiToolkit::MenuItemIcon(15, 16, "Slow down " ICON_FA_CARET_RIGHT, NULL, false, fabs(selection_target_slower_) > 0 )){ + selection_mediaplayer_->setPlaySpeed( selection_target_slower_ ); + info << ": Speed x" << std::setprecision(3) << selection_target_slower_; + Action::manager().store(info.str()); + } + if ( selection_mediaplayer_->timeline()->gapAt( selection_mediaplayer_->timeline()->end()) ) { + + if ( ImGuiToolkit::MenuItemIcon(7, 0, "Restore ending" )){ + info << ": Restore ending"; + if ( selection_mediaplayer_->timeline()->removeGaptAt(selection_mediaplayer_->timeline()->end()) ) + Action::manager().store(info.str()); + } + + } + ImGui::EndPopup(); + } + +} + +void DrawInspector(uint texture, ImVec2 texturesize, ImVec2 cropsize, ImVec2 origin) +{ + if (Settings::application.source.inspector_zoom > 0 && ImGui::IsWindowFocused()) + { + // region size is computed with zoom factor from settings + float region_sz = texturesize.x / Settings::application.source.inspector_zoom; + + // get coordinates of area to zoom at mouse position + const ImGuiIO& io = ImGui::GetIO(); + float region_x = io.MousePos.x - origin.x - region_sz * 0.5f; + if (region_x < 0.f) + region_x = 0.f; + else if (region_x > texturesize.x - region_sz) + region_x = texturesize.x - region_sz; + + float region_y = io.MousePos.y - origin.y - region_sz * 0.5f; + if (region_y < 0.f) + region_y = 0.f; + else if (region_y > texturesize.y - region_sz) + region_y = texturesize.y - region_sz; + + // Tooltip without border and 100% opaque + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 1.f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.f, 0.f) ); + ImGui::BeginTooltip(); + + // compute UV and display image in tooltip + ImVec2 uv0 = ImVec2((region_x) / cropsize.x, (region_y) / cropsize.y); + ImVec2 uv1 = ImVec2((region_x + region_sz) / cropsize.x, (region_y + region_sz) / cropsize.y); + ImVec2 uv2 = ImClamp( uv1, ImVec2(0.f, 0.f), ImVec2(1.f, 1.f)); + uv0 += (uv2-uv1); + ImGui::Image((void*)(intptr_t)texture, ImVec2(texturesize.x / 3.f, texturesize.x / 3.f), uv0, uv2, ImVec4(1.0f, 1.0f, 1.0f, 1.0f), ImVec4(1.0f, 1.0f, 1.0f, 0.5f)); + + ImGui::EndTooltip(); + ImGui::PopStyleVar(3); + } +} + +void DrawSource(Source *s, ImVec2 framesize, ImVec2 top_image, bool withslider, bool withinspector) +{ + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + + // info on source + CloneSource *cloned = dynamic_cast(s); + + // draw pre and post-processed parts if necessary + if (s->imageProcessingEnabled() || s->textureTransformed() || cloned != nullptr) { + + // + // LEFT of slider : original texture + // + ImVec2 slider = framesize * ImVec2(Settings::application.widget.media_player_slider,1.f); + ImGui::Image((void*)(uintptr_t) s->texture(), slider, ImVec2(0.f,0.f), ImVec2(Settings::application.widget.media_player_slider,1.f)); + if ( withinspector && ImGui::IsItemHovered() ) + DrawInspector(s->texture(), framesize, framesize, top_image); + + // + // RIGHT of slider : post-processed image (after crop and color correction) + // + ImVec2 cropsize = framesize * ImVec2 ( s->frame()->projectionArea().x, s->frame()->projectionArea().y); + ImVec2 croptop = (framesize - cropsize) * 0.5f; + // no overlap of slider with cropped area + if (slider.x < croptop.x) { + // draw cropped area + ImGui::SetCursorScreenPos( top_image + croptop ); + ImGui::Image((void*)(uintptr_t) s->frame()->texture(), cropsize, ImVec2(0.f, 0.f), ImVec2(1.f,1.f)); + if ( withinspector && ImGui::IsItemHovered() ) + DrawInspector(s->frame()->texture(), framesize, cropsize, top_image + croptop); + } + // overlap of slider with cropped area (horizontally) + else if (slider.x < croptop.x + cropsize.x ) { + // compute slider ratio of cropped area + float cropped_slider = (slider.x - croptop.x) / cropsize.x; + // top x moves with slider + ImGui::SetCursorScreenPos( top_image + ImVec2(slider.x, croptop.y) ); + // size is reduced by slider + ImGui::Image((void*)(uintptr_t) s->frame()->texture(), cropsize * ImVec2(1.f -cropped_slider, 1.f), ImVec2(cropped_slider, 0.f), ImVec2(1.f,1.f)); + if ( withinspector && ImGui::IsItemHovered() ) + DrawInspector(s->frame()->texture(), framesize, cropsize, top_image + croptop); + } + // else : no render of cropped area + + ImU32 slider_color = ImGui::GetColorU32(ImGuiCol_NavWindowingHighlight); + if (withslider) + { + // + // SLIDER + // + // user input : move slider horizontally + ImGui::SetCursorScreenPos(top_image + ImVec2(- 20.f, 0.5f * framesize.y - 20.0f)); + ImGuiToolkit::InvisibleSliderFloat("#media_player_slider2", &Settings::application.widget.media_player_slider, 0.f, 1.f, ImVec2(framesize.x + 40.f, 40.0f) ); + // affordance: cursor change to horizontal arrows + if (ImGui::IsItemHovered() || ImGui::IsItemFocused()) { + ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); + slider_color = ImGui::GetColorU32(ImGuiCol_Text); + } + // graphical indication of slider + draw_list->AddCircleFilled(top_image + slider * ImVec2(1.f, 0.5f), 20.f, slider_color, 26); + } + // graphical indication of separator (vertical line) + draw_list->AddLine(top_image + slider * ImVec2(1.f,0.0f), top_image + slider, slider_color, 1); + } + // no post-processed to show: draw simple texture + else { + ImGui::Image((void*)(uintptr_t) s->texture(), framesize); + if ( withinspector && ImGui::IsItemHovered() ) + DrawInspector(s->texture(), framesize, framesize, top_image); + } +} + +ImRect DrawSourceWithSlider(Source *s, ImVec2 top, ImVec2 rendersize, bool with_inspector) +{ + /// + /// Centered frame + /// + FrameBuffer *frame = s->frame(); + ImVec2 framesize = rendersize; + ImVec2 corner(0.f, 0.f); + ImVec2 tmp = ImVec2(framesize.y * frame->aspectRatio(), framesize.x / frame->aspectRatio()); + if (tmp.x > framesize.x) { + //vertically centered, modulo height of font for display of info under frame + corner.y = MAX( (framesize.y - tmp.y) / 2.f - ImGui::GetStyle().IndentSpacing, 0.f); + framesize.y = tmp.y; + } + else { + // horizontally centered + // corner.x = (framesize.x - tmp.x) - MAX( (framesize.x - tmp.x) / 2.f - ImGui::GetStyle().IndentSpacing, 0.f); + corner.x = (framesize.x - tmp.x) / 2.f; + framesize.x = tmp.x; + } + + /// + /// Image + /// + const ImVec2 top_image = top + corner; + ImGui::SetCursorScreenPos(top_image); + // 100% opacity for the image (ensure true colors) + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 1.f); + DrawSource(s, framesize, top_image, true, with_inspector); + ImGui::PopStyleVar(); + + return ImRect( top_image, top_image + framesize); +} + + +int SourceControlWindow::SourceButton(Source *s, ImVec2 framesize) +{ + // returns > 0 if clicked, >1 if clicked on center play/pause button + int ret = 0; + + // all subsequent buttons are identified under a unique source id + ImGui::PushID(s->id()); + + // Adjust size of font to frame size + ImGuiToolkit::PushFont(framesize.x > 350.f ? ImGuiToolkit::FONT_LARGE : ImGuiToolkit::FONT_MONO); + + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + const float H = ImGui::GetTextLineHeight(); + const ImVec2 frame_top = ImGui::GetCursorScreenPos(); + const ImVec2 frame_center = frame_top + ImVec2((framesize.x - H) / 2.f, (framesize.y - H) / 2.f); + ImU32 frame_color = ImGui::GetColorU32(ImGuiCol_Text); + ImU32 icon_color = ImGui::GetColorU32(ImGuiCol_NavWindowingHighlight); + + // 1. draw texture of source + DrawSource(s, framesize, frame_top); + + // 2. interactive centered button Play / Pause + if (s->active() && s->playable()) { + //draw_list->AddText(frame_center, ImGui::GetColorU32(ImGuiCol_Text), SourcePlayIcon(s)); + ImGui::SetCursorScreenPos(frame_center - ImVec2(H * 0.2f, H * 0.2f)); + ImGui::InvisibleButton("##sourcebutton_icon", ImVec2(H * 1.2f, H * 1.2f)); + if (ImGui::IsItemHovered() || ImGui::IsItemActive()){ + frame_color = ImGui::GetColorU32(ImGuiCol_NavWindowingHighlight); + icon_color = ImGui::GetColorU32(ImGuiCol_Text); + } + if (ImGui::IsItemClicked()) { + ret = 2; + } + } + + // back to draw overlay + ImGui::SetCursorScreenPos(frame_top + ImVec2(1.f, 0.f) ); + + // 3. draw initials in up-left corner + draw_list->AddText(frame_top + ImVec2(H * 0.2f, H * 0.1f), ImGui::GetColorU32(ImGuiCol_Text), s->initials()); + + // 4. interactive surface on whole texture with frame overlay on mouse over + ImGui::InvisibleButton("##sourcebutton", framesize); + if (ImGui::IsItemHovered() || ImGui::IsItemClicked()) + { + // border + draw_list->AddRect(frame_top, frame_top + framesize - ImVec2(1.f, 0.f), frame_color, 0, 0, 3.f); + // centered icon in front of dark background + if (s->active() && s->playable()) { + draw_list->AddRectFilled(frame_center - ImVec2(H * 0.2f, H * 0.2f), + frame_center + ImVec2(H * 1.1f, H * 1.1f), ImGui::GetColorU32(ImGuiCol_TitleBgCollapsed), 6.f); + draw_list->AddText(frame_center, icon_color, s->playing() ? ICON_FA_PAUSE : ICON_FA_PLAY); + } + } + if (ImGui::IsItemClicked()) { + ret = 1; + } + + // pops + ImGui::PopFont(); + ImGui::PopID(); + + return ret; +} + +void SourceControlWindow::RenderSelectedSources() +{ + ImVec2 top = ImGui::GetCursorScreenPos(); + ImVec2 rendersize = ImGui::GetContentRegionAvail() - ImVec2(0, buttons_height_ + scrollbar_ + v_space_); + ImVec2 bottom = ImVec2(top.x, top.y + rendersize.y + v_space_); + + // get new selection or validate previous list if selection was not updated + if (Mixer::selection().empty()) + selection_ = valid_only(Mixer::manager().validate(selection_)); + else + selection_ = valid_only(Mixer::selection().getCopy()); + int numsources = selection_.size(); + + // no source selected + if (numsources < 1) + { + /// + /// Centered text + /// + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.6, 0.6, 0.6, 0.5f)); + ImVec2 center = rendersize * ImVec2(0.5f, 0.5f); + ImGuiToolkit::PushFont(ImGuiToolkit::FONT_ITALIC); + center.x -= ImGui::GetTextLineHeight() * 2.f; + ImGui::SetCursorScreenPos(top + center); + ImGui::Text("Nothing to play"); + ImGui::PopFont(); + ImGui::PopStyleColor(1); + + /// + /// Play button bar (automatically disabled) + /// + DrawButtonBar(bottom, rendersize.x); + + } + // single source selected + else if (numsources < 2) + { + /// + /// Single Source display + /// + RenderSingleSource( selection_.front() ); + } + // Several sources selected + else { + /// + /// Sources grid + /// + ImGui::BeginChild("##v_scroll", rendersize, false); + { + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.f, 2.f * v_space_)); + + // area horizontal 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(numcolumns); + widthcolumn -= scrollbar_; + + // loop over sources in grid + for (auto source = selection_.begin(); source != selection_.end(); ++source) { + /// + /// Source Image Button + /// + ImVec2 image_top = ImGui::GetCursorPos(); + ImVec2 framesize(widthcolumn, widthcolumn / (*source)->frame()->aspectRatio()); + int action = SourceButton(*source, framesize); + if (action > 1) + (*source)->play( ! (*source)->playing() ); + else if (action > 0) + UserInterface::manager().showSourceEditor(*source); + + // source icon lower left corner + ImGuiToolkit::PushFont(framesize.x > 350.f ? ImGuiToolkit::FONT_LARGE : ImGuiToolkit::FONT_MONO); + float h = ImGui::GetTextLineHeightWithSpacing(); + ImGui::SetCursorPos(image_top + ImVec2( h_space_, framesize.y -v_space_ - h )); + ImGuiToolkit::Icon( (*source)->icon().x, (*source)->icon().y); + if ((*source)->playable()) { + ImGui::SameLine(); + ImGui::Text(" %s", GstToolkit::time_to_string((*source)->playtime()).c_str() ); + } + ImGui::PopFont(); + + ImGui::Spacing(); + ImGui::NextColumn(); + } + + ImGui::Columns(1); + ImGui::PopStyleVar(); + } + ImGui::EndChild(); + + /// + /// Play button bar + /// + DrawButtonBar(bottom, rendersize.x); + + /// + /// Menu to store Selection from current sources + /// + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.00f, 0.00f, 0.00f, 0.00f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.14f, 0.14f, 0.14f, 0.5f)); + + float space = ImGui::GetContentRegionAvail().x; + float width = buttons_height_; + std::string label(ICON_FA_PLUS_CIRCLE); + if (space > buttons_width_) { // enough space to show full button with label text + label += LABEL_STORE_SELECTION; + width = buttons_width_; + } + ImGui::SameLine(0, space -width); + ImGui::SetNextItemWidth(width); + if (ImGui::Button( label.c_str() )) { + active_selection_ = Mixer::manager().session()->numBatch(); + active_label_ = std::string("Batch #") + std::to_string(active_selection_); + Mixer::manager().session()->addBatch( ids(selection_) ); + } + if (space < buttons_width_ && ImGui::IsItemHovered()) + ImGuiToolkit::ToolTip(LABEL_STORE_SELECTION); + + ImGui::PopStyleColor(2); + } + +} + +void SourceControlWindow::RenderSingleSource(Source *s) +{ + static bool show_overlay_info = false; + + if ( s == nullptr) + return; + + // in case of a MediaSource + MediaSource *ms = dynamic_cast(s); + if ( ms != nullptr && ms->playable() ) { + RenderMediaPlayer( ms ); + } + else + { + /// + /// Draw centered Image + /// + ImVec2 top = ImGui::GetCursorScreenPos(); + ImVec2 rendersize = ImGui::GetContentRegionAvail() - ImVec2(0, buttons_height_ + scrollbar_ + v_space_); + ImVec2 bottom = ImVec2(top.x, top.y + rendersize.y + v_space_); + + ImRect imgarea = DrawSourceWithSlider(s, top, rendersize, magnifying_glass); + + /// + /// Info overlays + /// + if (!show_overlay_info){ + ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE); + + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.f, 0.f, 0.f, 0.8f)); + ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2(h_space_-1.f, v_space_-1.f)); + ImGui::Text("%s", s->initials()); + ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2(h_space_+1.f, v_space_+1.f)); + ImGui::Text("%s", s->initials()); + ImGui::PopStyleColor(1); + + ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2(h_space_, v_space_)); + ImGui::Text("%s", s->initials()); + ImGui::PopFont(); + } + if (!magnifying_glass) { + + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.f, 0.f, 0.f, 0.8f)); + ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2(imgarea.GetWidth() - ImGui::GetTextLineHeightWithSpacing(), v_space_)); + ImGui::Text(ICON_FA_CIRCLE); + ImGui::PopStyleColor(1); + + ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2(imgarea.GetWidth() - ImGui::GetTextLineHeightWithSpacing(), v_space_)); + ImGui::Text(ICON_FA_INFO_CIRCLE); + show_overlay_info = ImGui::IsItemHovered(); + if (show_overlay_info){ + // fill info string + s->accept(info_); + // draw overlay frame and text + float tooltip_height = 3.f * ImGui::GetTextLineHeightWithSpacing(); + ImGui::GetWindowDrawList()->AddRectFilled(imgarea.GetTL(), imgarea.GetTL() + ImVec2(imgarea.GetWidth(), tooltip_height), IMGUI_COLOR_OVERLAY); + ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2(h_space_, v_space_)); + ImGui::Text("%s", info_.str().c_str()); + // special case Streams: print framerate + StreamSource *sts = dynamic_cast(s); + if (sts && s->playing()) { + ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2(imgarea.GetWidth() - 1.5f * buttons_height_, 0.5f * tooltip_height)); + ImGui::Text("%.1f Hz", sts->stream()->updateFrameRate()); + } + } + else + // make sure next ItemHovered refreshes the info_ + info_.reset(); + } + + /// + /// icon & timing in lower left corner + /// + ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE); + ImGui::SetCursorScreenPos(bottom + ImVec2(h_space_, -ImGui::GetTextLineHeightWithSpacing() - h_space_ )); + ImGuiToolkit::Icon( s->icon().x, s->icon().y); + ImGui::SameLine(); + ImGui::Text("%s", s->playable() ? GstToolkit::time_to_string(s->playtime()).c_str() : " " ); + ImGui::PopFont(); + + /// + /// Play button bar + /// + DrawButtonBar(bottom, rendersize.x); + } +} + +void SourceControlWindow::RenderMediaPlayer(MediaSource *ms) +{ + static bool show_overlay_info = false; + + mediaplayer_active_ = ms->mediaplayer(); + + // for action manager + std::ostringstream oss; + oss << SystemToolkit::base_filename( mediaplayer_active_->filename() ); + + // for draw + const float slider_zoom_width = timeline_height_ / 2.f; + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + + /// + /// Draw centered Image + /// + const ImVec2 top = ImGui::GetCursorScreenPos(); + const ImVec2 rendersize = ImGui::GetContentRegionAvail() - ImVec2(0, mediaplayer_height_); + ImVec2 bottom = ImVec2(top.x, top.y + rendersize.y + v_space_); + + ImRect imgarea = DrawSourceWithSlider(ms, top, rendersize, magnifying_glass); + + /// + /// Info overlays + /// + if (!show_overlay_info){ + ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE); + + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.f, 0.f, 0.f, 0.8f)); + ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2(h_space_-1.f, v_space_-1.f)); + ImGui::Text("%s", ms->initials()); + ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2(h_space_+1.f, v_space_+1.f)); + ImGui::Text("%s", ms->initials()); + ImGui::PopStyleColor(1); + + ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2(h_space_, v_space_)); + ImGui::Text("%s", ms->initials()); + ImGui::PopFont(); + } + if (!magnifying_glass) { + + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.f, 0.f, 0.f, 0.8f)); + ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2(imgarea.GetWidth() - ImGui::GetTextLineHeightWithSpacing(), v_space_)); + ImGui::Text(ICON_FA_CIRCLE); + ImGui::PopStyleColor(1); + + ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2(imgarea.GetWidth() - ImGui::GetTextLineHeightWithSpacing(), v_space_)); + ImGui::Text(ICON_FA_INFO_CIRCLE); + show_overlay_info = ImGui::IsItemHovered(); + if (show_overlay_info){ + // information visitor + mediaplayer_active_->accept(info_); + float tooltip_height = 3.f * ImGui::GetTextLineHeightWithSpacing(); + draw_list->AddRectFilled(imgarea.GetTL(), imgarea.GetTL() + ImVec2(imgarea.GetWidth(), tooltip_height), IMGUI_COLOR_OVERLAY); + ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2(h_space_, v_space_)); + ImGui::Text("%s", info_.str().c_str()); + + // Icon to inform hardware decoding + if ( mediaplayer_active_->decoderName().compare("software") != 0) { + ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2( imgarea.GetWidth() - ImGui::GetTextLineHeightWithSpacing(), 0.35f * tooltip_height)); + ImGui::Text(ICON_FA_MICROCHIP); + } + + // refresh frequency + if ( mediaplayer_active_->isPlaying()) { + ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2( imgarea.GetWidth() - 1.5f * buttons_height_, 0.667f * tooltip_height)); + ImGui::Text("%.1f Hz", mediaplayer_active_->updateFrameRate()); + } + } + } + + /// + /// icon & timing in lower left corner + /// + ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE); + ImVec2 S (h_space_, -ImGui::GetTextLineHeightWithSpacing() - h_space_ ); + ImGui::SetCursorScreenPos(bottom + S); + ImGuiToolkit::Icon( ms->icon().x, ms->icon().y); + ImGui::SameLine(); + ImGui::Text( "%s", GstToolkit::time_to_string(mediaplayer_active_->position()).c_str() ); + + /// + /// Sync info lower right corner + /// + Metronome::Synchronicity sync = mediaplayer_active_->syncToMetronome(); + if ( sync > Metronome::SYNC_NONE) { + static bool show = true; + if (mediaplayer_active_->pending()) + show = !show; + else + show = true; + if (show) { + S.x = rendersize.x + S.y; + ImGui::SetCursorScreenPos(bottom + S); + ImGuiToolkit::Icon( sync > Metronome::SYNC_BEAT ? 7 : 6, 13); + } + } + + ImGui::PopFont(); + + /// + /// media player timelines + /// + const ImVec2 scrollwindow = ImVec2(ImGui::GetContentRegionAvail().x - slider_zoom_width - 3.0, + 2.f * timeline_height_ + scrollbar_ ); + + if ( mediaplayer_active_->isEnabled() ) { + + // ignore actual play status of mediaplayer when slider is pressed + if (!mediaplayer_slider_pressed_) + mediaplayer_mode_ = mediaplayer_active_->isPlaying(); + + // seek position + guint64 seek_t = mediaplayer_active_->position(); + + // scrolling sub-window + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(1.f, 1.f)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 1.f); + ImGui::PushStyleColor(ImGuiCol_ScrollbarBg, ImVec4(0.f, 0.f, 0.f, 0.0f)); + + ImGui::SetCursorScreenPos(bottom + ImVec2(1.f, 0.f)); + ImGui::BeginChild("##scrolling", scrollwindow, false, ImGuiWindowFlags_HorizontalScrollbar); + { + ImVec2 size = ImGui::CalcItemSize(ImVec2(-FLT_MIN, 0.0f), ImGui::CalcItemWidth(), timeline_height_ -1); + size.x *= mediaplayer_timeline_zoom_; + + Timeline *tl = mediaplayer_active_->timeline(); + if (tl->is_valid()) + { + bool released = false; + if ( 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 + // TODO : if (mediaplayer_active_->syncToMetronome() > Metronome::SYNC_NONE) + mediaplayer_slider_pressed_ = ImGuiToolkit::TimelineSlider("##timeline", &seek_t, tl->begin(), + tl->first(), tl->end(), tl->step(), size.x); + + } + } + ImGui::EndChild(); + + // 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 > icons_loop = { {0,15}, {1,15}, {19,14} }; + static std::vector< std::string > tooltips_loop = { "Stop at end", "Loop to start", "Bounce (reverse speed)" }; + current_loop = (int) mediaplayer_active_->loop(); + if ( ImGuiToolkit::IconMultistate(icons_loop, ¤t_loop, tooltips_loop) ) + mediaplayer_active_->setLoop( (MediaPlayer::LoopMode) current_loop ); + + // speed slider (if enough space) + if ( rendersize.x > min_width_ * 1.2f ) { + ImGui::SameLine(0, MAX(h_space_ * 2.f, rendersize.x - min_width_ * 1.4f) ); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - buttons_height_ ); + // speed slider + float speed = static_cast(mediaplayer_active_->playSpeed()); + if (ImGui::DragFloat( "##Speed", &speed, 0.01f, -10.f, 10.f, UNICODE_MULTIPLY " %.2f")) + mediaplayer_active_->setPlaySpeed( static_cast(speed) ); + // store action on mouse release + if (ImGui::IsItemDeactivatedAfterEdit()){ + oss << ": Speed x" << std::setprecision(3) << speed; + Action::manager().store(oss.str()); + } + if (ImGui::IsItemHovered()) + ImGuiToolkit::ToolTip("Play speed"); + } + + ImGui::SameLine(); + ImGui::SetCursorPosX(rendersize.x - buttons_height_ / 1.4f); + if (ImGuiToolkit::IconButton(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 { + + ImGui::SetCursorScreenPos(bottom + ImVec2(1.f, 0.f)); + + const ImGuiContext& g = *GImGui; + const double width_ratio = static_cast(scrollwindow.x - slider_zoom_width + g.Style.FramePadding.x ) / static_cast(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::tuple > fading_options = { + {19, 7, "Fade in"}, + {18, 7, "Fade out"}, + { 0, 8, "Auto fade in & out"} + }; + ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); + ImGuiToolkit::ComboIcon("Fading", &l, fading_options); + + static int c = 0; + static std::vector< std::tuple > curve_options = { + {18, 3, "Linear"}, + {19, 3, "Progressive"}, + {17, 3, "Abrupt"} + }; + ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); + ImGuiToolkit::ComboIcon("Curve", &c, curve_options); + + static uint d = 1000; + ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); + ImGuiToolkit::SliderTiming ("Duration", &d, 200, 5050, 50, "Maximum"); + if (d > 5000) + d = UINT_MAX; + + bool close = false; + ImGui::SetCursorPos(pos + ImVec2(0.f, area.y - buttons_height_)); + if (ImGui::Button(ICON_FA_TIMES " Cancel", ImVec2(area.x * 0.3f, 0))) + close = true; + ImGui::SetCursorPos(pos + ImVec2(area.x * 0.7f, area.y - buttons_height_)); + ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetStyleColorVec4(ImGuiCol_Tab)); + if (ImGui::Button(ICON_FA_CHECK " Apply", ImVec2(area.x * 0.3f, 0)) + || ImGui::IsKeyPressedMap(ImGuiKey_Enter) || ImGui::IsKeyPressedMap(ImGuiKey_KeyPadEnter) ) { + close = true; + // timeline to edit + Timeline *tl = mediaplayer_active_->timeline(); + switch (l) { + case 0: + tl->fadeIn(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()); + } + ImGui::PopStyleColor(1); + + if (close) + ImGui::CloseCurrentPopup(); + ImGui::EndPopup(); + } + + + if (mediaplayer_edit_pipeline_) { + ImGui::OpenPopup("Edit gstreamer pipeline"); + mediaplayer_edit_pipeline_ = false; + } + const ImVec2 mpp_dialog_size(buttons_width_ * 3.f, buttons_height_ * 6); + ImGui::SetNextWindowSize(mpp_dialog_size, ImGuiCond_Always); + const ImVec2 mpp_dialog_pos = top + rendersize * 0.5f - mpp_dialog_size * 0.5f; + ImGui::SetNextWindowPos(mpp_dialog_pos, ImGuiCond_Always); + if (ImGui::BeginPopupModal("Edit gstreamer pipeline", NULL, ImGuiWindowFlags_NoResize)) + { + bool update_new_source = false; + const ImVec2 pos = ImGui::GetCursorPos(); + const ImVec2 area = ImGui::GetContentRegionAvail(); + + static std::vector< std::pair< std::string, std::string> > _examples = { {"Primary color", "frei0r-filter-primaries" }, + {"Histogram", "frei0r-filter-rgb-parade"}, + {"Emboss", "frei0r-filter-emboss"}, + {"B&W", "frei0r-filter-bw0r"}, + {"Gamma", "frei0r-filter-gamma gamma=0.5"}, + {"Broken tv", "frei0r-filter-nosync0r "} + }; + 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; + + ImGui::Spacing(); + ImGui::Text("Enter pipeline element and apply:"); + ImGui::Spacing(); + + // Editor + if ( ImGuiToolkit::InputCodeMultiline("Filter", &_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(); + } + + // TODO use update_new_source + + bool close = false; + ImGui::SetCursorPos(pos + ImVec2(0.f, area.y - buttons_height_)); + if (ImGui::Button(ICON_FA_TIMES " Cancel", ImVec2(area.x * 0.3f, 0))) + close = true; + ImGui::SetCursorPos(pos + ImVec2(area.x * 0.7f, area.y - buttons_height_)); + ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetStyleColorVec4(ImGuiCol_Tab)); + if (ImGui::Button(ICON_FA_CHECK " Apply", ImVec2(area.x * 0.3f, 0)) + || ImGui::IsKeyPressedMap(ImGuiKey_Enter) || ImGui::IsKeyPressedMap(ImGuiKey_KeyPadEnter) ) { + close = true; + + // apply to pipeline + mediaplayer_active_->setEffect(_description); + } + ImGui::PopStyleColor(1); + + if (close) + ImGui::CloseCurrentPopup(); + ImGui::EndPopup(); + } +} + +void SourceControlWindow::DrawButtonBar(ImVec2 bottom, float width) +{ + // draw box + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + draw_list->AddRectFilled(bottom, bottom + ImVec2(width, buttons_height_), ImGui::GetColorU32(ImGuiCol_FrameBg), h_space_); + + // prepare position to draw text + ImGui::SetCursorScreenPos(bottom + ImVec2(h_space_, v_space_) ); + + // play bar is enabled if only one source selected is enabled + bool enabled = false; + size_t n_play = 0; + for (auto source = selection_.begin(); source != selection_.end(); ++source){ + if ( (*source)->active() && (*source)->playable()) + enabled = true; + if ( (*source)->playing() ) + n_play++; + } + + // buttons style for disabled / enabled bar + if (enabled) { + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.00f, 0.00f, 0.00f, 0.00f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.14f, 0.14f, 0.14f, 0.5f)); + } + else { + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.6, 0.6, 0.6, 0.5f)); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.00f, 0.00f, 0.00f, 0.00f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.00f, 0.00f, 0.00f, 0.00f)); + } + + // Always the rewind button + if (ImGui::Button(ICON_FA_FAST_BACKWARD) && enabled) { + for (auto source = selection_.begin(); source != selection_.end(); ++source) + (*source)->replay(); + } + ImGui::SameLine(0, h_space_); + + // unique play / pause button + if (n_play < 1 || selection_.size() == n_play) { + if (n_play) { + if (ImGui::Button(ICON_FA_PAUSE) && enabled) { + for (auto source = selection_.begin(); source != selection_.end(); ++source) + (*source)->play(false); + } + } + 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); +} diff --git a/src/SourceControlWindow.h b/src/SourceControlWindow.h new file mode 100644 index 0000000..0746e65 --- /dev/null +++ b/src/SourceControlWindow.h @@ -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 diff --git a/src/TimerMetronomeWindow.cpp b/src/TimerMetronomeWindow.cpp new file mode 100644 index 0000000..71a702f --- /dev/null +++ b/src/TimerMetronomeWindow.cpp @@ -0,0 +1,323 @@ +/* + * This file is part of vimix - video live mixer + * + * **Copyright** (C) 2019-2023 Bruno Herbelin + * + * 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 . +**/ + +// 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(); +} diff --git a/src/TimerMetronomeWindow.h b/src/TimerMetronomeWindow.h new file mode 100644 index 0000000..bd15cdc --- /dev/null +++ b/src/TimerMetronomeWindow.h @@ -0,0 +1,31 @@ +#ifndef TIMERMETRONOMEWINDOW_H +#define TIMERMETRONOMEWINDOW_H + +#include +#include + +#include + +#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 diff --git a/src/UserInterfaceManager.cpp b/src/UserInterfaceManager.cpp index 509f305..d3bb2c8 100644 --- a/src/UserInterfaceManager.cpp +++ b/src/UserInterfaceManager.cpp @@ -17,18 +17,14 @@ * along with this program. If not, see . **/ +#define PLOT_ARRAY_SIZE 180 +#define WINDOW_TOOLBOX_ALPHA 0.35f +#define WINDOW_TOOLBOX_DIST_TO_BORDER 10.f + #include #include #include -#include -#include -#include -#include -#include -#include -#include - -using namespace std; +#include // ImGui #include "imgui.h" @@ -44,9 +40,6 @@ using namespace std; #include #include -#include -#include -#include // generic image loader #define STB_IMAGE_IMPLEMENTATION @@ -55,58 +48,42 @@ using namespace std; #include #include "defines.h" +#include "Settings.h" #include "Log.h" #include "SystemToolkit.h" #include "DialogToolkit.h" #include "BaseToolkit.h" +#include "NetworkToolkit.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 "Connection.h" #include "SessionCreator.h" #include "Mixer.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 "MediaSource.h" #include "PatternSource.h" #include "DeviceSource.h" -#include "StreamSource.h" #include "MultiFileSource.h" -#include "ImageFilter.h" -#include "Metronome.h" -#include "VideoBroadcast.h" #include "ShmdataBroadcast.h" +#include "VideoBroadcast.h" #include "MultiFileRecorder.h" -#include "TextEditor.h" -TextEditor _editor; - #include "UserInterfaceManager.h" -#define PLOT_CIRCLE_SEGMENTS 64 -#define PLOT_ARRAY_SIZE 180 -#define WINDOW_TOOLBOX_ALPHA 0.35f -#define WINDOW_TOOLBOX_DIST_TO_BORDER 10.f + // 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()); -void DrawInspector(uint texture, ImVec2 texturesize, ImVec2 cropsize, ImVec2 origin); -void DrawSource(Source *s, ImVec2 framesize, ImVec2 top_image, bool withslider = false, bool withinspector = false); -ImRect DrawSourceWithSlider(Source *s, ImVec2 top, ImVec2 rendersize, bool with_inspector); + std::string readable_date_time_string(std::string date){ if (date.length()<12) @@ -116,16 +93,19 @@ std::string readable_date_time_string(std::string date){ return s; } -// Helper functions for imgui window aspect-ratio constraints -struct CustomConstraints +class Thumbnail { - 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); - } + float aspect_ratio_; + uint texture_; + +public: + Thumbnail(); + ~Thumbnail(); + + void reset(); + void fill (const FrameBufferImage *image); + bool filled(); + void Render(float width); }; UserInterface::UserInterface() @@ -888,7 +868,7 @@ void UserInterface::Render() else { // windows if (Settings::application.widget.help) - helpwindow.Render(); + RenderHelp(); if (Settings::application.widget.toolbox) toolbox.Render(); @@ -2237,15 +2217,8 @@ void ToolBox::Render() } -/// -/// SESSION REPAIR WINDOW -/// -HelperToolbox::HelperToolbox() -{ -} - -void HelperToolbox::Render() +void UserInterface::RenderHelp() { // first run ImGui::SetNextWindowPos(ImVec2(520, 20), ImGuiCond_FirstUseEver); @@ -2614,4598 +2587,6 @@ void HelperToolbox::Render() } -/// -/// SMART WINDOW -/// - -bool WorkspaceWindow::clear_workspace_enabled = false; -std::list 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); - } - } -} - - -/// -/// 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_edit_pipeline_(false), mediaplayer_mode_(false), mediaplayer_slider_pressed_(false), mediaplayer_timeline_zoom_(1.f), - magnifying_glass(false) -{ - info_.setExtendedStringMode(); - - captureFolderDialog = new DialogToolkit::OpenFolderDialog("Capture frame Location"); -} - - -void SourceController::resetActiveSelection() -{ - info_.reset(); - active_selection_ = -1; - active_label_ = LABEL_AUTO_MEDIA_PLAYER; - play_toggle_request_ = false; - replay_request_ = false; - capture_request_ = false; -} - -void SourceController::setVisible(bool on) -{ - magnifying_glass = false; - - // restore workspace to show the window - if (WorkspaceWindow::clear_workspace_enabled) { - WorkspaceWindow::restoreWorkspace(on); - // do not change status if ask to hide (consider user asked to toggle because the window was not visible) - if (!on) return; - } - - if (Settings::application.widget.media_player_view > 0 && Settings::application.widget.media_player_view != Settings::application.current_view) { - Settings::application.widget.media_player_view = -1; - on = true; - } - - // if no selection in the player and in the source selection, show all sources - if (on && selection_.empty() && Mixer::selection().empty() ) - selection_ = valid_only( Mixer::manager().session()->getDepthSortedList() ); - - Settings::application.widget.media_player = on; -} - -bool 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; - - if (Settings::application.widget.media_player == false) - selection_.clear(); - - // get new selection or validate previous list if selection was not updated - selectedsources = Mixer::manager().validate(selection_); - if (selectedsources.empty() && !Mixer::selection().empty()) - selectedsources = valid_only(Mixer::selection().getCopy()); - - // compute number of source selected and playable - size_t n_source = selectedsources.size(); - size_t n_play = 0; - for (auto source = selectedsources.begin(); source != selectedsources.end(); ++source){ - if ( (*source)->active() && (*source)->playing() ) - n_play++; - } - - // - // Play button or keyboard [Space] was pressed - // - if ( play_toggle_request_ ) { - - for (auto source = selectedsources.begin(); source != selectedsources.end(); ++source) - (*source)->play( n_play < n_source ); - - play_toggle_request_ = false; - } - - // - // Replay / rewind button or keyboard [CTRL+Space] was pressed - // - if ( replay_request_ ) { - - for (auto source = selectedsources.begin(); source != selectedsources.end(); ++source) - (*source)->replay(); - - replay_request_ = false; - } - - // - // return from thread for selecting capture folder - // - if (captureFolderDialog->closed() && !captureFolderDialog->path().empty()) - // get the folder from this file dialog - Settings::application.source.capture_path = captureFolderDialog->path(); - - // - // Capture frame on current selection - // - Source *s = nullptr; - if ( selection_.size() == 1 ) - s = selection_.front(); - if ( s != nullptr) { - // back from capture of FBO: can save file - if ( capture.isFull() ){ - std::string filename; - // if sequencial naming of file is selected - if (Settings::application.source.capture_naming == 0 ) - filename = SystemToolkit::filename_sequential(Settings::application.source.capture_path, s->name(), "png"); - else - filename = SystemToolkit::filename_dateprefix(Settings::application.source.capture_path, s->name(), "png"); - // save capture and inform user - capture.save( filename ); - Log::Notify("Frame saved in %s", filename.c_str() ); - } - // request capture : initiate capture of FBO - if ( capture_request_ ) { - capture.captureFramebuffer( s->frame() ); - capture_request_ = false; - } - } - - // reset on session change - static Session *__session = nullptr; - if ( Mixer::manager().session() != __session ) { - __session = Mixer::manager().session(); - resetActiveSelection(); - } -} - -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 * 8.0f ; - min_width_ = 6.f * buttons_height_; - timeline_height_ = (g.FontSize + v_space_) * 2.0f ; // double line for each timeline - scrollbar_ = g.Style.ScrollbarSize; - // all together: 1 title bar + spacing + 1 toolbar + spacing + 2 timelines + scrollbar - mediaplayer_height_ = buttons_height_ + 2.f * timeline_height_ + 2.f * scrollbar_ + v_space_; - - // constraint size - ImGui::SetNextWindowSizeConstraints(ImVec2(min_width_, 2.f * mediaplayer_height_), ImVec2(FLT_MAX, FLT_MAX)); - ImGui::SetNextWindowPos(ImVec2(1180, 400), ImGuiCond_FirstUseEver); - ImGui::SetNextWindowSize(ImVec2(400, 400), ImGuiCond_FirstUseEver); - - // Window named "SourceController" at instanciation - if ( !ImGui::Begin(name_, &Settings::application.widget.media_player, - ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse )) - { - ImGui::End(); - return; - } - - // menu (no title bar) - if (ImGui::BeginMenuBar()) - { - if (ImGuiToolkit::IconButton(4,16)){ - Settings::application.widget.media_player = false; - } - if (ImGui::BeginMenu(IMGUI_TITLE_MEDIAPLAYER)) - { - // - // Menu section for play control - // - if (ImGui::MenuItem( ICON_FA_FAST_BACKWARD " Restart", CTRL_MOD "Space", nullptr, !selection_.empty())) - replay_request_ = true; - if (ImGui::MenuItem( ICON_FA_PLAY " Play | Pause", "Space", nullptr, !selection_.empty())) - play_toggle_request_ = true; - - ImGui::Separator(); - // - // Menu section for display - // - if (ImGui::BeginMenu( ICON_FA_IMAGE " Displayed image")) - { - if (ImGuiToolkit::MenuItemIcon(8, 9, " Render")) - Settings::application.widget.media_player_slider = 0.0; - if (ImGuiToolkit::MenuItemIcon(6, 9, " Split")) - Settings::application.widget.media_player_slider = 0.5; - if (ImGuiToolkit::MenuItemIcon(7, 9, " Input")) - Settings::application.widget.media_player_slider = 1.0; - ImGui::EndMenu(); - } - if (ImGui::MenuItem( ICON_FA_TH " List all")) { - selection_.clear(); - resetActiveSelection(); - Mixer::manager().unsetCurrentSource(); - Mixer::selection().clear(); - selection_ = valid_only(Mixer::manager().session()->getDepthSortedList()); - } - if (ImGui::MenuItem( ICON_FA_MINUS " Clear")) { - selection_.clear(); - resetActiveSelection(); - Mixer::manager().unsetCurrentSource(); - Mixer::selection().clear(); - } - // - // Menu section for window management - // - ImGui::Separator(); - bool pinned = Settings::application.widget.media_player_view == Settings::application.current_view; - std::string menutext = std::string( ICON_FA_MAP_PIN " Stick to ") + Settings::application.views[Settings::application.current_view].name + " view"; - if ( ImGui::MenuItem( menutext.c_str(), nullptr, &pinned) ){ - if (pinned) - Settings::application.widget.media_player_view = Settings::application.current_view; - else - Settings::application.widget.media_player_view = -1; - } - if ( ImGui::MenuItem( MENU_CLOSE, SHORTCUT_PLAYER) ) { - Settings::application.widget.media_player = false; - selection_.clear(); - } - - ImGui::EndMenu(); - } - - if (ImGui::BeginMenu(active_label_.c_str())) - { - // info on selection status - size_t N = Mixer::manager().session()->numBatch(); - bool enabled = !selection_.empty() && active_selection_ < 0; - - // Menu : Dynamic selection - if (ImGui::MenuItem(LABEL_AUTO_MEDIA_PLAYER)) - resetActiveSelection(); - // Menu : store selection - if (ImGui::MenuItem(ICON_FA_PLUS_CIRCLE LABEL_STORE_SELECTION, NULL, false, enabled)) - { - active_selection_ = N; - active_label_ = std::string(ICON_FA_CHECK_CIRCLE " Batch #") + std::to_string(active_selection_); - Mixer::manager().session()->addBatch( ids(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_CIRCLE " Batch #") + std::to_string(i); - if (ImGui::MenuItem( label.c_str() )) - { - active_selection_ = i; - active_label_ = label; - info_.reset(); - } - } - } - - ImGui::EndMenu(); - } - - // - // Menu for capture frame - // - if ( ImGui::BeginMenu(ICON_FA_ARROW_ALT_CIRCLE_DOWN " Capture", selection_.size() == 1 ) ) - { - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_CAPTURE, 0.8f)); - if (ImGui::MenuItem( MENU_CAPTUREFRAME, "F10" )) - capture_request_ = true; - ImGui::PopStyleColor(1); - - // separator and hack for extending menu width - ImGui::Separator(); - ImGui::MenuItem("Settings ", nullptr, false, false); - - // path menu selection - static char* name_path[4] = { nullptr }; - if ( name_path[0] == nullptr ) { - for (int i = 0; i < 4; ++i) - name_path[i] = (char *) malloc( 1024 * sizeof(char)); - sprintf( name_path[1], "%s", ICON_FA_HOME " Home"); - sprintf( name_path[2], "%s", ICON_FA_FOLDER " File location"); - sprintf( name_path[3], "%s", ICON_FA_FOLDER_PLUS " Select"); - } - if (Settings::application.source.capture_path.empty()) - Settings::application.source.capture_path = SystemToolkit::home_path(); - sprintf( name_path[0], "%s", Settings::application.source.capture_path.c_str()); - int selected_path = 0; - ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); - ImGui::Combo("Path", &selected_path, name_path, 4); - if (selected_path > 2) - captureFolderDialog->open(); - else if (selected_path > 1) { - // file location of media player - if (mediaplayer_active_) - Settings::application.source.capture_path = SystemToolkit::path_filename( mediaplayer_active_->filename() ); - // else file location of session - else - Settings::application.source.capture_path = SystemToolkit::path_filename( Mixer::manager().session()->filename() ); - } - else if (selected_path > 0) - Settings::application.source.capture_path = SystemToolkit::home_path(); - - // 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.source.capture_path.c_str())) - SystemToolkit::open(Settings::application.source.capture_path); - ImGui::SetCursorPos(draw_pos); - - // Naming menu selection - static const char* naming_style[2] = { ICON_FA_SORT_NUMERIC_DOWN " Sequential", ICON_FA_CALENDAR " Date prefix" }; - ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); - ImGui::Combo("Filename", &Settings::application.source.capture_naming, naming_style, IM_ARRAYSIZE(naming_style)); - - ImGui::EndMenu(); - } - - // - // Menu for video Mediaplayer control - // - if (ImGui::BeginMenu(ICON_FA_FILM " Video", mediaplayer_active_) ) - { - 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", NULL, active )) - mediaplayer_active_->setSyncToMetronome(Metronome::SYNC_NONE); - active = sync == Metronome::SYNC_BEAT; - if (ImGuiToolkit::MenuItemIcon(6, 13, " Sync to beat", NULL, active )) - mediaplayer_active_->setSyncToMetronome(Metronome::SYNC_BEAT); - active = sync == Metronome::SYNC_PHASE; - if (ImGuiToolkit::MenuItemIcon(7, 13, " Sync to phase", NULL, active )) - mediaplayer_active_->setSyncToMetronome(Metronome::SYNC_PHASE); - ImGui::EndMenu(); - } - - ImGui::Separator(); - if (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(); - } - // always allow for hardware decoding to be disabled - 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(); - } - // TODO finalize pipeline editor -// if (ImGui::MenuItem(ICON_FA_HAT_WIZARD " gst pipeline")) -// mediaplayer_edit_pipeline_ = true; - - ImGui::EndMenu(); - } - - // button to activate the magnifying glass at top right corner - ImVec2 p = g.CurrentWindow->Pos; - p.x += g.CurrentWindow->Size.x - 2.1f * g.FontSize; - if (g.CurrentWindow->DC.CursorPos.x < p.x) - { - ImGui::SetCursorScreenPos(p); - if (selection_.size() == 1) { - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.f, 0.f, 0.f, 0.f)); - ImGuiToolkit::ButtonToggle( ICON_FA_SEARCH, &magnifying_glass); - ImGui::PopStyleColor(); - } - else - ImGui::TextDisabled(" " ICON_FA_SEARCH); - } - - ImGui::EndMenuBar(); - } - - // disable magnifying glass if window is deactivated - if ( g.NavWindow != g.CurrentWindow ) - magnifying_glass = false; - - // reset mediaplayer ptr - mediaplayer_active_ = nullptr; - - // render with appropriate method - if (active_selection_ > -1) - RenderSelection(active_selection_); - else - RenderSelectedSources(); - - ImGui::End(); - -} - -void DrawTimeScale(const char* label, guint64 duration, double width_ratio) -{ - // get window - ImGuiWindow* window = ImGui::GetCurrentWindow(); - if (window->SkipItems) - return; - - // get style & id - const ImGuiContext& g = *GImGui; - const ImGuiStyle& style = g.Style; - const ImGuiID id = window->GetID(label); - - const ImVec2 timeline_size( static_cast( static_cast(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 > DrawTimeline(const char* label, Timeline *timeline, guint64 time, - double width_ratio, float height) -{ - std::list< std::pair > 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( static_cast(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(d) / static_cast(duration) ; - ImVec2 section_bbox_max = ImLerp(timeline_bbox.GetBL(), timeline_bbox.GetBR(), percent); - - // adjust bbox of section and render a timeline - ImRect section_bbox(section_bbox_min, section_bbox_max); - // render the timeline - ImGuiToolkit::RenderTimeline(section_bbox_min, section_bbox_max, section->begin, section->end, timeline->step()); - - // draw the cursor - float time_ = static_cast ( static_cast(time - section->begin) / static_cast(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(section_bbox_min.x,section->begin ) ); - ret.push_back( std::pair(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()->getBatch(i); - int numsources = selection_.size(); - - // no source selected - if (numsources < 1) - { - /// - /// Centered text - /// - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.6, 0.6, 0.6, 0.5f)); - ImVec2 center = rendersize * ImVec2(0.5f, 0.5f); - ImGuiToolkit::PushFont(ImGuiToolkit::FONT_ITALIC); - center.x -= ImGui::GetTextLineHeight() * 2.f; - ImGui::SetCursorScreenPos(top + center); - ImGui::Text("Empty selection"); - ImGui::PopFont(); - ImGui::PopStyleColor(1); - } - else { - /// - /// Sources LIST - /// - /// - - // get max duration and max frame width - GstClockTime maxduration = 0; - std::list durations; - float maxframewidth = 0.f; - for (auto source = selection_.begin(); source != selection_.end(); ++source) { - // collect durations of all media sources - MediaSource *ms = dynamic_cast(*source); - if (ms != nullptr && !ms->mediaplayer()->isImage()) - durations.push_back(static_cast(static_cast(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(w) / static_cast(maxduration); - - // draw list in a scroll area - ImGui::BeginChild("##v_scroll2", rendersize, false); - { - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.f, v_space_)); - - // draw play time scale if a duration is set - if (maxduration > 0) { - ImGui::SetCursorPos(ImGui::GetCursorPos() + ImVec2( maxframewidth + h_space_, 0)); - DrawTimeScale("##timescale", maxduration, width_ratio); - } - - // loop over all sources - SourceList selection_sources = selection_; - /// - /// First pass: loop over media sources only (with timeline) - /// - for (auto source = selection_sources.begin(); source != selection_sources.end(); ) { - - // get media source - MediaSource *ms = dynamic_cast(*source); - if (ms == nullptr || ms->mediaplayer()->isImage()) { - // leave the source in the list and continue - ++source; - continue; - } - - // ok, get the media player of the media source - MediaPlayer *mp = ms->mediaplayer(); - - /// - /// Source Image Button - /// - ImVec2 image_top = ImGui::GetCursorPos(); - const ImVec2 framesize(1.5f * timeline_height_ * (*source)->frame()->aspectRatio(), 1.5f * timeline_height_); - int action = SourceButton(*source, framesize); - if (action > 1) - (*source)->play( ! (*source)->playing() ); - else if (action > 0) - UserInterface::manager().showSourceEditor(*source); - - // icon and text below thumbnail - ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO); - ImGuiToolkit::Icon( (*source)->icon().x, (*source)->icon().y); - if ((*source)->playable()) { - ImGui::SameLine(); - ImGui::Text(" %s", GstToolkit::time_to_string((*source)->playtime()).c_str() ); - } - ImGui::PopFont(); - - // start to draw timeline aligned at maximum frame width + horizontal space - ImVec2 pos = image_top + ImVec2( maxframewidth + h_space_, 0); - ImGui::SetCursorPos(pos); - - // draw the mediaplayer's timeline, with the indication of cursor position - // NB: use the same width/time ratio for all to ensure timing vertical correspondance - - // TODO : if (mp->syncToMetronome() > Metronome::SYNC_NONE) - DrawTimeline("##timeline_mediaplayer", mp->timeline(), mp->position(), - width_ratio / fabs(mp->playSpeed()), framesize.y); - - if ( w > maxframewidth ) { - - // next icon buttons are small - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(3.f, 3.f)); - - // next buttons sub id - ImGui::PushID( static_cast(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(*d)); - - // calculate position of icons - double x = static_cast(*d) * width_ratio; - ImGui::SetCursorPos(pos + ImVec2( static_cast(x) - 2.f, framesize.y + v_space_) ); - // depending on position relative to media play duration, offer corresponding action - double secdur = static_cast(mp->timeline()->sectionsDuration()); - guint64 playdur = static_cast( 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(*prev); - auto next = d; - next++; - selection_target_faster_ = SIGN(mp->playSpeed()) * secdur / static_cast(*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(*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(*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 " < 0) { - - // calculate position of icon - double x = static_cast(durations.front()) * width_ratio; - ImGui::SetCursorPos(pos + ImVec2( static_cast(x) - 2.f, framesize.y + v_space_) ); - - // offer only to adjust size by removing ending gap - if ( mp->timeline()->gapAt( mp->timeline()->end() ) ) { - if ( ImGuiToolkit::ButtonIcon(7, 0, "Remove end gap" )){ - if ( mp->timeline()->removeGaptAt(mp->timeline()->end()) ) { - std::ostringstream info; - info << SystemToolkit::base_filename( mp->filename() ) << ": Timeline Remove end gap"; - Action::manager().store(info.str()); - } - } - } - } - - ImGui::PopStyleVar(); - ImGui::PopID(); - } - - // next line position - ImGui::SetCursorPos(image_top + ImVec2(0, 2.0f * timeline_height_ + 2.f * v_space_)); - - // next source - source = selection_sources.erase(source); - } - - ImGui::Spacing(); - - /// - /// Second pass: loop over remaining sources (no timeline) - /// - ImGui::Columns( CLAMP( int(ceil(w / 250.f)), 1, (int)selection_sources.size()), "##selectioncolumns", false); - for (auto source = selection_sources.begin(); source != selection_sources.end(); ++source) { - /// - /// Source Image Button - /// - const ImVec2 framesize(1.5f * timeline_height_ * (*source)->frame()->aspectRatio(), 1.5f * timeline_height_); - int action = SourceButton(*source, framesize); - if (action > 1) - (*source)->play( ! (*source)->playing() ); - else if (action > 0) - UserInterface::manager().showSourceEditor(*source); - - // icon and text below thumbnail - ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO); - ImGuiToolkit::Icon( (*source)->icon().x, (*source)->icon().y); - if ((*source)->playable()) { - ImGui::SameLine(); - ImGui::Text(" %s", GstToolkit::time_to_string((*source)->playtime()).c_str() ); - } - ImGui::PopFont(); - - // next line position - ImGui::Spacing(); - ImGui::NextColumn(); - } - ImGui::Columns(1); - - ImGui::PopStyleVar(); - } - ImGui::EndChild(); - - } - - /// - /// context menu from actions above - /// - RenderSelectionContextMenu(); - - /// - /// Play button bar - /// - DrawButtonBar(bottom, rendersize.x); - - /// - /// Selection of sources - /// - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.00f, 0.00f, 0.00f, 0.00f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.14f, 0.14f, 0.14f, 0.5f)); - ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.00f, 0.00f, 0.00f, 0.00f)); - ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(0.14f, 0.14f, 0.14f, 0.7f)); - - float width_combo = ImGui::GetContentRegionAvail().x - buttons_height_; - if (width_combo > buttons_width_) - { - ImGui::SameLine(0, width_combo -buttons_width_ ); - ImGui::SetNextItemWidth(buttons_width_); - std::string label = std::string(ICON_FA_CHECK_CIRCLE " ") + std::to_string(numsources) + ( numsources > 1 ? " sources" : " source"); - if (ImGui::BeginCombo("##SelectionImport", label.c_str())) - { - // select all playable sources - for (auto s = Mixer::manager().session()->begin(); s != Mixer::manager().session()->end(); ++s) { - if ( !(*s)->failed() ) { - std::string label = std::string((*s)->initials()) + " - " + (*s)->name(); - if (std::find(selection_.begin(),selection_.end(),*s) == selection_.end()) { - if (ImGui::MenuItem( label.c_str() , 0, false )) - Mixer::manager().session()->addSourceToBatch(i, *s); - } - else { - if (ImGui::MenuItem( label.c_str(), 0, true )) - Mixer::manager().session()->removeSourceFromBatch(i, *s); - } - } - } - ImGui::EndCombo(); - } - } - - ImGui::SameLine(); - ImGui::SetCursorPosX(rendersize.x - buttons_height_ / 1.3f); - if (ImGui::Button(ICON_FA_MINUS_CIRCLE)) { - resetActiveSelection(); - Mixer::manager().session()->deleteBatch(i); - } - if (ImGui::IsItemHovered()) - ImGuiToolkit::ToolTip("Delete batch"); - - 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", NULL, false, fabs(selection_target_faster_) > 0 )){ - selection_mediaplayer_->setPlaySpeed( selection_target_faster_ ); - info << ": Speed x" << std::setprecision(3) << selection_target_faster_; - Action::manager().store(info.str()); - } - if ( ImGuiToolkit::MenuItemIcon(15, 16, "Slow down " ICON_FA_CARET_RIGHT, NULL, false, fabs(selection_target_slower_) > 0 )){ - selection_mediaplayer_->setPlaySpeed( selection_target_slower_ ); - info << ": Speed x" << std::setprecision(3) << selection_target_slower_; - Action::manager().store(info.str()); - } - if ( selection_mediaplayer_->timeline()->gapAt( selection_mediaplayer_->timeline()->end()) ) { - - if ( ImGuiToolkit::MenuItemIcon(7, 0, "Restore ending" )){ - info << ": Restore ending"; - if ( selection_mediaplayer_->timeline()->removeGaptAt(selection_mediaplayer_->timeline()->end()) ) - Action::manager().store(info.str()); - } - - } - ImGui::EndPopup(); - } - -} - -void DrawInspector(uint texture, ImVec2 texturesize, ImVec2 cropsize, ImVec2 origin) -{ - if (Settings::application.source.inspector_zoom > 0 && ImGui::IsWindowFocused()) - { - // region size is computed with zoom factor from settings - float region_sz = texturesize.x / Settings::application.source.inspector_zoom; - - // get coordinates of area to zoom at mouse position - const ImGuiIO& io = ImGui::GetIO(); - float region_x = io.MousePos.x - origin.x - region_sz * 0.5f; - if (region_x < 0.f) - region_x = 0.f; - else if (region_x > texturesize.x - region_sz) - region_x = texturesize.x - region_sz; - - float region_y = io.MousePos.y - origin.y - region_sz * 0.5f; - if (region_y < 0.f) - region_y = 0.f; - else if (region_y > texturesize.y - region_sz) - region_y = texturesize.y - region_sz; - - // Tooltip without border and 100% opaque - ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 1.f); - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.f); - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.f, 0.f) ); - ImGui::BeginTooltip(); - - // compute UV and display image in tooltip - ImVec2 uv0 = ImVec2((region_x) / cropsize.x, (region_y) / cropsize.y); - ImVec2 uv1 = ImVec2((region_x + region_sz) / cropsize.x, (region_y + region_sz) / cropsize.y); - ImVec2 uv2 = ImClamp( uv1, ImVec2(0.f, 0.f), ImVec2(1.f, 1.f)); - uv0 += (uv2-uv1); - ImGui::Image((void*)(intptr_t)texture, ImVec2(texturesize.x / 3.f, texturesize.x / 3.f), uv0, uv2, ImVec4(1.0f, 1.0f, 1.0f, 1.0f), ImVec4(1.0f, 1.0f, 1.0f, 0.5f)); - - ImGui::EndTooltip(); - ImGui::PopStyleVar(3); - } -} - -void DrawSource(Source *s, ImVec2 framesize, ImVec2 top_image, bool withslider, bool withinspector) -{ - ImDrawList* draw_list = ImGui::GetWindowDrawList(); - - // info on source - CloneSource *cloned = dynamic_cast(s); - - // draw pre and post-processed parts if necessary - if (s->imageProcessingEnabled() || s->textureTransformed() || cloned != nullptr) { - - // - // LEFT of slider : original texture - // - ImVec2 slider = framesize * ImVec2(Settings::application.widget.media_player_slider,1.f); - ImGui::Image((void*)(uintptr_t) s->texture(), slider, ImVec2(0.f,0.f), ImVec2(Settings::application.widget.media_player_slider,1.f)); - if ( withinspector && ImGui::IsItemHovered() ) - DrawInspector(s->texture(), framesize, framesize, top_image); - - // - // RIGHT of slider : post-processed image (after crop and color correction) - // - ImVec2 cropsize = framesize * ImVec2 ( s->frame()->projectionArea().x, s->frame()->projectionArea().y); - ImVec2 croptop = (framesize - cropsize) * 0.5f; - // no overlap of slider with cropped area - if (slider.x < croptop.x) { - // draw cropped area - ImGui::SetCursorScreenPos( top_image + croptop ); - ImGui::Image((void*)(uintptr_t) s->frame()->texture(), cropsize, ImVec2(0.f, 0.f), ImVec2(1.f,1.f)); - if ( withinspector && ImGui::IsItemHovered() ) - DrawInspector(s->frame()->texture(), framesize, cropsize, top_image + croptop); - } - // overlap of slider with cropped area (horizontally) - else if (slider.x < croptop.x + cropsize.x ) { - // compute slider ratio of cropped area - float cropped_slider = (slider.x - croptop.x) / cropsize.x; - // top x moves with slider - ImGui::SetCursorScreenPos( top_image + ImVec2(slider.x, croptop.y) ); - // size is reduced by slider - ImGui::Image((void*)(uintptr_t) s->frame()->texture(), cropsize * ImVec2(1.f -cropped_slider, 1.f), ImVec2(cropped_slider, 0.f), ImVec2(1.f,1.f)); - if ( withinspector && ImGui::IsItemHovered() ) - DrawInspector(s->frame()->texture(), framesize, cropsize, top_image + croptop); - } - // else : no render of cropped area - - ImU32 slider_color = ImGui::GetColorU32(ImGuiCol_NavWindowingHighlight); - if (withslider) - { - // - // SLIDER - // - // user input : move slider horizontally - ImGui::SetCursorScreenPos(top_image + ImVec2(- 20.f, 0.5f * framesize.y - 20.0f)); - ImGuiToolkit::InvisibleSliderFloat("#media_player_slider2", &Settings::application.widget.media_player_slider, 0.f, 1.f, ImVec2(framesize.x + 40.f, 40.0f) ); - // affordance: cursor change to horizontal arrows - if (ImGui::IsItemHovered() || ImGui::IsItemFocused()) { - ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); - slider_color = ImGui::GetColorU32(ImGuiCol_Text); - } - // graphical indication of slider - draw_list->AddCircleFilled(top_image + slider * ImVec2(1.f, 0.5f), 20.f, slider_color, 26); - } - // graphical indication of separator (vertical line) - draw_list->AddLine(top_image + slider * ImVec2(1.f,0.0f), top_image + slider, slider_color, 1); - } - // no post-processed to show: draw simple texture - else { - ImGui::Image((void*)(uintptr_t) s->texture(), framesize); - if ( withinspector && ImGui::IsItemHovered() ) - DrawInspector(s->texture(), framesize, framesize, top_image); - } -} - -ImRect DrawSourceWithSlider(Source *s, ImVec2 top, ImVec2 rendersize, bool with_inspector) -{ - /// - /// Centered frame - /// - FrameBuffer *frame = s->frame(); - ImVec2 framesize = rendersize; - ImVec2 corner(0.f, 0.f); - ImVec2 tmp = ImVec2(framesize.y * frame->aspectRatio(), framesize.x / frame->aspectRatio()); - if (tmp.x > framesize.x) { - //vertically centered, modulo height of font for display of info under frame - corner.y = MAX( (framesize.y - tmp.y) / 2.f - ImGui::GetStyle().IndentSpacing, 0.f); - framesize.y = tmp.y; - } - else { - // horizontally centered - // corner.x = (framesize.x - tmp.x) - MAX( (framesize.x - tmp.x) / 2.f - ImGui::GetStyle().IndentSpacing, 0.f); - corner.x = (framesize.x - tmp.x) / 2.f; - framesize.x = tmp.x; - } - - /// - /// Image - /// - const ImVec2 top_image = top + corner; - ImGui::SetCursorScreenPos(top_image); - // 100% opacity for the image (ensure true colors) - ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 1.f); - DrawSource(s, framesize, top_image, true, with_inspector); - ImGui::PopStyleVar(); - - return ImRect( top_image, top_image + framesize); -} - - -int SourceController::SourceButton(Source *s, ImVec2 framesize) -{ - // returns > 0 if clicked, >1 if clicked on center play/pause button - int ret = 0; - - // all subsequent buttons are identified under a unique source id - ImGui::PushID(s->id()); - - // Adjust size of font to frame size - ImGuiToolkit::PushFont(framesize.x > 350.f ? ImGuiToolkit::FONT_LARGE : ImGuiToolkit::FONT_MONO); - - ImDrawList* draw_list = ImGui::GetWindowDrawList(); - const float H = ImGui::GetTextLineHeight(); - const ImVec2 frame_top = ImGui::GetCursorScreenPos(); - const ImVec2 frame_center = frame_top + ImVec2((framesize.x - H) / 2.f, (framesize.y - H) / 2.f); - ImU32 frame_color = ImGui::GetColorU32(ImGuiCol_Text); - ImU32 icon_color = ImGui::GetColorU32(ImGuiCol_NavWindowingHighlight); - - // 1. draw texture of source - DrawSource(s, framesize, frame_top); - - // 2. interactive centered button Play / Pause - if (s->active() && s->playable()) { - //draw_list->AddText(frame_center, ImGui::GetColorU32(ImGuiCol_Text), SourcePlayIcon(s)); - ImGui::SetCursorScreenPos(frame_center - ImVec2(H * 0.2f, H * 0.2f)); - ImGui::InvisibleButton("##sourcebutton_icon", ImVec2(H * 1.2f, H * 1.2f)); - if (ImGui::IsItemHovered() || ImGui::IsItemActive()){ - frame_color = ImGui::GetColorU32(ImGuiCol_NavWindowingHighlight); - icon_color = ImGui::GetColorU32(ImGuiCol_Text); - } - if (ImGui::IsItemClicked()) { - ret = 2; - } - } - - // back to draw overlay - ImGui::SetCursorScreenPos(frame_top + ImVec2(1.f, 0.f) ); - - // 3. draw initials in up-left corner - draw_list->AddText(frame_top + ImVec2(H * 0.2f, H * 0.1f), ImGui::GetColorU32(ImGuiCol_Text), s->initials()); - - // 4. interactive surface on whole texture with frame overlay on mouse over - ImGui::InvisibleButton("##sourcebutton", framesize); - if (ImGui::IsItemHovered() || ImGui::IsItemClicked()) - { - // border - draw_list->AddRect(frame_top, frame_top + framesize - ImVec2(1.f, 0.f), frame_color, 0, 0, 3.f); - // centered icon in front of dark background - if (s->active() && s->playable()) { - draw_list->AddRectFilled(frame_center - ImVec2(H * 0.2f, H * 0.2f), - frame_center + ImVec2(H * 1.1f, H * 1.1f), ImGui::GetColorU32(ImGuiCol_TitleBgCollapsed), 6.f); - draw_list->AddText(frame_center, icon_color, s->playing() ? ICON_FA_PAUSE : ICON_FA_PLAY); - } - } - if (ImGui::IsItemClicked()) { - ret = 1; - } - - // pops - ImGui::PopFont(); - ImGui::PopID(); - - return ret; -} - -void 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_ = valid_only(Mixer::manager().validate(selection_)); - else - selection_ = valid_only(Mixer::selection().getCopy()); - int numsources = selection_.size(); - - // no source selected - if (numsources < 1) - { - /// - /// Centered text - /// - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.6, 0.6, 0.6, 0.5f)); - ImVec2 center = rendersize * ImVec2(0.5f, 0.5f); - ImGuiToolkit::PushFont(ImGuiToolkit::FONT_ITALIC); - center.x -= ImGui::GetTextLineHeight() * 2.f; - ImGui::SetCursorScreenPos(top + center); - ImGui::Text("Nothing to play"); - ImGui::PopFont(); - ImGui::PopStyleColor(1); - - /// - /// Play button bar (automatically disabled) - /// - DrawButtonBar(bottom, rendersize.x); - - } - // single source selected - else if (numsources < 2) - { - /// - /// Single Source display - /// - RenderSingleSource( selection_.front() ); - } - // Several sources selected - else { - /// - /// Sources grid - /// - ImGui::BeginChild("##v_scroll", rendersize, false); - { - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.f, 2.f * v_space_)); - - // area horizontal 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(numcolumns); - widthcolumn -= scrollbar_; - - // loop over sources in grid - for (auto source = selection_.begin(); source != selection_.end(); ++source) { - /// - /// Source Image Button - /// - ImVec2 image_top = ImGui::GetCursorPos(); - ImVec2 framesize(widthcolumn, widthcolumn / (*source)->frame()->aspectRatio()); - int action = SourceButton(*source, framesize); - if (action > 1) - (*source)->play( ! (*source)->playing() ); - else if (action > 0) - UserInterface::manager().showSourceEditor(*source); - - // source icon lower left corner - ImGuiToolkit::PushFont(framesize.x > 350.f ? ImGuiToolkit::FONT_LARGE : ImGuiToolkit::FONT_MONO); - float h = ImGui::GetTextLineHeightWithSpacing(); - ImGui::SetCursorPos(image_top + ImVec2( h_space_, framesize.y -v_space_ - h )); - ImGuiToolkit::Icon( (*source)->icon().x, (*source)->icon().y); - if ((*source)->playable()) { - ImGui::SameLine(); - ImGui::Text(" %s", GstToolkit::time_to_string((*source)->playtime()).c_str() ); - } - ImGui::PopFont(); - - ImGui::Spacing(); - ImGui::NextColumn(); - } - - ImGui::Columns(1); - ImGui::PopStyleVar(); - } - ImGui::EndChild(); - - /// - /// Play button bar - /// - DrawButtonBar(bottom, rendersize.x); - - /// - /// Menu to store Selection from current sources - /// - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.00f, 0.00f, 0.00f, 0.00f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.14f, 0.14f, 0.14f, 0.5f)); - - float space = ImGui::GetContentRegionAvail().x; - float width = buttons_height_; - std::string label(ICON_FA_PLUS_CIRCLE); - if (space > buttons_width_) { // enough space to show full button with label text - label += LABEL_STORE_SELECTION; - width = buttons_width_; - } - ImGui::SameLine(0, space -width); - ImGui::SetNextItemWidth(width); - if (ImGui::Button( label.c_str() )) { - active_selection_ = Mixer::manager().session()->numBatch(); - active_label_ = std::string("Batch #") + std::to_string(active_selection_); - Mixer::manager().session()->addBatch( ids(selection_) ); - } - if (space < buttons_width_ && ImGui::IsItemHovered()) - ImGuiToolkit::ToolTip(LABEL_STORE_SELECTION); - - ImGui::PopStyleColor(2); - } - -} - -void SourceController::RenderSingleSource(Source *s) -{ - static bool show_overlay_info = false; - - if ( s == nullptr) - return; - - // in case of a MediaSource - MediaSource *ms = dynamic_cast(s); - if ( ms != nullptr && ms->playable() ) { - RenderMediaPlayer( ms ); - } - else - { - /// - /// Draw centered Image - /// - ImVec2 top = ImGui::GetCursorScreenPos(); - ImVec2 rendersize = ImGui::GetContentRegionAvail() - ImVec2(0, buttons_height_ + scrollbar_ + v_space_); - ImVec2 bottom = ImVec2(top.x, top.y + rendersize.y + v_space_); - - ImRect imgarea = DrawSourceWithSlider(s, top, rendersize, magnifying_glass); - - /// - /// Info overlays - /// - if (!show_overlay_info){ - ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE); - - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.f, 0.f, 0.f, 0.8f)); - ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2(h_space_-1.f, v_space_-1.f)); - ImGui::Text("%s", s->initials()); - ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2(h_space_+1.f, v_space_+1.f)); - ImGui::Text("%s", s->initials()); - ImGui::PopStyleColor(1); - - ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2(h_space_, v_space_)); - ImGui::Text("%s", s->initials()); - ImGui::PopFont(); - } - if (!magnifying_glass) { - - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.f, 0.f, 0.f, 0.8f)); - ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2(imgarea.GetWidth() - ImGui::GetTextLineHeightWithSpacing(), v_space_)); - ImGui::Text(ICON_FA_CIRCLE); - ImGui::PopStyleColor(1); - - ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2(imgarea.GetWidth() - ImGui::GetTextLineHeightWithSpacing(), v_space_)); - ImGui::Text(ICON_FA_INFO_CIRCLE); - show_overlay_info = ImGui::IsItemHovered(); - if (show_overlay_info){ - // fill info string - s->accept(info_); - // draw overlay frame and text - float tooltip_height = 3.f * ImGui::GetTextLineHeightWithSpacing(); - ImGui::GetWindowDrawList()->AddRectFilled(imgarea.GetTL(), imgarea.GetTL() + ImVec2(imgarea.GetWidth(), tooltip_height), IMGUI_COLOR_OVERLAY); - ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2(h_space_, v_space_)); - ImGui::Text("%s", info_.str().c_str()); - // special case Streams: print framerate - StreamSource *sts = dynamic_cast(s); - if (sts && s->playing()) { - ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2(imgarea.GetWidth() - 1.5f * buttons_height_, 0.5f * tooltip_height)); - ImGui::Text("%.1f Hz", sts->stream()->updateFrameRate()); - } - } - else - // make sure next ItemHovered refreshes the info_ - info_.reset(); - } - - /// - /// icon & timing in lower left corner - /// - ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE); - ImGui::SetCursorScreenPos(bottom + ImVec2(h_space_, -ImGui::GetTextLineHeightWithSpacing() - h_space_ )); - ImGuiToolkit::Icon( s->icon().x, s->icon().y); - ImGui::SameLine(); - ImGui::Text("%s", s->playable() ? GstToolkit::time_to_string(s->playtime()).c_str() : " " ); - ImGui::PopFont(); - - /// - /// Play button bar - /// - DrawButtonBar(bottom, rendersize.x); - } -} - -void SourceController::RenderMediaPlayer(MediaSource *ms) -{ - static bool show_overlay_info = false; - - mediaplayer_active_ = ms->mediaplayer(); - - // for action manager - std::ostringstream oss; - oss << SystemToolkit::base_filename( mediaplayer_active_->filename() ); - - // for draw - const float slider_zoom_width = timeline_height_ / 2.f; - ImDrawList* draw_list = ImGui::GetWindowDrawList(); - - /// - /// Draw centered Image - /// - const ImVec2 top = ImGui::GetCursorScreenPos(); - const ImVec2 rendersize = ImGui::GetContentRegionAvail() - ImVec2(0, mediaplayer_height_); - ImVec2 bottom = ImVec2(top.x, top.y + rendersize.y + v_space_); - - ImRect imgarea = DrawSourceWithSlider(ms, top, rendersize, magnifying_glass); - - /// - /// Info overlays - /// - if (!show_overlay_info){ - ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE); - - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.f, 0.f, 0.f, 0.8f)); - ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2(h_space_-1.f, v_space_-1.f)); - ImGui::Text("%s", ms->initials()); - ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2(h_space_+1.f, v_space_+1.f)); - ImGui::Text("%s", ms->initials()); - ImGui::PopStyleColor(1); - - ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2(h_space_, v_space_)); - ImGui::Text("%s", ms->initials()); - ImGui::PopFont(); - } - if (!magnifying_glass) { - - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.f, 0.f, 0.f, 0.8f)); - ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2(imgarea.GetWidth() - ImGui::GetTextLineHeightWithSpacing(), v_space_)); - ImGui::Text(ICON_FA_CIRCLE); - ImGui::PopStyleColor(1); - - ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2(imgarea.GetWidth() - ImGui::GetTextLineHeightWithSpacing(), v_space_)); - ImGui::Text(ICON_FA_INFO_CIRCLE); - show_overlay_info = ImGui::IsItemHovered(); - if (show_overlay_info){ - // information visitor - mediaplayer_active_->accept(info_); - float tooltip_height = 3.f * ImGui::GetTextLineHeightWithSpacing(); - draw_list->AddRectFilled(imgarea.GetTL(), imgarea.GetTL() + ImVec2(imgarea.GetWidth(), tooltip_height), IMGUI_COLOR_OVERLAY); - ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2(h_space_, v_space_)); - ImGui::Text("%s", info_.str().c_str()); - - // Icon to inform hardware decoding - if ( mediaplayer_active_->decoderName().compare("software") != 0) { - ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2( imgarea.GetWidth() - ImGui::GetTextLineHeightWithSpacing(), 0.35f * tooltip_height)); - ImGui::Text(ICON_FA_MICROCHIP); - } - - // refresh frequency - if ( mediaplayer_active_->isPlaying()) { - ImGui::SetCursorScreenPos(imgarea.GetTL() + ImVec2( imgarea.GetWidth() - 1.5f * buttons_height_, 0.667f * tooltip_height)); - ImGui::Text("%.1f Hz", mediaplayer_active_->updateFrameRate()); - } - } - } - - /// - /// icon & timing in lower left corner - /// - ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE); - ImVec2 S (h_space_, -ImGui::GetTextLineHeightWithSpacing() - h_space_ ); - ImGui::SetCursorScreenPos(bottom + S); - ImGuiToolkit::Icon( ms->icon().x, ms->icon().y); - ImGui::SameLine(); - ImGui::Text( "%s", GstToolkit::time_to_string(mediaplayer_active_->position()).c_str() ); - - /// - /// Sync info lower right corner - /// - Metronome::Synchronicity sync = mediaplayer_active_->syncToMetronome(); - if ( sync > Metronome::SYNC_NONE) { - static bool show = true; - if (mediaplayer_active_->pending()) - show = !show; - else - show = true; - if (show) { - S.x = rendersize.x + S.y; - ImGui::SetCursorScreenPos(bottom + S); - ImGuiToolkit::Icon( sync > Metronome::SYNC_BEAT ? 7 : 6, 13); - } - } - - ImGui::PopFont(); - - /// - /// media player timelines - /// - const ImVec2 scrollwindow = ImVec2(ImGui::GetContentRegionAvail().x - slider_zoom_width - 3.0, - 2.f * timeline_height_ + scrollbar_ ); - - if ( mediaplayer_active_->isEnabled() ) { - - // ignore actual play status of mediaplayer when slider is pressed - if (!mediaplayer_slider_pressed_) - mediaplayer_mode_ = mediaplayer_active_->isPlaying(); - - // seek position - guint64 seek_t = mediaplayer_active_->position(); - - // scrolling sub-window - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(1.f, 1.f)); - ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 1.f); - ImGui::PushStyleColor(ImGuiCol_ScrollbarBg, ImVec4(0.f, 0.f, 0.f, 0.0f)); - - ImGui::SetCursorScreenPos(bottom + ImVec2(1.f, 0.f)); - ImGui::BeginChild("##scrolling", scrollwindow, false, ImGuiWindowFlags_HorizontalScrollbar); - { - ImVec2 size = ImGui::CalcItemSize(ImVec2(-FLT_MIN, 0.0f), ImGui::CalcItemWidth(), timeline_height_ -1); - size.x *= mediaplayer_timeline_zoom_; - - Timeline *tl = mediaplayer_active_->timeline(); - if (tl->is_valid()) - { - bool released = false; - if ( 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 - // TODO : if (mediaplayer_active_->syncToMetronome() > Metronome::SYNC_NONE) - mediaplayer_slider_pressed_ = ImGuiToolkit::TimelineSlider("##timeline", &seek_t, tl->begin(), - tl->first(), tl->end(), tl->step(), size.x); - - } - } - ImGui::EndChild(); - - // 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 > icons_loop = { {0,15}, {1,15}, {19,14} }; - static std::vector< std::string > tooltips_loop = { "Stop at end", "Loop to start", "Bounce (reverse speed)" }; - current_loop = (int) mediaplayer_active_->loop(); - if ( ImGuiToolkit::IconMultistate(icons_loop, ¤t_loop, tooltips_loop) ) - mediaplayer_active_->setLoop( (MediaPlayer::LoopMode) current_loop ); - - // speed slider (if enough space) - if ( rendersize.x > min_width_ * 1.2f ) { - ImGui::SameLine(0, MAX(h_space_ * 2.f, rendersize.x - min_width_ * 1.4f) ); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - buttons_height_ ); - // speed slider - float speed = static_cast(mediaplayer_active_->playSpeed()); - if (ImGui::DragFloat( "##Speed", &speed, 0.01f, -10.f, 10.f, UNICODE_MULTIPLY " %.2f")) - mediaplayer_active_->setPlaySpeed( static_cast(speed) ); - // store action on mouse release - if (ImGui::IsItemDeactivatedAfterEdit()){ - oss << ": Speed x" << std::setprecision(3) << speed; - Action::manager().store(oss.str()); - } - if (ImGui::IsItemHovered()) - ImGuiToolkit::ToolTip("Play speed"); - } - - ImGui::SameLine(); - ImGui::SetCursorPosX(rendersize.x - buttons_height_ / 1.4f); - if (ImGuiToolkit::IconButton(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 { - - ImGui::SetCursorScreenPos(bottom + ImVec2(1.f, 0.f)); - - const ImGuiContext& g = *GImGui; - const double width_ratio = static_cast(scrollwindow.x - slider_zoom_width + g.Style.FramePadding.x ) / static_cast(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::tuple > fading_options = { - {19, 7, "Fade in"}, - {18, 7, "Fade out"}, - { 0, 8, "Auto fade in & out"} - }; - ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); - ImGuiToolkit::ComboIcon("Fading", &l, fading_options); - - static int c = 0; - static std::vector< std::tuple > curve_options = { - {18, 3, "Linear"}, - {19, 3, "Progressive"}, - {17, 3, "Abrupt"} - }; - ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); - ImGuiToolkit::ComboIcon("Curve", &c, curve_options); - - static uint d = 1000; - ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); - ImGuiToolkit::SliderTiming ("Duration", &d, 200, 5050, 50, "Maximum"); - if (d > 5000) - d = UINT_MAX; - - bool close = false; - ImGui::SetCursorPos(pos + ImVec2(0.f, area.y - buttons_height_)); - if (ImGui::Button(ICON_FA_TIMES " Cancel", ImVec2(area.x * 0.3f, 0))) - close = true; - ImGui::SetCursorPos(pos + ImVec2(area.x * 0.7f, area.y - buttons_height_)); - ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetStyleColorVec4(ImGuiCol_Tab)); - if (ImGui::Button(ICON_FA_CHECK " Apply", ImVec2(area.x * 0.3f, 0)) - || ImGui::IsKeyPressed(GLFW_KEY_ENTER) || ImGui::IsKeyPressed(GLFW_KEY_KP_ENTER) ) { - 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()); - } - ImGui::PopStyleColor(1); - - if (close) - ImGui::CloseCurrentPopup(); - ImGui::EndPopup(); - } - - - if (mediaplayer_edit_pipeline_) { - ImGui::OpenPopup("Edit gstreamer pipeline"); - mediaplayer_edit_pipeline_ = false; - } - const ImVec2 mpp_dialog_size(buttons_width_ * 3.f, buttons_height_ * 6); - ImGui::SetNextWindowSize(mpp_dialog_size, ImGuiCond_Always); - const ImVec2 mpp_dialog_pos = top + rendersize * 0.5f - mpp_dialog_size * 0.5f; - ImGui::SetNextWindowPos(mpp_dialog_pos, ImGuiCond_Always); - if (ImGui::BeginPopupModal("Edit gstreamer pipeline", NULL, ImGuiWindowFlags_NoResize)) - { - bool update_new_source = false; - const ImVec2 pos = ImGui::GetCursorPos(); - const ImVec2 area = ImGui::GetContentRegionAvail(); - - static std::vector< std::pair< std::string, std::string> > _examples = { {"Primary color", "frei0r-filter-primaries" }, - {"Histogram", "frei0r-filter-rgb-parade"}, - {"Emboss", "frei0r-filter-emboss"}, - {"B&W", "frei0r-filter-bw0r"}, - {"Gamma", "frei0r-filter-gamma gamma=0.5"}, - {"Broken tv", "frei0r-filter-nosync0r "} - }; - 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; - - ImGui::Spacing(); - ImGui::Text("Enter pipeline element and apply:"); - ImGui::Spacing(); - - // Editor - if ( ImGuiToolkit::InputCodeMultiline("Filter", &_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(); - } - - - bool close = false; - ImGui::SetCursorPos(pos + ImVec2(0.f, area.y - buttons_height_)); - if (ImGui::Button(ICON_FA_TIMES " Cancel", ImVec2(area.x * 0.3f, 0))) - close = true; - ImGui::SetCursorPos(pos + ImVec2(area.x * 0.7f, area.y - buttons_height_)); - ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetStyleColorVec4(ImGuiCol_Tab)); - if (ImGui::Button(ICON_FA_CHECK " Apply", ImVec2(area.x * 0.3f, 0)) - || ImGui::IsKeyPressed(GLFW_KEY_ENTER) || ImGui::IsKeyPressed(GLFW_KEY_KP_ENTER) ) { - close = true; - - // apply to pipeline - mediaplayer_active_->setEffect(_description); - } - ImGui::PopStyleColor(1); - - if (close) - ImGui::CloseCurrentPopup(); - ImGui::EndPopup(); - } -} - -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() && (*source)->playable()) - enabled = true; - if ( (*source)->playing() ) - n_play++; - } - - // buttons style for disabled / enabled bar - if (enabled) { - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.0f)); - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.00f, 0.00f, 0.00f, 0.00f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.14f, 0.14f, 0.14f, 0.5f)); - } - else { - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.6, 0.6, 0.6, 0.5f)); - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.00f, 0.00f, 0.00f, 0.00f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.00f, 0.00f, 0.00f, 0.00f)); - } - - // Always the rewind button - if (ImGui::Button(ICON_FA_FAST_BACKWARD) && enabled) { - for (auto source = selection_.begin(); source != selection_.end(); ++source) - (*source)->replay(); - } - ImGui::SameLine(0, h_space_); - - // unique play / pause button - if (n_play < 1 || selection_.size() == n_play) { - if (n_play) { - if (ImGui::Button(ICON_FA_PAUSE) && enabled) { - for (auto source = selection_.begin(); source != selection_.end(); ++source) - (*source)->play(false); - } - } - 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), loopback_broadcaster_(nullptr), - magnifying_glass(false) -{ - - recordFolderDialog = new DialogToolkit::OpenFolderDialog("Recording Location"); -} - -void OutputPreview::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 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 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 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(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 OutputPreview::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 OutputPreview::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 OutputPreview::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 OutputPreview::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), 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 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::IsKeyPressed(GLFW_KEY_ENTER) || ImGui::IsKeyPressed(GLFW_KEY_KP_ENTER) ) { - ImGui::CloseCurrentPopup(); - } - -#endif - ImGui::EndPopup(); - } -} - -/// -/// 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 (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 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); - - 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(); -} - - -/// -/// KEYBOARDS -/// - -InputMappingInterface::InputMappingInterface() : WorkspaceWindow("InputMappingInterface") -{ - input_mode = { ICON_FA_KEYBOARD " Keyboard", - ICON_FA_CALCULATOR " Numpad" , - ICON_FA_TABLET_ALT " TouchOSC" , - ICON_FA_GAMEPAD " Gamepad" }; - current_input_for_mode = { INPUT_KEYBOARD_FIRST, INPUT_NUMPAD_FIRST, INPUT_MULTITOUCH_FIRST, INPUT_JOYSTICK_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 (Settings::application.widget.inputs_view > 0 && Settings::application.widget.inputs_view != Settings::application.current_view) { - Settings::application.widget.inputs_view = -1; - on = true; - } - - 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 ) - ); -} - -/// -/// Draw a combo box listing all sources and all batch of the current session -/// Returns a Target variant : non-assigned by default (std::monostate), or a Source, or a Batch -/// If current element is indicated, it is displayed first -/// -Target InputMappingInterface::ComboSelectTarget(const Target ¤t) -{ - Target selected; - std::string label = "Select"; - - // depending on variant, fill the label of combo - if (current.index() > 0) { - if (Source * const* v = std::get_if(¤t)) { - label = std::string((*v)->initials()) + " - " + (*v)->name(); - } - else if ( const size_t* v = std::get_if(¤t)) { - label = std::string("Batch #") + std::to_string(*v); - } - } - - if (ImGui::BeginCombo("##ComboSelectSource", label.c_str()) ) - { - Session *ses = Mixer::manager().session(); - for (auto sit = ses->begin(); sit != ses->end(); ++sit) { - label = std::string((*sit)->initials()) + " - " + (*sit)->name(); - if (ImGui::Selectable( label.c_str() )) { - selected = *sit; - } - } - for (size_t b = 0; b < Mixer::manager().session()->numBatch(); ++b){ - label = std::string("Batch #") + std::to_string(b); - if (ImGui::Selectable( label.c_str() )) { - selected = b; - } - } - - ImGui::EndCombo(); - } - - return selected; -} - -uint InputMappingInterface::ComboSelectCallback(uint current, bool imageprocessing) -{ - const char* callback_names[23] = { "Select", - ICON_FA_BULLSEYE " Alpha", - ICON_FA_BULLSEYE " Loom", - ICON_FA_OBJECT_UNGROUP " Geometry", - 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 " Speed", - ICON_FA_PLAY_CIRCLE " Fast forward", - ICON_FA_PLAY_CIRCLE " Seek", - " None", - " None", - " None", - ICON_FA_PALETTE " Gamma", - ICON_FA_PALETTE " Brightness", - ICON_FA_PALETTE " Contrast", - ICON_FA_PALETTE " Saturation", - ICON_FA_PALETTE " Hue", - ICON_FA_PALETTE " Threshold", - ICON_FA_PALETTE " Invert", - " None" - }; - - uint selected = 0; - if (ImGui::BeginCombo("##ComboSelectCallback", callback_names[current]) ) { - for (uint i = SourceCallback::CALLBACK_ALPHA; i <= SourceCallback::CALLBACK_SEEK; ++i){ - if ( ImGui::Selectable( callback_names[i]) ) { - selected = i; - } - } - if (imageprocessing) { - for (uint i = SourceCallback::CALLBACK_GAMMA; i <= SourceCallback::CALLBACK_INVERT; ++i){ - if ( ImGui::Selectable( callback_names[i]) ) { - selected = i; - } - } - } - ImGui::EndCombo(); - } - - return selected; -} - -struct ClosestIndex -{ - int index; - float val; - ClosestIndex (float v) { val = v; index = 0; } - void operator()(float v) { if (v < val) ++index; } -}; - -void InputMappingInterface::SliderParametersCallback(SourceCallback *callback, const Target &target) -{ - const float right_align = -1.05f * ImGui::GetTextLineHeightWithSpacing(); - static const char *press_tooltip[3] = {"Key Press\nApply value on key press", - "Key Down\nApply value on key down,\nrevert on key up", - "Repeat\nMaintain key down to repeat and iterate" }; - static std::vector< std::pair > speed_icon = { {18,15}, {17,15}, {16,15}, {15,15}, {14,15} }; - static std::vector< std::string > speed_tooltip = { "Fastest\n0 ms", "Fast\n60 ms", "Smooth\n120 ms", "Slow\n240 ms", "Slowest\n500 ms" }; - static std::vector< float > speed_values = { 0.f, 60.f, 120.f, 240.f, 500.f }; - - switch ( callback->type() ) { - case SourceCallback::CALLBACK_ALPHA: - { - SetAlpha *edited = static_cast(callback); - - bool bd = edited->bidirectional(); - if ( ImGuiToolkit::IconToggle(2, 13, 3, 13, &bd, press_tooltip ) ) - edited->setBidirectional(bd); - - ClosestIndex d = std::for_each(speed_values.begin(), speed_values.end(), ClosestIndex(edited->duration())); - int speed_index = d.index; - ImGui::SameLine(0, IMGUI_SAME_LINE / 2); - if (ImGuiToolkit::IconMultistate(speed_icon, &speed_index, speed_tooltip )) - edited->setDuration(speed_values[speed_index]); - - float val = edited->value(); - ImGui::SameLine(0, IMGUI_SAME_LINE / 2); - ImGui::SetNextItemWidth(right_align); - if (ImGui::SliderFloat("##CALLBACK_ALPHA", &val, -1.f, 1.f, "%.2f")) - edited->setValue(val); - - ImGui::SameLine(0, IMGUI_SAME_LINE / 2); - ImGuiToolkit::Indication("Alpha value to set if the source is\nvisible (1.0), transparent (0.0),\nor innactive (-1.0)", 18, 12); - - } - break; - case SourceCallback::CALLBACK_LOOM: - { - ImGuiToolkit::Indication(press_tooltip[2], 18, 5); - ImGui::SameLine(0, IMGUI_SAME_LINE / 2); - Loom *edited = static_cast(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, IMGUI_SAME_LINE / 2); - ImGuiToolkit::Indication("Increment alpha to make the source more visible (>0) or more transparent (<0)", 19, 12); - } - break; - case SourceCallback::CALLBACK_GEOMETRY: - { - SetGeometry *edited = static_cast(callback); - - bool bd = edited->bidirectional(); - if ( ImGuiToolkit::IconToggle(2, 13, 3, 13, &bd, press_tooltip ) ) - edited->setBidirectional(bd); - - ClosestIndex d = std::for_each(speed_values.begin(), speed_values.end(), ClosestIndex(edited->duration())); - int speed_index = d.index; - ImGui::SameLine(0, IMGUI_SAME_LINE / 2); - if (ImGuiToolkit::IconMultistate(speed_icon, &speed_index, speed_tooltip )) - edited->setDuration(speed_values[speed_index]); - - if (target.index() > 0) { - // 1. Case of variant as Source pointer - if (Source * const* v = std::get_if(&target)) { - // Button to capture the source current geometry - ImGui::SameLine(0, IMGUI_SAME_LINE / 2); - if (ImGui::Button("Capture", ImVec2(right_align, 0))) { - edited->setTarget( (*v)->group(View::GEOMETRY) ); - } - } - // 2. Case of variant as index of batch - else if ( const size_t* v = std::get_if(&target)) { - - std::vector _batch = Mixer::manager().session()->getAllBatch(); - - std::string label = "Capture"; - // Combo box to set which source to capture - if (ImGui::BeginCombo("##ComboSelectGeometryCapture", label.c_str()) ) - { - if ( *v < _batch.size() ) - { - for (auto sid = _batch[*v].begin(); sid != _batch[*v].end(); ++sid){ - SourceList::iterator sit = Mixer::manager().session()->find(*sid); - if ( sit != Mixer::manager().session()->end() ) { - - label = std::string((*sit)->initials()) + " - " + (*sit)->name(); - // C to capture the source current geometry - if (ImGui::Selectable( label.c_str() )) { - edited->setTarget( (*sit)->group(View::GEOMETRY) ); - } - } - } - } - - ImGui::EndCombo(); - } - } - - ImGui::SameLine(0, IMGUI_SAME_LINE / 2); - ImGuiToolkit::Indication("Capture source geometry to restore it later (position, scale and rotation).", 1, 16); - } - else { - - ImGui::SameLine(0, IMGUI_SAME_LINE / 2); - ImGui::TextDisabled("Invalid"); - } - } - break; - - case SourceCallback::CALLBACK_GRAB: - { - ImGuiToolkit::Indication(press_tooltip[2], 18, 5); - ImGui::SameLine(0, IMGUI_SAME_LINE / 2); - Grab *edited = static_cast(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, IMGUI_SAME_LINE / 2); - ImGuiToolkit::Indication("Increment vector (x,y) to move the source horizontally and vertically.", 6, 15); - } - break; - - case SourceCallback::CALLBACK_RESIZE: - { - ImGuiToolkit::Indication(press_tooltip[2], 18, 5); - ImGui::SameLine(0, IMGUI_SAME_LINE / 2); - Resize *edited = static_cast(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, IMGUI_SAME_LINE / 2); - ImGuiToolkit::Indication("Increment vector (x,y) to scale the source horizontally and vertically.", 2, 15); - - } - break; - - case SourceCallback::CALLBACK_TURN: - { - ImGuiToolkit::Indication(press_tooltip[2], 18, 5); - ImGui::SameLine(0, IMGUI_SAME_LINE / 2); - Turn *edited = static_cast(callback); - float val = edited->value(); - ImGui::SetNextItemWidth(right_align); - if ( ImGui::SliderAngle("##CALLBACK_TURN", &val, -180.f, 180.f) ) - edited->setValue(val ); - - ImGui::SameLine(0, IMGUI_SAME_LINE / 3); - ImGuiToolkit::Indication("Rotation speed (\u00B0/s) to turn the source clockwise (>0) or counterclockwise (<0)", 18, 9); - } - break; - - case SourceCallback::CALLBACK_DEPTH: - { - SetDepth *edited = static_cast(callback); - - bool bd = edited->bidirectional(); - if ( ImGuiToolkit::IconToggle(2, 13, 3, 13, &bd, press_tooltip ) ) - edited->setBidirectional(bd); - - ClosestIndex d = std::for_each(speed_values.begin(), speed_values.end(), ClosestIndex(edited->duration())); - int speed_index = d.index; - ImGui::SameLine(0, IMGUI_SAME_LINE / 2); - if (ImGuiToolkit::IconMultistate(speed_icon, &speed_index, speed_tooltip )) - edited->setDuration(speed_values[speed_index]); - - float val = edited->value(); - ImGui::SetNextItemWidth(right_align); - ImGui::SameLine(0, IMGUI_SAME_LINE / 2); - if (ImGui::SliderFloat("##CALLBACK_DEPTH", &val, 11.9f, 0.1f, "%.1f")) - edited->setValue(val); - - ImGui::SameLine(0, IMGUI_SAME_LINE / 2); - ImGuiToolkit::Indication("Depth value to place the source front (12) or back (0) in the scene.", 6, 6); - } - break; - - case SourceCallback::CALLBACK_PLAY: - { - Play *edited = static_cast(callback); - - bool bd = edited->bidirectional(); - if ( ImGuiToolkit::IconToggle(2, 13, 3, 13, &bd, press_tooltip ) ) - edited->setBidirectional(bd); - ImGui::SameLine(0, IMGUI_SAME_LINE / 2); - - int val = edited->value() ? 1 : 0; - ImGui::SetNextItemWidth(right_align); - if (ImGui::SliderInt("##CALLBACK_PLAY", &val, 0, 1, "Pause | Play ")) - edited->setValue(val>0); - ImGui::SameLine(0, IMGUI_SAME_LINE / 2); - ImGuiToolkit::Indication("Play or pause the source.", 12, 7); - } - break; - - case SourceCallback::CALLBACK_PLAYSPEED: - { - PlaySpeed *edited = static_cast(callback); - - bool bd = edited->bidirectional(); - if ( ImGuiToolkit::IconToggle(2, 13, 3, 13, &bd, press_tooltip ) ) - edited->setBidirectional(bd); - - ClosestIndex d = std::for_each(speed_values.begin(), speed_values.end(), ClosestIndex(edited->duration())); - int speed_index = d.index; - ImGui::SameLine(0, IMGUI_SAME_LINE / 2); - if (ImGuiToolkit::IconMultistate(speed_icon, &speed_index, speed_tooltip )) - edited->setDuration(speed_values[speed_index]); - - float val = edited->value(); - ImGui::SetNextItemWidth(right_align); - ImGui::SameLine(0, IMGUI_SAME_LINE / 2); - if (ImGui::SliderFloat("##CALLBACK_PLAYSPEED", &val, -10.f, 10.f, UNICODE_MULTIPLY " %.2f")) - edited->setValue(val); - - ImGui::SameLine(0, IMGUI_SAME_LINE / 2); - ImGuiToolkit::Indication("Factor to multiply the playback speed of a video source.", 16, 7); - } - break; - - case SourceCallback::CALLBACK_PLAYFFWD: - { - PlayFastForward *edited = static_cast(callback); - - ImGuiToolkit::Indication(press_tooltip[2], 18, 5); - ImGui::SameLine(0, IMGUI_SAME_LINE / 2); - - int val = (int) edited->value(); - ImGui::SetNextItemWidth(right_align); - ImGui::SameLine(0, IMGUI_SAME_LINE / 2); - if (ImGui::SliderInt("##CALLBACK_PLAYFFWD", &val, 30, 1000, "%d ms")) - edited->setValue( MAX(1, val) ); - - ImGui::SameLine(0, IMGUI_SAME_LINE / 2); - ImGuiToolkit::Indication("Step increment (in miliseconds) to jump fast-forward in a video source.", 13, 7); - } - break; - - case SourceCallback::CALLBACK_SEEK: - { - Seek *edited = static_cast(callback); - - bool bd = edited->bidirectional(); - if ( ImGuiToolkit::IconToggle(2, 13, 3, 13, &bd, press_tooltip ) ) - edited->setBidirectional(bd); - - // get value (seconds) and convert to minutes and seconds - float val = edited->value(); - int min = (int) floor(val / 60.f); - float sec = val - 60.f * (float) min; - - // filtering for reading MM:SS.MS text entry - static bool valid = true; - static std::regex RegExTime("([0-9]+\\:)*([0-5][0-9]|[0-9])(\\.[0-9]+)*"); - struct TextFilters { static int FilterTime(ImGuiInputTextCallbackData* data) { if (data->EventChar < 256 && strchr("0123456789.:", (char)data->EventChar)) return 0; return 1; } }; - char buf6[64] = ""; - sprintf(buf6, "%d:%.2f", min, sec ); - - // Text input field for MM:SS:MS seek target time - ImGui::SetNextItemWidth(right_align); - ImGui::SameLine(0, IMGUI_SAME_LINE / 2); - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, valid ? 1.0f : 0.2f, valid ? 1.0f : 0.2f, 1.f)); - ImGui::InputText("##CALLBACK_SEEK", buf6, 64, ImGuiInputTextFlags_CallbackCharFilter, TextFilters::FilterTime); - valid = std::regex_match(buf6, RegExTime); - if (ImGui::IsItemDeactivatedAfterEdit()) { - if (valid){ - // user confirmed the entry and the input is valid - min = 0; - std::string minutes = buf6; - std::string seconds = buf6; - const size_t min_idx = minutes.rfind(':'); - if (string::npos != min_idx) { - seconds = minutes.substr(min_idx+1); - minutes.erase(min_idx); - BaseToolkit::is_a_number(minutes, &min); - } - if (BaseToolkit::is_a_value(seconds, &sec)) - edited->setValue( sec + 60.f * (float) min ); - } - // force to test validity next frame - valid = false; - } - ImGui::PopStyleColor(); - - ImGui::SameLine(0, IMGUI_SAME_LINE / 3); - ImGuiToolkit::Indication("Target time (minutes and seconds, MM:SS.MS) to set where to jump to in a video source.", 15, 7); - } - break; - - case SourceCallback::CALLBACK_BRIGHTNESS: - { - SetBrightness *edited = static_cast(callback); - - bool bd = edited->bidirectional(); - if ( ImGuiToolkit::IconToggle(2, 13, 3, 13, &bd, press_tooltip ) ) - edited->setBidirectional(bd); - - ClosestIndex d = std::for_each(speed_values.begin(), speed_values.end(), ClosestIndex(edited->duration())); - int speed_index = d.index; - ImGui::SameLine(0, IMGUI_SAME_LINE / 2); - if (ImGuiToolkit::IconMultistate(speed_icon, &speed_index, speed_tooltip )) - edited->setDuration(speed_values[speed_index]); - - float val = edited->value(); - ImGui::SetNextItemWidth(right_align); - ImGui::SameLine(0, IMGUI_SAME_LINE / 2); - if (ImGui::SliderFloat("##CALLBACK_BRIGHTNESS", &val, -1.f, 1.f)) - edited->setValue(val); - - ImGui::SameLine(0, IMGUI_SAME_LINE / 2); - ImGuiToolkit::Indication("Brightness for color correction.", 5, 16); - } - break; - - case SourceCallback::CALLBACK_CONTRAST: - { - SetContrast *edited = static_cast(callback); - - bool bd = edited->bidirectional(); - if ( ImGuiToolkit::IconToggle(2, 13, 3, 13, &bd, press_tooltip ) ) - edited->setBidirectional(bd); - - ClosestIndex d = std::for_each(speed_values.begin(), speed_values.end(), ClosestIndex(edited->duration())); - int speed_index = d.index; - ImGui::SameLine(0, IMGUI_SAME_LINE / 2); - if (ImGuiToolkit::IconMultistate(speed_icon, &speed_index, speed_tooltip )) - edited->setDuration(speed_values[speed_index]); - - float val = edited->value(); - ImGui::SetNextItemWidth(right_align); - ImGui::SameLine(0, IMGUI_SAME_LINE / 2); - if (ImGui::SliderFloat("##CALLBACK_CONTRAST", &val, -1.f, 1.f)) - edited->setValue(val); - - ImGui::SameLine(0, IMGUI_SAME_LINE / 2); - ImGuiToolkit::Indication("Contrast for color correction.", 5, 16); - } - break; - - case SourceCallback::CALLBACK_SATURATION: - { - SetSaturation *edited = static_cast(callback); - - bool bd = edited->bidirectional(); - if ( ImGuiToolkit::IconToggle(2, 13, 3, 13, &bd, press_tooltip ) ) - edited->setBidirectional(bd); - - ClosestIndex d = std::for_each(speed_values.begin(), speed_values.end(), ClosestIndex(edited->duration())); - int speed_index = d.index; - ImGui::SameLine(0, IMGUI_SAME_LINE / 2); - if (ImGuiToolkit::IconMultistate(speed_icon, &speed_index, speed_tooltip )) - edited->setDuration(speed_values[speed_index]); - - float val = edited->value(); - ImGui::SetNextItemWidth(right_align); - ImGui::SameLine(0, IMGUI_SAME_LINE / 2); - if (ImGui::SliderFloat("##CALLBACK_SATURATION", &val, -1.f, 1.f)) - edited->setValue(val); - - ImGui::SameLine(0, IMGUI_SAME_LINE / 2); - ImGuiToolkit::Indication("Saturation for color correction.", 9, 16); - } - break; - - case SourceCallback::CALLBACK_HUE: - { - SetHue *edited = static_cast(callback); - - bool bd = edited->bidirectional(); - if ( ImGuiToolkit::IconToggle(2, 13, 3, 13, &bd, press_tooltip ) ) - edited->setBidirectional(bd); - - ClosestIndex d = std::for_each(speed_values.begin(), speed_values.end(), ClosestIndex(edited->duration())); - int speed_index = d.index; - ImGui::SameLine(0, IMGUI_SAME_LINE / 2); - if (ImGuiToolkit::IconMultistate(speed_icon, &speed_index, speed_tooltip )) - edited->setDuration(speed_values[speed_index]); - - float val = edited->value(); - ImGui::SetNextItemWidth(right_align); - ImGui::SameLine(0, IMGUI_SAME_LINE / 2); - if (ImGui::SliderFloat("##CALLBACK_HUE", &val, 0.f, 1.f)) - edited->setValue(val); - - ImGui::SameLine(0, IMGUI_SAME_LINE / 2); - ImGuiToolkit::Indication("Hue shift for color correction.", 12, 4); - } - break; - - case SourceCallback::CALLBACK_THRESHOLD: - { - SetThreshold *edited = static_cast(callback); - - bool bd = edited->bidirectional(); - if ( ImGuiToolkit::IconToggle(2, 13, 3, 13, &bd, press_tooltip ) ) - edited->setBidirectional(bd); - - ClosestIndex d = std::for_each(speed_values.begin(), speed_values.end(), ClosestIndex(edited->duration())); - int speed_index = d.index; - ImGui::SameLine(0, IMGUI_SAME_LINE / 2); - if (ImGuiToolkit::IconMultistate(speed_icon, &speed_index, speed_tooltip )) - edited->setDuration(speed_values[speed_index]); - - float val = edited->value(); - ImGui::SetNextItemWidth(right_align); - ImGui::SameLine(0, IMGUI_SAME_LINE / 2); - if (ImGui::SliderFloat("##CALLBACK_THRESHOLD", &val, 0.0, 1.0, val < 0.001 ? "None" : "%.2f")) - edited->setValue(val); - - ImGui::SameLine(0, IMGUI_SAME_LINE / 3); - ImGuiToolkit::Indication("Threshold for color correction.", 8, 1); - } - break; - - case SourceCallback::CALLBACK_GAMMA: - { - SetGamma *edited = static_cast(callback); - - bool bd = edited->bidirectional(); - if ( ImGuiToolkit::IconToggle(2, 13, 3, 13, &bd, press_tooltip ) ) - edited->setBidirectional(bd); - - ClosestIndex d = std::for_each(speed_values.begin(), speed_values.end(), ClosestIndex(edited->duration())); - int speed_index = d.index; - ImGui::SameLine(0, IMGUI_SAME_LINE / 2); - if (ImGuiToolkit::IconMultistate(speed_icon, &speed_index, speed_tooltip )) - edited->setDuration(speed_values[speed_index]); - - glm::vec4 val = edited->value(); - ImGui::SameLine(0, IMGUI_SAME_LINE / 2); - if (ImGui::ColorEdit3("##CALLBACK_GAMMA Color", glm::value_ptr(val), ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel) ) - edited->setValue(val); - ImGui::SetNextItemWidth(right_align); - ImGui::SameLine(0, IMGUI_SAME_LINE / 2); - if (ImGui::SliderFloat("##CALLBACK_GAMMA Gamma", &val.w, 0.5f, 10.f, "%.2f", 2.f) ) - edited->setValue(val); - - ImGui::SameLine(0, IMGUI_SAME_LINE / 2); - ImGuiToolkit::Indication("Set Gamma color correction.", 6, 4); - } - break; - - case SourceCallback::CALLBACK_INVERT: - { - SetInvert *edited = static_cast(callback); - - bool bd = edited->bidirectional(); - if ( ImGuiToolkit::IconToggle(2, 13, 3, 13, &bd, press_tooltip ) ) - edited->setBidirectional(bd); - ImGui::SameLine(0, IMGUI_SAME_LINE / 2); - - int val = (int) edited->value(); - ImGui::SetNextItemWidth(right_align); - if (ImGui::Combo("##CALLBACK_INVERT", &val, "None\0Color RGB\0Luminance\0")) - edited->setValue( (float) val); - ImGui::SameLine(0, IMGUI_SAME_LINE / 2); - ImGuiToolkit::Indication("Invert mode for color correction.", 6, 16); - } - break; - - default: - break; - } -} - - -void InputMappingInterface::Render() -{ - const ImGuiContext& g = *GImGui; - const ImVec2 keyItemSpacing = ImVec2(g.FontSize * 0.2f, g.FontSize * 0.2f); - const ImVec2 keyLetterIconSize = ImVec2(g.FontSize * 1.9f, g.FontSize * 1.9f); - const ImVec2 keyLetterItemSize = keyLetterIconSize + keyItemSpacing; - const ImVec2 keyNumpadIconSize = ImVec2(g.FontSize * 2.4f, g.FontSize * 2.4f); - const ImVec2 keyNumpadItemSize = keyNumpadIconSize + keyItemSpacing; - const float fixed_height = keyLetterItemSize.y * 5.f + g.Style.WindowBorderSize + g.FontSize + g.Style.FramePadding.y * 2.0f + keyItemSpacing.y; - const 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; - } - - // short variable to refer to session - Session *S = Mixer::manager().session(); - - // 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)) - { - // Disable - ImGui::MenuItem( ICON_FA_BAN " Disable", nullptr, &Settings::application.mapping.disabled); - - // Clear all - if ( ImGui::MenuItem( ICON_FA_BACKSPACE " Clear all" ) ) - S->clearInputCallbacks(); - - // 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(); - } - - // Options for current key - const std::string key = (current_input_ < INPUT_NUMPAD_LAST) ? " Key " : " "; - const std::string keymenu = ICON_FA_HAND_POINT_RIGHT + key + Control::manager().inputLabel(current_input_); - if (ImGui::BeginMenu(keymenu.c_str()) ) - { - if ( ImGui::MenuItem( ICON_FA_WINDOW_CLOSE " Reset mapping", NULL, false, S->inputAssigned(current_input_) ) ) - // remove all source callback of this input - S->deleteInputCallbacks(current_input_); - - if (ImGui::BeginMenu(ICON_FA_CLOCK " Metronome", S->inputAssigned(current_input_))) - { - Metronome::Synchronicity sync = S->inputSynchrony(current_input_); - bool active = sync == Metronome::SYNC_NONE; - if (ImGuiToolkit::MenuItemIcon(5, 13, " Not synchronized", NULL, active )){ - S->setInputSynchrony(current_input_, Metronome::SYNC_NONE); - } - active = sync == Metronome::SYNC_BEAT; - if (ImGuiToolkit::MenuItemIcon(6, 13, " Sync to beat", NULL, active )){ - S->setInputSynchrony(current_input_, Metronome::SYNC_BEAT); - } - active = sync == Metronome::SYNC_PHASE; - if (ImGuiToolkit::MenuItemIcon(7, 13, " Sync to phase", NULL, active )){ - S->setInputSynchrony(current_input_, Metronome::SYNC_PHASE); - } - ImGui::EndMenu(); - } - - std::list models = S->assignedInputs(); - if (ImGui::BeginMenu(ICON_FA_COPY " Duplicate", models.size() > 0) ) - { - for (auto m = models.cbegin(); m != models.cend(); ++m) { - if ( *m != current_input_ ) { - if ( ImGui::MenuItem( Control::inputLabel( *m ).c_str() ) ){ - S->copyInputCallback( *m, current_input_); - } - } - } - ImGui::EndMenu(); - } - - ImGui::EndMenu(); - } - ImGui::EndMenuBar(); - } - - // current window draw parameters - const ImGuiWindow* window = ImGui::GetCurrentWindow(); - ImDrawList* draw_list = window->DrawList; - ImVec2 frame_top = ImGui::GetCursorScreenPos(); - - // - // 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)); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, keyItemSpacing); - ImVec4 color = ImGui::GetStyle().Colors[ImGuiCol_Header]; - color.w /= Settings::application.mapping.disabled ? 2.f : 0.9f; - ImGui::PushStyleColor(ImGuiCol_Header, color); - color = ImGui::GetStyle().Colors[ImGuiCol_Text]; - color.w /= Settings::application.mapping.disabled ? 2.f : 1.0f; - ImGui::PushStyleColor(ImGuiCol_Text, 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::manager().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(ik); - if (ImGui::Selectable(Control::manager().inputLabel(ik).c_str(), S->inputAssigned(ik), 0, keyLetterIconSize)) { - current_input_ = ik; - // TODO SET VAR current input assigned?? - } - ImGui::PopID(); - - // if user clics and drags an assigned key icon... - if (S->inputAssigned(ik) && ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) { - ImGui::SetDragDropPayload("DND_KEYBOARD", &ik, sizeof(uint)); - ImGui::Text( ICON_FA_HAND_POINT_RIGHT " %s ", Control::manager().inputLabel(ik).c_str()); - ImGui::EndDragDropSource(); - } - // ...and drops it onto another key icon - if (ImGui::BeginDragDropTarget()) { - if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("DND_KEYBOARD")) { - if ( payload->DataSize == sizeof(uint) ) { - // drop means change key of input callbacks - uint previous_input_key = *(const int*)payload->Data; - // swap - S->swapInputCallback(previous_input_key, ik); - // switch to this key - current_input_ = ik; - } - } - ImGui::EndDragDropTarget(); - } - - // 5 elements in a row - if ((i % 5) < 4) ImGui::SameLine(); - - // Draw frame - ImVec2 pos = frame_top + keyLetterItemSize * ImVec2( i % 5, i / 5); - if (ik == current_input_) - draw_list->AddRect(pos, pos + keyLetterIconSize, ImGui::GetColorU32(ImGuiCol_Text), 6.f, ImDrawCornerFlags_All, 3.f); - else - draw_list->AddRect(pos, pos + keyLetterIconSize, ImGui::GetColorU32(ImGuiCol_Button), 6.f, ImDrawCornerFlags_All, 0.1f); - - } - ImGui::PopStyleColor(2); - ImGui::PopStyleVar(2); - ImGui::PopFont(); - - } - // - // NUMPAD - // - else if ( Settings::application.mapping.mode == 1 ) { - - // custom layout of numerical keypad - std::vector 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)); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, keyItemSpacing); - ImVec4 color = ImGui::GetStyle().Colors[ImGuiCol_Header]; - color.w /= Settings::application.mapping.disabled ? 2.f : 0.9f; - ImGui::PushStyleColor(ImGuiCol_Header, color); - color = ImGui::GetStyle().Colors[ImGuiCol_Text]; - color.w /= Settings::application.mapping.disabled ? 2.f : 1.0f; - ImGui::PushStyleColor(ImGuiCol_Text, 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::manager().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(ik); - if (ImGui::Selectable(Control::manager().inputLabel(ik).c_str(), S->inputAssigned(ik), 0, iconsize)) { - current_input_ = ik; - } - ImGui::PopID(); - // if user clics and drags an assigned key icon... - if (S->inputAssigned(ik) && ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) { - ImGui::SetDragDropPayload("DND_NUMPAD", &ik, sizeof(uint)); - ImGui::Text( ICON_FA_HAND_POINT_RIGHT " %s ", Control::manager().inputLabel(ik).c_str()); - ImGui::EndDragDropSource(); - } - // ...and drops it onto another key icon - if (ImGui::BeginDragDropTarget()) { - if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("DND_NUMPAD")) { - if ( payload->DataSize == sizeof(uint) ) { - // drop means change key of input callbacks - uint previous_input_key = *(const int*)payload->Data; - // swap - S->swapInputCallback(previous_input_key, ik); - // switch to this key - current_input_ = ik; - } - } - ImGui::EndDragDropTarget(); - } - - // 4 elements in a row - if ((p % 4) < 3) ImGui::SameLine(); - - // Draw frame - 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); - if (ik == current_input_) - draw_list->AddRect(pos, pos + iconsize, ImGui::GetColorU32(ImGuiCol_Text), 6.f, ImDrawCornerFlags_All, 3.f); - else - draw_list->AddRect(pos, pos + iconsize, ImGui::GetColorU32(ImGuiCol_Button), 6.f, ImDrawCornerFlags_All, 0.1f); - - } - ImGui::PopStyleColor(2); - ImGui::PopStyleVar(2); - ImGui::PopFont(); - - } - // - // MULTITOUCH OSC - // - else if ( Settings::application.mapping.mode == 2 ) { - - // Draw table of TouchOSC buttons - ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE); - ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, ImVec2(0.5f, 0.5f)); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, keyItemSpacing); - ImVec4 color = ImGui::GetStyle().Colors[ImGuiCol_Header]; - color.w /= Settings::application.mapping.disabled ? 2.f : 0.9f; - ImGui::PushStyleColor(ImGuiCol_Header, color); - color = ImGui::GetStyle().Colors[ImGuiCol_Text]; - color.w /= Settings::application.mapping.disabled ? 2.f : 1.0f; - ImGui::PushStyleColor(ImGuiCol_Text, color); - - const ImVec2 touch_bar_size = keyNumpadItemSize * ImVec2(0.65f, 0.2f); - const ImVec2 touch_bar_pos = keyNumpadItemSize * ImVec2(0.125f, 0.6f); - - for (size_t t = 0; t < INPUT_MULTITOUCH_COUNT; ++t){ - uint it = INPUT_MULTITOUCH_FIRST + t; - ImVec2 pos = frame_top + keyNumpadItemSize * ImVec2( t % 4, t / 4); - - // draw overlay on active keys - if ( Control::manager().inputActive(it) ) { - draw_list->AddRectFilled(pos, pos + keyNumpadIconSize, ImGui::GetColorU32(ImGuiCol_Border), 6.f); - // set current - current_input_ = it; - } - - // draw key button - ImGui::PushID(it); - if (ImGui::Selectable(" ", S->inputAssigned(it), 0, keyNumpadIconSize)) - current_input_ = it; - ImGui::PopID(); - - // if user clics and drags an assigned key icon... - if (S->inputAssigned(it) && ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) { - ImGui::SetDragDropPayload("DND_MULTITOUCH", &it, sizeof(uint)); - ImGui::Text( ICON_FA_HAND_POINT_RIGHT " %s ", Control::manager().inputLabel(it).c_str()); - ImGui::EndDragDropSource(); - } - // ...and drops it onto another key icon - if (ImGui::BeginDragDropTarget()) { - if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("DND_MULTITOUCH")) { - if ( payload->DataSize == sizeof(uint) ) { - // drop means change key of input callbacks - uint previous_input_key = *(const int*)payload->Data; - // swap - S->swapInputCallback(previous_input_key, it); - // switch to this key - current_input_ = it; - } - } - ImGui::EndDragDropTarget(); - } - - // 4 elements in a row - if ((t % 4) < 3) ImGui::SameLine(); - - // Draw frame - if (it == current_input_) - draw_list->AddRect(pos, pos + keyNumpadIconSize, ImGui::GetColorU32(ImGuiCol_Text), 6.f, ImDrawCornerFlags_All, 3.f); - else - draw_list->AddRect(pos, pos + keyNumpadIconSize, ImGui::GetColorU32(ImGuiCol_Button), 6.f, ImDrawCornerFlags_All, 0.1f); - - // Draw value bar - ImVec2 prev = ImGui::GetCursorScreenPos(); - ImGui::SetCursorScreenPos( pos + touch_bar_pos); - ImGui::ProgressBar(Control::manager().inputValue(it), touch_bar_size, ""); - ImGui::SetCursorScreenPos( prev ); - - } - - ImGui::PopStyleColor(2); - ImGui::PopStyleVar(2); - ImGui::PopFont(); - - } - // - // JOYSTICK - // - else if ( Settings::application.mapping.mode == 3 ) { - - // custom layout of gamepad buttons - std::vector gamepad_inputs = { INPUT_JOYSTICK_FIRST_BUTTON+11, INPUT_JOYSTICK_FIRST_BUTTON+13, - INPUT_JOYSTICK_FIRST_BUTTON+6, - INPUT_JOYSTICK_FIRST_BUTTON+2, INPUT_JOYSTICK_FIRST_BUTTON+3, - - INPUT_JOYSTICK_FIRST_BUTTON+14, INPUT_JOYSTICK_FIRST_BUTTON+12, - INPUT_JOYSTICK_FIRST_BUTTON+7, - INPUT_JOYSTICK_FIRST_BUTTON+0, INPUT_JOYSTICK_FIRST_BUTTON+1, - - INPUT_JOYSTICK_FIRST_BUTTON+4, INPUT_JOYSTICK_FIRST_BUTTON+9, - INPUT_JOYSTICK_FIRST_BUTTON+8, - INPUT_JOYSTICK_FIRST_BUTTON+10, INPUT_JOYSTICK_FIRST_BUTTON+5 }; - - std::vector< std::string > gamepad_labels = { ICON_FA_ARROW_UP, ICON_FA_ARROW_DOWN, - ICON_FA_CHEVRON_CIRCLE_LEFT, "X", "Y", - ICON_FA_ARROW_LEFT, ICON_FA_ARROW_RIGHT, - ICON_FA_CHEVRON_CIRCLE_RIGHT, "A", "B", - "L1", "LT", ICON_FA_DOT_CIRCLE, "RT", "R1" }; - - // Draw table of Gamepad Buttons - ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE); - ImVec4 color = ImGui::GetStyle().Colors[ImGuiCol_Header]; - color.w /= Settings::application.mapping.disabled ? 2.f : 0.9f; - ImGui::PushStyleColor(ImGuiCol_Header, color); - color = ImGui::GetStyle().Colors[ImGuiCol_Text]; - color.w /= Settings::application.mapping.disabled ? 2.f : 1.0f; - ImGui::PushStyleColor(ImGuiCol_Text, color); - - // CENTER text for button - ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, ImVec2(0.5f, 0.5f)); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, keyItemSpacing); - for (size_t b = 0; b < gamepad_inputs.size(); ++b ){ - uint ig = gamepad_inputs[b]; - // draw overlay on active keys - if ( Control::manager().inputActive(ig) ) { - ImVec2 pos = frame_top + keyLetterItemSize * ImVec2( b % 5, b / 5); - draw_list->AddRectFilled(pos, pos + keyLetterIconSize, ImGui::GetColorU32(ImGuiCol_Border), 6.f); - // set current - current_input_ = ig; - } - // draw key button - ImGui::PushID(ig); - if (ImGui::Selectable(gamepad_labels[b].c_str(), S->inputAssigned(ig), 0, keyLetterIconSize)) - current_input_ = ig; - ImGui::PopID(); - - // if user clics and drags an assigned key icon... - if (S->inputAssigned(ig) && ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) { - ImGui::SetDragDropPayload("DND_GAMEPAD", &ig, sizeof(uint)); - ImGui::Text( ICON_FA_HAND_POINT_RIGHT " %s ", Control::manager().inputLabel(ig).c_str()); - ImGui::EndDragDropSource(); - } - // ...and drops it onto another key icon - if (ImGui::BeginDragDropTarget()) { - if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("DND_GAMEPAD")) { - if ( payload->DataSize == sizeof(uint) ) { - // drop means change key of input callbacks - uint previous_input_key = *(const int*)payload->Data; - // swap - S->swapInputCallback(previous_input_key, ig); - // switch to this key - current_input_ = ig; - } - } - ImGui::EndDragDropTarget(); - } - - if ((b % 5) < 4) ImGui::SameLine(); - - // Draw frame - ImVec2 pos = frame_top + keyLetterItemSize * ImVec2( b % 5, b / 5); - if (ig == current_input_) - draw_list->AddRect(pos, pos + keyLetterIconSize, ImGui::GetColorU32(ImGuiCol_Text), 6.f, ImDrawCornerFlags_All, 3.f); - else if ( b!= 2 && b!= 7 && b!=12 ) - draw_list->AddRect(pos, pos + keyLetterIconSize, ImGui::GetColorU32(ImGuiCol_Button), 6.f, ImDrawCornerFlags_All, 0.1f); - - } - ImGui::PopStyleVar(); - ImGui::PopFont(); - - // Table of Gamepad Axis - const ImVec2 axis_top = frame_top + ImVec2(0.f, 3.f * keyLetterItemSize.y); - const ImVec2 axis_item_size(inputarea_width / 2.f, (2.f * keyLetterItemSize.y) / 3.f); - const ImVec2 axis_icon_size = axis_item_size - g.Style.ItemSpacing; - const ImVec2 axis_bar_size = axis_icon_size * ImVec2(0.4f, 0.4f); - ImVec2 axis_bar_pos(axis_icon_size.x * 0.5f, axis_icon_size.y *0.3f ); - - // LEFT align for 3 axis on the left - ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, ImVec2(0.09f, 0.5f)); - - // define top left screen cursor position - ImVec2 pos = axis_top; - // Draw a little bar showing activity on the gamepad axis - ImGui::SetCursorScreenPos( pos + axis_bar_pos); - ImGuiToolkit::ValueBar(Control::manager().inputValue(INPUT_JOYSTICK_FIRST_AXIS), axis_bar_size); - // Draw button to assign the axis to an action - ImGui::SetCursorScreenPos( pos ); - if (ImGui::Selectable("LX", S->inputAssigned(INPUT_JOYSTICK_FIRST_AXIS), 0, axis_icon_size)) - current_input_ = INPUT_JOYSTICK_FIRST_AXIS; - // Draw frame around current gamepad axis - if (current_input_ == INPUT_JOYSTICK_FIRST_AXIS) - draw_list->AddRect(pos, pos + axis_icon_size, ImGui::GetColorU32(ImGuiCol_Text), 6.f, ImDrawCornerFlags_All, 3.f); - - pos = axis_top + ImVec2( 0, axis_item_size.y); - ImGui::SetCursorScreenPos( pos + axis_bar_pos); - ImGuiToolkit::ValueBar(Control::manager().inputValue(INPUT_JOYSTICK_FIRST_AXIS+1), axis_bar_size); - ImGui::SetCursorScreenPos( pos ); - if (ImGui::Selectable("LY", S->inputAssigned(INPUT_JOYSTICK_FIRST_AXIS+1), 0, axis_icon_size)) - current_input_ = INPUT_JOYSTICK_FIRST_AXIS+1; - if (current_input_ == INPUT_JOYSTICK_FIRST_AXIS+1) - draw_list->AddRect(pos, pos + axis_icon_size, ImGui::GetColorU32(ImGuiCol_Text), 6.f, ImDrawCornerFlags_All, 3.f); - - pos = axis_top + ImVec2( 0, 2.f * axis_item_size.y); - ImGui::SetCursorScreenPos( pos + axis_bar_pos); - ImGuiToolkit::ValueBar(Control::manager().inputValue(INPUT_JOYSTICK_FIRST_AXIS+2), axis_bar_size); - ImGui::SetCursorScreenPos( pos ); - if (ImGui::Selectable("L2", S->inputAssigned(INPUT_JOYSTICK_FIRST_AXIS+2), 0, axis_icon_size)) - current_input_ = INPUT_JOYSTICK_FIRST_AXIS+2; - if (current_input_ == INPUT_JOYSTICK_FIRST_AXIS+2) - draw_list->AddRect(pos, pos + axis_icon_size, ImGui::GetColorU32(ImGuiCol_Text), 6.f, ImDrawCornerFlags_All, 3.f); - - ImGui::PopStyleVar(); - - // RIGHT align for 3 axis on the right - ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, ImVec2(0.91f, 0.5f)); - axis_bar_pos.x = g.Style.ItemSpacing.x; - - pos = axis_top + ImVec2( axis_item_size.x, 0.f); - ImGui::SetCursorScreenPos( pos + axis_bar_pos); - ImGuiToolkit::ValueBar(Control::manager().inputValue(INPUT_JOYSTICK_FIRST_AXIS+3), axis_bar_size); - ImGui::SetCursorScreenPos( pos ); - if (ImGui::Selectable("RX", S->inputAssigned(INPUT_JOYSTICK_FIRST_AXIS+3), 0, axis_icon_size)) - current_input_ = INPUT_JOYSTICK_FIRST_AXIS+3; - if (current_input_ == INPUT_JOYSTICK_FIRST_AXIS+3) - draw_list->AddRect(pos, pos + axis_icon_size, ImGui::GetColorU32(ImGuiCol_Text), 6.f, ImDrawCornerFlags_All, 3.f); - - pos = axis_top + ImVec2( axis_item_size.x, axis_item_size.y); - ImGui::SetCursorScreenPos( pos + axis_bar_pos); - ImGuiToolkit::ValueBar(Control::manager().inputValue(INPUT_JOYSTICK_FIRST_AXIS+4), axis_bar_size); - ImGui::SetCursorScreenPos( pos ); - if (ImGui::Selectable("RY", S->inputAssigned(INPUT_JOYSTICK_FIRST_AXIS+4), 0, axis_icon_size)) - current_input_ = INPUT_JOYSTICK_FIRST_AXIS+4; - if (current_input_ == INPUT_JOYSTICK_FIRST_AXIS+4) - draw_list->AddRect(pos, pos + axis_icon_size, ImGui::GetColorU32(ImGuiCol_Text), 6.f, ImDrawCornerFlags_All, 3.f); - - pos = axis_top + ImVec2( axis_item_size.x, 2.f * axis_item_size.y); - ImGui::SetCursorScreenPos( pos + axis_bar_pos); - ImGuiToolkit::ValueBar(Control::manager().inputValue(INPUT_JOYSTICK_FIRST_AXIS+5), axis_bar_size); - ImGui::SetCursorScreenPos( pos ); - if (ImGui::Selectable("R2", S->inputAssigned(INPUT_JOYSTICK_FIRST_AXIS+5), 0, axis_icon_size)) - current_input_ = INPUT_JOYSTICK_FIRST_AXIS+5; - if (current_input_ == INPUT_JOYSTICK_FIRST_AXIS+5) - draw_list->AddRect(pos, pos + axis_icon_size, ImGui::GetColorU32(ImGuiCol_Text), 6.f, ImDrawCornerFlags_All, 3.f); - - ImGui::PopStyleVar(2); - - // Done with color and font change - ImGui::PopStyleColor(2); - - } - - // Draw child Window (right) to list reactions to input - ImGui::SetCursorScreenPos(frame_top + g.Style.FramePadding + ImVec2(inputarea_width,0.f)); - { - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(2.f, g.Style.ItemSpacing.y * 2.f) ); - ImGui::BeginChild("InputsMappingInterfacePanel", ImVec2(0, 0), false); - - float w = ImGui::GetWindowWidth() *0.25f; - - /// - /// list of input callbacks for the current input - /// - if (S->inputAssigned(current_input_)) { - - auto result = S->getSourceCallbacks(current_input_); - for (auto kit = result.cbegin(); kit != result.cend(); ++kit) { - - Target target = kit->first; - SourceCallback *callback = kit->second; - - // push ID because we repeat the same UI - ImGui::PushID( (void*) callback); - - // Delete interface - if (ImGuiToolkit::IconButton(ICON_FA_MINUS, "Remove") ){ - S->deleteInputCallback(callback); - // reload - ImGui::PopID(); - break; - } - - // Select Target - ImGui::SameLine(0, IMGUI_SAME_LINE); - ImGui::SetNextItemWidth(w); - Target selected_target = ComboSelectTarget(target); - // if the selected target variant was filled with a value - if (selected_target.index() > 0) { - // reassign the callback to newly selected source - S->assignInputCallback(current_input_, selected_target, callback); - // reload - ImGui::PopID(); - break; - } - - // check if target is a Source with image processing enabled - bool withimageprocessing = false; - if ( target.index() == 1 ) { - if (Source * const* v = std::get_if(&target)) { - withimageprocessing = (*v)->imageProcessingEnabled(); - } - } - - // Select Reaction - ImGui::SameLine(0, IMGUI_SAME_LINE); - ImGui::SetNextItemWidth(w); - uint type = ComboSelectCallback( callback->type(), withimageprocessing ); - if (type > 0) { - // remove previous callback - S->deleteInputCallback(callback); - // assign callback - S->assignInputCallback(current_input_, target, SourceCallback::create((SourceCallback::CallbackType)type) ); - // reload - ImGui::PopID(); - break; - } - - // Adjust parameters - ImGui::SameLine(0, IMGUI_SAME_LINE); - if (callback) - SliderParametersCallback( callback, target ); - - ImGui::PopID(); - - } - - } - else { - ImGui::Text("No action mapped to this input. Add one with +."); - } - - /// - /// Add a new interface - /// - static bool temp_new_input = false; - static Target temp_new_target; - static uint temp_new_callback = 0; - - // step 1 : press '+' - if (temp_new_input) { - if (ImGuiToolkit::IconButton(ICON_FA_TIMES, "Cancel") ){ - temp_new_target = std::monostate(); - temp_new_callback = 0; - temp_new_input = false; - } - } - else if (ImGuiToolkit::IconButton(ICON_FA_PLUS, "Add mapping") ) - temp_new_input = true; - - if (temp_new_input) { - // step 2 : Get input for source - ImGui::SameLine(0, IMGUI_SAME_LINE); - ImGui::SetNextItemWidth(w); - - Target selected_target = ComboSelectTarget(temp_new_target); - // if the selected target variant was filled with a value - if (selected_target.index() > 0) { - temp_new_target = selected_target; - temp_new_callback = 0; - } - // possible new target - if (temp_new_target.index() > 0) { - // check if target is a Source with image processing enabled - bool withimageprocessing = false; - if ( temp_new_target.index() == 1 ) { - if (Source * const* v = std::get_if(&temp_new_target)) { - withimageprocessing = (*v)->imageProcessingEnabled(); - } - } - // step 3: Get input for callback type - ImGui::SameLine(0, IMGUI_SAME_LINE); - ImGui::SetNextItemWidth(w); - temp_new_callback = ComboSelectCallback( temp_new_callback, withimageprocessing ); - // user selected a callback type - if (temp_new_callback > 0) { - // step 4 : create new callback and add it to source - S->assignInputCallback(current_input_, temp_new_target, SourceCallback::create((SourceCallback::CallbackType)temp_new_callback) ); - // done - temp_new_target = std::monostate(); - temp_new_callback = 0; - temp_new_input = false; - } - } - } - - /// - /// Sync info lower right corner - /// - Metronome::Synchronicity sync = S->inputSynchrony(current_input_); - if ( sync > Metronome::SYNC_NONE) { - ImGui::SetCursorPos(ImGui::GetWindowSize() - ImVec2(50, 50)); - ImGuiToolkit::Icon( sync > Metronome::SYNC_BEAT ? 7 : 6, 13); - } - - // close child window - ImGui::EndChild(); - ImGui::PopStyleVar(); - } - - - ImGui::End(); -} - - -/// -/// SHADER EDITOR -/// -/// -ShaderEditor::ShaderEditor() : 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 ShaderEditor::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 ShaderEditor::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 ShaderEditor::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(); - current_->setProgram( filters_[current_], compilation_ ); - compilation_return_ = compilation_->get_future(); - - // inform status - status_ = "Building..."; - } -} - -void ShaderEditor::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(); - 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(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(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(GLFW_KEY_B)) - BuildShader(); - - // render main editor - _editor.Render("Shader Editor"); - - ImGui::PopFont(); - - ImGui::End(); -} - /// /// NAVIGATOR @@ -9578,7 +4959,7 @@ SourcePreview::SourcePreview() : source_(nullptr), label_(""), reset_(0) } -void SourcePreview::setSource(Source *s, const string &label) +void SourcePreview::setSource(Source *s, const std::string &label) { if(source_) delete source_; diff --git a/src/UserInterfaceManager.h b/src/UserInterfaceManager.h index bfcf290..f73d8b7 100644 --- a/src/UserInterfaceManager.h +++ b/src/UserInterfaceManager.h @@ -1,136 +1,28 @@ #ifndef __UI_MANAGER_H_ #define __UI_MANAGER_H_ -#include "Session.h" -#include -#include -#include -#include -#include - -#include - #define NAV_COUNT 68 #define NAV_MAX 64 #define NAV_NEW 65 #define NAV_MENU 66 #define NAV_TRANS 67 -#ifdef APPLE -#define CTRL_MOD "Cmd+" -#define ALT_MOD "Option+" -#else -#define CTRL_MOD "Ctrl+" -#define ALT_MOD "Alt+" -#endif +#include -#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" - -#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 "View.h" #include "DialogToolkit.h" -#include "SessionParser.h" -#include "ImageFilter.h" -#include "Screenshot.h" +#include "SourceControlWindow.h" +#include "OutputPreviewWindow.h" +#include "TimerMetronomeWindow.h" +#include "InputMappingWindow.h" +#include "ShaderEditWindow.h" struct ImVec2; -class MediaPlayer; -class FrameBufferImage; -class FrameGrabber; -class VideoRecorder; -class VideoBroadcast; -class ShmdataBroadcast; -class Loopback; - -class SourcePreview { +void DrawInspector(uint texture, ImVec2 texturesize, ImVec2 cropsize, ImVec2 origin); +class SourcePreview +{ Source *source_; std::string label_; bool reset_; @@ -146,20 +38,6 @@ public: 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 { @@ -246,217 +124,11 @@ public: void Render(); }; -class HelperToolbox -{ - SessionParser parser_; - -public: - HelperToolbox(); - - void Render(); - -}; - -class WorkspaceWindow -{ - static std::list 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 > _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 ¤t); - 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 filters_; - std::promise *compilation_; - std::future 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 { friend class ImGuiVisitor; friend class Navigator; - friend class OutputPreview; + friend class OutputPreviewWindow; // Private Constructor UserInterface(); @@ -525,12 +197,11 @@ protected: // objects and windows Navigator navigator; ToolBox toolbox; - SourceController sourcecontrol; - OutputPreview outputcontrol; - TimerMetronome timercontrol; - InputMappingInterface inputscontrol; - ShaderEditor shadercontrol; - HelperToolbox helpwindow; + SourceControlWindow sourcecontrol; + OutputPreviewWindow outputcontrol; + TimerMetronomeWindow timercontrol; + InputMappingWindow inputscontrol; + ShaderEditWindow shadercontrol; void showMenuFile(); void showMenuEdit(); @@ -544,6 +215,7 @@ protected: void RenderOutputView(); void RenderAbout(bool* p_open); void RenderNotes(); + void RenderHelp(); void setView(View::Mode mode); void handleKeyboard(); diff --git a/src/WorkspaceWindow.cpp b/src/WorkspaceWindow.cpp new file mode 100644 index 0000000..9d0a468 --- /dev/null +++ b/src/WorkspaceWindow.cpp @@ -0,0 +1,228 @@ +/* + * This file is part of vimix - video live mixer + * + * **Copyright** (C) 2019-2023 Bruno Herbelin + * + * 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 . +**/ + + +#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::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); + } + } +} diff --git a/src/WorkspaceWindow.h b/src/WorkspaceWindow.h new file mode 100644 index 0000000..30a6091 --- /dev/null +++ b/src/WorkspaceWindow.h @@ -0,0 +1,32 @@ +#ifndef WORKSPACEWINDOW_H +#define WORKSPACEWINDOW_H + +#include + +class WorkspaceWindow +{ + static std::list 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 diff --git a/src/defines.h b/src/defines.h index 3d05c12..a183ccc 100644 --- a/src/defines.h +++ b/src/defines.h @@ -122,4 +122,100 @@ #define OSC_PORT_SEND_DEFAULT 7001 #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