Skip to content

Instantly share code, notes, and snippets.

@rtm223
Created October 10, 2022 15:13
Show Gist options
  • Save rtm223/a43ca4d9003d7c0470d3f886d30a4d7d to your computer and use it in GitHub Desktop.
Save rtm223/a43ca4d9003d7c0470d3f886d30a4d7d to your computer and use it in GitHub Desktop.
Cursor Receiver Widget
// Copyright (c) Richard Meredith AB. All Rights Reserved
#include "UI/UMG/RTMCursorReceiverWidget.h"
#include "UI/Slate/SRTMCursorReceiver.h"
#define LOCTEXT_NAMESPACE "RTM"
TSharedRef<SWidget> URTMCursorReceiverWidget::RebuildWidget()
{
Receiver = SNew(SRTMCursorReceiver)
.bDetectDragEvents(bDetectDragEvents)
.OnMoved(BIND_UOBJECT_DELEGATE(FRTMCursorReceiverMoveDelegate, SlateHandleMoved))
.OnClicked(BIND_UOBJECT_DELEGATE(FRTMCursorReceiverClickDelegate, SlateHandleClicked))
.OnPressed(BIND_UOBJECT_DELEGATE(FRTMCursorReceiverDelegate, SlateHandlePressed))
.OnReleased(BIND_UOBJECT_DELEGATE(FRTMCursorReceiverDelegate, SlateHandleReleased))
.OnDragMoved(BIND_UOBJECT_DELEGATE(FRTMCursorReceiverMoveDelegate, SlateHandleDragMoved))
.OnWheelChanged(BIND_UOBJECT_DELEGATE(FRTMCursorReceiverWheelDelegate, SlateHandleWheelChanged))
.OnHovered_UObject(this, &ThisClass::SlateHandleHovered)
.OnUnhovered_UObject(this, &ThisClass::SlateHandleUnhovered)
.OnDragStarted_UObject(this, &ThisClass::SlateHandleDragStarted)
.OnDragEnded_UObject(this, &ThisClass::SlateHandleDragEnded);
return Receiver.ToSharedRef();
}
void URTMCursorReceiverWidget::ReleaseSlateResources(bool bReleaseChildren)
{
Super::ReleaseSlateResources(bReleaseChildren);
Receiver.Reset();
}
#if WITH_EDITOR
TSharedRef<SWidget> URTMCursorReceiverWidget::RebuildDesignWidget(TSharedRef<SWidget> Content)
{
return Content;
}
const FText URTMCursorReceiverWidget::GetPaletteCategory()
{
return LOCTEXT("WIDGETS_CATEGORY", "RTM");
}
#endif
void URTMCursorReceiverWidget::SlateHandleHovered() { OnHovered.Broadcast(); }
void URTMCursorReceiverWidget::SlateHandleUnhovered() { OnUnhovered.Broadcast(); }
void URTMCursorReceiverWidget::SlateHandleMoved(const FVector2D& delta) {OnCursorMoved.Broadcast(delta); }
void URTMCursorReceiverWidget::SlateHandlePressed(const FKey& key) { OnPressed.Broadcast(key); }
void URTMCursorReceiverWidget::SlateHandleReleased(const FKey& key) { OnReleased.Broadcast(key); }
void URTMCursorReceiverWidget::SlateHandleDragStarted(const FKey& key) { OnDragStarted.Broadcast(key); }
void URTMCursorReceiverWidget::SlateHandleDragEnded(const FKey& key) { OnDragEnded.Broadcast(key); }
void URTMCursorReceiverWidget::SlateHandleDragMoved(const FVector2D& positionDelta) { OnDragMoved.Broadcast(positionDelta); }
void URTMCursorReceiverWidget::SlateHandleWheelChanged(float wheelDelta) { OnWheelChanged.Broadcast(wheelDelta); }
FReply URTMCursorReceiverWidget::SlateHandleClicked(const FKey& key)
{
OnClicked.Broadcast(key);
return FReply::Handled();
}
// Copyright (c) Richard Meredith AB. All Rights Reserved
#pragma once
#include "CoreMinimal.h"
#include "Components/Widget.h"
#include "RTMCursorReceiverWidget.generated.h"
class SRTMCursorReceiver;
UCLASS()
class RTMCOMMON_API URTMCursorReceiverWidget : public UWidget
{
GENERATED_BODY()
public:
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FEvent);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FButtonEvent, const FKey&, key);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FMoveEvent, const FVector2D&, positionDelta);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FWheelEvent, float, wheelDelta);
UPROPERTY(BlueprintAssignable, Category="Cursor|Events")
FEvent OnHovered;
UPROPERTY(BlueprintAssignable, Category="Cursor|Events")
FEvent OnUnhovered;
UPROPERTY(BlueprintAssignable, Category="Cursor|Events")
FMoveEvent OnCursorMoved;
UPROPERTY(BlueprintAssignable, Category="Cursor|Events")
FButtonEvent OnClicked;
UPROPERTY(BlueprintAssignable, Category="Cursor|Events")
FButtonEvent OnPressed;
UPROPERTY(BlueprintAssignable, Category="Cursor|Events")
FButtonEvent OnReleased;
UPROPERTY(BlueprintAssignable, Category="Cursor|Events")
FButtonEvent OnDragStarted;
UPROPERTY(BlueprintAssignable, Category="Cursor|Events")
FButtonEvent OnDragEnded;
UPROPERTY(BlueprintAssignable, Category="Cursor|Events")
FMoveEvent OnDragMoved;
UPROPERTY(BlueprintAssignable, Category="Cursor|Events")
FWheelEvent OnWheelChanged;
protected:
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Cursor")
bool bDetectDragEvents = false;
virtual TSharedRef<SWidget> RebuildWidget() override;
virtual void ReleaseSlateResources(bool bReleaseChildren) override;
#if WITH_EDITOR
virtual TSharedRef<SWidget> RebuildDesignWidget(TSharedRef<SWidget> Content) override;
virtual const FText GetPaletteCategory() override;
#endif
//#if WITH_ACCESSIBILITY
// virtual TSharedPtr<SWidget> GetAccessibleWidget() const override;
//#endif
private:
TSharedPtr<SRTMCursorReceiver> Receiver;
void SlateHandleHovered();
void SlateHandleUnhovered();
void SlateHandleMoved(const FVector2D& delta);
void SlateHandlePressed(const FKey& key);
void SlateHandleReleased(const FKey& key);
FReply SlateHandleClicked(const FKey& key);
void SlateHandleDragStarted(const FKey& key);
void SlateHandleDragEnded(const FKey& key);
void SlateHandleDragMoved(const FVector2D& positionDelta);
void SlateHandleWheelChanged(float wheelDelta);
};
// Copyright (c) Richard Meredith AB. All Rights Reserved
#include "UI/Slate/SRTMCursorReceiver.h"
#include "SlateOptMacros.h"
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SRTMCursorReceiver::Construct(const FArguments& InArgs)
{
SetCanTick(false);
OnHovered = InArgs._OnHovered;
OnUnhovered = InArgs._OnUnhovered;
OnMoved = InArgs._OnMoved;
OnPressed = InArgs._OnPressed;
OnReleased = InArgs._OnReleased;
OnClicked = InArgs._OnClicked;
OnDragStarted = InArgs._OnDragStarted;
OnDragEnded = InArgs._OnDragEnded;
OnDragMoved = InArgs._OnDragMoved;
OnWheelChanged = InArgs._OnWheelChanged;
bDetectDragEvents = InArgs._bDetectDragEvents;
ActiveKey = EKeys::Invalid;
PressedCursorPosition = {0.0f, 0.0f};
}
bool SRTMCursorReceiver::ComputeVolatility() const
{
#if ENGINE_MAJOR_VERSION >= 5
return GetContentScaleAttribute().IsBound();
#else
return ContentScale.IsBound();
#endif
}
void SRTMCursorReceiver::OnMouseEnter(const FGeometry& myGeometry, const FPointerEvent& mouseEvent)
{
Super::OnMouseEnter(myGeometry, mouseEvent);
OnHovered.ExecuteIfBound();
Invalidate(EInvalidateWidgetReason::Layout);
}
void SRTMCursorReceiver::OnMouseLeave(const FPointerEvent& MouseEvent)
{
const bool wasHovered = IsHovered();
Super::OnMouseLeave(MouseEvent);
MaybeRelease(EKeys::AnyKey);
if(wasHovered)
OnUnhovered.ExecuteIfBound();
Invalidate(EInvalidateWidgetReason::Layout);
}
FReply SRTMCursorReceiver::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& mouseEvent)
{
FReply reply = FReply::Unhandled();
if(IsEnabled() && IsInterestingCursorButton(mouseEvent))
{
MaybePress(mouseEvent.GetEffectingButton());
PressedCursorPosition = mouseEvent.GetScreenSpacePosition();
reply = FReply::Handled().CaptureMouse(AsShared());
if(bDetectDragEvents)
reply.DetectDrag(this->AsShared(), mouseEvent.GetEffectingButton());
}
Invalidate(EInvalidateWidgetReason::Layout);
return reply;
}
FReply SRTMCursorReceiver::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& mouseEvent)
{
FReply reply = FReply::Unhandled();
if(IsEnabled() && IsInterestingCursorButton(mouseEvent))
{
reply = FReply::Handled();
const FKey effectingButton = GetEffectiveCursorButton(mouseEvent);
if(ActiveKey == effectingButton)
reply = ExecuteOnClick(effectingButton);
else
reply = FReply::Handled();
if(DragKey == effectingButton)
{
OnDragEnded.ExecuteIfBound(DragKey);
DragKey = EKeys::Invalid;
}
MaybeRelease(effectingButton);
}
if(reply.GetMouseCaptor().IsValid() == false && HasMouseCapture())
reply.ReleaseMouseCapture();
Invalidate(EInvalidateWidgetReason::Layout);
return reply;
}
FReply SRTMCursorReceiver::OnMouseButtonDoubleClick(const FGeometry& myGeometry, const FPointerEvent& mouseEvent)
{
return OnMouseButtonDown(myGeometry, mouseEvent);
}
FReply SRTMCursorReceiver::OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& mouseEvent)
{
if(bDetectDragEvents && FSlateApplication::Get().HasTraveledFarEnoughToTriggerDrag(mouseEvent, PressedCursorPosition))
MaybeRelease(EKeys::AnyKey);
auto delta = mouseEvent.GetCursorDelta();
if(!delta.IsNearlyZero())
(IsDragging() ? OnDragMoved : OnMoved).ExecuteIfBound(delta);
return FReply::Unhandled();
}
void SRTMCursorReceiver::OnMouseCaptureLost(const FCaptureLostEvent& captureLostEvent)
{
if(HasMouseCapture())
MaybeRelease(EKeys::AnyKey);
}
FReply SRTMCursorReceiver::OnMouseWheel(const FGeometry& myGeometry, const FPointerEvent& mouseEvent)
{
OnWheelChanged.ExecuteIfBound(mouseEvent.GetWheelDelta());
return FReply::Handled();
}
FReply SRTMCursorReceiver::OnDragDetected(const FGeometry& myGeometry, const FPointerEvent& mouseEvent)
{
Super::OnDragDetected(myGeometry, mouseEvent);
DragKey = ActiveKey;
MaybeRelease(ActiveKey);
OnDragStarted.ExecuteIfBound(DragKey);
return FReply::Handled();
}
FReply SRTMCursorReceiver::ExecuteOnClick(const FKey& key)
{
if(OnClicked.IsBound())
{
FReply reply = OnClicked.Execute(key);
//#if WITH_ACCESSIBILITY
// FSlateApplicationBase::Get().GetAccessibleMessageHandler()->OnWidgetEventRaised(AsShared(), EAccessibleEvent::Activate);
//#endif
return reply;
}
return FReply::Handled();
}
void SRTMCursorReceiver::MaybePress(FKey key)
{
if(ActiveKey == EKeys::Invalid)
{
ActiveKey = key;
OnPressed.ExecuteIfBound(key);
}
}
void SRTMCursorReceiver::MaybeRelease(FKey key)
{
if(ActiveKey == key)
{
ActiveKey = EKeys::Invalid;
OnReleased.ExecuteIfBound(key);
}
}
FKey SRTMCursorReceiver::GetEffectiveCursorButton(const FPointerEvent& mouseEvent)
{
if(mouseEvent.IsTouchEvent())
return mouseEvent.GetPointerIndex() == 0 ? EKeys::LeftMouseButton : EKeys::Invalid;
return mouseEvent.GetEffectingButton();
}
bool SRTMCursorReceiver::IsInterestingCursorButton(const FPointerEvent& mouseEvent)
{
const FKey effectingButton = GetEffectiveCursorButton(mouseEvent);
return effectingButton == EKeys::LeftMouseButton || effectingButton == EKeys::RightMouseButton;
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
// Copyright (c) Richard Meredith AB. All Rights Reserved
#pragma once
#include "CoreMinimal.h"
#include "Widgets/SCompoundWidget.h"
DECLARE_DELEGATE_OneParam(FRTMCursorReceiverDelegate, const FKey&)
DECLARE_DELEGATE_OneParam(FRTMCursorReceiverMoveDelegate, const FVector2D&)
DECLARE_DELEGATE_OneParam(FRTMCursorReceiverWheelDelegate, float)
DECLARE_DELEGATE_RetVal_OneParam(FReply, FRTMCursorReceiverClickDelegate, const FKey&)
class RTMCOMMON_API SRTMCursorReceiver : public SCompoundWidget
{
private:
using Super = SCompoundWidget;
public:
SLATE_BEGIN_ARGS(SRTMCursorReceiver) {}
SLATE_ARGUMENT(bool, bDetectDragEvents)
SLATE_EVENT(FSimpleDelegate, OnHovered)
SLATE_EVENT(FSimpleDelegate, OnUnhovered)
SLATE_EVENT(FRTMCursorReceiverMoveDelegate, OnMoved)
SLATE_EVENT(FRTMCursorReceiverDelegate, OnPressed)
SLATE_EVENT(FRTMCursorReceiverDelegate, OnReleased)
SLATE_EVENT(FRTMCursorReceiverClickDelegate, OnClicked)
SLATE_EVENT(FRTMCursorReceiverDelegate, OnDragStarted)
SLATE_EVENT(FRTMCursorReceiverDelegate, OnDragEnded)
SLATE_EVENT(FRTMCursorReceiverMoveDelegate, OnDragMoved)
SLATE_EVENT(FRTMCursorReceiverWheelDelegate, OnWheelChanged)
SLATE_DEFAULT_SLOT(FArguments, Content)
SLATE_END_ARGS()
void Construct(const FArguments& InArgs);
FORCEINLINE bool IsButtonPressed() const { return ActiveKey != EKeys::Invalid; }
FORCEINLINE bool IsDragging() const { return DragKey != EKeys::Invalid; }
protected:
virtual bool SupportsKeyboardFocus() const override { return false; }
virtual bool IsInteractable() const override { return IsEnabled(); }
virtual bool ComputeVolatility() const override;
virtual void OnMouseEnter(const FGeometry& myGeometry, const FPointerEvent& mouseEvent) override;
virtual void OnMouseLeave(const FPointerEvent& MouseEvent) override;
virtual FReply OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& mouseEvent) override;
virtual FReply OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& mouseEvent) override;
virtual FReply OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& mouseEvent) override;
virtual FReply OnMouseButtonDoubleClick(const FGeometry& myGeometry, const FPointerEvent& mouseEvent) override;
virtual void OnMouseCaptureLost(const FCaptureLostEvent& captureLostEvent) override;
virtual FReply OnMouseWheel(const FGeometry& myGeometry, const FPointerEvent& mouseEvent) override;
virtual FReply OnDragDetected(const FGeometry& myGeometry, const FPointerEvent& mouseEvent) override;
private:
bool bDetectDragEvents = true;
FVector2D PressedCursorPosition = FVector2D::ZeroVector;
FKey ActiveKey = EKeys::Invalid;
FKey DragKey = EKeys::Invalid;
FReply ExecuteOnClick(const FKey& key);
void MaybePress(FKey key);
void MaybeRelease(FKey key);
FSimpleDelegate OnHovered;
FSimpleDelegate OnUnhovered;
FRTMCursorReceiverMoveDelegate OnMoved;
FRTMCursorReceiverDelegate OnPressed;
FRTMCursorReceiverDelegate OnReleased;
FRTMCursorReceiverClickDelegate OnClicked;
FRTMCursorReceiverDelegate OnDragStarted;
FRTMCursorReceiverDelegate OnDragEnded;
FRTMCursorReceiverMoveDelegate OnDragMoved;
FRTMCursorReceiverWheelDelegate OnWheelChanged;
static FKey GetEffectiveCursorButton(const FPointerEvent& mouseEvent);
bool IsInterestingCursorButton(const FPointerEvent& mouseEvent);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment