View Issue Details

IDProjectCategoryView StatusLast Update
0003457NoesisGUIUnrealpublic2024-08-01 16:05
Reporterskokon Assigned Tohcpizzi  
PrioritynormalSeveritymajor 
Status resolvedResolutionfixed 
Product Version3.2.3 
Target Version3.2.5Fixed in Version3.2.5 
Summary0003457: Handling Focus and Input in UE5
Description

Hello,

I'm using a custom drag behavior implemented in C++ and when I drag an item and then press the Tab key, the focus doesn't behave as expected. The UI retains focus, and I need to click outside the widget and press Tab again for the game to receive the action, and the keyboard input flushs

I've also tried to set all the xaml elements focusable variable to be false.

Thank you.

Steps To Reproduce

1- Create a drag and drop behaviour in unreal engine and WPF, implement a drag and drop behavior in C++.

2- Create input keys for movement using enhanced input component

3- When the widget is visible, click on the slot or try to drag and drop item, then try to walk using the assigned keys (WASD) note that movement isn't possible until you click outside the slot widget

PlatformWindows

Activities

hcpizzi

hcpizzi

2024-07-04 13:21

developer   ~0009738

Hi,

I've attached a patch that I've been testing. With it I've been able to drag and drop the items and have the PlayerController receive Enhanced Input events triggered by keys without having to move the cursor in a modified version of our Inventory sample. It also resolves the weird behavior after right clicking.

Please, note the changes to the DragItemBehavior itself, in case yours is based on our sample. Marking the mouse events as handled is necessary to correctly mark the corresponding Unreal events as handled or unhandled.

Let us know if this fixes your issue.

DragBehavior.patch (6,457 bytes)   
Index: Plugins/NoesisGUI/Source/NoesisRuntime/Classes/NoesisInstance.h
===================================================================
--- Plugins/NoesisGUI/Source/NoesisRuntime/Classes/NoesisInstance.h	(revision 14126)
+++ Plugins/NoesisGUI/Source/NoesisRuntime/Classes/NoesisInstance.h	(working copy)
@@ -216,6 +216,7 @@
 	void OnPreviewLostKeyboardFocus(Noesis::BaseComponent* Component, const Noesis::KeyboardFocusChangedEventArgs& Args);
 
 	bool HitTest(FVector2D Position) const;
+	bool HasMouseCapture() const;
 
 	void TermInstance();
 
@@ -264,6 +265,8 @@
 	virtual FReply NativeOnTouchEnded(const FGeometry& MyGeometry, const FPointerEvent& InTouchEvent) override;
 	virtual FCursorReply NativeOnCursorQuery(const FGeometry& MyGeometry, const FPointerEvent& CursorEvent) override;
 	virtual FReply NativeOnMouseButtonDoubleClick(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
+	virtual void NativeOnMouseEnter(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) override;
+	virtual void NativeOnMouseLeave(const FPointerEvent& InMouseEvent) override;
 	virtual bool NativeSupportsKeyboardFocus() const override;
 	virtual void NativeConstruct() override;
 	virtual void NativeDestruct() override;
Index: Plugins/NoesisGUI/Source/NoesisRuntime/Private/NoesisInstance.cpp
===================================================================
--- Plugins/NoesisGUI/Source/NoesisRuntime/Private/NoesisInstance.cpp	(revision 14126)
+++ Plugins/NoesisGUI/Source/NoesisRuntime/Private/NoesisInstance.cpp	(working copy)
@@ -1162,6 +1162,18 @@
 	return HitTester.Hit != nullptr;
 }
 
+bool UNoesisInstance::HasMouseCapture() const
+{
+	if (XamlView)
+	{
+		auto Root = XamlView->GetContent();
+		auto Mouse = Root != nullptr ? Root->GetMouse() : nullptr;
+		return Mouse != nullptr && Mouse->GetCaptured() != nullptr;
+	}
+
+	return false;
+}
+
 void UNoesisInstance::TermInstance()
 {
 	if (XamlView)
@@ -1670,7 +1682,7 @@
 
 			if (Handled)
 			{
-				return FReply::Handled().PreventThrottling().CaptureMouse(MyWidget.Pin().ToSharedRef());
+				return FReply::Handled().PreventThrottling().CaptureMouse(TakeWidget());
 			}
 		}
 		else
@@ -1683,7 +1695,16 @@
 
 			if (Handled && Hit)
 			{
-				return FReply::Handled().PreventThrottling();
+				auto Reply = FReply::Handled().PreventThrottling();
+				if (HasMouseCapture())
+				{
+					Reply.CaptureMouse(TakeWidget());
+				}
+				else
+				{
+					Reply.ReleaseMouseCapture();
+				}
+				return Reply;
 			}
 		}
 	}
@@ -1716,7 +1737,16 @@
 
 			if (Handled && Hit)
 			{
-				return FReply::Handled().PreventThrottling();
+				auto Reply = FReply::Handled().PreventThrottling();
+				if (HasMouseCapture())
+				{
+					Reply.CaptureMouse(TakeWidget());
+				}
+				else
+				{
+					Reply.ReleaseMouseCapture();
+				}
+				return Reply;
 			}
 		}
 	}
@@ -1736,7 +1766,16 @@
 
 		if (Handled && Hit)
 		{
-			return FReply::Handled().PreventThrottling();
+			auto Reply = FReply::Handled().PreventThrottling();
+			if (HasMouseCapture())
+			{
+				Reply.CaptureMouse(TakeWidget());
+			}
+			else
+			{
+				Reply.ReleaseMouseCapture();
+			}
+			return Reply;
 		}
 	}
 
@@ -1756,7 +1795,16 @@
 
 		if (Handled && Hit)
 		{
-			return FReply::Handled().PreventThrottling();
+			auto Reply = FReply::Handled().PreventThrottling();
+			if (HasMouseCapture())
+			{
+				Reply.CaptureMouse(TakeWidget());
+			}
+			else
+			{
+				Reply.ReleaseMouseCapture();
+			}
+			return Reply;
 		}
 	}
 
@@ -1776,7 +1824,16 @@
 
 		if (Handled && Hit)
 		{
-			return FReply::Handled().PreventThrottling();
+			auto Reply = FReply::Handled().PreventThrottling();
+			if (HasMouseCapture())
+			{
+				Reply.CaptureMouse(TakeWidget());
+			}
+			else
+			{
+				Reply.ReleaseMouseCapture();
+			}
+			return Reply;
 		}
 	}
 
@@ -1796,7 +1853,16 @@
 
 		if (Handled && Hit)
 		{
-			return FReply::Handled().PreventThrottling();
+			auto Reply = FReply::Handled().PreventThrottling();
+			if (HasMouseCapture())
+			{
+				Reply.CaptureMouse(TakeWidget());
+			}
+			else
+			{
+				Reply.ReleaseMouseCapture();
+			}
+			return Reply;
 		}
 	}
 
@@ -1816,7 +1882,16 @@
 
 		if (Handled && Hit)
 		{
-			return FReply::Handled().PreventThrottling();
+			auto Reply = FReply::Handled().PreventThrottling();
+			if (HasMouseCapture())
+			{
+				Reply.CaptureMouse(TakeWidget());
+			}
+			else
+			{
+				Reply.ReleaseMouseCapture();
+			}
+			return Reply;
 		}
 	}
 
@@ -1841,7 +1916,16 @@
 
 		if (Handled && Hit)
 		{
-			return FReply::Handled().PreventThrottling();
+			auto Reply = FReply::Handled().PreventThrottling();
+			if (HasMouseCapture())
+			{
+				Reply.CaptureMouse(TakeWidget());
+			}
+			else
+			{
+				Reply.ReleaseMouseCapture();
+			}
+			return Reply;
 		}
 	}
 
@@ -1848,6 +1932,22 @@
 	return FReply::Unhandled();
 }
 
