diff --git a/Source/StevesUEHelpers/Private/StevesUI/FocusableCheckBox.cpp b/Source/StevesUEHelpers/Private/StevesUI/FocusableCheckBox.cpp new file mode 100644 index 0000000..e9d5c15 --- /dev/null +++ b/Source/StevesUEHelpers/Private/StevesUI/FocusableCheckBox.cpp @@ -0,0 +1,78 @@ +#include "StevesUI/FocusableCheckBox.h" +#include "StevesUI/SFocusableCheckBox.h" + + +TSharedRef UFocusableCheckBox::RebuildWidget() +{ + MyCheckbox = SNew(SFocusableCheckBox) + .OnCheckStateChanged( BIND_UOBJECT_DELEGATE(FOnCheckStateChanged, SlateOnCheckStateChangedCallback) ) + .Style(&WidgetStyle) + .HAlign( HorizontalAlignment ) + .ClickMethod(ClickMethod) + .TouchMethod(TouchMethod) + .PressMethod(PressMethod) + .IsFocusable(IsFocusable) + // Our new events + .OnHovered(BIND_UOBJECT_DELEGATE(FSimpleDelegate, SlateHandleHovered)) + .OnUnhovered(BIND_UOBJECT_DELEGATE(FSimpleDelegate, SlateHandleUnhovered)) + .OnFocusReceived(BIND_UOBJECT_DELEGATE(FSimpleDelegate, SlateHandleFocusReceived)) + .OnFocusLost(BIND_UOBJECT_DELEGATE(FSimpleDelegate, SlateHandleFocusLost)) + ; + + if ( GetChildrenCount() > 0 ) + { + MyCheckbox->SetContent(GetContentSlot()->Content ? GetContentSlot()->Content->TakeWidget() : SNullWidget::NullWidget); + } + + // Copy Widget style but make normal same as hovered + FocussedStyle = WidgetStyle; + FocussedStyle.UncheckedImage = FocussedStyle.UncheckedHoveredImage; + FocussedStyle.CheckedImage = FocussedStyle.CheckedHoveredImage; + FocussedStyle.UndeterminedImage = FocussedStyle.UndeterminedHoveredImage; + + return MyCheckbox.ToSharedRef(); + +} + +void UFocusableCheckBox::SlateHandleFocusReceived() +{ + ApplyFocusStyle(); + OnFocusReceived.Broadcast(); +} + +void UFocusableCheckBox::SlateHandleFocusLost() +{ + UndoFocusStyle(); + OnFocusLost.Broadcast(); +} + + +void UFocusableCheckBox::ApplyFocusStyle() +{ + if (MyCheckbox.IsValid() && bUseHoverStyleWhenFocussed) + { + MyCheckbox->SetStyle(&FocussedStyle); + } +} + +void UFocusableCheckBox::UndoFocusStyle() +{ + if (MyCheckbox.IsValid()) + { + MyCheckbox->SetStyle(&WidgetStyle); + } +} + +void UFocusableCheckBox::SlateHandleHovered() +{ + if (bTakeFocusOnHover) + { + SetFocus(); + } + OnHovered.Broadcast(); +} + +void UFocusableCheckBox::SlateHandleUnhovered() +{ + OnUnhovered.Broadcast(); +} diff --git a/Source/StevesUEHelpers/Private/StevesUI/SFocusableCheckBox.cpp b/Source/StevesUEHelpers/Private/StevesUI/SFocusableCheckBox.cpp new file mode 100644 index 0000000..38c583b --- /dev/null +++ b/Source/StevesUEHelpers/Private/StevesUI/SFocusableCheckBox.cpp @@ -0,0 +1,93 @@ +#include "SFocusableCheckBox.h" + +void SFocusableCheckBox::Construct(const FArguments& InArgs) +{ + // Call superclass, have to re-construct the args because they're not compatible + SCheckBox::Construct(SCheckBox::FArguments() + .Padding(InArgs._Padding) + .Style(InArgs._Style) + .Type(InArgs._Type) + .CheckedImage(InArgs._CheckedImage) + .ClickMethod(InArgs._ClickMethod) + .ForegroundColor(InArgs._ForegroundColor) + .HAlign(InArgs._HAlign) + .IsChecked(InArgs._IsChecked) + .IsFocusable(InArgs._IsFocusable) + .PressMethod(InArgs._PressMethod) + .TouchMethod(InArgs._TouchMethod) + .UncheckedImage(InArgs._UncheckedImage) + .UndeterminedImage(InArgs._UndeterminedImage) + .CheckedImage(InArgs._CheckedImage) + .UncheckedHoveredImage(InArgs._UncheckedHoveredImage) + .UndeterminedHoveredImage(InArgs._UndeterminedHoveredImage) + .CheckedHoveredImage(InArgs._CheckedHoveredImage) + .UncheckedPressedImage(InArgs._UncheckedPressedImage) + .UndeterminedPressedImage(InArgs._UndeterminedPressedImage) + .CheckedPressedImage(InArgs._CheckedPressedImage) + .CheckedSoundOverride(InArgs._CheckedSoundOverride) + .UncheckedSoundOverride(InArgs._UncheckedSoundOverride) + .HoveredSoundOverride(InArgs._HoveredSoundOverride) + .BorderBackgroundColor(InArgs._BorderBackgroundColor) + .OnCheckStateChanged(InArgs._OnCheckStateChanged) + .IsEnabled(InArgs._IsEnabled) + .Clipping(InArgs._Clipping) + .Cursor(InArgs._Cursor) + .Tag(InArgs._Tag) + .Visibility(InArgs._Visibility) + .AccessibleParams(InArgs._AccessibleParams) + .AccessibleText(InArgs._AccessibleText) + .ForceVolatile(InArgs._ForceVolatile) + .IsEnabled(InArgs._IsEnabled) + .RenderOpacity(InArgs._RenderOpacity) + .RenderTransform(InArgs._RenderTransform) + .RenderTransformPivot(InArgs._RenderTransformPivot) + .ToolTip(InArgs._ToolTip) + .ToolTipText(InArgs._ToolTipText) + ); + + OnHoveredDelegate = InArgs._OnHovered; + OnUnhoveredDelegate = InArgs._OnUnhovered; + OnFocusReceivedDelegate = InArgs._OnFocusReceived; + OnFocusLostDelegate = InArgs._OnFocusLost; + + +} + + +void SFocusableCheckBox::OnMouseEnter(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) +{ + SCheckBox::OnMouseEnter(MyGeometry, MouseEvent); + + // SCheckbox doesn't have hovered / unhovered events so we need to add them +} + +void SFocusableCheckBox::OnMouseLeave(const FPointerEvent& MouseEvent) +{ + SCheckBox::OnMouseLeave(MouseEvent); +} + +FReply SFocusableCheckBox::OnFocusReceived(const FGeometry& MyGeometry, const FFocusEvent& InFocusEvent) +{ + auto Ret = SCheckBox::OnFocusReceived(MyGeometry, InFocusEvent); + + OnFocusReceivedDelegate.ExecuteIfBound(); + + return Ret; +} + +void SFocusableCheckBox::OnFocusLost(const FFocusEvent& InFocusEvent) +{ + SCheckBox::OnFocusLost(InFocusEvent); + + OnFocusLostDelegate.ExecuteIfBound(); +} + +void SFocusableCheckBox::SetOnFocusReceived(FSimpleDelegate InOnFocusReceived) +{ + OnFocusReceivedDelegate = InOnFocusReceived; +} + +void SFocusableCheckBox::SetOnFocusLost(FSimpleDelegate InOnFocusLost) +{ + OnFocusLostDelegate = InOnFocusLost; +} diff --git a/Source/StevesUEHelpers/Private/StevesUI/SFocusableCheckBox.h b/Source/StevesUEHelpers/Private/StevesUI/SFocusableCheckBox.h new file mode 100644 index 0000000..87b6ce2 --- /dev/null +++ b/Source/StevesUEHelpers/Private/StevesUI/SFocusableCheckBox.h @@ -0,0 +1,146 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Widgets/Input/SCheckBox.h" + + +class SFocusableCheckBox : public SCheckBox +{ + public: + + // Seems in slate we have to duplicate all the args from superclass + SLATE_BEGIN_ARGS( SFocusableCheckBox ) + : _Content() + , _Style( &FCoreStyle::Get().GetWidgetStyle< FCheckBoxStyle >("Checkbox") ) + , _Type() + , _OnCheckStateChanged() + , _IsChecked( ECheckBoxState::Unchecked ) + , _HAlign( HAlign_Fill ) + , _CheckBoxContentUsesAutoWidth(true) + , _Padding() + , _ClickMethod( EButtonClickMethod::DownAndUp ) + , _TouchMethod(EButtonTouchMethod::DownAndUp) + , _PressMethod(EButtonPressMethod::DownAndUp) + , _ForegroundColor() + , _BorderBackgroundColor () + , _IsFocusable( true ) + , _UncheckedImage( nullptr ) + , _UncheckedHoveredImage( nullptr ) + , _UncheckedPressedImage( nullptr ) + , _CheckedImage( nullptr ) + , _CheckedHoveredImage( nullptr ) + , _CheckedPressedImage( nullptr ) + , _UndeterminedImage( nullptr ) + , _UndeterminedHoveredImage( nullptr ) + , _UndeterminedPressedImage( nullptr ) + { + } + /** Content to be placed next to the check box, or for a toggle button, the content to be placed inside the button */ + SLATE_DEFAULT_SLOT( FArguments, Content ) + + /** The style structure for this checkbox' visual style */ + SLATE_STYLE_ARGUMENT( FCheckBoxStyle, Style ) + + /** Type of check box (set by the Style arg but the Style can be overridden with this) */ + SLATE_ARGUMENT( TOptional, Type ) + + /** Called when the checked state has changed */ + SLATE_EVENT( FOnCheckStateChanged, OnCheckStateChanged ) + + /** Whether the check box is currently in a checked state */ + SLATE_ATTRIBUTE( ECheckBoxState, IsChecked ) + + /** How the content of the toggle button should align within the given space*/ + SLATE_ARGUMENT( EHorizontalAlignment, HAlign ) + + /** Whether or not the content portion of the checkbox should layout using auto-width. When true the content will always be arranged at its desired size as opposed to resizing to the available space. */ + SLATE_ARGUMENT(bool, CheckBoxContentUsesAutoWidth) + + /** Spacing between the check box image and its content (set by the Style arg but the Style can be overridden with this) */ + SLATE_ATTRIBUTE( FMargin, Padding ) + + /** Sets the rules to use for determining whether the button was clicked. This is an advanced setting and generally should be left as the default. */ + SLATE_ARGUMENT( EButtonClickMethod::Type, ClickMethod ) + + /** How should the button be clicked with touch events? */ + SLATE_ARGUMENT(EButtonTouchMethod::Type, TouchMethod) + + /** How should the button be clicked with keyboard/controller button events? */ + SLATE_ARGUMENT(EButtonPressMethod::Type, PressMethod) + + /** Foreground color for the checkbox's content and parts (set by the Style arg but the Style can be overridden with this) */ + SLATE_ATTRIBUTE( FSlateColor, ForegroundColor ) + + /** The color of the background border (set by the Style arg but the Style can be overridden with this) */ + SLATE_ATTRIBUTE( FSlateColor, BorderBackgroundColor ) + + SLATE_ARGUMENT( bool, IsFocusable ) + + SLATE_EVENT( FOnGetContent, OnGetMenuContent ) + + /** The sound to play when the check box is checked */ + SLATE_ARGUMENT( TOptional, CheckedSoundOverride ) + + /** The sound to play when the check box is unchecked */ + SLATE_ARGUMENT( TOptional, UncheckedSoundOverride ) + + /** The sound to play when the check box is hovered */ + SLATE_ARGUMENT( TOptional, HoveredSoundOverride ) + + /** The unchecked image for the checkbox - overrides the style's */ + SLATE_ARGUMENT(const FSlateBrush*, UncheckedImage) + + /** The unchecked hovered image for the checkbox - overrides the style's */ + SLATE_ARGUMENT(const FSlateBrush*, UncheckedHoveredImage) + + /** The unchecked pressed image for the checkbox - overrides the style's */ + SLATE_ARGUMENT(const FSlateBrush*, UncheckedPressedImage) + + /** The checked image for the checkbox - overrides the style's */ + SLATE_ARGUMENT(const FSlateBrush*, CheckedImage) + + /** The checked hovered image for the checkbox - overrides the style's */ + SLATE_ARGUMENT(const FSlateBrush*, CheckedHoveredImage) + + /** The checked pressed image for the checkbox - overrides the style's */ + SLATE_ARGUMENT(const FSlateBrush*, CheckedPressedImage) + + /** The undetermined image for the checkbox - overrides the style's */ + SLATE_ARGUMENT(const FSlateBrush*, UndeterminedImage) + + /** The undetermined hovered image for the checkbox - overrides the style's */ + SLATE_ARGUMENT(const FSlateBrush*, UndeterminedHoveredImage) + + /** The undetermined pressed image for the checkbox - overrides the style's */ + SLATE_ARGUMENT(const FSlateBrush*, UndeterminedPressedImage) + + // This is the bit we're adding + // SCheckBox is missing hovered/unhovered too + SLATE_EVENT( FSimpleDelegate, OnHovered ) + SLATE_EVENT( FSimpleDelegate, OnUnhovered ) + SLATE_EVENT( FSimpleDelegate, OnFocusReceived ) + SLATE_EVENT( FSimpleDelegate, OnFocusLost ) + + SLATE_END_ARGS() + +public: + + void Construct( const FArguments& InArgs ); + + virtual FReply OnFocusReceived(const FGeometry& MyGeometry, const FFocusEvent& InFocusEvent) override; + virtual void OnFocusLost(const FFocusEvent& InFocusEvent) override; + + void SetOnFocusReceived(FSimpleDelegate InOnFocusReceived); + void SetOnFocusLost(FSimpleDelegate InOnFocusLost); + +protected: + FSimpleDelegate OnHoveredDelegate; + FSimpleDelegate OnUnhoveredDelegate; + FSimpleDelegate OnFocusReceivedDelegate; + FSimpleDelegate OnFocusLostDelegate; + + +public: + virtual void OnMouseEnter(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; + virtual void OnMouseLeave(const FPointerEvent& MouseEvent) override; +}; \ No newline at end of file diff --git a/Source/StevesUEHelpers/Public/StevesUI/FocusableCheckBox.h b/Source/StevesUEHelpers/Public/StevesUI/FocusableCheckBox.h new file mode 100644 index 0000000..509c349 --- /dev/null +++ b/Source/StevesUEHelpers/Public/StevesUI/FocusableCheckBox.h @@ -0,0 +1,59 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Components/CheckBox.h" +#include "FocusableCheckBox.generated.h" + + +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnCheckBoxHoveredEvent); +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnCheckBoxUnhoveredEvent); +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnCheckBoxFocusReceivedEvent); +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnCheckBoxFocusLostEvent); +/** +* This is a simple subclass of UCheckBox to provide some missing features +* +* * Focus events +* * Hover events +* * Focus style based on hover style +* * Assign focus to self on hover to prevent double-highlighting +*/ +UCLASS() +class STEVESUEHELPERS_API UFocusableCheckBox : public UCheckBox +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintReadWrite, EditAnywhere) + bool bUseHoverStyleWhenFocussed = true; + + UPROPERTY( BlueprintAssignable, Category = "CheckBox|Event" ) + FOnCheckBoxHoveredEvent OnHovered; + + UPROPERTY( BlueprintAssignable, Category = "CheckBox|Event" ) + FOnCheckBoxUnhoveredEvent OnUnhovered; + + + UPROPERTY(BlueprintAssignable, Category="CheckBox|Event") + FOnCheckBoxFocusReceivedEvent OnFocusReceived; + + UPROPERTY(BlueprintAssignable, Category="CheckBox|Event") + FOnCheckBoxFocusLostEvent OnFocusLost; + + UPROPERTY(BlueprintReadWrite, EditAnywhere) + bool bTakeFocusOnHover = true; +protected: + FCheckBoxStyle FocussedStyle; + + void SlateHandleFocusReceived(); + void SlateHandleFocusLost(); + + void ApplyFocusStyle(); + void UndoFocusStyle(); + void SlateHandleHovered(); + void SlateHandleUnhovered(); + + virtual TSharedRef RebuildWidget() override; + +};