diff --git a/src/command/video.cpp b/src/command/video.cpp index c0499a38cd..db9f7dcd91 100644 --- a/src/command/video.cpp +++ b/src/command/video.cpp @@ -603,6 +603,17 @@ struct video_opt_autoscroll final : public Command { } }; +struct video_pan_reset final : public validator_video_loaded { + CMD_NAME("video/pan_reset") + STR_MENU("Reset video pan") + STR_DISP("Reset video pan") + STR_HELP("Reset the video pan to the original value") + + void operator()(agi::Context *c) override { + c->videoDisplay->ResetPan(); + } +}; + struct video_play final : public validator_video_loaded { CMD_NAME("video/play") CMD_ICON(button_play) @@ -658,7 +669,7 @@ class video_zoom_100: public validator_video_attached { void operator()(agi::Context *c) override { c->videoController->Stop(); - c->videoDisplay->SetZoom(1.); + c->videoDisplay->SetWindowZoom(1.); } }; @@ -689,7 +700,7 @@ class video_zoom_200: public validator_video_attached { void operator()(agi::Context *c) override { c->videoController->Stop(); - c->videoDisplay->SetZoom(2.); + c->videoDisplay->SetWindowZoom(2.); } }; @@ -707,7 +718,7 @@ class video_zoom_50: public validator_video_attached { void operator()(agi::Context *c) override { c->videoController->Stop(); - c->videoDisplay->SetZoom(.5); + c->videoDisplay->SetWindowZoom(.5); } }; @@ -719,7 +730,7 @@ struct video_zoom_in final : public validator_video_attached { STR_HELP("Zoom video in") void operator()(agi::Context *c) override { - c->videoDisplay->SetZoom(c->videoDisplay->GetZoom() + .125); + c->videoDisplay->SetWindowZoom(c->videoDisplay->GetZoom() + .125); } }; @@ -731,7 +742,7 @@ struct video_zoom_out final : public validator_video_attached { STR_HELP("Zoom video out") void operator()(agi::Context *c) override { - c->videoDisplay->SetZoom(c->videoDisplay->GetZoom() - .125); + c->videoDisplay->SetWindowZoom(c->videoDisplay->GetZoom() - .125); } }; } @@ -767,6 +778,7 @@ namespace cmd { reg(agi::make_unique()); reg(agi::make_unique()); reg(agi::make_unique()); + reg(agi::make_unique()); reg(agi::make_unique()); reg(agi::make_unique()); reg(agi::make_unique()); diff --git a/src/frame_main.cpp b/src/frame_main.cpp index 91531f5cf3..6c17602c72 100644 --- a/src/frame_main.cpp +++ b/src/frame_main.cpp @@ -276,9 +276,9 @@ void FrameMain::OnVideoOpen(AsyncVideoProvider *provider) { double zoom = context->videoDisplay->GetZoom(); wxSize windowSize = GetSize(); if (vidx*3*zoom > windowSize.GetX()*4 || vidy*4*zoom > windowSize.GetY()*6) - context->videoDisplay->SetZoom(zoom * .25); + context->videoDisplay->SetWindowZoom(zoom * .25); else if (vidx*3*zoom > windowSize.GetX()*2 || vidy*4*zoom > windowSize.GetY()*3) - context->videoDisplay->SetZoom(zoom * .5); + context->videoDisplay->SetWindowZoom(zoom * .5); SetDisplayMode(1,-1); diff --git a/src/libresrc/default_config.json b/src/libresrc/default_config.json index 3ea90c8de6..14ce090a57 100644 --- a/src/libresrc/default_config.json +++ b/src/libresrc/default_config.json @@ -592,6 +592,8 @@ }, "Video" : { + "Disable Scroll Zoom" : false, + "Reverse Zoom" : false, "Default Zoom" : 7, "Detached" : { "Enabled" : false, @@ -619,6 +621,7 @@ "Fast Jump Step" : 10, "Show Keyframes" : true }, - "Subtitle Sync" : true + "Subtitle Sync" : true, + "Default to Video Zoom": false } } diff --git a/src/libresrc/default_menu.json b/src/libresrc/default_menu.json index f213feff3b..a6d969a755 100644 --- a/src/libresrc/default_menu.json +++ b/src/libresrc/default_menu.json @@ -157,6 +157,7 @@ { "submenu" : "main/video/set zoom", "text" : "Set &Zoom" }, { "submenu" : "main/video/override ar", "text" : "Override &AR" }, { "command" : "video/show_overscan" }, + { "command" : "video/pan_reset" }, {}, { "command" : "video/jump" }, { "command" : "video/jump/start" }, diff --git a/src/libresrc/osx/default_config.json b/src/libresrc/osx/default_config.json index 4d55817884..bae06dbe96 100644 --- a/src/libresrc/osx/default_config.json +++ b/src/libresrc/osx/default_config.json @@ -592,6 +592,8 @@ }, "Video" : { + "Disable Scroll Zoom" : false, + "Reverse Zoom" : false, "Default Zoom" : 7, "Detached" : { "Enabled" : false, @@ -619,6 +621,7 @@ "Fast Jump Step" : 10, "Show Keyframes" : true }, - "Subtitle Sync" : true + "Subtitle Sync" : true, + "Default to Video Zoom": false } } diff --git a/src/preferences.cpp b/src/preferences.cpp index 79b5b2b932..fd19478216 100644 --- a/src/preferences.cpp +++ b/src/preferences.cpp @@ -175,7 +175,11 @@ void Video(wxTreebook *book, Preferences *parent) { p->OptionAdd(general, _("Seek video to line start on selection change"), "Video/Subtitle Sync"); p->CellSkip(general); p->OptionAdd(general, _("Automatically open audio when opening video"), "Video/Open Audio"); - p->CellSkip(general); + p->OptionAdd(general, _("Default to Video Zoom"), "Video/Default to Video Zoom") + ->SetToolTip("Reverses the behavior of Ctrl while scrolling the video display. If not set, scrolling will default to UI zoom and Ctrl+scrolling will zoom the video. If set, this will be reversed."); + p->OptionAdd(general, _("Disable zooming with scroll bar"), "Video/Disable Scroll Zoom") + ->SetToolTip("Makes the scroll bar not zoom the video. Useful when using a track pad that often scrolls accidentally."); + p->OptionAdd(general, _("Reverse zoom direction"), "Video/Reverse Zoom"); const wxString czoom_arr[24] = { "12.5%", "25%", "37.5%", "50%", "62.5%", "75%", "87.5%", "100%", "112.5%", "125%", "137.5%", "150%", "162.5%", "175%", "187.5%", "200%", "212.5%", "225%", "237.5%", "250%", "262.5%", "275%", "287.5%", "300%" }; wxArrayString choice_zoom(24, czoom_arr); @@ -455,6 +459,7 @@ void Advanced_Video(wxTreebook *book, Preferences *parent) { wxArrayString sp_choice = to_wx(SubtitlesProviderFactory::GetClasses()); p->OptionChoice(expert, _("Subtitles provider"), sp_choice, "Subtitle/Provider"); + #ifdef WITH_AVISYNTH auto avisynth = p->PageSizer("Avisynth"); diff --git a/src/project.cpp b/src/project.cpp index 5abb79b36d..abe2feb3a4 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -221,7 +221,7 @@ void Project::LoadUnloadFiles(ProjectProperties properties) { vc->SetAspectRatio(properties.ar_value); else vc->SetAspectRatio(ar_mode); - context->videoDisplay->SetZoom(properties.video_zoom); + context->videoDisplay->SetWindowZoom(properties.video_zoom); } } diff --git a/src/video_display.cpp b/src/video_display.cpp index 00e3081121..36271e3d24 100644 --- a/src/video_display.cpp +++ b/src/video_display.cpp @@ -87,19 +87,20 @@ VideoDisplay::VideoDisplay(wxToolBar *toolbar, bool freeSize, wxComboBox *zoomBo : wxGLCanvas(parent, -1, attribList) , autohideTools(OPT_GET("Tool/Visual/Autohide")) , con(c) -, zoomValue(OPT_GET("Video/Default Zoom")->GetInt() * .125 + .125) +, windowZoomValue(OPT_GET("Video/Default Zoom")->GetInt() * .125 + .125) +, videoZoomValue(1) , toolBar(toolbar) , zoomBox(zoomBox) , freeSize(freeSize) , retina_helper(agi::make_unique(this)) , scale_factor(retina_helper->GetScaleFactor()) , scale_factor_connection(retina_helper->AddScaleFactorListener([=](int new_scale_factor) { - double new_zoom = zoomValue * new_scale_factor / scale_factor; + double new_zoom = windowZoomValue * new_scale_factor / scale_factor; scale_factor = new_scale_factor; - SetZoom(new_zoom); + SetWindowZoom(new_zoom); })) { - zoomBox->SetValue(fmt_wx("%g%%", zoomValue * 100.)); + zoomBox->SetValue(fmt_wx("%g%%", windowZoomValue * 100.)); zoomBox->Bind(wxEVT_COMBOBOX, &VideoDisplay::SetZoomFromBox, this); zoomBox->Bind(wxEVT_TEXT_ENTER, &VideoDisplay::SetZoomFromBoxText, this); @@ -119,6 +120,8 @@ VideoDisplay::VideoDisplay(wxToolBar *toolbar, bool freeSize, wxComboBox *zoomBo Bind(wxEVT_LEFT_DCLICK, &VideoDisplay::OnMouseEvent, this); Bind(wxEVT_LEFT_DOWN, &VideoDisplay::OnMouseEvent, this); Bind(wxEVT_LEFT_UP, &VideoDisplay::OnMouseEvent, this); + Bind(wxEVT_MIDDLE_DOWN, &VideoDisplay::OnMouseEvent, this); + Bind(wxEVT_MIDDLE_UP, &VideoDisplay::OnMouseEvent, this); Bind(wxEVT_MOTION, &VideoDisplay::OnMouseEvent, this); Bind(wxEVT_MOUSEWHEEL, &VideoDisplay::OnMouseWheel, this); @@ -210,11 +213,14 @@ void VideoDisplay::DoRender() try { PositionVideo(); videoOut->Render(viewport_left, viewport_bottom, viewport_width, viewport_height); - E(glViewport(0, std::min(viewport_bottom, 0), videoSize.GetWidth(), videoSize.GetHeight())); + + int client_w, client_h; + GetClientSize(&client_w, &client_h); + E(glViewport(0, 0, client_w * scale_factor, client_h * scale_factor)); E(glMatrixMode(GL_PROJECTION)); E(glLoadIdentity()); - E(glOrtho(0.0f, videoSize.GetWidth() / scale_factor, videoSize.GetHeight() / scale_factor, 0.0f, -1000.0f, 1000.0f)); + E(glOrtho(0.0f, std::max(client_w, 1), std::max(client_h, 1), 0.0f, -1000.0f, 1000.0f)); if (OPT_GET("Video/Overscan Mask")->GetBool()) { double ar = con->videoController->GetAspectRatioValue(); @@ -287,8 +293,13 @@ void VideoDisplay::PositionVideo() { auto provider = con->project->VideoProvider(); if (!provider || !IsShownOnScreen()) return; + int client_w, client_h; + GetClientSize(&client_w, &client_h); + client_w *= scale_factor; + client_h *= scale_factor; + viewport_left = 0; - viewport_bottom = GetClientSize().GetHeight() * scale_factor - videoSize.GetHeight(); + viewport_bottom = client_h - videoSize.GetHeight(); viewport_top = 0; viewport_width = videoSize.GetWidth(); viewport_height = videoSize.GetHeight(); @@ -298,27 +309,33 @@ void VideoDisplay::PositionVideo() { int vidH = provider->GetHeight(); AspectRatio arType = con->videoController->GetAspectRatioType(); - double displayAr = double(viewport_width) / viewport_height; + double displayAr = double(client_w) / client_h; double videoAr = arType == AspectRatio::Default ? double(vidW) / vidH : con->videoController->GetAspectRatioValue(); // Window is wider than video, blackbox left/right if (displayAr - videoAr > 0.01) { - int delta = viewport_width - videoAr * viewport_height; + int delta = client_w - videoAr * client_h; viewport_left = delta / 2; - viewport_width -= delta; } // Video is wider than window, blackbox top/bottom else if (videoAr - displayAr > 0.01) { - int delta = viewport_height - viewport_width / videoAr; - viewport_top = viewport_bottom = delta / 2; + int delta = client_h - client_w / videoAr; + viewport_top += delta / 2; + viewport_bottom += delta / 2; viewport_height -= delta; + viewport_width = viewport_height * videoAr; } } - if (tool) + viewport_left += pan_x; + viewport_top += pan_y; + viewport_bottom -= pan_y; + + if (tool) { + tool->SetClientSize(client_w, client_h); tool->SetDisplayArea(viewport_left / scale_factor, viewport_top / scale_factor, viewport_width / scale_factor, viewport_height / scale_factor); - + } Render(); } @@ -327,7 +344,7 @@ void VideoDisplay::UpdateSize() { if (!provider || !IsShownOnScreen()) return; videoSize.Set(provider->GetWidth(), provider->GetHeight()); - videoSize *= zoomValue; + videoSize *= windowZoomValue; if (con->videoController->GetAspectRatioType() != AspectRatio::Default) videoSize.SetWidth(videoSize.GetHeight() * con->videoController->GetAspectRatioValue()); @@ -336,10 +353,12 @@ void VideoDisplay::UpdateSize() { wxWindow *top = GetParent(); while (!top->IsTopLevel()) top = top->GetParent(); - wxSize cs = GetClientSize(); + wxSize oldClientSize = GetClientSize(); + double csAr = (double)oldClientSize.GetWidth() / (double)oldClientSize.GetHeight(); + wxSize newClientSize = wxSize(std::lround(provider->GetHeight() * csAr), provider->GetHeight()) * windowZoomValue / scale_factor; wxSize oldSize = top->GetSize(); - top->SetSize(top->GetSize() + videoSize / scale_factor - cs); - SetClientSize(cs + top->GetSize() - oldSize); + top->SetSize(oldSize + (newClientSize - oldClientSize)); + SetClientSize(oldClientSize + (top->GetSize() - oldSize)); } else { SetMinClientSize(videoSize / scale_factor); @@ -347,17 +366,23 @@ void VideoDisplay::UpdateSize() { GetGrandParent()->Layout(); } + videoSize *= videoZoomValue; PositionVideo(); } void VideoDisplay::OnSizeEvent(wxSizeEvent &event) { if (freeSize) { - videoSize = GetClientSize() * scale_factor; - PositionVideo(); - zoomValue = double(viewport_height) / con->project->VideoProvider()->GetHeight(); - zoomBox->ChangeValue(fmt_wx("%g%%", zoomValue * 100.)); - con->ass->Properties.video_zoom = zoomValue; + /* If the video is not moved */ + if (videoZoomValue == 1.0f && pan_x == 0 && pan_y == 0) + videoSize = GetClientSize() * scale_factor; + /* If the video is moving, we only need to update the size in this case */ + else if (videoSize.GetWidth() == 0 && videoSize.GetHeight() == 0) + videoSize = GetClientSize() * videoZoomValue * scale_factor; + windowZoomValue = double(GetClientSize().GetHeight() * scale_factor) / con->project->VideoProvider()->GetHeight(); + zoomBox->ChangeValue(fmt_wx("%g%%", windowZoomValue * 100.)); + con->ass->Properties.video_zoom = windowZoomValue; + UpdateSize(); } else { PositionVideo(); @@ -370,6 +395,20 @@ void VideoDisplay::OnMouseEvent(wxMouseEvent& event) { last_mouse_pos = mouse_pos = event.GetPosition(); + if (event.GetButton() == wxMOUSE_BTN_MIDDLE) { + if ((panning = event.ButtonDown())) + pan_last_pos = event.GetPosition(); + } + if (panning && event.Dragging()) { + pan_x += event.GetX() - pan_last_pos.X(); + pan_y += event.GetY() - pan_last_pos.Y(); + pan_last_pos = event.GetPosition(); + + PositionVideo(); + } + + /// + if (tool) tool->OnMouseEvent(event); } @@ -382,8 +421,16 @@ void VideoDisplay::OnMouseLeave(wxMouseEvent& event) { void VideoDisplay::OnMouseWheel(wxMouseEvent& event) { if (int wheel = event.GetWheelRotation()) { - if (ForwardMouseWheelEvent(this, event)) - SetZoom(zoomValue + .125 * (wheel / event.GetWheelDelta())); + if (ForwardMouseWheelEvent(this, event) && !OPT_GET("Video/Disable Scroll Zoom")->GetBool()) { + if (OPT_GET("Video/Reverse Zoom")->GetBool()) { + wheel = -wheel; + } + if (event.ControlDown() == OPT_GET("Video/Default to Video Zoom")->GetBool()) { + SetWindowZoom(windowZoomValue + .125 * (wheel / event.GetWheelDelta())); + } else { + SetVideoZoom(wheel / event.GetWheelDelta()); + } + } } } @@ -397,22 +444,66 @@ void VideoDisplay::OnKeyDown(wxKeyEvent &event) { hotkey::check("Video", con, event); } -void VideoDisplay::SetZoom(double value) { +void VideoDisplay::ResetPan() { + pan_x = pan_y = 0; + videoZoomValue = 1; + UpdateSize(); + PositionVideo(); +} + +void VideoDisplay::SetWindowZoom(double value) { if (value == 0) return; - zoomValue = std::max(value, .125); - size_t selIndex = zoomValue / .125 - 1; + value = std::max(value, .125); + pan_x *= value / windowZoomValue; + pan_y *= value / windowZoomValue; + windowZoomValue = value; + size_t selIndex = windowZoomValue / .125 - 1; if (selIndex < zoomBox->GetCount()) zoomBox->SetSelection(selIndex); - zoomBox->ChangeValue(fmt_wx("%g%%", zoomValue * 100.)); - con->ass->Properties.video_zoom = zoomValue; + zoomBox->ChangeValue(fmt_wx("%g%%", windowZoomValue * 100.)); + con->ass->Properties.video_zoom = windowZoomValue; + UpdateSize(); +} + +void VideoDisplay::SetVideoZoom(int step) { + if (step == 0) return; + double newVideoZoom = videoZoomValue + (.125 * step) * videoZoomValue; + if (newVideoZoom < 0.125 || newVideoZoom > 10.0) + return; + + // With the current blackbox algorithm in PositionVideo(), viewport_{width,height} could go negative. Stop that here + wxSize cs = GetClientSize(); + wxSize videoNewSize = videoSize * (newVideoZoom / videoZoomValue); + float windowAR = (float)cs.GetWidth() / cs.GetHeight(); + float videoAR = (float)videoNewSize.GetWidth() / videoNewSize.GetHeight(); + if (windowAR < videoAR) { + int delta = cs.GetHeight() - cs.GetWidth() / videoAR; + if (videoNewSize.GetHeight() - delta < 0) + return; + } + + // Mouse coordinates, relative to the video, at the current zoom level + Vector2D mp = last_mouse_pos - Vector2D(viewport_left, viewport_top) / scale_factor; + + // The video size will change by this many pixels + int pixelChangeW = std::lround(videoSize.GetWidth() * (newVideoZoom / videoZoomValue - 1.0)); + int pixelChangeH = std::lround(videoSize.GetHeight() * (newVideoZoom / videoZoomValue - 1.0)); + + AsyncVideoProvider *provider = con->project->VideoProvider(); + double arfactor = (double) provider->GetHeight() * (double) videoSize.GetWidth() / (double) provider->GetWidth() / (double) videoSize.GetHeight(); + + pan_x -= pixelChangeW * (mp.X() / videoSize.GetWidth() * arfactor); + pan_y -= pixelChangeH * (mp.Y() / videoSize.GetHeight()); + + videoZoomValue = newVideoZoom; UpdateSize(); } void VideoDisplay::SetZoomFromBox(wxCommandEvent &) { int sel = zoomBox->GetSelection(); if (sel != wxNOT_FOUND) { - zoomValue = (sel + 1) * .125; - con->ass->Properties.video_zoom = zoomValue; + windowZoomValue = (sel + 1) * .125; + con->ass->Properties.video_zoom = windowZoomValue; UpdateSize(); } } @@ -424,7 +515,7 @@ void VideoDisplay::SetZoomFromBoxText(wxCommandEvent &) { double value; if (strValue.ToDouble(&value)) - SetZoom(value / 100.); + SetWindowZoom(value / 100.); } void VideoDisplay::SetTool(std::unique_ptr new_tool) { diff --git a/src/video_display.h b/src/video_display.h index 62113e5534..fe64557ee1 100644 --- a/src/video_display.h +++ b/src/video_display.h @@ -84,8 +84,19 @@ class VideoDisplay final : public wxGLCanvas { /// The height of the video in screen pixels int viewport_height = 0; - /// The current zoom level, where 1.0 = 100% - double zoomValue; + /// The current window zoom level, where 1.0 = 100% + double windowZoomValue; + /// The current video zoom level, where 1.0 = 100% relative to the display window size + double videoZoomValue; + + /// The last position of the mouse, when dragging + Vector2D pan_last_pos; + /// True if middle mouse button is down, and we should update pan_{x,y} + bool panning = false; + /// The current video pan offset width + int pan_x = 0; + /// The current video pan offset height + int pan_y = 0; /// The video renderer std::unique_ptr videoOut; @@ -160,9 +171,13 @@ class VideoDisplay final : public wxGLCanvas { /// @brief Set the zoom level /// @param value The new zoom level - void SetZoom(double value); + void SetWindowZoom(double value); + void SetVideoZoom(int step); /// @brief Get the current zoom level - double GetZoom() const { return zoomValue; } + double GetZoom() const { return windowZoomValue; } + + /// @brief Reset the video pan + void ResetPan(); /// Get the last seen position of the mouse in script coordinates Vector2D GetMousePosition() const; diff --git a/src/visual_tool.cpp b/src/visual_tool.cpp index 61d3207226..6465fe2484 100644 --- a/src/visual_tool.cpp +++ b/src/visual_tool.cpp @@ -132,6 +132,10 @@ AssDialogue* VisualToolBase::GetActiveDialogueLine() { return nullptr; } +void VisualToolBase::SetClientSize(int w, int h) { + client_size = Vector2D(w, h); +} + void VisualToolBase::SetDisplayArea(int x, int y, int w, int h) { if (x == video_pos.X() && y == video_pos.Y() && w == video_res.X() && h == video_res.Y()) return; diff --git a/src/visual_tool.h b/src/visual_tool.h index 72eec42249..15d0b007dc 100644 --- a/src/visual_tool.h +++ b/src/visual_tool.h @@ -104,6 +104,7 @@ class VisualToolBase { Vector2D script_res; ///< Script resolution Vector2D video_pos; ///< Top-left corner of the video in the display area Vector2D video_res; ///< Video resolution + Vector2D client_size; ///< The size of the display area const agi::OptionValue *highlight_color_primary_opt; const agi::OptionValue *highlight_color_secondary_opt; @@ -144,6 +145,7 @@ class VisualToolBase { // Stuff called by VideoDisplay virtual void OnMouseEvent(wxMouseEvent &event)=0; virtual void Draw()=0; + virtual void SetClientSize(int w, int h); virtual void SetDisplayArea(int x, int y, int w, int h); virtual void SetToolbar(wxToolBar *) { } virtual ~VisualToolBase() = default; diff --git a/src/visual_tool_cross.cpp b/src/visual_tool_cross.cpp index ffbf5329b3..ae82be7750 100644 --- a/src/visual_tool_cross.cpp +++ b/src/visual_tool_cross.cpp @@ -70,9 +70,9 @@ void VisualToolCross::Draw() { gl.SetLineColour(*wxWHITE, 1.0, 1); float lines[] = { 0.f, mouse_pos.Y(), - video_res.X() + video_pos.X() * 2, mouse_pos.Y(), + client_size.X(), mouse_pos.Y(), mouse_pos.X(), 0.f, - mouse_pos.X(), video_res.Y() + video_pos.Y() * 2 + mouse_pos.X(), client_size.Y(), }; gl.DrawLines(2, lines, 4); gl.ClearInvert(); @@ -87,12 +87,12 @@ void VisualToolCross::Draw() { // Place the text in the corner of the cross closest to the center of the video int dx = mouse_pos.X(); int dy = mouse_pos.Y(); - if (dx > video_res.X() / 2) + if (dx > client_size.X() / 2) dx -= tw + 4; else dx += 4; - if (dy < video_res.Y() / 2) + if (dy < client_size.Y() / 2) dy += 3; else dy -= th + 3;