FSceneViewport::OnMouseButtonUp releases capture even if it shouldn't

Hello!

It seems there’s a bug in FSceneViewport::OnMouseButtonUp related to mouse capture. The function always releases mouse capture when the cursor is visible. However, in some cases, it’s valid to maintain permanent capture even when the cursor is visible.

Although OnMouseButtonUp is a virtual function, it relies heavily on private members to manage the viewport’s state. This makes it extremely difficult to override or work around the behavior without modifying the engine source directly.

I’ve attached a diff below that shows a fix for the issue.

Currently, we’re using the stock engine - do you have any suggestions or workarounds that wouldn’t require engine modifications?

Thanks,

Marcell

`— C:/Users/zen/AppData/Local/Temp/SceneViewport.cpp-rev4166.svn004.tmp.cpp Thu May 25 16:46:39 2023
+++ C:/Users/zen/AppData/Local/Temp/SceneViewport.cpp-rev4167.svn004.tmp.cpp Tue Aug 1 09:14:12 2023
[Content removed]14 @@ FReply FSceneViewport::OnMouseButtonUp( const FGeo

  • bReleaseMouseCapture = true;
  • UE_LOG(LogViewport, Verbose, TEXT(“Releasing Mouse Capture; Cursor is visible”));
    +// >>> fix: with permanent capture we should NOT release the capture
  • bool bIsInPermanentCapture = ViewportClient->GetMouseCaptureMode() == EMouseCaptureMode::CapturePermanently ||
  • ViewportClient->GetMouseCaptureMode() == EMouseCaptureMode::CapturePermanently_IncludingInitialMouseDown;
  • if (!bIsInPermanentCapture)
  • {
  • bReleaseMouseCapture = true;
  • UE_LOG(LogViewport, Verbose, TEXT(“Releasing Mouse Capture; Cursor is visible”));
  • }
  • else
  • {
  • bReleaseMouseCapture = false;
  • UE_LOG(LogViewport, Verbose, TEXT(“Not releasing Mouse Capture; The cursor is visible, but Permanent capture is set”));
  • }
    +// <<<`

Steps to Reproduce
Unreal Engine 5, using Common UI plugin and Enhanced Input System

  • Create a CommonActivatableWidget (e.g. MyScreen)
  • override GetDesiredInputConfig function the following way:

TOptional<FUIInputConfig> UMyScreen::GetDesiredInputConfig() const { return FUIInputConfig(ECommonInputMode::Game, EMouseCaptureMode::CapturePermanently, EMouseLockMode::DoNotLock, false); }* Create a UserWidget in blueprint, name it “WB_RootWidget” and add at least one ActivatableWidgetStack child, name it “MainWidgetStack”

  • Create a new Input Action with “Axis 2D” value type, name IA_UpdateView
  • Create (or use an existing) InputMappingContext, add IA_UpdateView and bind it to “Mouse XY 2D-Axis”
  • In your PlayerController set a listener to the IA_UpdateView and print its input when it Triggers
  • In your PlayerController do the following steps in BeginPlay
    • Add the InputMappingContext
    • Construct a widget instance of WB_RootWidget and add it to the viewport
    • Find the MainWidgetStack in the WB_RootWidget instance and Push the MyScreen widget to the stack.
  • Observe the following: the IA_UpdateView is called properly when the cursor is moved, however pressing and releasing the IA_UpdateView is not called any more without holding a mouse button down.

Hi,

Your change does seem reasonable, though I’d be concerned that some projects may rely on the default behavior of releasing the “permanent” capture so it may be a risky change for us. As for a workaround, perhaps you could override OnMouseButtonUp in a derived class and call the base implementation, then restore capture on the resulting FReply before returning it. Capture changes won’t actually be processed until the FReply makes it back to SlateApplication, so you should be able to undo the capture loss before handing the reply onward. You could also try mutating the mouse event before sending it to FSceneViewport::OnMouseButtonUp to make it register as having a mouse button held down, though that seems a bit riskier than just mutating the FReply on the way out of the function.

Best,

Cody

Hi Cody!

Thank you for your response!

From what I can tell, overriding FSceneViewport::OnMouseButtonUp isn’t an ideal solution. Unlike UGameEngine::CreateGameViewport, which does call CreateGameViewport on my custom viewport client class, the editor uses SEditorViewport::Construct and UEditorEngine::GeneratePIEViewportWindow to create a plain FSceneViewport instance. This means my custom scene viewport class is never instantiated during editor testing, so any overridden methods won’t be called.

That said, I was able to work around the issue by implementing a custom input trigger that processes mouse movement input regardless of the mouse capture state.

Regards,

Marcell

Ah, happy to hear you were able to find a workaround! I’ll pass this feedback along in case we want to look into making things more accessible on our end.

Best,

Cody