diff --git a/ActionManager.cpp b/ActionManager.cpp index 1e40bd4..ad73c3b 100644 --- a/ActionManager.cpp +++ b/ActionManager.cpp @@ -38,11 +38,13 @@ void captureMixerSession(tinyxml2::XMLDocument *doc, std::string node, std::stri Session *se = Mixer::manager().session(); // get the thumbnail (requires one opengl update to render) - FrameBufferImage *thumbnail = se->thumbnail(); - XMLElement *imageelement = SessionVisitor::ImageToXML(thumbnail, doc); - if (imageelement) - sessionNode->InsertEndChild(imageelement); - delete thumbnail; + FrameBufferImage *thumbnail = se->renderThumbnail(); + if (thumbnail) { + XMLElement *imageelement = SessionVisitor::ImageToXML(thumbnail, doc); + if (imageelement) + sessionNode->InsertEndChild(imageelement); + delete thumbnail; + } // save all sources using source visitor SessionVisitor sv(doc, sessionNode); diff --git a/Session.cpp b/Session.cpp index c29b5b8..bf4bd1a 100644 --- a/Session.cpp +++ b/Session.cpp @@ -18,7 +18,7 @@ SessionNote::SessionNote(const std::string &t, bool l, int s): label(std::to_str { } -Session::Session() : active_(true), filename_(""), failedSource_(nullptr), fading_target_(0.f) +Session::Session() : active_(true), filename_(""), failedSource_(nullptr), fading_target_(0.f), thumbnail_(nullptr) { config_[View::RENDERING] = new Group; config_[View::RENDERING]->scale_ = glm::vec3(0.f); @@ -228,6 +228,33 @@ Source *Session::popSource() return s; } +static void replaceThumbnail(Session *s) +{ + if (s != nullptr) { + FrameBufferImage *t = s->renderThumbnail(); + if (t != nullptr) // avoid recursive infinite loop + s->setThumbnail(t); + } +} + +void Session::setThumbnail(FrameBufferImage *t) +{ + resetThumbnail(); + // replace with given image + if (t != nullptr) + thumbnail_ = t; + // no thumbnail image given: capture from rendering in a parallel thread + else + std::thread( replaceThumbnail, this ).detach(); +} + +void Session::resetThumbnail() +{ + if (thumbnail_ != nullptr) + delete thumbnail_; + thumbnail_ = nullptr; +} + void Session::setResolution(glm::vec3 resolution, bool useAlpha) { // setup the render view: if not specified the default config resulution will be used diff --git a/Session.h b/Session.h index c0fc263..19c332d 100644 --- a/Session.h +++ b/Session.h @@ -85,8 +85,13 @@ public: // get frame result of render inline FrameBuffer *frame () const { return render_.frame(); } - // get thumbnail image - inline FrameBufferImage *thumbnail () { return render_.thumbnail(); } + // get an newly rendered thumbnail + inline FrameBufferImage *renderThumbnail () { return render_.thumbnail(); } + + // get / set thumbnail image + inline FrameBufferImage *thumbnail () const { return thumbnail_; } + void setThumbnail(FrameBufferImage *t = nullptr); + void resetThumbnail(); // configure rendering resolution void setResolution (glm::vec3 resolution, bool useAlpha = false); @@ -153,7 +158,7 @@ protected: std::vector play_groups_; float fading_target_; std::mutex access_; - + FrameBufferImage *thumbnail_; }; diff --git a/SessionCreator.cpp b/SessionCreator.cpp index ca7b027..ae1d433 100644 --- a/SessionCreator.cpp +++ b/SessionCreator.cpp @@ -54,7 +54,13 @@ SessionInformation SessionCreator::info(const std::string& filename) } const XMLElement *session = doc.FirstChildElement("Session"); if (session != nullptr ) { - ret.thumbnail = XMLToImage(session); + const XMLElement *thumbnailelement = session->FirstChildElement("Thumbnail"); + // if there is a user defined thumbnail, get it + if (thumbnailelement) + ret.thumbnail = XMLToImage(thumbnailelement); + // otherwise get the default saved thumbnail in session + else + ret.thumbnail = XMLToImage(session); } } } @@ -100,7 +106,8 @@ void SessionCreator::load(const std::string& filename) // ready to read sources sessionFilePath_ = SystemToolkit::path_filename(filename); - SessionLoader::load( xmlDoc_.FirstChildElement("Session") ); + XMLElement *sessionNode = xmlDoc_.FirstChildElement("Session"); + SessionLoader::load( sessionNode ); // create groups std::list< SourceList > groups = getMixingGroups(); @@ -116,6 +123,15 @@ void SessionCreator::load(const std::string& filename) // load playlists loadPlayGroups( xmlDoc_.FirstChildElement("PlayGroups") ); + // thumbnail + const XMLElement *thumbnailelement = sessionNode->FirstChildElement("Thumbnail"); + // if there is a user-defined thumbnail, get it + if (thumbnailelement) { + FrameBufferImage *thumbnail = XMLToImage(thumbnailelement); + if (thumbnail != nullptr) + session_->setThumbnail( thumbnail ); + } + // all good session_->setFilename(filename); } diff --git a/SessionVisitor.cpp b/SessionVisitor.cpp index ea76088..e9b7a65 100644 --- a/SessionVisitor.cpp +++ b/SessionVisitor.cpp @@ -51,12 +51,26 @@ bool SessionVisitor::saveSession(const std::string& filename, Session *session) // source visitor (*iter)->accept(sv); - // get the thumbnail + // save the thumbnail FrameBufferImage *thumbnail = session->thumbnail(); - XMLElement *imageelement = SessionVisitor::ImageToXML(thumbnail, &xmlDoc); - if (imageelement) - sessionNode->InsertEndChild(imageelement); - delete thumbnail; + if (thumbnail != nullptr && thumbnail->width > 0 && thumbnail->height > 0) { + XMLElement *thumbnailelement = xmlDoc.NewElement("Thumbnail"); + XMLElement *imageelement = SessionVisitor::ImageToXML(thumbnail, &xmlDoc); + if (imageelement) { + sessionNode->InsertEndChild(thumbnailelement); + thumbnailelement->InsertEndChild(imageelement); + } + } + // if no thumbnail is set by user, capture thumbnail now + else { + thumbnail = session->renderThumbnail(); + if (thumbnail) { + XMLElement *imageelement = SessionVisitor::ImageToXML(thumbnail, &xmlDoc); + if (imageelement) + sessionNode->InsertEndChild(imageelement); + delete thumbnail; + } + } // 2. config of views saveConfig( &xmlDoc, session ); diff --git a/UserInterfaceManager.cpp b/UserInterfaceManager.cpp index 15918ad..effdbb1 100644 --- a/UserInterfaceManager.cpp +++ b/UserInterfaceManager.cpp @@ -767,7 +767,10 @@ void UserInterface::Render() } // verify the video recorder is valid FrameGrabbing::manager().verify(&video_recorder_); - if (video_recorder_ && video_recorder_->duration() > Settings::application.record.timeout ){ + 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(); video_recorder_ = nullptr; } @@ -1078,27 +1081,33 @@ void UserInterface::RenderPreview() Settings::application.widget.preview = false; if (ImGui::BeginMenu(IMGUI_TITLE_PREVIEW)) { - glm::ivec2 p = FrameBuffer::getParametersFromResolution(output->resolution()); - std::ostringstream info; - info << "Resolution " << output->width() << "x" << output->height(); - if (p.x > -1) - info << " " << FrameBuffer::aspect_ratio_name[p.x] ; - ImGui::MenuItem(info.str().c_str(), nullptr, false, false); - // cannot change resolution when recording - 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) ) ) - { - glm::vec3 res = FrameBuffer::getResolutionFromParameters(p.x, p.y); - Mixer::manager().session()->setResolution(res); - } - } +// glm::ivec2 p = FrameBuffer::getParametersFromResolution(output->resolution()); +// std::ostringstream info; +// info << "Resolution " << output->width() << "x" << output->height(); +// if (p.x > -1) +// info << " " << FrameBuffer::aspect_ratio_name[p.x] ; +// ImGui::MenuItem(info.str().c_str(), nullptr, false, false); +// // cannot change resolution when recording +// 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) ) ) +// { +// glm::vec3 res = FrameBuffer::getResolutionFromParameters(p.x, p.y); +// Mixer::manager().session()->setResolution(res); +// } +// } if ( ImGui::MenuItem( ICON_FA_PLUS " Insert Rendering Source") ) Mixer::manager().addSource( Mixer::manager().createSourceRender() ); if ( ImGui::MenuItem( ICON_FA_WINDOW_RESTORE " Show output window") ) Rendering::manager().outputWindow().show(); + bool isfullscreen = Rendering::manager().outputWindow().isFullscreen(); + if ( ImGui::MenuItem( ICON_FA_EXPAND_ALT " Fullscreen output window", nullptr, &isfullscreen) ) { + Rendering::manager().outputWindow().show(); + Rendering::manager().outputWindow().toggleFullscreen(); + } + ImGui::Separator(); bool pinned = Settings::application.widget.preview_view == Settings::application.current_view; @@ -4105,7 +4114,7 @@ void Navigator::RenderMainPannelVimix() _file_info = info.description; if (info.thumbnail) { // set image content to thumbnail display - _file_thumbnail.set( info.thumbnail ); + _file_thumbnail.fill( info.thumbnail ); delete info.thumbnail; } else _file_thumbnail.reset(); @@ -4148,7 +4157,7 @@ void Navigator::RenderMainPannelVimix() // Right side of the list: helper and options ImGui::SetCursorPos( ImVec2( pannel_width_ IMGUI_RIGHT_ALIGN, pos_top.y)); - if ( ImGuiToolkit::IconButton( ICON_FA_FILE )) { + if ( ImGuiToolkit::IconButton( ICON_FA_FILE " +" )) { Mixer::manager().close(Settings::application.smooth_transition ); hidePannel(); } @@ -4174,13 +4183,112 @@ void Navigator::RenderMainPannelVimix() // Status // ImGui::Spacing(); - ImGui::Text("Status"); - ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); - ImGui::Combo("##SelectHistory", &Settings::application.pannel_history_mode, ICON_FA_STAR " Snapshots\0" ICON_FA_HISTORY " Undo history\0"); + ImGui::Text("Current"); + ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); + ImGui::Combo("##SelectHistory", &Settings::application.pannel_history_mode, + ICON_FA_STAR " Snapshots\0" ICON_FA_HISTORY " Undo history\0" ICON_FA_FILE_ALT " Properties\0"); + + if (Settings::application.pannel_history_mode > 1) { + + std::string sessionfilename = Mixer::manager().session()->filename(); + + // Information and resolution + FrameBuffer *output = Mixer::manager().session()->frame(); + if (output) + { + // fill information buffer + ImGuiTextBuffer info; + if (!sessionfilename.empty()) + info.appendf("%s\n", SystemToolkit::filename(sessionfilename).c_str()); + else + info.append("\n"); + info.appendf("Sources: %d\n", Mixer::manager().session()->numSource()); + + glm::ivec2 p = FrameBuffer::getParametersFromResolution(output->resolution()); + if (p.x > -1) + info.appendf("Ratio: %s\n", FrameBuffer::aspect_ratio_name[p.x]); + info.appendf("Resolution: %dx%d", output->width(), output->height()); + + // Show info text bloc (multi line, dark background) + ImGuiToolkit::PushFont( ImGuiToolkit::FONT_MONO ); + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.14f, 0.14f, 0.14f, 0.9f)); + ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); + ImGui::InputTextMultiline("##Info", (char *)info.c_str(), info.size(), ImVec2(IMGUI_RIGHT_ALIGN, 4*ImGui::GetTextLineHeightWithSpacing()), ImGuiInputTextFlags_ReadOnly); + ImGui::PopStyleColor(1); + ImGui::PopFont(); + + // change resolution (height only) + if (p.y > -1) { + ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); + // cannot change resolution when recording + if ( UserInterface::manager().isRecording() ) { + // show static info (same size than combo) + static char dummy_str[512]; + sprintf(dummy_str, "%s", FrameBuffer::resolution_name[p.y]); + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.14f, 0.14f, 0.14f, 0.9f)); + ImGui::InputText("Height", dummy_str, IM_ARRAYSIZE(dummy_str), ImGuiInputTextFlags_ReadOnly); + ImGui::PopStyleColor(1); + } + else { + // combo box to select height + if (ImGui::Combo("Height", &p.y, FrameBuffer::resolution_name, IM_ARRAYSIZE(FrameBuffer::resolution_name) ) ) + { + glm::vec3 res = FrameBuffer::getResolutionFromParameters(p.x, p.y); + Mixer::manager().session()->setResolution(res); + } + } + } + } + + // the session file exists + if (!sessionfilename.empty()) + { + // Thumbnail + static Thumbnail _file_thumbnail; + static FrameBufferImage *thumbnail = nullptr; + if ( ImGui::Button( ICON_FA_TAGS " Capture thumbnail", ImVec2(IMGUI_RIGHT_ALIGN, 0)) ) { + Mixer::manager().session()->setThumbnail(); + thumbnail = nullptr; + } + pos_bot = ImGui::GetCursorPos(); + if (ImGui::IsItemHovered()){ + // thumbnail changed + if (thumbnail != Mixer::manager().session()->thumbnail()) { + _file_thumbnail.reset(); + thumbnail = Mixer::manager().session()->thumbnail(); + if (thumbnail != nullptr) + _file_thumbnail.fill( thumbnail ); + } + if (_file_thumbnail.filled()) { + ImGui::BeginTooltip(); + _file_thumbnail.Render(230); + ImGui::EndTooltip(); + } + } + if (Mixer::manager().session()->thumbnail()) { + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.7); + ImGui::SameLine(); + if (ImGuiToolkit::IconButton(ICON_FA_BACKSPACE, "Remove captured thumbnail")) { + Mixer::manager().session()->resetThumbnail(); + _file_thumbnail.reset(); + thumbnail = nullptr; + } + ImGui::PopStyleVar(); + } + ImGui::SetCursorPos( pos_bot ); + + // Folder + std::string path = SystemToolkit::path_filename(sessionfilename); + std::string label = BaseToolkit::trunc_string(path, 23); + label = BaseToolkit::transliterate(label); + ImGuiToolkit::ButtonOpenUrl( label.c_str(), path.c_str(), ImVec2(IMGUI_RIGHT_ALIGN, 0) ); + } + + } // // UNDO History - if (Settings::application.pannel_history_mode > 0) { + else if (Settings::application.pannel_history_mode > 0) { static uint _over = 0; static uint64_t _displayed_over = 0; @@ -4220,7 +4328,7 @@ void Navigator::RenderMainPannelVimix() FrameBufferImage *im = Action::manager().thumbnail(_over); if (im) { // set image content to thumbnail display - _undo_thumbnail.set( im ); + _undo_thumbnail.fill( im ); delete im; } else @@ -4329,7 +4437,7 @@ void Navigator::RenderMainPannelVimix() FrameBufferImage *im = Action::manager().thumbnail(_over); if (im) { // set image content to thumbnail display - _snap_thumbnail.set( im ); + _snap_thumbnail.fill( im ); delete im; } else @@ -4742,12 +4850,17 @@ Thumbnail::~Thumbnail() glDeleteTextures(1, &texture_); } +bool Thumbnail::filled() +{ + return aspect_ratio_ > 0.f; +} + void Thumbnail::reset() { aspect_ratio_ = -1.f; } -void Thumbnail::set(const FrameBufferImage *image) +void Thumbnail::fill(const FrameBufferImage *image) { if (!texture_) { glGenTextures(1, &texture_); @@ -4764,7 +4877,7 @@ void Thumbnail::set(const FrameBufferImage *image) void Thumbnail::Render(float width) { - if (aspect_ratio_>0.f) + if (filled()) ImGui::Image((void*)(intptr_t)texture_, ImVec2(width, width/aspect_ratio_), ImVec2(0,0), ImVec2(0.5f*aspect_ratio_, 1.f)); } diff --git a/UserInterfaceManager.h b/UserInterfaceManager.h index 1d7c03f..92cb0bf 100644 --- a/UserInterfaceManager.h +++ b/UserInterfaceManager.h @@ -45,7 +45,8 @@ public: ~Thumbnail(); void reset(); - void set (const FrameBufferImage *image); + void fill (const FrameBufferImage *image); + bool filled(); void Render(float width); }; @@ -218,6 +219,7 @@ public: void fillShaderEditor(const std::string &text); void StartScreenshot(); + inline bool isRecording() const { return video_recorder_ != nullptr; } protected: