From 5a2c0e15e947d2b86b87d21f80d736c1f9c89977 Mon Sep 17 00:00:00 2001 From: Bruno Herbelin Date: Sat, 23 Jul 2022 22:45:14 +0200 Subject: [PATCH] Change Player menu and new Frame inspector Frame menu is active when a single source is selected. The Frame menu include actions to capture frame and to enable Frame Inspector. Frame inspector zooms on the image at cursor coordinate. Previous Control menu actions are back to main menu. --- Settings.cpp | 2 + Settings.h | 2 + UserInterfaceManager.cpp | 268 +++++++++++++++++++++++---------------- 3 files changed, 163 insertions(+), 109 deletions(-) diff --git a/Settings.cpp b/Settings.cpp index c863381..96a7c17 100644 --- a/Settings.cpp +++ b/Settings.cpp @@ -173,6 +173,7 @@ void Settings::Save(uint64_t runtime) SourceConfNode->SetAttribute("res", application.source.res); SourceConfNode->SetAttribute("capture_naming", application.source.capture_naming); SourceConfNode->SetAttribute("capture_path", application.source.capture_path.c_str()); + SourceConfNode->SetAttribute("inspector_zoom", application.source.inspector_zoom); pRoot->InsertEndChild(SourceConfNode); // Brush @@ -422,6 +423,7 @@ void Settings::Load() application.source.capture_path = std::string(path_); else application.source.capture_path = SystemToolkit::home_path(); + sourceconfnode->QueryFloatAttribute("inspector_zoom", &application.source.inspector_zoom); } // Transition diff --git a/Settings.h b/Settings.h index b9233be..cf1a6cf 100644 --- a/Settings.h +++ b/Settings.h @@ -169,12 +169,14 @@ struct SourceConfig int res; std::string capture_path; int capture_naming; + float inspector_zoom; SourceConfig() { new_type = 0; ratio = 3; res = 1; capture_naming = 0; + inspector_zoom = 8.f; } }; diff --git a/UserInterfaceManager.cpp b/UserInterfaceManager.cpp index 1ca350b..1dd4991 100644 --- a/UserInterfaceManager.cpp +++ b/UserInterfaceManager.cpp @@ -2235,25 +2235,27 @@ void SourceController::Update() Settings::application.source.capture_path = captureFolderDialog->path(); // - // Capture frame on current source + // Capture frame on current selection // - Source *source = Mixer::manager().currentSource(); - if (source != nullptr) { + 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, source->name(), "png"); + filename = SystemToolkit::filename_sequential(Settings::application.source.capture_path, s->name(), "png"); else - filename = SystemToolkit::filename_dateprefix(Settings::application.source.capture_path, source->name(), "png"); + 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(source->frame()); + capture.captureFramebuffer( s->frame() ); capture_request_ = false; } } @@ -2300,6 +2302,15 @@ void SourceController::Render() } 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 // @@ -2346,26 +2357,58 @@ void SourceController::Render() ImGui::EndMenu(); } - if (ImGui::BeginMenu(ICON_FA_PLAY " Control")) + if (ImGui::BeginMenu(active_label_.c_str())) { - // - // 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; + // info on selection status + size_t N = Mixer::manager().session()->numPlayGroups(); + bool enabled = !selection_.empty() && active_selection_ < 0; + + // Menu : Dynamic selection + if (ImGui::MenuItem(LABEL_AUTO_MEDIA_PLAYER)) + resetActiveSelection(); + // Menu : store selection + if (ImGui::MenuItem(ICON_FA_PLUS_SQUARE LABEL_STORE_SELECTION, NULL, false, enabled)) + { + active_selection_ = N; + active_label_ = std::string(ICON_FA_CHECK_SQUARE " Selection #") + std::to_string(active_selection_); + Mixer::manager().session()->addPlayGroup( ids(playable_only(selection_)) ); + info_.reset(); + } + // Menu : list of selections + if (N>0) { + ImGui::Separator(); + for (size_t i = 0 ; i < N; ++i) + { + std::string label = std::string(ICON_FA_CHECK_SQUARE " Selection #") + std::to_string(i); + if (ImGui::MenuItem( label.c_str() )) + { + active_selection_ = i; + active_label_ = label; + info_.reset(); + } + } + } + + ImGui::EndMenu(); + } + + // + // Menu for capture frame + // + if ( ImGui::BeginMenu(ICON_FA_PHOTO_VIDEO " Frame", selection_.size() == 1 ) ) + { + bool inspect = Settings::application.source.inspector_zoom > 0.f; + if (ImGui::MenuItem( ICON_FA_SEARCH " Inspector", NULL, &inspect )) + Settings::application.source.inspector_zoom = inspect ? 8.f : 0.f; - ImGui::Separator(); - // - // Menu for capture frame - // ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(IMGUI_COLOR_CAPTURE, 0.8f)); - if (ImGui::MenuItem( ICON_FA_CAMERA_RETRO " Capture frame", "F10", nullptr, Mixer::manager().currentSource() != nullptr )) + if (ImGui::MenuItem( MENU_CAPTUREFRAME, "F10" )) capture_request_ = true; ImGui::PopStyleColor(1); - ImGui::MenuItem("Settings", nullptr, false, false); + // separator and hack for extending menu width + ImGui::Separator(); + ImGui::MenuItem("Settings ", nullptr, false, false); // path menu selection static char* name_path[4] = { nullptr }; @@ -2410,105 +2453,67 @@ void SourceController::Render() ImGui::EndMenu(); } - if (ImGui::BeginMenu(active_label_.c_str())) + // + // Menu for video Mediaplayer control + // + if (ImGui::BeginMenu(ICON_FA_FILM " Video", mediaplayer_active_) ) { - // info on selection status - size_t N = Mixer::manager().session()->numPlayGroups(); - bool enabled = !selection_.empty() && active_selection_ < 0; - - // Menu : Dynamic selection - if (ImGui::MenuItem(LABEL_AUTO_MEDIA_PLAYER)) - resetActiveSelection(); - // Menu : store selection - if (ImGui::MenuItem(ICON_FA_PLUS_SQUARE LABEL_STORE_SELECTION, NULL, false, enabled)) - { - active_selection_ = N; - active_label_ = std::string(ICON_FA_CHECK_SQUARE " Selection #") + std::to_string(active_selection_); - Mixer::manager().session()->addPlayGroup( ids(playable_only(selection_)) ); - info_.reset(); + 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()); } - // Menu : list of selections - if (N>0) { + + if (ImGui::MenuItem(LABEL_EDIT_FADING)) + mediaplayer_edit_fading_ = true; + + if (ImGui::BeginMenu(ICON_FA_CLOCK " Metronome")) + { + Metronome::Synchronicity sync = mediaplayer_active_->syncToMetronome(); + bool active = sync == Metronome::SYNC_NONE; + if (ImGuiToolkit::MenuItemIcon(5, 13, " Not synchronized", active )) + mediaplayer_active_->setSyncToMetronome(Metronome::SYNC_NONE); + active = sync == Metronome::SYNC_BEAT; + if (ImGuiToolkit::MenuItemIcon(6, 13, " Sync to beat", active )) + mediaplayer_active_->setSyncToMetronome(Metronome::SYNC_BEAT); + active = sync == Metronome::SYNC_PHASE; + if (ImGuiToolkit::MenuItemIcon(7, 13, " Sync to phase", active )) + mediaplayer_active_->setSyncToMetronome(Metronome::SYNC_PHASE); + ImGui::EndMenu(); + } + + if (ImGui::BeginMenu(ICON_FA_SNOWFLAKE " Deactivation")) + { + bool option = !mediaplayer_active_->rewindOnDisabled(); + if (ImGui::MenuItem(ICON_FA_STOP " Stop", NULL, &option )) + mediaplayer_active_->setRewindOnDisabled(false); + option = mediaplayer_active_->rewindOnDisabled(); + if (ImGui::MenuItem(ICON_FA_FAST_BACKWARD " Rewind & Stop", NULL, &option )) + mediaplayer_active_->setRewindOnDisabled(true); + ImGui::EndMenu(); + } + if (Settings::application.render.gpu_decoding) + { ImGui::Separator(); - for (size_t i = 0 ; i < N; ++i) + if (ImGui::BeginMenu(ICON_FA_MICROCHIP " Hardware decoding")) { - std::string label = std::string(ICON_FA_CHECK_SQUARE " Selection #") + std::to_string(i); - if (ImGui::MenuItem( label.c_str() )) - { - active_selection_ = i; - active_label_ = label; - info_.reset(); - } + bool hwdec = !mediaplayer_active_->softwareDecodingForced(); + if (ImGui::MenuItem("Auto", "", &hwdec )) + mediaplayer_active_->setSoftwareDecodingForced(false); + hwdec = mediaplayer_active_->softwareDecodingForced(); + if (ImGui::MenuItem("Disabled", "", &hwdec )) + mediaplayer_active_->setSoftwareDecodingForced(true); + ImGui::EndMenu(); } } ImGui::EndMenu(); } - if (mediaplayer_active_) { - if (ImGui::BeginMenu(ICON_FA_FILM " Video") ) - { - if (ImGui::MenuItem(ICON_FA_WINDOW_CLOSE " Reset timeline")){ - mediaplayer_timeline_zoom_ = 1.f; - mediaplayer_active_->timeline()->clearFading(); - mediaplayer_active_->timeline()->clearGaps(); - std::ostringstream oss; - oss << SystemToolkit::base_filename( mediaplayer_active_->filename() ); - oss << ": Reset timeline"; - Action::manager().store(oss.str()); - } - - if (ImGui::MenuItem(LABEL_EDIT_FADING)) - mediaplayer_edit_fading_ = true; - - if (ImGui::BeginMenu(ICON_FA_CLOCK " Metronome")) - { - Metronome::Synchronicity sync = mediaplayer_active_->syncToMetronome(); - bool active = sync == Metronome::SYNC_NONE; - if (ImGuiToolkit::MenuItemIcon(5, 13, " Not synchronized", active )) - mediaplayer_active_->setSyncToMetronome(Metronome::SYNC_NONE); - active = sync == Metronome::SYNC_BEAT; - if (ImGuiToolkit::MenuItemIcon(6, 13, " Sync to beat", active )) - mediaplayer_active_->setSyncToMetronome(Metronome::SYNC_BEAT); - active = sync == Metronome::SYNC_PHASE; - if (ImGuiToolkit::MenuItemIcon(7, 13, " Sync to phase", active )) - mediaplayer_active_->setSyncToMetronome(Metronome::SYNC_PHASE); - ImGui::EndMenu(); - } - - if (ImGui::BeginMenu(ICON_FA_SNOWFLAKE " Deactivation")) - { - bool option = !mediaplayer_active_->rewindOnDisabled(); - if (ImGui::MenuItem(ICON_FA_STOP " Stop", NULL, &option )) - mediaplayer_active_->setRewindOnDisabled(false); - option = mediaplayer_active_->rewindOnDisabled(); - if (ImGui::MenuItem(ICON_FA_FAST_BACKWARD " Rewind & Stop", NULL, &option )) - mediaplayer_active_->setRewindOnDisabled(true); - ImGui::EndMenu(); - } - if (Settings::application.render.gpu_decoding) - { - ImGui::Separator(); - if (ImGui::BeginMenu(ICON_FA_MICROCHIP " Hardware decoding")) - { - bool hwdec = !mediaplayer_active_->softwareDecodingForced(); - if (ImGui::MenuItem("Auto", "", &hwdec )) - mediaplayer_active_->setSoftwareDecodingForced(false); - hwdec = mediaplayer_active_->softwareDecodingForced(); - if (ImGui::MenuItem("Disabled", "", &hwdec )) - mediaplayer_active_->setSoftwareDecodingForced(true); - ImGui::EndMenu(); - } - } - - ImGui::EndMenu(); - } - } - else { - ImGui::SameLine(0, 2.f * g.Style.ItemSpacing.x ); - ImGui::TextDisabled(ICON_FA_FILM " Video"); - } - ImGui::EndMenuBar(); } @@ -3015,6 +3020,43 @@ bool SourceController::SourceButton(Source *s, ImVec2 framesize) return ret; } +void DrawInspector(uint texture, ImVec2 texturesize, 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) / texturesize.x, (region_y) / texturesize.y); + ImVec2 uv1 = ImVec2((region_x + region_sz) / texturesize.x, (region_y + region_sz) / texturesize.y); + ImGui::Image((void*)(intptr_t)texture, ImVec2(texturesize.x / 3.f, texturesize.x / 3.f), uv0, uv1, ImVec4(1.0f, 1.0f, 1.0f, 1.0f), ImVec4(1.0f, 1.0f, 1.0f, 0.5f)); + + ImGui::EndTooltip(); + ImGui::PopStyleVar(3); + } +} + ImRect DrawSourceWithSlider(Source *s, ImVec2 top, ImVec2 rendersize) { ImDrawList* draw_list = ImGui::GetWindowDrawList(); @@ -3049,6 +3091,8 @@ ImRect DrawSourceWithSlider(Source *s, ImVec2 top, ImVec2 rendersize) // 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 ( ImGui::IsItemHovered() ) + DrawInspector(s->texture(), framesize, top_image); // // RIGHT of slider : post-processed image (after crop and color correction) @@ -3060,6 +3104,8 @@ ImRect DrawSourceWithSlider(Source *s, ImVec2 top, ImVec2 rendersize) // 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 ( ImGui::IsItemHovered() ) + DrawInspector(s->frame()->texture(), framesize, top_image); } // overlap of slider with cropped area (horizontally) else if (slider.x < croptop.x + cropsize.x ) { @@ -3071,6 +3117,8 @@ ImRect DrawSourceWithSlider(Source *s, ImVec2 top, ImVec2 rendersize) // size is reduced by slider cropsize = cropsize * ImVec2(1.f -cropped_slider, 1.f); ImGui::Image((void*)(uintptr_t) s->frame()->texture(), cropsize, ImVec2(cropped_slider, 0.f), ImVec2(1.f,1.f)); + if ( ImGui::IsItemHovered() ) + DrawInspector(s->frame()->texture(), framesize, top_image); } // else : no render of cropped area @@ -3090,6 +3138,8 @@ ImRect DrawSourceWithSlider(Source *s, ImVec2 top, ImVec2 rendersize) } else { ImGui::Image((void*)(uintptr_t) s->texture(), framesize); + if ( ImGui::IsItemHovered() ) + DrawInspector(s->texture(), framesize, top_image); } return ImRect( top_image, top_image + framesize);