+void UNoesisInstance::NativeOnMouseEnter(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent)
+{
+	if (XamlView)
+	{
+		XamlView->Activate();
+	}
+}
+
+void UNoesisInstance::NativeOnMouseLeave(const FPointerEvent& InMouseEvent)
+{
+	if (XamlView && !HasMouseCapture())
+	{
+		XamlView->Deactivate();
+	}
+}
+
 bool UNoesisInstance::NativeSupportsKeyboardFocus() const
 {
 	// SObjectWidget::OnKeyDown calls UNoesisInstance::NativeOnKeyDown. There we set SupportsKeyboardFocus = !EnableActions.
Index: Source/Inventory/DragItemBehavior.cpp
===================================================================
--- Source/Inventory/DragItemBehavior.cpp	(revision 14126)
+++ Source/Inventory/DragItemBehavior.cpp	(working copy)
@@ -105,9 +105,10 @@
 }
 
 ////////////////////////////////////////////////////////////////////////////////////////////////////
-void DragItemBehavior::OnMouseUp(BaseComponent*, const MouseButtonEventArgs&)
+void DragItemBehavior::OnMouseUp(BaseComponent*, const MouseButtonEventArgs& e)
 {
     _mouseClicked = false;
+    e.handled = true;
 }
 
 ////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -143,6 +144,8 @@
             });
         }
     }
+
+    e.handled = true;
 }
 
 ////////////////////////////////////////////////////////////////////////////////////////////////////
DragBehavior.patch (6,457 bytes)   
skokon

skokon

2024-07-04 21:35

reporter   ~0009741

Hello,

Thanks for the update.

I've tried it, but I'm still having some issues.

When I tried to drag the slot, it looked like the click was not registered. So, I debugged DragItemBehavior. For some reason, when I hold the left click, the OnMouseUp event is called, which causes the problem.

However, clicking twice really fast and holding the second click allows me to drag the item. When I release the mouse button, it sets the slot, but it's like the left mouse button is stuck. Unreal recognizes the left mouse button as being held down.

Note that I set the input mode to game and UI without setting the widget in focus. Setting the widget to "widget to focus" will cause the Tab key to switch focus on the elements, marking the borders white.

I also tried to debug and see why OnMouseUp is called. It looks like OnMouseLeave is called on the Slate object widget, which calls NativeOnMouseLeave. This is overridden in NoesisInstance and calls XamlView->Deactivate(), which causes OnMouseUp to be called.

I tried to comment out XamlView->Deactivate();, which fixes that issue but introduced more issues.

I also tried to comment out e.handled = true; in OnMouseUp in DragItemBehavior, but it didn't make any difference.

hcpizzi

hcpizzi

2024-07-05 12:10

developer   ~0009742

OK, I've made a few changes and have also added a lot of log warnings (so they pop out in the log output window). Could you give this new patch a try and send us the output logs?

Thank you!

DragBehavior-2.patch (10,439 bytes)   
Index: Plugins/NoesisGUI/Source/NoesisRuntime/Private/NoesisInstance.cpp
===================================================================
--- Plugins/NoesisGUI/Source/NoesisRuntime/Private/NoesisInstance.cpp	(revision 14126)
+++ Plugins/NoesisGUI/Source/NoesisRuntime/Private/NoesisInstance.cpp	(working copy)
@@ -1162,6 +1162,18 @@
 	return HitTester.Hit != nullptr;
 }
 
+bool UNoesisInstance::HasMouseCapture() const
+{
+	if (XamlView)
+	{
+		auto Root = XamlView->GetContent();
+		auto Mouse = Root != nullptr ? Root->GetMouse() : nullptr;
+		return Mouse != nullptr && Mouse->GetCaptured() != nullptr;
+	}
+
+	return false;
+}
+
 void UNoesisInstance::TermInstance()
 {
 	if (XamlView)
@@ -1465,10 +1477,15 @@
 	{
 		TCHAR Character = CharacterEvent.GetCharacter();
 
-		XamlView->Char(CharCast<char>(Character));
+		bool Handled = XamlView->Char(CharCast<char>(Character));
+
+		if (Handled)
+		{
+			return FReply::Handled();
+		}
 	}
 
-	return Super::NativeOnKeyChar(MyGeometry, CharacterEvent);
+	return FReply::Unhandled();
 }
 
 static TMap<FKey, Noesis::Key> InitKeyMap()
@@ -1595,11 +1612,16 @@
 		Noesis::Key* NoesisKey = KeyToNoesisKey(Key);
 		if (NoesisKey != nullptr)
 		{
-			XamlView->KeyDown(*NoesisKey);
+			bool Handled = XamlView->KeyDown(*NoesisKey);
+
+			if (Handled)
+			{
+				return FReply::Handled();
+			}
 		}
 	}
 
-	return Super::NativeOnKeyDown(MyGeometry, KeyEvent);
+	return FReply::Unhandled();
 }
 
 FReply UNoesisInstance::NativeOnKeyUp(const FGeometry& MyGeometry, const FKeyEvent& KeyEvent)
@@ -1611,31 +1633,20 @@
 		Noesis::Key* NoesisKey = KeyToNoesisKey(Key);
 		if (NoesisKey != nullptr)
 		{
-			XamlView->KeyUp(*NoesisKey);
+			bool Handled = XamlView->KeyUp(*NoesisKey);
+
+			if (Handled)
+			{
+				return FReply::Handled();
+			}
 		}
 	}
 
-	return Super::NativeOnKeyUp(MyGeometry, KeyEvent);
+	return FReply::Unhandled();
 }
 
 FReply UNoesisInstance::NativeOnAnalogValueChanged(const FGeometry& MyGeometry, const FAnalogInputEvent& InAnalogEvent)
 {
-	SCOPE_CYCLE_COUNTER(STAT_NoesisInstance_OnAnalogValueChanged);
-	if (XamlView)
-	{
-		if (FMath::Abs(InAnalogEvent.GetAnalogValue()) > 0.25f)
-		{
-			if (InAnalogEvent.GetKey() == EKeys::Gamepad_RightX)
-			{
-				XamlView->HScroll(InAnalogEvent.GetAnalogValue());
-			}
-			else if (InAnalogEvent.GetKey() == EKeys::Gamepad_RightY)
-			{
-				XamlView->Scroll(InAnalogEvent.GetAnalogValue());
-			}
-		}
-	}
-
 	return Super::NativeOnAnalogValueChanged(MyGeometry, InAnalogEvent);
 }
 
@@ -1670,7 +1681,7 @@
 
 			if (Handled)
 			{
-				return FReply::Handled().PreventThrottling().CaptureMouse(MyWidget.Pin().ToSharedRef());
+				return FReply::Handled().PreventThrottling().CaptureMouse(TakeWidget());
 			}
 		}
 		else
@@ -1679,15 +1690,27 @@
 			bool Hit = HitTest(Position);
 
 			Noesis::MouseButton MouseButton = GetNoesisMouseButton(MouseEvent.GetEffectingButton());
-			bool Handled = XamlView->MouseButtonDown(FPlatformMath::RoundToInt(Position.X), FPlatformMath::RoundToInt(Position.Y), MouseButton);
+			bool Handled = XamlView->MouseButtonDown(FPlatformMath::RoundToInt(Position.X), FPlatformMath::RoundToInt(Position.Y), MouseButton) || HasMouseCapture();
 
 			if (Handled && Hit)
 			{
-				return FReply::Handled().PreventThrottling();
+				auto Reply = FReply::Handled().PreventThrottling();
+				if (HasMouseCapture())
+				{
+					UE_LOG(LogNoesis, Warning, TEXT("MouseButtonDown CaptureMouse"));
+					Reply.CaptureMouse(TakeWidget());
+				}
+				else
+				{
+					UE_LOG(LogNoesis, Warning, TEXT("MouseButtonDown RemeaseMouseCapture"));
+					Reply.ReleaseMouseCapture();
+				}
+				return Reply;
 			}
 		}
 	}
 
+	UE_LOG(LogNoesis, Warning, TEXT("MouseButtonDown Unhandled"));
 	return FReply::Unhandled();
 }
 
@@ -1712,15 +1735,27 @@
 			bool Hit = HitTest(Position);
 
 			Noesis::MouseButton MouseButton = GetNoesisMouseButton(MouseEvent.GetEffectingButton());
