diff --git a/LayerView.cpp b/LayerView.cpp index 5d4005a..d22c6f2 100644 --- a/LayerView.cpp +++ b/LayerView.cpp @@ -311,11 +311,9 @@ View::Cursor LayerView::grab (Source *s, glm::vec2 from, glm::vec2 to, std::pair // apply change float d = setDepth( s, MAX( -dest_translation.x, 0.f) ); + // store action in history std::ostringstream info; info << "Depth " << std::fixed << std::setprecision(2) << d << " "; -// info << (s->locked() ? ICON_FA_LOCK : ICON_FA_LOCK_OPEN); // TODO static not locked - - // store action in history current_action_ = s->name() + ": " + info.str(); return Cursor(Cursor_ResizeNESW, info.str() ); diff --git a/Settings.cpp b/Settings.cpp index cfc9dd8..68b5101 100644 --- a/Settings.cpp +++ b/Settings.cpp @@ -99,6 +99,7 @@ void Settings::Save() RecordNode->SetAttribute("path", application.record.path.c_str()); RecordNode->SetAttribute("profile", application.record.profile); RecordNode->SetAttribute("timeout", application.record.timeout); + RecordNode->SetAttribute("delay", application.record.delay); pRoot->InsertEndChild(RecordNode); // Transition @@ -293,6 +294,7 @@ void Settings::Load() if (recordnode != nullptr) { recordnode->QueryIntAttribute("profile", &application.record.profile); recordnode->QueryFloatAttribute("timeout", &application.record.timeout); + recordnode->QueryIntAttribute("delay", &application.record.delay); const char *path_ = recordnode->Attribute("path"); if (path_) diff --git a/Settings.h b/Settings.h index 874d256..bc3f026 100644 --- a/Settings.h +++ b/Settings.h @@ -69,10 +69,12 @@ struct RecordConfig std::string path; int profile; float timeout; + int delay; RecordConfig() : path("") { profile = 0; timeout = RECORD_MAX_TIMEOUT; + delay = 0; } }; diff --git a/UserInterfaceManager.cpp b/UserInterfaceManager.cpp index 2b9fc6d..4b598be 100644 --- a/UserInterfaceManager.cpp +++ b/UserInterfaceManager.cpp @@ -89,6 +89,12 @@ static std::vector< std::future > fileImportFileDialogs; static std::vector< std::future > recentFolderFileDialogs; static std::vector< std::future > recordFolderFileDialogs; +static std::vector< std::future > _video_recorders; +FrameGrabber *delayTrigger(FrameGrabber *g, std::chrono::milliseconds delay) { + std::this_thread::sleep_for (delay); + return g; +} + UserInterface::UserInterface() { @@ -105,8 +111,8 @@ UserInterface::UserInterface() screenshot_step = 0; // keep hold on frame grabbers - video_recorder_ = 0; - webcam_emulator_ = 0; + video_recorder_ = nullptr; + webcam_emulator_ = nullptr; } bool UserInterface::Init() @@ -246,16 +252,18 @@ void UserInterface::handleKeyboard() Mixer::manager().view()->selectAll(); } else if (ImGui::IsKeyPressed( GLFW_KEY_R )) { - // toggle recording - FrameGrabber *rec = FrameGrabbing::manager().get(video_recorder_); - if (rec) { - rec->stop(); - video_recorder_ = 0; + if (shift_modifier_active) { + FrameGrabbing::manager().add(new PNGRecorder); } else { - FrameGrabber *fg = new VideoRecorder; - video_recorder_ = fg->id(); - FrameGrabbing::manager().add(fg); + // toggle recording + if (video_recorder_) { + video_recorder_->stop(); + video_recorder_ = nullptr; + } + else { + _video_recorders.emplace_back( std::async(std::launch::async, delayTrigger, new VideoRecorder, std::chrono::seconds(Settings::application.record.delay)) ); + } } } else if (ImGui::IsKeyPressed( GLFW_KEY_Y )) { @@ -749,11 +757,21 @@ void UserInterface::Render() &Settings::application.widget.stats_corner, &Settings::application.widget.stats_mode); - // management of video_recorder - FrameGrabber *rec = FrameGrabbing::manager().get(video_recorder_); - if (rec && rec->duration() > Settings::application.record.timeout ){ - rec->stop(); - video_recorder_ = 0; + // management of video_recorders + if ( !_video_recorders.empty() ) { + // check that file dialog thread finished + if (_video_recorders.back().wait_for(timeout) == std::future_status::ready ) { + + video_recorder_ = _video_recorders.back().get(); + FrameGrabbing::manager().add(video_recorder_); + + _video_recorders.pop_back(); + } + } + + if (video_recorder_ && video_recorder_->duration() > Settings::application.record.timeout ){ + video_recorder_->stop(); + video_recorder_ = nullptr; } // all IMGUI Rendering @@ -996,10 +1014,6 @@ void UserInterface::RenderPreview() preview_window_pos = ImGui::GetWindowPos(); preview_window_size = ImGui::GetWindowSize(); - FrameGrabber *rec = FrameGrabbing::manager().get(video_recorder_); -#if defined(LINUX) - FrameGrabber *cam = FrameGrabbing::manager().get(webcam_emulator_); -#endif // return from thread for folder openning if ( !recordFolderFileDialogs.empty() ) { // check that file dialog thread finished @@ -1026,7 +1040,7 @@ void UserInterface::RenderPreview() info << " " << FrameBuffer::aspect_ratio_name[p.x] ; ImGui::MenuItem(info.str().c_str(), nullptr, false, false); // cannot change resolution when recording - if (rec == nullptr && p.y > -1) { + if (video_recorder_ == nullptr && p.y > -1) { ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); if (ImGui::Combo("Height", &p.y, FrameBuffer::resolution_name, IM_ARRAYSIZE(FrameBuffer::resolution_name) ) ) { @@ -1049,36 +1063,47 @@ void UserInterface::RenderPreview() } if (ImGui::BeginMenu("Record")) { - if ( ImGui::MenuItem( ICON_FA_CAMERA_RETRO " Capture frame (PNG)") ) + if ( ImGui::MenuItem( ICON_FA_CAMERA_RETRO " Capture frame (PNG)", CTRL_MOD "Shitf+R") ) FrameGrabbing::manager().add(new PNGRecorder); // Stop recording menu if main recorder already exists - if (rec) { + + if (!_video_recorders.empty()) { + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_RECORD, 0.8f)); + ImGui::MenuItem( ICON_FA_SQUARE " Record starting", CTRL_MOD "R", false, false); + ImGui::PopStyleColor(1); + static char dummy_str[512]; + sprintf(dummy_str, "%s", VideoRecorder::profile_name[Settings::application.record.profile]); + ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.14f, 0.14f, 0.14f, 0.5f)); + ImGui::InputText("Codec", dummy_str, IM_ARRAYSIZE(dummy_str), ImGuiInputTextFlags_ReadOnly); + ImGui::PopStyleColor(1); + } + else if (video_recorder_) { ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_RECORD, 0.8f)); if ( ImGui::MenuItem( ICON_FA_SQUARE " Stop Record", CTRL_MOD "R") ) { - rec->stop(); - video_recorder_ = 0; + video_recorder_->stop(); + video_recorder_ = nullptr; } ImGui::PopStyleColor(1); + static char dummy_str[512]; + sprintf(dummy_str, "%s", VideoRecorder::profile_name[Settings::application.record.profile]); + ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.14f, 0.14f, 0.14f, 0.5f)); + ImGui::InputText("Codec", dummy_str, IM_ARRAYSIZE(dummy_str), ImGuiInputTextFlags_ReadOnly); + ImGui::PopStyleColor(1); } // start recording else { - // detecting the absence of video recorder but the variable is still not 0: fix this! - if (video_recorder_ > 0) - video_recorder_ = 0; ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_RECORD, 0.9f)); if ( ImGui::MenuItem( ICON_FA_CIRCLE " Record", CTRL_MOD "R") ) { - FrameGrabber *fg = new VideoRecorder; - video_recorder_ = fg->id(); - FrameGrabbing::manager().add(fg); + _video_recorders.emplace_back( std::async(std::launch::async, delayTrigger, new VideoRecorder, std::chrono::seconds(Settings::application.record.delay)) ); } ImGui::PopStyleColor(1); // select profile ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); ImGui::Combo("Codec", &Settings::application.record.profile, VideoRecorder::profile_name, IM_ARRAYSIZE(VideoRecorder::profile_name) ); } - - // Options menu ImGui::Separator(); ImGui::MenuItem("Options", nullptr, false, false); @@ -1110,31 +1135,30 @@ void UserInterface::RenderPreview() Settings::application.record.path = SystemToolkit::home_path(); ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); - ImGui::SliderFloat("Timeout", &Settings::application.record.timeout, 1.f, RECORD_MAX_TIMEOUT, - Settings::application.record.timeout < (RECORD_MAX_TIMEOUT - 1.f) ? "%.0f s" : "None", 3.f); + ImGui::SliderFloat("Duration", &Settings::application.record.timeout, 1.f, RECORD_MAX_TIMEOUT, + Settings::application.record.timeout < (RECORD_MAX_TIMEOUT - 1.f) ? "%.0f s" : "Until stopped", 3.f); + 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("Share stream")) { #if defined(LINUX) - bool on = cam != nullptr; + bool on = webcam_emulator_ != nullptr; if ( ImGui::MenuItem( ICON_FA_CAMERA " Emulate video camera", NULL, &on) ) { - if (on && cam == nullptr) { - if (webcam_emulator_ > 0) - webcam_emulator_ = 0; + if (on) { if (Loopback::systemLoopbackInitialized()) { - FrameGrabber *fg = new Loopback; - webcam_emulator_ = fg->id(); - FrameGrabbing::manager().add(fg); + webcam_emulator_ = new Loopback; + FrameGrabbing::manager().add(webcam_emulator_); } else openInitializeSystemLoopback = true; } - if (!on && cam != nullptr) { - cam->stop(); - webcam_emulator_ = 0; + else { + webcam_emulator_->stop(); + webcam_emulator_ = nullptr; } } #endif @@ -1179,13 +1203,25 @@ void UserInterface::RenderPreview() ImGui::Text(" %d x %d px, %.d fps", output->width(), output->height(), int(Mixer::manager().fps()) ); } // recording indicator overlay - if (rec) + if (video_recorder_) { float r = ImGui::GetTextLineHeightWithSpacing(); ImGui::SetCursorScreenPos(ImVec2(draw_pos.x + r, draw_pos.y + r)); ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE); ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_RECORD, 0.8f)); - ImGui::Text(ICON_FA_CIRCLE " %s", rec->info().c_str() ); + ImGui::Text(ICON_FA_CIRCLE " %s", video_recorder_->info().c_str() ); + ImGui::PopStyleColor(1); + ImGui::PopFont(); + } + else if (!_video_recorders.empty()) + { + float r = ImGui::GetTextLineHeightWithSpacing(); + ImGui::SetCursorScreenPos(ImVec2(draw_pos.x + r, draw_pos.y + r)); + ImGuiToolkit::PushFont(ImGuiToolkit::FONT_LARGE); + static double anim = 0.f; + double a = sin(anim+=0.104); // 2 * pi / 60fps + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_RECORD, a)); + ImGui::Text(ICON_FA_CIRCLE); ImGui::PopStyleColor(1); ImGui::PopFont(); } @@ -1220,7 +1256,7 @@ void UserInterface::RenderPreview() static char dummy_str[512]; sprintf(dummy_str, "sudo apt install v4l2loopback-dkms"); - ImGui::Text("Install v4l2loopack:"); + ImGui::Text("Install v4l2loopack (once):"); ImGui::SetNextItemWidth(600-40); ImGui::InputText("##cmd1", dummy_str, IM_ARRAYSIZE(dummy_str), ImGuiInputTextFlags_ReadOnly); ImGui::SameLine(); @@ -1230,7 +1266,7 @@ void UserInterface::RenderPreview() ImGui::PopID(); sprintf(dummy_str, "sudo modprobe v4l2loopback exclusive_caps=1 video_nr=10 card_label=\"vimix loopback\""); - ImGui::Text("Initialize v4l2loopack:"); + ImGui::Text("Initialize v4l2loopack (after reboot):"); ImGui::SetNextItemWidth(600-40); ImGui::InputText("##cmd2", dummy_str, IM_ARRAYSIZE(dummy_str), ImGuiInputTextFlags_ReadOnly); ImGui::SameLine(); @@ -1241,7 +1277,7 @@ void UserInterface::RenderPreview() ImGui::Separator(); ImGui::SetItemDefaultFocus(); - if (ImGui::Button("Thank you, I'll do this and try again later.", ImVec2(w, 0)) ) { + if (ImGui::Button("Ok, I'll do this in a terminal and try again later.", ImVec2(w, 0)) ) { ImGui::CloseCurrentPopup(); } diff --git a/UserInterfaceManager.h b/UserInterfaceManager.h index ba10fd2..d5f579c 100644 --- a/UserInterfaceManager.h +++ b/UserInterfaceManager.h @@ -14,6 +14,7 @@ struct ImVec2; class Source; class MediaPlayer; class FrameBufferImage; +class FrameGrabber; class SourcePreview { @@ -138,8 +139,11 @@ class UserInterface unsigned int screenshot_step; // frame grabbers - uint64_t video_recorder_; - uint64_t webcam_emulator_; + FrameGrabber *video_recorder_; + +#if defined(LINUX) + FrameGrabber *webcam_emulator_; +#endif // Private Constructor UserInterface();