-			bool Handled = XamlView->MouseButtonUp(FPlatformMath::RoundToInt(Position.X), FPlatformMath::RoundToInt(Position.Y), MouseButton);
+			bool Handled = XamlView->MouseButtonUp(FPlatformMath::RoundToInt(Position.X), FPlatformMath::RoundToInt(Position.Y), MouseButton) || HasMouseCapture();
 
 			if (Handled && Hit)
 			{
-				return FReply::Handled().PreventThrottling();
+				auto Reply = FReply::Handled().PreventThrottling();
+				if (HasMouseCapture())
+				{
+					UE_LOG(LogNoesis, Warning, TEXT("MouseButtonUp CaptureMouse"));
+					Reply.CaptureMouse(TakeWidget());
+				}
+				else
+				{
+					UE_LOG(LogNoesis, Warning, TEXT("MouseButtonUp ReleaseMouseCapture"));
+					Reply.ReleaseMouseCapture();
+				}
+				return Reply;
 			}
 		}
 	}
 
+	UE_LOG(LogNoesis, Warning, TEXT("MouseButtonUp Unhandled"));
 	return FReply::Unhandled();
 }
 
@@ -1732,14 +1767,29 @@
 		FVector2D Position = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition()) * MyGeometry.Scale;
 		bool Hit = HitTest(Position);
 
-		bool Handled = XamlView->MouseMove(FPlatformMath::RoundToInt(Position.X), FPlatformMath::RoundToInt(Position.Y));
+		bool Handled = XamlView->MouseMove(FPlatformMath::RoundToInt(Position.X), FPlatformMath::RoundToInt(Position.Y)) || HasMouseCapture();
 
 		if (Handled && Hit)
 		{
-			return FReply::Handled().PreventThrottling();
+			auto Reply = FReply::Handled().PreventThrottling();
+			if (HasMouseCapture())
+			{
+				UE_LOG(LogNoesis, Warning, TEXT("MouseMove CaptureMouse"));
+				Reply.CaptureMouse(TakeWidget());
+			}
+			else
+			{
+				UE_LOG(LogNoesis, Warning, TEXT("MouseMove ReleaseMOuseCapture"));
+				Reply.ReleaseMouseCapture();
+			}
+			return Reply;
 		}
 	}
 
+	if (!MouseEvent.GetCursorDelta().IsZero())
+	{
+		UE_LOG(LogNoesis, Warning, TEXT("MouseMove Unhandled"));
+	}
 	return FReply::Unhandled();
 }
 
@@ -1756,7 +1806,16 @@
 
 		if (Handled && Hit)
 		{
-			return FReply::Handled().PreventThrottling();
+			auto Reply = FReply::Handled().PreventThrottling();
+			if (HasMouseCapture())
+			{
+				Reply.CaptureMouse(TakeWidget());
+			}
+			else
+			{
+				Reply.ReleaseMouseCapture();
+			}
+			return Reply;
 		}
 	}
 
@@ -1776,7 +1835,16 @@
 
 		if (Handled && Hit)
 		{
-			return FReply::Handled().PreventThrottling();
+			auto Reply = FReply::Handled().PreventThrottling();
+			if (HasMouseCapture())
+			{
+				Reply.CaptureMouse(TakeWidget());
+			}
+			else
+			{
+				Reply.ReleaseMouseCapture();
+			}
+			return Reply;
 		}
 	}
 
@@ -1796,7 +1864,16 @@
 
 		if (Handled && Hit)
 		{
-			return FReply::Handled().PreventThrottling();
+			auto Reply = FReply::Handled().PreventThrottling();
+			if (HasMouseCapture())
+			{
+				Reply.CaptureMouse(TakeWidget());
+			}
+			else
+			{
+				Reply.ReleaseMouseCapture();
+			}
+			return Reply;
 		}
 	}
 
@@ -1816,7 +1893,16 @@
 
 		if (Handled && Hit)
 		{
-			return FReply::Handled().PreventThrottling();
+			auto Reply = FReply::Handled().PreventThrottling();
+			if (HasMouseCapture())
+			{
+				Reply.CaptureMouse(TakeWidget());
+			}
+			else
+			{
+				Reply.ReleaseMouseCapture();
+			}
+			return Reply;
 		}
 	}
 
@@ -1841,7 +1927,16 @@
 
 		if (Handled && Hit)
 		{
-			return FReply::Handled().PreventThrottling();
+			auto Reply = FReply::Handled().PreventThrottling();
+			if (HasMouseCapture())
+			{
+				Reply.CaptureMouse(TakeWidget());
+			}
+			else
+			{
+				Reply.ReleaseMouseCapture();
+			}
+			return Reply;
 		}
 	}
 
@@ -1848,6 +1943,26 @@
 	return FReply::Unhandled();
 }
 
+void UNoesisInstance::NativeOnMouseEnter(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent)
+{
+	UE_LOG(LogNoesis, Warning, TEXT("MouseEnter"));
+	if (XamlView)
+	{
+		UE_LOG(LogNoesis, Warning, TEXT("Activate View"));
+		XamlView->Activate();
+	}
+}
+
+void UNoesisInstance::NativeOnMouseLeave(const FPointerEvent& InMouseEvent)
+{
+	UE_LOG(LogNoesis, Warning, TEXT("MouseLeave"));
+	if (XamlView && !HasMouseCapture())
+	{
+		UE_LOG(LogNoesis, Warning, TEXT("Deactivate View"));
+		XamlView->Deactivate();
+	}
+}
+
 bool UNoesisInstance::NativeSupportsKeyboardFocus() const
 {
 	// SObjectWidget::OnKeyDown calls UNoesisInstance::NativeOnKeyDown. There we set SupportsKeyboardFocus = !EnableActions.
Index: Source/Inventory/DragItemBehavior.cpp
===================================================================
--- Source/Inventory/DragItemBehavior.cpp	(revision 14126)
+++ Source/Inventory/DragItemBehavior.cpp	(working copy)
@@ -100,14 +100,17 @@
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 void DragItemBehavior::OnMouseDown(BaseComponent*, const MouseButtonEventArgs& e)
 {
+    UE_LOG(LogNoesisInventory, Warning, TEXT("DragItemBehavior::OnMouseDown"));
     _mouseClicked = true;
     e.handled = true;
 }
 
 ////////////////////////////////////////////////////////////////////////////////////////////////////
-void DragItemBehavior::OnMouseUp(BaseComponent*, const MouseButtonEventArgs&)
+void DragItemBehavior::OnMouseUp(BaseComponent*, const MouseButtonEventArgs& e)
 {
+    UE_LOG(LogNoesisInventory, Warning, TEXT("DragItemBehavior::OnMouseUp"));
     _mouseClicked = false;
+    e.handled = true;
 }
 
 ////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -118,6 +121,7 @@
         return;
     }
 
+    UE_LOG(LogNoesisInventory, Warning, TEXT("DragItemBehavior::OnMouseMove"));
     _mouseClicked = false;
 
     FrameworkElement* element = GetAssociatedObject();
@@ -143,6 +147,8 @@
             });
         }
     }
+
+    e.handled = true;
 }
 
 ////////////////////////////////////////////////////////////////////////////////////////////////////
Index: Source/Inventory/InventoryGame.cpp
===================================================================
--- Source/Inventory/InventoryGame.cpp	(revision 14126)
+++ Source/Inventory/InventoryGame.cpp	(working copy)
@@ -11,6 +11,8 @@
 #include "DragItemBehavior.h"
 #include "DropItemBehavior.h"
 
+DEFINE_LOG_CATEGORY(LogNoesisInventory);
+
 class InventoryModule : public FDefaultGameModuleImpl
 {
     virtual void StartupModule() override
DragBehavior-2.patch (10,439 bytes)   
skokon

skokon

2024-07-06 21:34

reporter   ~0009753

Hi, is this patch supposed to be applied after the first patch you sent?

I've tried to apply the patch on both scenarios but its failing

skokon

skokon

2024-07-06 21:59

reporter   ~0009754

Hello again, Thank you for the update

I've manually applied the second patch to a freshly cloned repository.

It’s working now, but there’s a very strange behavior. When I click on the slot and drag it, the input isn’t flushed. However, if I hold "S" and "D" to move the player, then click on the background or anywhere else besides the slot, and then click on the slot to drag it, the input flushes the "S" key but keeps the "D" key active because I’m still holding it. It seems to flush all inputs except the last key held down. Additionally, the Tab key is not captured by the enhanced input component after I click on the slot. I have to click on the background and press Tab again for it to be recognized. This issue seems to affect only the Tab key, as other keys like 1, 2, 3, 4, WASD, jump, and left shift work fine.

Here is the output after reproducing the input flush behavior:

https://pastebin.com/RwTFT9rN

Please note that "OnMouseDown" is called once after I hold the left click and keep holding it.

hcpizzi

hcpizzi

2024-07-09 18:00

developer   ~0009774

Hi,

The issue with the tab key is that the UI is using it. With the second patch I sent you, if the UI handles a key it returns FReply::Handled(), preventing the enhanced input system from receiving the key press events. Even without the patch, FReply::Unhandled() would be returned from our event handler, and the event would reach the enhanced input component, but the UI would still be reacting to the tab presses. If you don't want the UI to react to tab presses, you can disable it by setting the property KeyboardNavigation.TabNavigation="None" at the topmost level. That way the UI won't handle Tab key presses and they will make it to the enhanced input component.

As for the behavior of the other keys, that seems to be the behavior I'd expect. If I press a key on any windows app, and simultaneously press another, only events related to the second pressed key are sent.

skokon

skokon

2024-07-09 18:49

reporter   ~0009778

Hi,

You’re correct that in most Windows apps, pressing a key will process events for that app.
but in FPS games like Call of Duty, Rust, and DayZ, players need the ability to move while looting to avoid getting killed.

Thanks!

skokon

skokon

2024-07-09 18:56

reporter   ~0009779

I also want to note that this issue only occurs if I click on the slot once. It doesn't happen if I drag the slot.

hcpizzi

hcpizzi

2024-07-09 19:33

developer   ~0009781

Sorry, it was only now that I've seen the problem. I could see the events for both keys making it to the enhanced input system before. I'll keep looking at it, it seems to be related to the first MouseButtonDown event, but not returning Handled from that one breaks the drag and drop... I'll have to see exactly where and why the input system is "forgetting" about the other keys.

skokon

skokon

2024-07-09 19:54

reporter   ~0009784

Exactly, that's what I meant. Thank you again!

hcpizzi

hcpizzi

2024-07-10 14:40

developer   ~0009790

Hi again. Here's a new patch, to be applied on top of a clean codebase.

The key states were being cleared when the game viewport lost focus, so I've made it so that mouse events set the focus on it.

I have to do more tests before I'm confident these changes are safe to commit for everyone.

For now, once you're satisfied this does indeed work for your use case, feel free to remove all the log messages so you don't get bombarded constantly by them.

And, once more, please let us know if you still run into any issues with this latest patch.

Thanks!

DragBehavior-3.patch (15,413 bytes)   
Index: Plugins/NoesisGUI/Source/NoesisRuntime/Classes/NoesisInstance.h
===================================================================
--- Plugins/NoesisGUI/Source/NoesisRuntime/Classes/NoesisInstance.h	(revision 14201)
+++ Plugins/NoesisGUI/Source/NoesisRuntime/Classes/NoesisInstance.h	(working copy)
@@ -216,6 +216,7 @@
 	void OnPreviewLostKeyboardFocus(Noesis::BaseComponent* Component, const Noesis::KeyboardFocusChangedEventArgs& Args);
 
 	bool HitTest(FVector2D Position) const;
+	bool HasMouseCapture() const;
 
 	void TermInstance();
 
@@ -264,9 +265,17 @@
 	virtual FReply NativeOnTouchEnded(const FGeometry& MyGeometry, const FPointerEvent& InTouchEvent) override;
 	virtual FCursorReply NativeOnCursorQuery(const FGeometry& MyGeometry, const FPointerEvent& CursorEvent) override;
 	virtual FReply NativeOnMouseButtonDoubleClick(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
+	virtual void NativeOnMouseEnter(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) override;
+	virtual void NativeOnMouseLeave(const FPointerEvent& InMouseEvent) override;
 	virtual bool NativeSupportsKeyboardFocus() const override;
 	virtual void NativeConstruct() override;
 	virtual void NativeDestruct() override;
+	virtual FReply NativeOnFocusReceived(const FGeometry& InGeometry, const FFocusEvent& InFocusEvent) override;
+	virtual void NativeOnFocusLost(const FFocusEvent& InFocusEvent) override;
+	virtual void NativeOnFocusChanging(const FWeakWidgetPath& PreviousFocusPath, const FWidgetPath& NewWidgetPath, const FFocusEvent& InFocusEvent) override;
+	virtual void NativeOnAddedToFocusPath(const FFocusEvent& InFocusEvent) override;
+	virtual void NativeOnRemovedFromFocusPath(const FFocusEvent& InFocusEvent) override;
+	virtual FNavigationReply NativeOnNavigation(const FGeometry& MyGeometry, const FNavigationEvent& InNavigationEvent, const FNavigationReply& InDefaultReply) override;
 	// End of UUserWidget interface
 };
 
Index: Plugins/NoesisGUI/Source/NoesisRuntime/Private/NoesisInstance.cpp
===================================================================
--- Plugins/NoesisGUI/Source/NoesisRuntime/Private/NoesisInstance.cpp	(revision 14201)
+++ Plugins/NoesisGUI/Source/NoesisRuntime/Private/NoesisInstance.cpp	(working copy)
@@ -1162,6 +1162,18 @@
 	return HitTester.Hit != nullptr;
 }
 
+bool UNoesisInstance::HasMouseCapture() const
+{
+	if (XamlView)
+	{
+		auto Root = XamlView->GetContent();
+		auto Mouse = Root != nullptr ? Root->GetMouse() : nullptr;
+		return Mouse != nullptr && Mouse->GetCaptured() != nullptr;
+	}
+
+	return false;
+}
+
 void UNoesisInstance::TermInstance()
 {
 	if (XamlView)
@@ -1465,10 +1477,17 @@
 	{
 		TCHAR Character = CharacterEvent.GetCharacter();
 
-		XamlView->Char(CharCast<char>(Character));
+		bool Handled = XamlView->Char(CharCast<char>(Character));
+
+		if (Handled)
+		{
+			UE_LOG(LogNoesis, Warning, TEXT("Char handled %c"), Character);
+			return FReply::Handled();
+		}
 	}
 
-	return Super::NativeOnKeyChar(MyGeometry, CharacterEvent);
+	UE_LOG(LogNoesis, Warning, TEXT("Char unhandled %c"), CharacterEvent.GetCharacter());
+	return FReply::Unhandled();
 }
 
 static TMap<FKey, Noesis::Key> InitKeyMap()
@@ -1595,11 +1614,18 @@
 		Noesis::Key* NoesisKey = KeyToNoesisKey(Key);
 		if (NoesisKey != nullptr)
 		{
-			XamlView->KeyDown(*NoesisKey);
+			bool Handled = XamlView->KeyDown(*NoesisKey);
+
+			if (Handled)
+			{
+				UE_LOG(LogNoesis, Warning, TEXT("KeyDown handled %s"), *KeyEvent.GetKey().ToString());
+				return FReply::Handled();
+			}
 		}
 	}
 
-	return Super::NativeOnKeyDown(MyGeometry, KeyEvent);
+	UE_LOG(LogNoesis, Warning, TEXT("KeyDown unhandled %s"), *KeyEvent.GetKey().ToString());
+	return FReply::Unhandled();
 }
 
 FReply UNoesisInstance::NativeOnKeyUp(const FGeometry& MyGeometry, const FKeyEvent& KeyEvent)
@@ -1611,31 +1637,22 @@
 		Noesis::Key* NoesisKey = KeyToNoesisKey(Key);
 		if (NoesisKey != nullptr)
 		{
-			XamlView->KeyUp(*NoesisKey);
+			bool Handled = XamlView->KeyUp(*NoesisKey);
+
+			if (Handled)
+			{
+				UE_LOG(LogNoesis, Warning, TEXT("KeyUp handled %s"), *KeyEvent.GetKey().ToString());
+				return FReply::Handled();
+			}
 		}
 	}
 
-	return Super::NativeOnKeyUp(MyGeometry, KeyEvent);
+	UE_LOG(LogNoesis, Warning, TEXT("KeyUp unhandled %s"), *KeyEvent.GetKey().ToString());
+	return FReply::Unhandled();
 }
 
 FReply UNoesisInstance::NativeOnAnalogValueChanged(const FGeometry& MyGeometry, const FAnalogInputEvent& InAnalogEvent)
 {
-	SCOPE_CYCLE_COUNTER(STAT_NoesisInstance_OnAnalogValueChanged);
-	if (XamlView)
-	{
-		if (FMath::Abs(InAnalogEvent.GetAnalogValue()) > 0.25f)
-		{
-			if (InAnalogEvent.GetKey() == EKeys::Gamepad_RightX)
-			{
-				XamlView->HScroll(InAnalogEvent.GetAnalogValue());
-			}
-			else if (InAnalogEvent.GetKey() == EKeys::Gamepad_RightY)
-			{
-				XamlView->Scroll(InAnalogEvent.GetAnalogValue());
-			}
-		}
-	}
-
 	return Super::NativeOnAnalogValueChanged(MyGeometry, InAnalogEvent);
 }
 
@@ -1670,7 +1687,7 @@
 
 			if (Handled)
 			{
-				return FReply::Handled().PreventThrottling().CaptureMouse(MyWidget.Pin().ToSharedRef());
+				return FReply::Handled().PreventThrottling().CaptureMouse(TakeWidget());
 			}
 		}
 		else
@@ -1679,15 +1696,27 @@
 			bool Hit = HitTest(Position);
 
 			Noesis::MouseButton MouseButton = GetNoesisMouseButton(MouseEvent.GetEffectingButton());
-			bool Handled = XamlView->MouseButtonDown(FPlatformMath::RoundToInt(Position.X), FPlatformMath::RoundToInt(Position.Y), MouseButton);
+			bool Handled = XamlView->MouseButtonDown(FPlatformMath::RoundToInt(Position.X), FPlatformMath::RoundToInt(Position.Y), MouseButton) || HasMouseCapture();
 
 			if (Handled && Hit)
 			{
-				return FReply::Handled().PreventThrottling();
+				auto Reply = FReply::Handled().PreventThrottling().SetUserFocus(FSlateApplication::Get().GetGameViewport().ToSharedRef());
+				if (HasMouseCapture())
+				{
+					UE_LOG(LogNoesis, Warning, TEXT("MouseButtonDown CaptureMouse"));
+					Reply.CaptureMouse(TakeWidget());
+				}
+				else
+				{
+					UE_LOG(LogNoesis, Warning, TEXT("MouseButtonDown ReleaseMouseCapture"));
+					Reply.ReleaseMouseCapture();
+				}
+				return Reply;
 			}
 		}
 	}
 
+	UE_LOG(LogNoesis, Warning, TEXT("MouseButtonDown Unhandled"));
 	return FReply::Unhandled();
 }
 
@@ -1712,15 +1741,27 @@
 			bool Hit = HitTest(Position);
 
 			Noesis::MouseButton MouseButton = GetNoesisMouseButton(MouseEvent.GetEffectingButton());
-			bool Handled = XamlView->MouseButtonUp(FPlatformMath::RoundToInt(Position.X), FPlatformMath::RoundToInt(Position.Y), MouseButton);
+			bool Handled = XamlView->MouseButtonUp(FPlatformMath::RoundToInt(Position.X), FPlatformMath::RoundToInt(Position.Y), MouseButton) || HasMouseCapture();
 
 			if (Handled && Hit)
 			{
-				return FReply::Handled().PreventThrottling();
+				auto Reply = FReply::Handled().PreventThrottling().SetUserFocus(FSlateApplication::Get().GetGameViewport().ToSharedRef());
+				if (HasMouseCapture())
+				{
+					UE_LOG(LogNoesis, Warning, TEXT("MouseButtonUp CaptureMouse"));
+					Reply.CaptureMouse(TakeWidget());
+				}
+				else
+				{
+					UE_LOG(LogNoesis, Warning, TEXT("MouseButtonUp ReleaseMouseCapture"));
+					Reply.ReleaseMouseCapture();
+				}
+				return Reply;
 			}
 		}
 	}
 
+	UE_LOG(LogNoesis, Warning, TEXT("MouseButtonUp Unhandled"));
 	return FReply::Unhandled();
 }
 
@@ -1732,14 +1773,29 @@
 		FVector2D Position = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition()) * MyGeometry.Scale;
 		bool Hit = HitTest(Position);
 
-		bool Handled = XamlView->MouseMove(FPlatformMath::RoundToInt(Position.X), FPlatformMath::RoundToInt(Position.Y));
+		bool Handled = XamlView->MouseMove(FPlatformMath::RoundToInt(Position.X), FPlatformMath::RoundToInt(Position.Y)) || HasMouseCapture();
 
 		if (Handled && Hit)
 		{
-			return FReply::Handled().PreventThrottling();
+			auto Reply = FReply::Handled().PreventThrottling().SetUserFocus(FSlateApplication::Get().GetGameViewport().ToSharedRef());
+			if (HasMouseCapture())
+			{
+				UE_LOG(LogNoesis, Warning, TEXT("MouseMove CaptureMouse"));
+				Reply.CaptureMouse(TakeWidget());
+			}
+			else
+			{
+				UE_LOG(LogNoesis, Warning, TEXT("MouseMove ReleaseMouseCapture"));
+				Reply.ReleaseMouseCapture();
+			}
+			return Reply;
 		}
 	}
 
+	if (!MouseEvent.GetCursorDelta().IsZero())
+	{
+		UE_LOG(LogNoesis, Warning, TEXT("MouseMove Unhandled"));
+	}
 	return FReply::Unhandled();
 }
 
@@ -1756,7 +1812,16 @@
 
 		if (Handled && Hit)
 		{
-			return FReply::Handled().PreventThrottling();
+			auto Reply = FReply::Handled().PreventThrottling();
+			if (HasMouseCapture())
+			{
+				Reply.CaptureMouse(TakeWidget());
+			}
+			else
+			{
+				Reply.ReleaseMouseCapture();
+			}
+			return Reply;
 		}
 	}
 
@@ -1776,7 +1841,16 @@
 
 		if (Handled && Hit)
 		{
-			return FReply::Handled().PreventThrottling();
+			auto Reply = FReply::Handled().PreventThrottling();
+			if (HasMouseCapture())
+			{
+				Reply.CaptureMouse(TakeWidget());
+			}
+			else
+			{
+				Reply.ReleaseMouseCapture();
+			}
+			return Reply;
 		}
 	}
 
@@ -1796,7 +1870,16 @@
 
 		if (Handled && Hit)
 		{
-			return FReply::Handled().PreventThrottling();
+			auto Reply = FReply::Handled().PreventThrottling();
+			if (HasMouseCapture())
+			{
+				Reply.CaptureMouse(TakeWidget());
+			}
+			else
+			{
+				Reply.ReleaseMouseCapture();
+			}
+			return Reply;
 		}
 	}
 
@@ -1816,7 +1899,16 @@
 
 		if (Handled && Hit)
 		{
-			return FReply::Handled().PreventThrottling();
+			auto Reply = FReply::Handled().PreventThrottling();
+			if (HasMouseCapture())
+			{
+				Reply.CaptureMouse(TakeWidget());
+			}
+			else
+			{
+				Reply.ReleaseMouseCapture();
+			}
+			return Reply;
 		}
 	}
 
@@ -1841,7 +1933,16 @@
 
 		if (Handled && Hit)
 		{
-			return FReply::Handled().PreventThrottling();
+			auto Reply = FReply::Handled().PreventThrottling();
+			if (HasMouseCapture())
+			{
+				Reply.CaptureMouse(TakeWidget());
+			}
+			else
+			{
+				Reply.ReleaseMouseCapture();
+			}
+			return Reply;
 		}
 	}
 
@@ -1848,6 +1949,59 @@
 	return FReply::Unhandled();
 }
 
+void UNoesisInstance::NativeOnMouseEnter(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent)
+{
+	UE_LOG(LogNoesis, Warning, TEXT("MouseEnter"));
+	if (XamlView)
+	{
+		UE_LOG(LogNoesis, Warning, TEXT("Activate View"));
+		XamlView->Activate();
+	}
+}
+
+void UNoesisInstance::NativeOnMouseLeave(const FPointerEvent& InMouseEvent)
+{
+	UE_LOG(LogNoesis, Warning, TEXT("MouseLeave"));
+	if (XamlView && !HasMouseCapture())
+	{
+		UE_LOG(LogNoesis, Warning, TEXT("Deactivate View"));
+		XamlView->Deactivate();
+	}
+}
+
+FReply UNoesisInstance::NativeOnFocusReceived(const FGeometry& InGeometry, const FFocusEvent& InFocusEvent)
+{
+	UE_LOG(LogNoesis, Warning, TEXT("FocusReceived"));
+	return Super::NativeOnFocusReceived(InGeometry, InFocusEvent);
+	//return FReply::Handled().ClearUserFocus();
+}
+
+void UNoesisInstance::NativeOnFocusLost(const FFocusEvent& InFocusEvent)
+{
+	UE_LOG(LogNoesis, Warning, TEXT("FocusLost"));
+}
+
+void UNoesisInstance::NativeOnFocusChanging(const FWeakWidgetPath& PreviousFocusPath, const FWidgetPath& NewWidgetPath, const FFocusEvent& InFocusEvent)
+{
+	UE_LOG(LogNoesis, Warning, TEXT("FocusChanging"));
+}
+
+void UNoesisInstance::NativeOnAddedToFocusPath(const FFocusEvent& InFocusEvent)
+{
+	UE_LOG(LogNoesis, Warning, TEXT("AddedToFocusPath"));
+}
+
+void UNoesisInstance::NativeOnRemovedFromFocusPath(const FFocusEvent& InFocusEvent)
+{
+	UE_LOG(LogNoesis, Warning, TEXT("RemovedFromFocusPath"));
+}
+
+FNavigationReply UNoesisInstance::NativeOnNavigation(const FGeometry& MyGeometry, const FNavigationEvent& InNavigationEvent, const FNavigationReply& InDefaultReply)
+{
+	UE_LOG(LogNoesis, Warning, TEXT("Navigation"));
+	return Super::NativeOnNavigation(MyGeometry, InNavigationEvent, InDefaultReply);
+}
+
 bool UNoesisInstance::NativeSupportsKeyboardFocus() const
 {
 	// SObjectWidget::OnKeyDown calls UNoesisInstance::NativeOnKeyDown. There we set SupportsKeyboardFocus = !EnableActions.
Index: Source/Inventory/DragItemBehavior.cpp
===================================================================
--- Source/Inventory/DragItemBehavior.cpp	(revision 14126)
+++ Source/Inventory/DragItemBehavior.cpp	(working copy)
@@ -100,14 +100,32 @@
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 void DragItemBehavior::OnMouseDown(BaseComponent*, const MouseButtonEventArgs& e)
 {
+    UE_LOG(LogNoesisInventory, Warning, TEXT("DragItemBehavior::OnMouseDown"));
     _mouseClicked = true;
     e.handled = true;
 }
 
+static void ResetFocus(BaseComponent* source)
+{
+    auto element = Noesis::DynamicCast<UIElement*>(source);
+    if (element != nullptr)
+    {
+        auto keyboard = element->GetKeyboard();
+        if (keyboard != nullptr)
+        {
+            keyboard->Focus(nullptr);
+        }
+    }
+}
+
 ////////////////////////////////////////////////////////////////////////////////////////////////////
-void DragItemBehavior::OnMouseUp(BaseComponent*, const MouseButtonEventArgs&)
+void DragItemBehavior::OnMouseUp(BaseComponent* source, const MouseButtonEventArgs& e)
 {
+    UE_LOG(LogNoesisInventory, Warning, TEXT("DragItemBehavior::OnMouseUp"));
     _mouseClicked = false;
+    e.handled = true;
+
+    ResetFocus(source);
 }
 
 ////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -118,6 +136,7 @@
         return;
     }
 
+    UE_LOG(LogNoesisInventory, Warning, TEXT("DragItemBehavior::OnMouseMove"));
     _mouseClicked = false;
 
     FrameworkElement* element = GetAssociatedObject();
@@ -131,7 +150,7 @@
 
             startDrag->Execute(item);
 
-            DragDrop::DoDragDrop(element, item, DragDropEffects_Move, [this](DependencyObject*,
+            DragDrop::DoDragDrop(element, item, DragDropEffects_Move, [this](DependencyObject* source,
                 BaseComponent*, UIElement*, const Noesis::Point&, uint32_t effects)
             {
                 Noesis::Ptr<BaseComponent> dragSuccess = Boxing::Box<bool>(effects != DragDropEffects_None);
@@ -140,9 +159,13 @@
                 {
                     endDrag->Execute(dragSuccess);
                 }
+
+                ResetFocus(source);
             });
         }
     }
+
+    e.handled = true;
 }
 
 ////////////////////////////////////////////////////////////////////////////////////////////////////
Index: Source/Inventory/InventoryGame.cpp
===================================================================
--- Source/Inventory/InventoryGame.cpp	(revision 14126)
+++ Source/Inventory/InventoryGame.cpp	(working copy)
@@ -11,6 +11,8 @@
 #include "DragItemBehavior.h"
 #include "DropItemBehavior.h"
 
+DEFINE_LOG_CATEGORY(LogNoesisInventory);
+
 class InventoryModule : public FDefaultGameModuleImpl
 {
     virtual void StartupModule() override
DragBehavior-3.patch (15,413 bytes)   
skokon

skokon

2024-07-11 01:30

reporter   ~0009795

Thanks for the update, the patch didn't apply to "DragItemBehavior.cpp" for some reason, so I had to apply it manually and include "NsGui/Keyboard.h".

I tried the same scenario again, and the bug still exists.

skokon

skokon

2024-07-11 02:11

reporter   ~0009796

I also added KeyboardNavigation.TabNavigation="None" to the UserControl, but the Tab key is still not being captured by the enhanced input component system.

hcpizzi

hcpizzi

2024-07-11 11:01

developer   ~0009797

Could you send us the log?

skokon

skokon

2024-07-13 12:15

reporter   ~0009808

Hi,

I double checked because some logs were missing, and discovered that the patch didn’t apply to the plugin due to an error: "patch failed: Inventory/DragItemBehavior.cpp:100 error: Inventory/DragItemBehavior.cpp: patch does not apply."

I used Rider to manually apply the patch (Rider has a feature for applying patches more effectively than Git) and tested it again. It’s finally working, and the Tab key is functioning as expected now!

Thank you so much for your time and assistance, and I apologize for any inconvenience caused.

hcpizzi

hcpizzi

2024-08-01 16:04

developer   ~0009854

This is the change I've committed to our codebase. There's an additional property to set in your NoesisView: SetUserFocusToViewport. It defaults to false, and you will have to set it to true. This prevents the focus from shifting to the Noesis widget, thus resetting the key presses. I wasn't confident in leaving that as the default behavior.

DragBehavior-4.patch (11,458 bytes)   
Index: Source/NoesisEditor/Private/NoesisBlueprintCompilerContext.cpp
===================================================================
--- Source/NoesisEditor/Private/NoesisBlueprintCompilerContext.cpp	(revision 14283)
+++ Source/NoesisEditor/Private/NoesisBlueprintCompilerContext.cpp	(revision 14284)
@@ -60,6 +60,7 @@
 		DefaultInstance->EnableKeyboard = NoesisBlueprint->EnableKeyboard;
 		DefaultInstance->EnableMouse = NoesisBlueprint->EnableMouse;
 		DefaultInstance->EmulateTouch = NoesisBlueprint->EmulateTouch;
+		DefaultInstance->SetUserFocusToViewport = NoesisBlueprint->SetUserFocusToViewport;
 		DefaultInstance->EnableTouch = NoesisBlueprint->EnableTouch;
 		DefaultInstance->EnableActions = NoesisBlueprint->EnableActions;
 		DefaultInstance->PixelDepthBias = NoesisBlueprint->PixelDepthBias;
Index: Source/NoesisRuntime/Classes/NoesisBlueprint.h
===================================================================
--- Source/NoesisRuntime/Classes/NoesisBlueprint.h	(revision 14283)
+++ Source/NoesisRuntime/Classes/NoesisBlueprint.h	(revision 14284)
@@ -56,6 +56,10 @@
 	UPROPERTY(EditAnywhere, Category = "Noesis View", meta = (EditCondition = "EnableMouse"))
 	bool EmulateTouch;
 
+	/** Handled mouse events set user focus to the game viewport. */
+	UPROPERTY(EditAnywhere, Category = "Noesis View", meta = (EditCondition = "EnableMouse"))
+	bool SetUserFocusToViewport;
+
 	UPROPERTY(EditAnywhere, Category = "Noesis View")
 	bool EnableTouch;
 
Index: Source/NoesisRuntime/Classes/NoesisInstance.h
===================================================================
--- Source/NoesisRuntime/Classes/NoesisInstance.h	(revision 14283)
+++ Source/NoesisRuntime/Classes/NoesisInstance.h	(revision 14284)
@@ -165,6 +165,9 @@
 	bool EmulateTouch;
 
 	UPROPERTY(BlueprintReadWrite, Category = "NoesisGUI")
+	bool SetUserFocusToViewport;
+
+	UPROPERTY(BlueprintReadWrite, Category = "NoesisGUI")
 	bool EnableTouch;
 
 	UPROPERTY(BlueprintReadWrite, Category = "NoesisGUI")
@@ -216,6 +219,7 @@
 	void OnPreviewLostKeyboardFocus(Noesis::BaseComponent* Component, const Noesis::KeyboardFocusChangedEventArgs& Args);
 
 	bool HitTest(FVector2D Position) const;
+	bool HasMouseCapture() const;
 
 	void TermInstance();
 
@@ -264,6 +268,8 @@
 	virtual FReply NativeOnTouchEnded(const FGeometry& MyGeometry, const FPointerEvent& InTouchEvent) override;
 	virtual FCursorReply NativeOnCursorQuery(const FGeometry& MyGeometry, const FPointerEvent& CursorEvent) override;
 	virtual FReply NativeOnMouseButtonDoubleClick(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
+	virtual void NativeOnMouseEnter(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) override;
+	virtual void NativeOnMouseLeave(const FPointerEvent& InMouseEvent) override;
 	virtual bool NativeSupportsKeyboardFocus() const override;
 	virtual void NativeConstruct() override;
 	virtual void NativeDestruct() override;
Index: Source/NoesisRuntime/Private/NoesisBlueprint.cpp
===================================================================
--- Source/NoesisRuntime/Private/NoesisBlueprint.cpp	(revision 14283)
+++ Source/NoesisRuntime/Private/NoesisBlueprint.cpp	(revision 14284)
@@ -18,6 +18,7 @@
 	EnableKeyboard = true;
 	EnableMouse = true;
 	EmulateTouch = false;
+	SetUserFocusToViewport = false;
 	EnableTouch = true;
 	EnableActions = false;
 	PixelDepthBias = -1.0f;
Index: Source/NoesisRuntime/Private/NoesisInstance.cpp
===================================================================
--- Source/NoesisRuntime/Private/NoesisInstance.cpp	(revision 14283)
+++ Source/NoesisRuntime/Private/NoesisInstance.cpp	(revision 14284)
@@ -526,6 +526,7 @@
 	EnableKeyboard = true;
 	EnableMouse = true;
 	EmulateTouch = false;
+	SetUserFocusToViewport = false;
 	EnableTouch = true;
 	EnableActions = false;
 	PixelDepthBias = -1.0f;
@@ -1162,6 +1163,18 @@
 	return HitTester.Hit != nullptr;
 }
 
+bool UNoesisInstance::HasMouseCapture() const
+{
+	if (XamlView)
+	{
+		auto Root = XamlView->GetContent();
+		auto Mouse = Root != nullptr ? Root->GetMouse() : nullptr;
+		return Mouse != nullptr && Mouse->GetCaptured() != nullptr;
+	}
+
+	return false;
+}
+
 void UNoesisInstance::TermInstance()
 {
 	if (XamlView)
@@ -1465,10 +1478,15 @@
 	{
 		TCHAR Character = CharacterEvent.GetCharacter();
 
-		XamlView->Char(CharCast<char>(Character));
+		bool Handled = XamlView->Char(CharCast<char>(Character));
+
+		if (Handled)
+		{
+			return FReply::Handled();
+		}
 	}
 
-	return Super::NativeOnKeyChar(MyGeometry, CharacterEvent);
+	return FReply::Unhandled();
 }
 
 static TMap<FKey, Noesis::Key> InitKeyMap()
@@ -1595,11 +1613,16 @@
 		Noesis::Key* NoesisKey = KeyToNoesisKey(Key);
 		if (NoesisKey != nullptr)
 		{
-			XamlView->KeyDown(*NoesisKey);
+			bool Handled = XamlView->KeyDown(*NoesisKey);
+
+			if (Handled)
+			{
+				return FReply::Handled();
+			}
 		}
 	}
 
-	return Super::NativeOnKeyDown(MyGeometry, KeyEvent);
+	return FReply::Unhandled();
 }
 
 FReply UNoesisInstance::NativeOnKeyUp(const FGeometry& MyGeometry, const FKeyEvent& KeyEvent)
@@ -1611,31 +1634,20 @@
 		Noesis::Key* NoesisKey = KeyToNoesisKey(Key);
 		if (NoesisKey != nullptr)
 		{
-			XamlView->KeyUp(*NoesisKey);
+			bool Handled = XamlView->KeyUp(*NoesisKey);
+
+			if (Handled)
+			{
+				return FReply::Handled();
+			}
 		}
 	}
 
-	return Super::NativeOnKeyUp(MyGeometry, KeyEvent);
+	return FReply::Unhandled();
 }
 
 FReply UNoesisInstance::NativeOnAnalogValueChanged(const FGeometry& MyGeometry, const FAnalogInputEvent& InAnalogEvent)
 {
-	SCOPE_CYCLE_COUNTER(STAT_NoesisInstance_OnAnalogValueChanged);
-	if (XamlView)
-	{
-		if (FMath::Abs(InAnalogEvent.GetAnalogValue()) > 0.25f)
-		{
-			if (InAnalogEvent.GetKey() == EKeys::Gamepad_RightX)
-			{
-				XamlView->HScroll(InAnalogEvent.GetAnalogValue());
-			}
-			else if (InAnalogEvent.GetKey() == EKeys::Gamepad_RightY)
-			{
-				XamlView->Scroll(InAnalogEvent.GetAnalogValue());
-			}
-		}
-	}
-
 	return Super::NativeOnAnalogValueChanged(MyGeometry, InAnalogEvent);
 }
 
@@ -1670,7 +1682,7 @@
 
 			if (Handled)
 			{
-				return FReply::Handled().PreventThrottling().CaptureMouse(MyWidget.Pin().ToSharedRef());
+				return FReply::Handled().PreventThrottling().CaptureMouse(TakeWidget());
 			}
 		}
 		else
@@ -1679,11 +1691,24 @@
 			bool Hit = HitTest(Position);
 
 			Noesis::MouseButton MouseButton = GetNoesisMouseButton(MouseEvent.GetEffectingButton());
-			bool Handled = XamlView->MouseButtonDown(FPlatformMath::RoundToInt(Position.X), FPlatformMath::RoundToInt(Position.Y), MouseButton);
+			bool Handled = XamlView->MouseButtonDown(FPlatformMath::RoundToInt(Position.X), FPlatformMath::RoundToInt(Position.Y), MouseButton) || HasMouseCapture();
 
 			if (Handled && Hit)
 			{
-				return FReply::Handled().PreventThrottling();
+				auto Reply = FReply::Handled().PreventThrottling();
+				if (SetUserFocusToViewport)
+				{
+					Reply.SetUserFocus(FSlateApplication::Get().GetGameViewport().ToSharedRef());
+				}
+				if (HasMouseCapture())
+				{
+					Reply.CaptureMouse(TakeWidget());
+				}
+				else
+				{
+					Reply.ReleaseMouseCapture();
+				}
+				return Reply;
 			}
 		}
 	}
@@ -1712,11 +1737,24 @@
 			bool Hit = HitTest(Position);
 
 			Noesis::MouseButton MouseButton = GetNoesisMouseButton(MouseEvent.GetEffectingButton());
-			bool Handled = XamlView->MouseButtonUp(FPlatformMath::RoundToInt(Position.X), FPlatformMath::RoundToInt(Position.Y), MouseButton);
+			bool Handled = XamlView->MouseButtonUp(FPlatformMath::RoundToInt(Position.X), FPlatformMath::RoundToInt(Position.Y), MouseButton) || HasMouseCapture();
 
 			if (Handled && Hit)
 			{
-				return FReply::Handled().PreventThrottling();
+				auto Reply = FReply::Handled().PreventThrottling();
+				if (SetUserFocusToViewport)
+				{
+					Reply.SetUserFocus(FSlateApplication::Get().GetGameViewport().ToSharedRef());
+				}
+				if (HasMouseCapture())
+				{
+					Reply.CaptureMouse(TakeWidget());
+				}
+				else
+				{
+					Reply.ReleaseMouseCapture();
+				}
+				return Reply;
 			}
 		}
 	}
@@ -1732,11 +1770,24 @@
 		FVector2D Position = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition()) * MyGeometry.Scale;
 		bool Hit = HitTest(Position);
 
-		bool Handled = XamlView->MouseMove(FPlatformMath::RoundToInt(Position.X), FPlatformMath::RoundToInt(Position.Y));
+		bool Handled = XamlView->MouseMove(FPlatformMath::RoundToInt(Position.X), FPlatformMath::RoundToInt(Position.Y)) || HasMouseCapture();
 
 		if (Handled && Hit)
 		{
-			return FReply::Handled().PreventThrottling();
+			auto Reply = FReply::Handled().PreventThrottling();
+			if (SetUserFocusToViewport)
+			{
+				Reply.SetUserFocus(FSlateApplication::Get().GetGameViewport().ToSharedRef());
+			}
+			if (HasMouseCapture())
+			{
+				Reply.CaptureMouse(TakeWidget());
+			}
+			else
+			{
+				Reply.ReleaseMouseCapture();
+			}
+			return Reply;
 		}
 	}
 
@@ -1756,7 +1807,16 @@
 
 		if (Handled && Hit)
 		{
-			return FReply::Handled().PreventThrottling();
+			auto Reply = FReply::Handled().PreventThrottling();
+			if (HasMouseCapture())
+			{
+				Reply.CaptureMouse(TakeWidget());
+			}
+			else
+			{
+				Reply.ReleaseMouseCapture();
+			}
+			return Reply;
 		}
 	}
 
@@ -1776,7 +1836,16 @@
 
 		if (Handled && Hit)
 		{
-			return FReply::Handled().PreventThrottling();
+			auto Reply = FReply::Handled().PreventThrottling();
+			if (HasMouseCapture())
+			{
+				Reply.CaptureMouse(TakeWidget());
+			}
+			else
+			{
+				Reply.ReleaseMouseCapture();
+			}
+			return Reply;
 		}
 	}
 
@@ -1796,7 +1865,16 @@
 
 		if (Handled && Hit)
 		{
-			return FReply::Handled().PreventThrottling();
+			auto Reply = FReply::Handled().PreventThrottling();
+			if (HasMouseCapture())
+			{
+				Reply.CaptureMouse(TakeWidget());
+			}
+			else
+			{
+				Reply.ReleaseMouseCapture();
+			}
+			return Reply;
 		}
 	}
 
@@ -1816,7 +1894,16 @@
 
 		if (Handled && Hit)
 		{
-			return FReply::Handled().PreventThrottling();
+			auto Reply = FReply::Handled().PreventThrottling();
+			if (HasMouseCapture())
+			{
+				Reply.CaptureMouse(TakeWidget());
+			}
+			else
+			{
+				Reply.ReleaseMouseCapture();
+			}
+			return Reply;
 		}
 	}
 
@@ -1841,7 +1928,16 @@
 
 		if (Handled && Hit)
 		{
-			return FReply::Handled().PreventThrottling();
+			auto Reply = FReply::Handled().PreventThrottling();
+			if (HasMouseCapture())
+			{
+				Reply.CaptureMouse(TakeWidget());
+			}
+			else
+			{
+				Reply.ReleaseMouseCapture();
+			}
+			return Reply;
 		}
 	}
 
@@ -1848,6 +1944,22 @@
 	return FReply::Unhandled();
 }
 
+void UNoesisInstance::NativeOnMouseEnter(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent)
+{
+	if (XamlView)
+	{
+		XamlView->Activate();
+	}
+}
+
+void UNoesisInstance::NativeOnMouseLeave(const FPointerEvent& InMouseEvent)
+{
+	if (XamlView && !HasMouseCapture())
+	{
+		XamlView->Deactivate();
+	}
+}
+
 bool UNoesisInstance::NativeSupportsKeyboardFocus() const
 {
 	// SObjectWidget::OnKeyDown calls UNoesisInstance::NativeOnKeyDown. There we set SupportsKeyboardFocus = !EnableActions.
DragBehavior-4.patch (11,458 bytes)   

Issue History

Date Modified Username Field Change
2024-07-04 12:12 skokon New Issue
2024-07-04 12:12 skokon Tag Attached: C++
2024-07-04 12:12 skokon Tag Attached: Input
2024-07-04 12:12 skokon Tag Attached: Unreal
2024-07-04 12:41 hcpizzi Assigned To => hcpizzi
2024-07-04 12:41 hcpizzi Status new => assigned
2024-07-04 13:21 hcpizzi Note Added: 0009738
2024-07-04 13:21 hcpizzi File Added: DragBehavior.patch
2024-07-04 13:21 hcpizzi Status assigned => feedback
2024-07-04 13:32 jsantos Target Version => 3.2.5
2024-07-04 21:35 skokon Note Added: 0009741
2024-07-04 21:35 skokon Status feedback => assigned
2024-07-05 12:10 hcpizzi Note Added: 0009742
2024-07-05 12:10 hcpizzi File Added: DragBehavior-2.patch
2024-07-05 12:10 hcpizzi Status assigned => feedback
2024-07-06 21:34 skokon Note Added: 0009753
2024-07-06 21:34 skokon Status feedback => assigned
2024-07-06 21:59 skokon Note Added: 0009754
2024-07-09 18:00 hcpizzi Note Added: 0009774
2024-07-09 18:00 hcpizzi Status assigned => feedback
2024-07-09 18:49 skokon Note Added: 0009778
2024-07-09 18:49 skokon Status feedback => assigned
2024-07-09 18:56 skokon Note Added: 0009779
2024-07-09 19:33 hcpizzi Note Added: 0009781
2024-07-09 19:54 skokon Note Added: 0009784
2024-07-10 14:40 hcpizzi Note Added: 0009790
2024-07-10 14:40 hcpizzi File Added: DragBehavior-3.patch
2024-07-10 14:41 hcpizzi Status assigned => feedback
2024-07-11 01:30 skokon Note Added: 0009795
2024-07-11 01:30 skokon Status feedback => assigned
2024-07-11 02:11 skokon Note Added: 0009796
2024-07-11 11:01 hcpizzi Note Added: 0009797
2024-07-11 11:01 hcpizzi Status assigned => feedback
2024-07-13 12:15 skokon Note Added: 0009808
2024-07-13 12:15 skokon Status feedback => assigned
2024-08-01 16:04 hcpizzi Note Added: 0009854
2024-08-01 16:04 hcpizzi File Added: DragBehavior-4.patch
2024-08-01 16:05 hcpizzi Status assigned => resolved
2024-08-01 16:05 hcpizzi Resolution open => fixed
2024-08-01 16:05 hcpizzi Fixed in Version => 3.2.5