From dc085003476b138934ef35fb96b695904477928c Mon Sep 17 00:00:00 2001 From: Thibaut Spreux Date: Wed, 24 Apr 2024 22:31:28 +0200 Subject: [PATCH 01/10] start of SplitCommonBoundActionBar --- .../Private/GBFSplitCommonBoundActionBar.cpp | 365 ++++++++++++++++++ .../Public/GBFSplitCommonBoundActionBar.h | 66 ++++ 2 files changed, 431 insertions(+) create mode 100644 Source/GameBaseFramework/Private/GBFSplitCommonBoundActionBar.cpp create mode 100644 Source/GameBaseFramework/Public/GBFSplitCommonBoundActionBar.h diff --git a/Source/GameBaseFramework/Private/GBFSplitCommonBoundActionBar.cpp b/Source/GameBaseFramework/Private/GBFSplitCommonBoundActionBar.cpp new file mode 100644 index 00000000..fbeba7b1 --- /dev/null +++ b/Source/GameBaseFramework/Private/GBFSplitCommonBoundActionBar.cpp @@ -0,0 +1,365 @@ +#include "GBFSplitCommonBoundActionBar.h" + +#include "CommonInputSubsystem.h" +#include "CommonInputTypeEnum.h" +#include "CommonUITypes.h" +#include "Editor/WidgetCompilerLog.h" +#include "Engine/GameInstance.h" +#include "Engine/GameViewportClient.h" +#include "Input/CommonBoundActionButtonInterface.h" +#include "Input/CommonUIActionRouterBase.h" +#include "Input/UIActionBinding.h" +#include "InputAction.h" +#include "OnlineSubsystemUtils.h" + +bool bActionBarIgnoreOptOut = false; +static FAutoConsoleVariableRef CVarActionBarIgnoreOptOut( + TEXT( "ActionBar.IgnoreOptOut" ), + bActionBarIgnoreOptOut, + TEXT( "If true, the Bound Action Bar will display bindings whether or not they are configured bDisplayInReflector" ), + ECVF_Default ); + +void UGBFSplitCommonBoundActionBar::Tick( float delta_time ) +{ + if ( bIsRefreshQueued ) + { + HandleDeferredDisplayUpdate(); + } +} + +ETickableTickType UGBFSplitCommonBoundActionBar::GetTickableTickType() const +{ + return ETickableTickType::Always; +} + +TStatId UGBFSplitCommonBoundActionBar::GetStatId() const +{ + RETURN_QUICK_DECLARE_CYCLE_STAT( UGBFSplitCommonBoundActionBar, STATGROUP_Tickables ) +} + +bool UGBFSplitCommonBoundActionBar::IsTickableWhenPaused() const +{ + return true; +} +bool UGBFSplitCommonBoundActionBar::IsEntryClassValid( TSubclassOf< UUserWidget > in_entry_class ) const +{ + if ( in_entry_class ) + { + // Would InEntryClass create an instance of the same DynamicEntryBox + if ( UWidgetTree * widget_tree = Cast< UWidgetTree >( GetOuter() ) ) + { + if ( UUserWidget * user_widget = Cast< UUserWidget >( widget_tree->GetOuter() ) ) + { + if ( in_entry_class->IsChildOf( user_widget->GetClass() ) ) + { + return false; + } + } + } + } + + return true; +} + +void UGBFSplitCommonBoundActionBar::OnWidgetRebuilt() +{ + Super::OnWidgetRebuilt(); + + if ( const auto * game_instance = GetGameInstance() ) + { + if ( game_instance->GetGameViewportClient() ) + { + game_instance->GetGameViewportClient()->OnPlayerAdded().AddUObject( this, &UGBFSplitCommonBoundActionBar::HandlePlayerAdded ); + } + + for ( const auto * local_player : game_instance->GetLocalPlayers() ) + { + MonitorPlayerActions( local_player ); + } + + // Establish entries (as needed) immediately upon construction + HandleDeferredDisplayUpdate(); + } +} + +void UGBFSplitCommonBoundActionBar::SynchronizeProperties() +{ + Super::SynchronizeProperties(); +} + +void UGBFSplitCommonBoundActionBar::ReleaseSlateResources( bool release_children ) +{ + Super::ReleaseSlateResources( release_children ); + + if ( const auto * game_instance = GetGameInstance() ) + { + for ( const auto * local_player : game_instance->GetLocalPlayers() ) + { + if ( const auto * action_router = ULocalPlayer::GetSubsystem< UCommonUIActionRouterBase >( local_player ) ) + { + action_router->OnBoundActionsUpdated().RemoveAll( this ); + } + } + } +} + +void UGBFSplitCommonBoundActionBar::AddEntryChild( UUserWidget & child_widget ) +{ + if ( MyPanelWidget.IsValid() ) + { + child_widget.TakeWidget(); + } +} + +namespace UGBFSplitCommonBoundActionBarInternal +{ + TArray< TSubclassOf< UUserWidget >, TInlineAllocator< 4 > > recursive_detection; +} + +UUserWidget * UGBFSplitCommonBoundActionBar::CreateEntryInternal( TSubclassOf< UUserWidget > in_entry_class ) +{ + const auto has_recursive_user_widget = UGBFSplitCommonBoundActionBarInternal::recursive_detection.ContainsByPredicate( [ in_entry_class ]( TSubclassOf< UUserWidget > recursive_item ) { + return in_entry_class->IsChildOf( recursive_item ); + } ); + if ( has_recursive_user_widget ) + { + UE_LOG( LogSlate, Error, TEXT( "'%s' cannot be added to DynamicEntry '%s' because it is already a child and it would create a recurssion." ), *in_entry_class->GetName(), *UGBFSplitCommonBoundActionBarInternal::recursive_detection.Last()->GetName() ); +#if 0 + for (TSubclassOf recursive_item : UGBFSplitCommonBoundActionBarInternal::recursive_detection) + { + UE_LOG(LogSlate, Log, TEXT("%s"), *recursive_item->GetName()); + } +#endif + return nullptr; + } + UGBFSplitCommonBoundActionBarInternal::recursive_detection.Push( in_entry_class ); + + UUserWidget * new_entry_widget = EntryWidgetPool.GetOrCreateInstance( in_entry_class ); + if ( MyPanelWidget.IsValid() ) + { + // If we've already been constructed, immediately add the child to our panel widget + AddEntryChild( *new_entry_widget ); + } + + UGBFSplitCommonBoundActionBarInternal::recursive_detection.Pop(); + return new_entry_widget; + + return in_entry_class.GetDefaultObject(); +} + +#if WITH_EDITOR +void UGBFSplitCommonBoundActionBar::ValidateCompiledDefaults( IWidgetCompilerLog & compile_log ) const +{ + Super::ValidateCompiledDefaults( compile_log ); + + if ( !ActionButtonClass ) + { + compile_log.Error( FText::FromString( FString::Printf( TEXT( "Error_BoundActionBar_MissingButtonClass, {0} has no ActionButtonClass specified." ) ) ) ); + } + else if ( compile_log.GetContextClass() && ActionButtonClass->IsChildOf( compile_log.GetContextClass() ) ) + { + compile_log.Error( FText::FromString( FString::Printf( TEXT( "Error_BoundActionBar_RecursiveButtonClass, {0} has a recursive ActionButtonClass specified (reference itself)." ) ) ) ); + } +} +#endif + +void UGBFSplitCommonBoundActionBar::HandleBoundActionsUpdated( bool from_owning_player ) +{ + if ( from_owning_player || !bDisplayOwningPlayerActionsOnly ) + { + bIsRefreshQueued = true; + } +} + +void UGBFSplitCommonBoundActionBar::HandleDeferredDisplayUpdate() +{ + bIsRefreshQueued = false; + //: TODO: implement a ResetFunction in this class + // ResetInternal(); + + const auto * game_instance = GetGameInstance(); + check( game_instance ); + const auto * owning_local_player = GetOwningLocalPlayer(); + + auto sorted_players = game_instance->GetLocalPlayers(); + sorted_players.StableSort( + [ &owning_local_player ]( const ULocalPlayer & PlayerA, const ULocalPlayer & PlayerB ) { + return &PlayerA != owning_local_player; + } ); + + for ( const ULocalPlayer * LocalPlayer : sorted_players ) + { + if ( LocalPlayer == owning_local_player || !bDisplayOwningPlayerActionsOnly ) + { + if ( IsEntryClassValid( ActionButtonClass ) ) + { + if ( const auto * action_router = ULocalPlayer::GetSubsystem< UCommonUIActionRouterBase >( owning_local_player ) ) + { + const auto & input_subsystem = action_router->GetInputSubsystem(); + const auto player_input_type = input_subsystem.GetCurrentInputType(); + const auto & player_gamepad_name = input_subsystem.GetCurrentGamepadName(); + + TSet< FName > accepted_bindings; + auto filtered_bindings = action_router->GatherActiveBindings().FilterByPredicate( [ action_router, player_input_type, player_gamepad_name, &accepted_bindings ]( const auto & handle ) mutable { + if ( auto binding = FUIActionBinding::FindBinding( handle ) ) + { + if ( !binding->bDisplayInActionBar && !bActionBarIgnoreOptOut ) + { + return false; + } + + if ( CommonUI::IsEnhancedInputSupportEnabled() ) + { + if ( auto input_action = binding->InputAction.Get() ) + { + return CommonUI::ActionValidForInputType( action_router->GetLocalPlayer(), player_input_type, input_action ); + } + } + + if ( auto * legacy_Data = binding->GetLegacyInputActionData() ) + { + if ( !legacy_Data->CanDisplayInReflector( player_input_type, player_gamepad_name ) ) + { + return false; + } + } + else + { + return false; + } + + bool already_accepted = false; + accepted_bindings.Add( binding->ActionName, &already_accepted ); + return !already_accepted; + } + + return false; + } ); + + Algo::Sort( filtered_bindings, [ action_router, player_input_type, player_gamepad_name ]( const FUIActionBindingHandle & handle_a, const FUIActionBindingHandle & handle_b ) { + auto binding_a = FUIActionBinding::FindBinding( handle_a ); + auto binding_b = FUIActionBinding::FindBinding( handle_b ); + + if ( ensureMsgf( ( binding_a && binding_b ), TEXT( "The array filter above should enforce that there are no null bindings" ) ) ) + { + auto is_key_back_action = [ action_router, player_input_type, player_gamepad_name ]( FCommonInputActionDataBase * legacy_data, const auto * input_action ) { + if ( legacy_data ) + { + auto key = legacy_data->GetInputTypeInfo( player_input_type, player_gamepad_name ).GetKey(); + + if ( player_input_type == ECommonInputType::Touch ) + { + if ( !key.IsValid() ) + { + key = legacy_data->GetInputTypeInfo( ECommonInputType::MouseAndKeyboard, player_gamepad_name ).GetKey(); + } + } + + return key == EKeys::Virtual_Back || key == EKeys::Escape || key == EKeys::Android_Back; + } + else if ( input_action ) + { + auto key = CommonUI::GetFirstKeyForInputType( action_router->GetLocalPlayer(), player_input_type, input_action ); + + if ( player_input_type == ECommonInputType::Touch ) + { + if ( !key.IsValid() ) + { + key = CommonUI::GetFirstKeyForInputType( action_router->GetLocalPlayer(), ECommonInputType::MouseAndKeyboard, input_action ); + } + } + + return key == EKeys::Virtual_Back || key == EKeys::Escape || key == EKeys::Android_Back; + } + + return false; + }; + + auto get_navbar_priority = []( FCommonInputActionDataBase * legacy_data, const auto * input_action ) { + if ( legacy_data ) + { + return legacy_data->NavBarPriority; + } + else if ( input_action ) + { + if ( auto input_action_meta_data = CommonUI::GetEnhancedInputActionMetadata( input_action ) ) + { + return input_action_meta_data->NavBarPriority; + } + } + + return 0; + }; + + auto legacy_data_a = binding_a->GetLegacyInputActionData(); + auto legacy_data_b = binding_b->GetLegacyInputActionData(); + + const UInputAction * input_action_a = nullptr; + const UInputAction * input_action_b = nullptr; + + if ( CommonUI::IsEnhancedInputSupportEnabled() ) + { + input_action_a = binding_a->InputAction.Get(); + input_action_b = binding_b->InputAction.Get(); + } + + bool is_valid_action_a = legacy_data_a || input_action_a; + bool is_valid_action_b = legacy_data_b || input_action_b; + + if ( ensureMsgf( ( is_valid_action_a && is_valid_action_b ), TEXT( "Action bindings not displayed yet -- array filter enforces they are not included" ) ) ) + { + bool a_is_back = is_key_back_action( legacy_data_a, input_action_a ); + bool b_is_back = is_key_back_action( legacy_data_b, input_action_b ); + + if ( a_is_back && b_is_back ) + { + return false; + } + + int32 nav_bar_priority_a = get_navbar_priority( legacy_data_a, input_action_a ); + int32 nav_bar_priority_b = get_navbar_priority( legacy_data_b, input_action_b ); + + if ( nav_bar_priority_a != nav_bar_priority_b ) + { + return nav_bar_priority_a < nav_bar_priority_b; + } + } + + return GetTypeHash( binding_a->Handle ) < GetTypeHash( binding_b->Handle ); + } + + return true; + } ); + + for ( int binding_index = 0; binding_index < filtered_bindings.Num() - 1; binding_index++ ) + { + auto * action_button = Cast< ICommonBoundActionButtonInterface >( CreateEntryInternal( ActionButtonClass ) ); + + if ( ensure( action_button ) ) + { + action_button->SetRepresentedAction( filtered_bindings[ binding_index ] ); + NativeOnActionButtonCreated( action_button, filtered_bindings[ binding_index ] ); + } + } + + //: TODO: handle the last item of filtered_bindings wich is the back handler + } + } + } + } +} + +void UGBFSplitCommonBoundActionBar::HandlePlayerAdded( int32 player_id ) +{ + const ULocalPlayer * new_player = GetGameInstance()->GetLocalPlayerByIndex( player_id ); + MonitorPlayerActions( new_player ); + HandleBoundActionsUpdated( new_player == GetOwningLocalPlayer() ); +} + +void UGBFSplitCommonBoundActionBar::MonitorPlayerActions( const ULocalPlayer * new_player ) +{ + if ( const auto * action_router = ULocalPlayer::GetSubsystem< UCommonUIActionRouterBase >( new_player ) ) + { + action_router->OnBoundActionsUpdated().AddUObject( this, &UGBFSplitCommonBoundActionBar::HandleBoundActionsUpdated, new_player == GetOwningLocalPlayer() ); + } +} diff --git a/Source/GameBaseFramework/Public/GBFSplitCommonBoundActionBar.h b/Source/GameBaseFramework/Public/GBFSplitCommonBoundActionBar.h new file mode 100644 index 00000000..ee6128c3 --- /dev/null +++ b/Source/GameBaseFramework/Public/GBFSplitCommonBoundActionBar.h @@ -0,0 +1,66 @@ +#pragma once + +#include "CommonButtonBase.h" + +#include +#include +#include + +#include "GBFSplitCommonBoundActionBar.generated.h" + +class ICommonBoundActionButtonInterface; +struct FUIActionBindingHandle; + +/** + * split action bar with one part snap to the left fort the back handler action and display the other actions snap to the right + */ +UCLASS() +class GAMEBASEFRAMEWORK_API UGBFSplitCommonBoundActionBar : public UUserWidget, public FTickableGameObject +{ + GENERATED_BODY() + +public: + // FTickableGameObject Begin + void Tick( float delta_time ) override; + ETickableTickType GetTickableTickType() const override; + TStatId GetStatId() const override; + bool IsTickableWhenPaused() const override; + // FTickableGameObject End + +protected: + bool IsEntryClassValid(TSubclassOf in_entry_class) const; + void OnWidgetRebuilt() override; + void SynchronizeProperties() override; + void ReleaseSlateResources( bool release_children ) override; + void AddEntryChild(UUserWidget& child_widget); + UUserWidget * CreateEntryInternal( TSubclassOf< UUserWidget > in_entry_class ); + + virtual void NativeOnActionButtonCreated( ICommonBoundActionButtonInterface * ActionButton, const FUIActionBindingHandle & RepresentedAction ) + {} + +#if WITH_EDITOR + void ValidateCompiledDefaults( IWidgetCompilerLog & compile_log ) const override; +#endif + + TSharedPtr MyPanelWidget; + +private: + void HandleBoundActionsUpdated( bool from_owning_player ); + void HandleDeferredDisplayUpdate(); + void HandlePlayerAdded( int32 player_id ); + void MonitorPlayerActions( const ULocalPlayer * new_player ); + + UPROPERTY( EditAnywhere, Category = EntryLayout, meta = ( MustImplement = "/Script/CommonUI.CommonBoundActionButtonInterface" ) ) + TSubclassOf< UCommonButtonBase > ActionButtonClass; + + UPROPERTY( EditAnywhere, AdvancedDisplay, Category = Display ) + uint8 bIgnoreDuplicateActions : 1; + + UPROPERTY( EditAnywhere, Category = Display ) + uint8 bDisplayOwningPlayerActionsOnly : 1; + + UPROPERTY(Transient) + FUserWidgetPool EntryWidgetPool; + + uint8 bIsRefreshQueued : 1; +}; From f414add5abea07040e72b8b0716bf79f9ca8092d Mon Sep 17 00:00:00 2001 From: Thibaut Spreux Date: Thu, 25 Apr 2024 17:05:35 +0200 Subject: [PATCH 02/10] SplitCommonBoundActionBar V1 --- .../Private/GBFSplitCommonBoundActionBar.cpp | 98 +++++++++++-------- .../Public/GBFSplitCommonBoundActionBar.h | 23 +++-- 2 files changed, 70 insertions(+), 51 deletions(-) diff --git a/Source/GameBaseFramework/Private/GBFSplitCommonBoundActionBar.cpp b/Source/GameBaseFramework/Private/GBFSplitCommonBoundActionBar.cpp index fbeba7b1..97b3f83d 100644 --- a/Source/GameBaseFramework/Private/GBFSplitCommonBoundActionBar.cpp +++ b/Source/GameBaseFramework/Private/GBFSplitCommonBoundActionBar.cpp @@ -19,6 +19,12 @@ static FAutoConsoleVariableRef CVarActionBarIgnoreOptOut( TEXT( "If true, the Bound Action Bar will display bindings whether or not they are configured bDisplayInReflector" ), ECVF_Default ); +UGBFSplitCommonBoundActionBar::UGBFSplitCommonBoundActionBar( const FObjectInitializer & object_initializer ) : + Super( object_initializer ), + WidgetPool( *this ) +{ +} + void UGBFSplitCommonBoundActionBar::Tick( float delta_time ) { if ( bIsRefreshQueued ) @@ -41,6 +47,7 @@ bool UGBFSplitCommonBoundActionBar::IsTickableWhenPaused() const { return true; } + bool UGBFSplitCommonBoundActionBar::IsEntryClassValid( TSubclassOf< UUserWidget > in_entry_class ) const { if ( in_entry_class ) @@ -77,7 +84,6 @@ void UGBFSplitCommonBoundActionBar::OnWidgetRebuilt() MonitorPlayerActions( local_player ); } - // Establish entries (as needed) immediately upon construction HandleDeferredDisplayUpdate(); } } @@ -103,20 +109,12 @@ void UGBFSplitCommonBoundActionBar::ReleaseSlateResources( bool release_children } } -void UGBFSplitCommonBoundActionBar::AddEntryChild( UUserWidget & child_widget ) -{ - if ( MyPanelWidget.IsValid() ) - { - child_widget.TakeWidget(); - } -} - namespace UGBFSplitCommonBoundActionBarInternal { TArray< TSubclassOf< UUserWidget >, TInlineAllocator< 4 > > recursive_detection; } -UUserWidget * UGBFSplitCommonBoundActionBar::CreateEntryInternal( TSubclassOf< UUserWidget > in_entry_class ) +UUserWidget * UGBFSplitCommonBoundActionBar::CreateEntryInternal( TSubclassOf< UUserWidget > in_entry_class, bool is_back_action ) { const auto has_recursive_user_widget = UGBFSplitCommonBoundActionBarInternal::recursive_detection.ContainsByPredicate( [ in_entry_class ]( TSubclassOf< UUserWidget > recursive_item ) { return in_entry_class->IsChildOf( recursive_item ); @@ -132,19 +130,16 @@ UUserWidget * UGBFSplitCommonBoundActionBar::CreateEntryInternal( TSubclassOf< U #endif return nullptr; } + UGBFSplitCommonBoundActionBarInternal::recursive_detection.Push( in_entry_class ); - UUserWidget * new_entry_widget = EntryWidgetPool.GetOrCreateInstance( in_entry_class ); - if ( MyPanelWidget.IsValid() ) - { - // If we've already been constructed, immediately add the child to our panel widget - AddEntryChild( *new_entry_widget ); - } + const auto content = WidgetPool.GetOrCreateInstance( in_entry_class ); + + auto * new_entry_widget = ( is_back_action ? LeftHorizontalBox : RightHorizontalBox )->AddChildToHorizontalBox( content ); UGBFSplitCommonBoundActionBarInternal::recursive_detection.Pop(); - return new_entry_widget; - return in_entry_class.GetDefaultObject(); + return content; } #if WITH_EDITOR @@ -154,11 +149,11 @@ void UGBFSplitCommonBoundActionBar::ValidateCompiledDefaults( IWidgetCompilerLog if ( !ActionButtonClass ) { - compile_log.Error( FText::FromString( FString::Printf( TEXT( "Error_BoundActionBar_MissingButtonClass, {0} has no ActionButtonClass specified." ) ) ) ); + compile_log.Error( FText::FromString( FString::Printf( TEXT( "Error_SplitBoundActionBar_MissingButtonClass, {0} has no ActionButtonClass specified." ) ) ) ); } else if ( compile_log.GetContextClass() && ActionButtonClass->IsChildOf( compile_log.GetContextClass() ) ) { - compile_log.Error( FText::FromString( FString::Printf( TEXT( "Error_BoundActionBar_RecursiveButtonClass, {0} has a recursive ActionButtonClass specified (reference itself)." ) ) ) ); + compile_log.Error( FText::FromString( FString::Printf( TEXT( "Error_SplitBoundActionBar_RecursiveButtonClass, {0} has a recursive ActionButtonClass specified (reference itself)." ) ) ) ); } } #endif @@ -174,8 +169,9 @@ void UGBFSplitCommonBoundActionBar::HandleBoundActionsUpdated( bool from_owning_ void UGBFSplitCommonBoundActionBar::HandleDeferredDisplayUpdate() { bIsRefreshQueued = false; - //: TODO: implement a ResetFunction in this class - // ResetInternal(); + + RightHorizontalBox->ClearChildren(); + LeftHorizontalBox->ClearChildren(); const auto * game_instance = GetGameInstance(); check( game_instance ); @@ -187,9 +183,9 @@ void UGBFSplitCommonBoundActionBar::HandleDeferredDisplayUpdate() return &PlayerA != owning_local_player; } ); - for ( const ULocalPlayer * LocalPlayer : sorted_players ) + for ( const auto * local_player : sorted_players ) { - if ( LocalPlayer == owning_local_player || !bDisplayOwningPlayerActionsOnly ) + if ( local_player == owning_local_player || !bDisplayOwningPlayerActionsOnly ) { if ( IsEntryClassValid( ActionButtonClass ) ) { @@ -237,8 +233,8 @@ void UGBFSplitCommonBoundActionBar::HandleDeferredDisplayUpdate() } ); Algo::Sort( filtered_bindings, [ action_router, player_input_type, player_gamepad_name ]( const FUIActionBindingHandle & handle_a, const FUIActionBindingHandle & handle_b ) { - auto binding_a = FUIActionBinding::FindBinding( handle_a ); - auto binding_b = FUIActionBinding::FindBinding( handle_b ); + const auto binding_a = FUIActionBinding::FindBinding( handle_a ); + const auto binding_b = FUIActionBinding::FindBinding( handle_b ); if ( ensureMsgf( ( binding_a && binding_b ), TEXT( "The array filter above should enforce that there are no null bindings" ) ) ) { @@ -291,8 +287,8 @@ void UGBFSplitCommonBoundActionBar::HandleDeferredDisplayUpdate() return 0; }; - auto legacy_data_a = binding_a->GetLegacyInputActionData(); - auto legacy_data_b = binding_b->GetLegacyInputActionData(); + const auto legacy_data_a = binding_a->GetLegacyInputActionData(); + const auto legacy_data_b = binding_b->GetLegacyInputActionData(); const UInputAction * input_action_a = nullptr; const UInputAction * input_action_b = nullptr; @@ -303,21 +299,21 @@ void UGBFSplitCommonBoundActionBar::HandleDeferredDisplayUpdate() input_action_b = binding_b->InputAction.Get(); } - bool is_valid_action_a = legacy_data_a || input_action_a; - bool is_valid_action_b = legacy_data_b || input_action_b; + const bool is_valid_action_a = legacy_data_a || input_action_a; + const bool is_valid_action_b = legacy_data_b || input_action_b; if ( ensureMsgf( ( is_valid_action_a && is_valid_action_b ), TEXT( "Action bindings not displayed yet -- array filter enforces they are not included" ) ) ) { - bool a_is_back = is_key_back_action( legacy_data_a, input_action_a ); - bool b_is_back = is_key_back_action( legacy_data_b, input_action_b ); + const bool a_is_back = is_key_back_action( legacy_data_a, input_action_a ); + const bool b_is_back = is_key_back_action( legacy_data_b, input_action_b ); if ( a_is_back && b_is_back ) { return false; } - int32 nav_bar_priority_a = get_navbar_priority( legacy_data_a, input_action_a ); - int32 nav_bar_priority_b = get_navbar_priority( legacy_data_b, input_action_b ); + const int32 nav_bar_priority_a = get_navbar_priority( legacy_data_a, input_action_a ); + const int32 nav_bar_priority_b = get_navbar_priority( legacy_data_b, input_action_b ); if ( nav_bar_priority_a != nav_bar_priority_b ) { @@ -331,18 +327,36 @@ void UGBFSplitCommonBoundActionBar::HandleDeferredDisplayUpdate() return true; } ); - for ( int binding_index = 0; binding_index < filtered_bindings.Num() - 1; binding_index++ ) + for ( auto binding_handle : filtered_bindings ) { - auto * action_button = Cast< ICommonBoundActionButtonInterface >( CreateEntryInternal( ActionButtonClass ) ); + const auto binding = FUIActionBinding::FindBinding( binding_handle ); - if ( ensure( action_button ) ) + if ( binding->bDisplayInActionBar ) { - action_button->SetRepresentedAction( filtered_bindings[ binding_index ] ); - NativeOnActionButtonCreated( action_button, filtered_bindings[ binding_index ] ); + FKey key; + bool is_back_action; + + if ( const auto legacy_data = binding->GetLegacyInputActionData() ) + { + key = legacy_data->GetInputTypeInfo( player_input_type, player_gamepad_name ).GetKey(); + is_back_action = key == EKeys::Virtual_Back || key == EKeys::Escape || key == EKeys::Android_Back; + } + else + { + const UInputAction * input_action = binding->InputAction.Get(); + key = CommonUI::GetFirstKeyForInputType( action_router->GetLocalPlayer(), player_input_type, input_action ); + is_back_action = key == EKeys::Virtual_Back || key == EKeys::Escape || key == EKeys::Android_Back; + } + + auto * action_button = Cast< ICommonBoundActionButtonInterface >( CreateEntryInternal( ActionButtonClass, is_back_action ) ); + + if ( ensure( action_button ) ) + { + action_button->SetRepresentedAction( binding_handle ); + NativeOnActionButtonCreated( action_button, binding_handle ); + } } } - - //: TODO: handle the last item of filtered_bindings wich is the back handler } } } @@ -362,4 +376,4 @@ void UGBFSplitCommonBoundActionBar::MonitorPlayerActions( const ULocalPlayer * n { action_router->OnBoundActionsUpdated().AddUObject( this, &UGBFSplitCommonBoundActionBar::HandleBoundActionsUpdated, new_player == GetOwningLocalPlayer() ); } -} +} \ No newline at end of file diff --git a/Source/GameBaseFramework/Public/GBFSplitCommonBoundActionBar.h b/Source/GameBaseFramework/Public/GBFSplitCommonBoundActionBar.h index ee6128c3..cadb0a48 100644 --- a/Source/GameBaseFramework/Public/GBFSplitCommonBoundActionBar.h +++ b/Source/GameBaseFramework/Public/GBFSplitCommonBoundActionBar.h @@ -1,6 +1,7 @@ #pragma once -#include "CommonButtonBase.h" +#include "Components/HorizontalBox.h" +#include "UI/Widgets/GBFBoundActionButton.h" #include #include @@ -20,6 +21,7 @@ class GAMEBASEFRAMEWORK_API UGBFSplitCommonBoundActionBar : public UUserWidget, GENERATED_BODY() public: + explicit UGBFSplitCommonBoundActionBar( const FObjectInitializer & object_initializer ); // FTickableGameObject Begin void Tick( float delta_time ) override; ETickableTickType GetTickableTickType() const override; @@ -28,12 +30,11 @@ class GAMEBASEFRAMEWORK_API UGBFSplitCommonBoundActionBar : public UUserWidget, // FTickableGameObject End protected: - bool IsEntryClassValid(TSubclassOf in_entry_class) const; + bool IsEntryClassValid( TSubclassOf< UUserWidget > in_entry_class ) const; void OnWidgetRebuilt() override; void SynchronizeProperties() override; void ReleaseSlateResources( bool release_children ) override; - void AddEntryChild(UUserWidget& child_widget); - UUserWidget * CreateEntryInternal( TSubclassOf< UUserWidget > in_entry_class ); + UUserWidget * CreateEntryInternal( TSubclassOf< UUserWidget > in_entry_class, bool is_back_action ); virtual void NativeOnActionButtonCreated( ICommonBoundActionButtonInterface * ActionButton, const FUIActionBindingHandle & RepresentedAction ) {} @@ -42,8 +43,6 @@ class GAMEBASEFRAMEWORK_API UGBFSplitCommonBoundActionBar : public UUserWidget, void ValidateCompiledDefaults( IWidgetCompilerLog & compile_log ) const override; #endif - TSharedPtr MyPanelWidget; - private: void HandleBoundActionsUpdated( bool from_owning_player ); void HandleDeferredDisplayUpdate(); @@ -51,7 +50,7 @@ class GAMEBASEFRAMEWORK_API UGBFSplitCommonBoundActionBar : public UUserWidget, void MonitorPlayerActions( const ULocalPlayer * new_player ); UPROPERTY( EditAnywhere, Category = EntryLayout, meta = ( MustImplement = "/Script/CommonUI.CommonBoundActionButtonInterface" ) ) - TSubclassOf< UCommonButtonBase > ActionButtonClass; + TSubclassOf< UGBFBoundActionButton > ActionButtonClass; UPROPERTY( EditAnywhere, AdvancedDisplay, Category = Display ) uint8 bIgnoreDuplicateActions : 1; @@ -59,8 +58,14 @@ class GAMEBASEFRAMEWORK_API UGBFSplitCommonBoundActionBar : public UUserWidget, UPROPERTY( EditAnywhere, Category = Display ) uint8 bDisplayOwningPlayerActionsOnly : 1; - UPROPERTY(Transient) - FUserWidgetPool EntryWidgetPool; + UPROPERTY( meta = ( BindWidget ) ) + TObjectPtr< UHorizontalBox > LeftHorizontalBox; + + UPROPERTY( meta = ( BindWidget ) ) + TObjectPtr< UHorizontalBox > RightHorizontalBox; + + UPROPERTY( Transient ) + FUserWidgetPool WidgetPool; uint8 bIsRefreshQueued : 1; }; From 0b28c693d57635468f28e5a07e17ff0fc4368df1 Mon Sep 17 00:00:00 2001 From: Thibaut Spreux Date: Fri, 26 Apr 2024 12:09:11 +0200 Subject: [PATCH 03/10] add event on bar update and make containers readable from bp --- .../Private/GBFSplitCommonBoundActionBar.cpp | 2 ++ .../Public/GBFSplitCommonBoundActionBar.h | 7 +++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Source/GameBaseFramework/Private/GBFSplitCommonBoundActionBar.cpp b/Source/GameBaseFramework/Private/GBFSplitCommonBoundActionBar.cpp index 97b3f83d..072228b3 100644 --- a/Source/GameBaseFramework/Private/GBFSplitCommonBoundActionBar.cpp +++ b/Source/GameBaseFramework/Private/GBFSplitCommonBoundActionBar.cpp @@ -361,6 +361,8 @@ void UGBFSplitCommonBoundActionBar::HandleDeferredDisplayUpdate() } } } + + OnActionBarUpdated(); } void UGBFSplitCommonBoundActionBar::HandlePlayerAdded( int32 player_id ) diff --git a/Source/GameBaseFramework/Public/GBFSplitCommonBoundActionBar.h b/Source/GameBaseFramework/Public/GBFSplitCommonBoundActionBar.h index cadb0a48..0d875805 100644 --- a/Source/GameBaseFramework/Public/GBFSplitCommonBoundActionBar.h +++ b/Source/GameBaseFramework/Public/GBFSplitCommonBoundActionBar.h @@ -38,6 +38,9 @@ class GAMEBASEFRAMEWORK_API UGBFSplitCommonBoundActionBar : public UUserWidget, virtual void NativeOnActionButtonCreated( ICommonBoundActionButtonInterface * ActionButton, const FUIActionBindingHandle & RepresentedAction ) {} + + UFUNCTION( BlueprintImplementableEvent ) + void OnActionBarUpdated(); #if WITH_EDITOR void ValidateCompiledDefaults( IWidgetCompilerLog & compile_log ) const override; @@ -58,10 +61,10 @@ class GAMEBASEFRAMEWORK_API UGBFSplitCommonBoundActionBar : public UUserWidget, UPROPERTY( EditAnywhere, Category = Display ) uint8 bDisplayOwningPlayerActionsOnly : 1; - UPROPERTY( meta = ( BindWidget ) ) + UPROPERTY( BlueprintReadOnly, meta = ( AllowPrivateAccess, BindWidget ) ) TObjectPtr< UHorizontalBox > LeftHorizontalBox; - UPROPERTY( meta = ( BindWidget ) ) + UPROPERTY( BlueprintReadOnly, meta = ( AllowPrivateAccess, BindWidget ) ) TObjectPtr< UHorizontalBox > RightHorizontalBox; UPROPERTY( Transient ) From 5938a551c977a28d26557a807f07b72386c45daa Mon Sep 17 00:00:00 2001 From: Thibaut Spreux Date: Fri, 26 Apr 2024 16:46:29 +0200 Subject: [PATCH 04/10] clang format --- Source/GameBaseFramework/Public/GBFSplitCommonBoundActionBar.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/GameBaseFramework/Public/GBFSplitCommonBoundActionBar.h b/Source/GameBaseFramework/Public/GBFSplitCommonBoundActionBar.h index 0d875805..ab23144e 100644 --- a/Source/GameBaseFramework/Public/GBFSplitCommonBoundActionBar.h +++ b/Source/GameBaseFramework/Public/GBFSplitCommonBoundActionBar.h @@ -38,7 +38,7 @@ class GAMEBASEFRAMEWORK_API UGBFSplitCommonBoundActionBar : public UUserWidget, virtual void NativeOnActionButtonCreated( ICommonBoundActionButtonInterface * ActionButton, const FUIActionBindingHandle & RepresentedAction ) {} - + UFUNCTION( BlueprintImplementableEvent ) void OnActionBarUpdated(); From 42acd38b533de52fd356a46f6a12416beba8f9ad Mon Sep 17 00:00:00 2001 From: Thibaut Spreux Date: Fri, 26 Apr 2024 16:54:48 +0200 Subject: [PATCH 05/10] fix duplicate symbole name --- .../Private/GBFSplitCommonBoundActionBar.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/GameBaseFramework/Private/GBFSplitCommonBoundActionBar.cpp b/Source/GameBaseFramework/Private/GBFSplitCommonBoundActionBar.cpp index 072228b3..3c268c8f 100644 --- a/Source/GameBaseFramework/Private/GBFSplitCommonBoundActionBar.cpp +++ b/Source/GameBaseFramework/Private/GBFSplitCommonBoundActionBar.cpp @@ -12,10 +12,10 @@ #include "InputAction.h" #include "OnlineSubsystemUtils.h" -bool bActionBarIgnoreOptOut = false; -static FAutoConsoleVariableRef CVarActionBarIgnoreOptOut( +bool bSplitActionBarIgnoreOptOut = false; +static FAutoConsoleVariableRef CVarSplitActionBarIgnoreOptOut( TEXT( "ActionBar.IgnoreOptOut" ), - bActionBarIgnoreOptOut, + bSplitActionBarIgnoreOptOut, TEXT( "If true, the Bound Action Bar will display bindings whether or not they are configured bDisplayInReflector" ), ECVF_Default ); @@ -199,7 +199,7 @@ void UGBFSplitCommonBoundActionBar::HandleDeferredDisplayUpdate() auto filtered_bindings = action_router->GatherActiveBindings().FilterByPredicate( [ action_router, player_input_type, player_gamepad_name, &accepted_bindings ]( const auto & handle ) mutable { if ( auto binding = FUIActionBinding::FindBinding( handle ) ) { - if ( !binding->bDisplayInActionBar && !bActionBarIgnoreOptOut ) + if ( !binding->bDisplayInActionBar && !bSplitActionBarIgnoreOptOut ) { return false; } From 46c09e618187ffd6041083f9490ccd574dc734ed Mon Sep 17 00:00:00 2001 From: Thibaut Spreux Date: Fri, 26 Apr 2024 17:04:24 +0200 Subject: [PATCH 06/10] rename console object name --- .../Private/GBFSplitCommonBoundActionBar.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/GameBaseFramework/Private/GBFSplitCommonBoundActionBar.cpp b/Source/GameBaseFramework/Private/GBFSplitCommonBoundActionBar.cpp index 3c268c8f..d6d037c0 100644 --- a/Source/GameBaseFramework/Private/GBFSplitCommonBoundActionBar.cpp +++ b/Source/GameBaseFramework/Private/GBFSplitCommonBoundActionBar.cpp @@ -14,9 +14,9 @@ bool bSplitActionBarIgnoreOptOut = false; static FAutoConsoleVariableRef CVarSplitActionBarIgnoreOptOut( - TEXT( "ActionBar.IgnoreOptOut" ), + TEXT( "SplitActionBar.IgnoreOptOut" ), bSplitActionBarIgnoreOptOut, - TEXT( "If true, the Bound Action Bar will display bindings whether or not they are configured bDisplayInReflector" ), + TEXT( "If true, the Split Bound Action Bar will display bindings whether or not they are configured bDisplayInReflector" ), ECVF_Default ); UGBFSplitCommonBoundActionBar::UGBFSplitCommonBoundActionBar( const FObjectInitializer & object_initializer ) : From eb2845377a13989d58c032a77997adfc424976c2 Mon Sep 17 00:00:00 2001 From: Thibaut Spreux Date: Tue, 30 Apr 2024 08:40:44 +0200 Subject: [PATCH 07/10] review --- .../Private/GBFSplitCommonBoundActionBar.cpp | 13 +++++++------ .../Public/GBFSplitCommonBoundActionBar.h | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Source/GameBaseFramework/Private/GBFSplitCommonBoundActionBar.cpp b/Source/GameBaseFramework/Private/GBFSplitCommonBoundActionBar.cpp index d6d037c0..3b33b349 100644 --- a/Source/GameBaseFramework/Private/GBFSplitCommonBoundActionBar.cpp +++ b/Source/GameBaseFramework/Private/GBFSplitCommonBoundActionBar.cpp @@ -4,14 +4,15 @@ #include "CommonInputTypeEnum.h" #include "CommonUITypes.h" #include "Editor/WidgetCompilerLog.h" -#include "Engine/GameInstance.h" -#include "Engine/GameViewportClient.h" #include "Input/CommonBoundActionButtonInterface.h" #include "Input/CommonUIActionRouterBase.h" #include "Input/UIActionBinding.h" #include "InputAction.h" #include "OnlineSubsystemUtils.h" +#include +#include + bool bSplitActionBarIgnoreOptOut = false; static FAutoConsoleVariableRef CVarSplitActionBarIgnoreOptOut( TEXT( "SplitActionBar.IgnoreOptOut" ), @@ -53,9 +54,9 @@ bool UGBFSplitCommonBoundActionBar::IsEntryClassValid( TSubclassOf< UUserWidget if ( in_entry_class ) { // Would InEntryClass create an instance of the same DynamicEntryBox - if ( UWidgetTree * widget_tree = Cast< UWidgetTree >( GetOuter() ) ) + if ( auto * widget_tree = Cast< UWidgetTree >( GetOuter() ) ) { - if ( UUserWidget * user_widget = Cast< UUserWidget >( widget_tree->GetOuter() ) ) + if ( auto * user_widget = Cast< UUserWidget >( widget_tree->GetOuter() ) ) { if ( in_entry_class->IsChildOf( user_widget->GetClass() ) ) { @@ -343,7 +344,7 @@ void UGBFSplitCommonBoundActionBar::HandleDeferredDisplayUpdate() } else { - const UInputAction * input_action = binding->InputAction.Get(); + const auto * input_action = binding->InputAction.Get(); key = CommonUI::GetFirstKeyForInputType( action_router->GetLocalPlayer(), player_input_type, input_action ); is_back_action = key == EKeys::Virtual_Back || key == EKeys::Escape || key == EKeys::Android_Back; } @@ -367,7 +368,7 @@ void UGBFSplitCommonBoundActionBar::HandleDeferredDisplayUpdate() void UGBFSplitCommonBoundActionBar::HandlePlayerAdded( int32 player_id ) { - const ULocalPlayer * new_player = GetGameInstance()->GetLocalPlayerByIndex( player_id ); + const auto * new_player = GetGameInstance()->GetLocalPlayerByIndex( player_id ); MonitorPlayerActions( new_player ); HandleBoundActionsUpdated( new_player == GetOwningLocalPlayer() ); } diff --git a/Source/GameBaseFramework/Public/GBFSplitCommonBoundActionBar.h b/Source/GameBaseFramework/Public/GBFSplitCommonBoundActionBar.h index ab23144e..2c4b95de 100644 --- a/Source/GameBaseFramework/Public/GBFSplitCommonBoundActionBar.h +++ b/Source/GameBaseFramework/Public/GBFSplitCommonBoundActionBar.h @@ -1,9 +1,9 @@ #pragma once -#include "Components/HorizontalBox.h" #include "UI/Widgets/GBFBoundActionButton.h" #include +#include #include #include From b9c3e7ade45f6dbfa5613a213fee53039477395f Mon Sep 17 00:00:00 2001 From: Thibaut Spreux Date: Mon, 13 May 2024 10:04:11 +0200 Subject: [PATCH 08/10] review --- .../Private/GBFSplitCommonBoundActionBar.cpp | 282 +++++++++--------- .../Public/GBFSplitCommonBoundActionBar.h | 8 +- 2 files changed, 139 insertions(+), 151 deletions(-) diff --git a/Source/GameBaseFramework/Private/GBFSplitCommonBoundActionBar.cpp b/Source/GameBaseFramework/Private/GBFSplitCommonBoundActionBar.cpp index 3b33b349..9ee992c5 100644 --- a/Source/GameBaseFramework/Private/GBFSplitCommonBoundActionBar.cpp +++ b/Source/GameBaseFramework/Private/GBFSplitCommonBoundActionBar.cpp @@ -3,22 +3,24 @@ #include "CommonInputSubsystem.h" #include "CommonInputTypeEnum.h" #include "CommonUITypes.h" -#include "Editor/WidgetCompilerLog.h" #include "Input/CommonBoundActionButtonInterface.h" #include "Input/CommonUIActionRouterBase.h" #include "Input/UIActionBinding.h" #include "InputAction.h" #include "OnlineSubsystemUtils.h" +#include #include #include bool bSplitActionBarIgnoreOptOut = false; +#if !UE_BUILD_SHIPPING static FAutoConsoleVariableRef CVarSplitActionBarIgnoreOptOut( TEXT( "SplitActionBar.IgnoreOptOut" ), bSplitActionBarIgnoreOptOut, TEXT( "If true, the Split Bound Action Bar will display bindings whether or not they are configured bDisplayInReflector" ), ECVF_Default ); +#endif UGBFSplitCommonBoundActionBar::UGBFSplitCommonBoundActionBar( const FObjectInitializer & object_initializer ) : Super( object_initializer ), @@ -51,7 +53,7 @@ bool UGBFSplitCommonBoundActionBar::IsTickableWhenPaused() const bool UGBFSplitCommonBoundActionBar::IsEntryClassValid( TSubclassOf< UUserWidget > in_entry_class ) const { - if ( in_entry_class ) + if ( in_entry_class != nullptr ) { // Would InEntryClass create an instance of the same DynamicEntryBox if ( auto * widget_tree = Cast< UWidgetTree >( GetOuter() ) ) @@ -89,11 +91,6 @@ void UGBFSplitCommonBoundActionBar::OnWidgetRebuilt() } } -void UGBFSplitCommonBoundActionBar::SynchronizeProperties() -{ - Super::SynchronizeProperties(); -} - void UGBFSplitCommonBoundActionBar::ReleaseSlateResources( bool release_children ) { Super::ReleaseSlateResources( release_children ); @@ -136,7 +133,7 @@ UUserWidget * UGBFSplitCommonBoundActionBar::CreateEntryInternal( TSubclassOf< U const auto content = WidgetPool.GetOrCreateInstance( in_entry_class ); - auto * new_entry_widget = ( is_back_action ? LeftHorizontalBox : RightHorizontalBox )->AddChildToHorizontalBox( content ); + auto * new_entry_widget = ( is_back_action ? CancelButtonContainer : ActionButtonsContainer )->AddChild( content ); UGBFSplitCommonBoundActionBarInternal::recursive_detection.Pop(); @@ -148,7 +145,7 @@ void UGBFSplitCommonBoundActionBar::ValidateCompiledDefaults( IWidgetCompilerLog { Super::ValidateCompiledDefaults( compile_log ); - if ( !ActionButtonClass ) + if ( ActionButtonClass == nullptr ) { compile_log.Error( FText::FromString( FString::Printf( TEXT( "Error_SplitBoundActionBar_MissingButtonClass, {0} has no ActionButtonClass specified." ) ) ) ); } @@ -171,8 +168,8 @@ void UGBFSplitCommonBoundActionBar::HandleDeferredDisplayUpdate() { bIsRefreshQueued = false; - RightHorizontalBox->ClearChildren(); - LeftHorizontalBox->ClearChildren(); + ActionButtonsContainer->ClearChildren(); + CancelButtonContainer->ClearChildren(); const auto * game_instance = GetGameInstance(); check( game_instance ); @@ -186,177 +183,172 @@ void UGBFSplitCommonBoundActionBar::HandleDeferredDisplayUpdate() for ( const auto * local_player : sorted_players ) { - if ( local_player == owning_local_player || !bDisplayOwningPlayerActionsOnly ) + const auto * action_router = ULocalPlayer::GetSubsystem< UCommonUIActionRouterBase >( owning_local_player ); + if ( IsEntryClassValid( ActionButtonClass ) && ( local_player == owning_local_player || !bDisplayOwningPlayerActionsOnly ) && action_router != nullptr ) { - if ( IsEntryClassValid( ActionButtonClass ) ) - { - if ( const auto * action_router = ULocalPlayer::GetSubsystem< UCommonUIActionRouterBase >( owning_local_player ) ) + const auto & input_subsystem = action_router->GetInputSubsystem(); + const auto player_input_type = input_subsystem.GetCurrentInputType(); + const auto & player_gamepad_name = input_subsystem.GetCurrentGamepadName(); + + TSet< FName > accepted_bindings; + auto filtered_bindings = action_router->GatherActiveBindings().FilterByPredicate( [ action_router, player_input_type, player_gamepad_name, &accepted_bindings ]( const auto & handle ) mutable { + if ( auto binding = FUIActionBinding::FindBinding( handle ) ) { - const auto & input_subsystem = action_router->GetInputSubsystem(); - const auto player_input_type = input_subsystem.GetCurrentInputType(); - const auto & player_gamepad_name = input_subsystem.GetCurrentGamepadName(); + if ( !binding->bDisplayInActionBar && !bSplitActionBarIgnoreOptOut ) + { + return false; + } - TSet< FName > accepted_bindings; - auto filtered_bindings = action_router->GatherActiveBindings().FilterByPredicate( [ action_router, player_input_type, player_gamepad_name, &accepted_bindings ]( const auto & handle ) mutable { - if ( auto binding = FUIActionBinding::FindBinding( handle ) ) + if ( CommonUI::IsEnhancedInputSupportEnabled() ) + { + if ( auto input_action = binding->InputAction.Get() ) { - if ( !binding->bDisplayInActionBar && !bSplitActionBarIgnoreOptOut ) - { - return false; - } + return CommonUI::ActionValidForInputType( action_router->GetLocalPlayer(), player_input_type, input_action ); + } + } + + if ( auto * legacy_Data = binding->GetLegacyInputActionData() ) + { + if ( !legacy_Data->CanDisplayInReflector( player_input_type, player_gamepad_name ) ) + { + return false; + } + } + else + { + return false; + } - if ( CommonUI::IsEnhancedInputSupportEnabled() ) + bool already_accepted = false; + accepted_bindings.Add( binding->ActionName, &already_accepted ); + return !already_accepted; + } + + return false; + } ); + + Algo::Sort( filtered_bindings, [ action_router, player_input_type, player_gamepad_name ]( const FUIActionBindingHandle & handle_a, const FUIActionBindingHandle & handle_b ) { + const auto binding_a = FUIActionBinding::FindBinding( handle_a ); + const auto binding_b = FUIActionBinding::FindBinding( handle_b ); + + if ( ensureMsgf( ( binding_a && binding_b ), TEXT( "The array filter above should enforce that there are no null bindings" ) ) ) + { + auto is_key_back_action = [ action_router, player_input_type, player_gamepad_name ]( FCommonInputActionDataBase * legacy_data, const auto * input_action ) { + if ( legacy_data ) + { + auto key = legacy_data->GetInputTypeInfo( player_input_type, player_gamepad_name ).GetKey(); + + if ( player_input_type == ECommonInputType::Touch ) { - if ( auto input_action = binding->InputAction.Get() ) + if ( !key.IsValid() ) { - return CommonUI::ActionValidForInputType( action_router->GetLocalPlayer(), player_input_type, input_action ); + key = legacy_data->GetInputTypeInfo( ECommonInputType::MouseAndKeyboard, player_gamepad_name ).GetKey(); } } - if ( auto * legacy_Data = binding->GetLegacyInputActionData() ) + return key == EKeys::Virtual_Back || key == EKeys::Escape || key == EKeys::Android_Back; + } + else if ( input_action ) + { + auto key = CommonUI::GetFirstKeyForInputType( action_router->GetLocalPlayer(), player_input_type, input_action ); + + if ( player_input_type == ECommonInputType::Touch ) { - if ( !legacy_Data->CanDisplayInReflector( player_input_type, player_gamepad_name ) ) + if ( !key.IsValid() ) { - return false; + key = CommonUI::GetFirstKeyForInputType( action_router->GetLocalPlayer(), ECommonInputType::MouseAndKeyboard, input_action ); } } - else - { - return false; - } - bool already_accepted = false; - accepted_bindings.Add( binding->ActionName, &already_accepted ); - return !already_accepted; + return key == EKeys::Virtual_Back || key == EKeys::Escape || key == EKeys::Android_Back; } return false; - } ); - - Algo::Sort( filtered_bindings, [ action_router, player_input_type, player_gamepad_name ]( const FUIActionBindingHandle & handle_a, const FUIActionBindingHandle & handle_b ) { - const auto binding_a = FUIActionBinding::FindBinding( handle_a ); - const auto binding_b = FUIActionBinding::FindBinding( handle_b ); + }; - if ( ensureMsgf( ( binding_a && binding_b ), TEXT( "The array filter above should enforce that there are no null bindings" ) ) ) + auto get_navbar_priority = []( FCommonInputActionDataBase * legacy_data, const auto * input_action ) { + if ( legacy_data ) { - auto is_key_back_action = [ action_router, player_input_type, player_gamepad_name ]( FCommonInputActionDataBase * legacy_data, const auto * input_action ) { - if ( legacy_data ) - { - auto key = legacy_data->GetInputTypeInfo( player_input_type, player_gamepad_name ).GetKey(); - - if ( player_input_type == ECommonInputType::Touch ) - { - if ( !key.IsValid() ) - { - key = legacy_data->GetInputTypeInfo( ECommonInputType::MouseAndKeyboard, player_gamepad_name ).GetKey(); - } - } - - return key == EKeys::Virtual_Back || key == EKeys::Escape || key == EKeys::Android_Back; - } - else if ( input_action ) - { - auto key = CommonUI::GetFirstKeyForInputType( action_router->GetLocalPlayer(), player_input_type, input_action ); - - if ( player_input_type == ECommonInputType::Touch ) - { - if ( !key.IsValid() ) - { - key = CommonUI::GetFirstKeyForInputType( action_router->GetLocalPlayer(), ECommonInputType::MouseAndKeyboard, input_action ); - } - } - - return key == EKeys::Virtual_Back || key == EKeys::Escape || key == EKeys::Android_Back; - } + return legacy_data->NavBarPriority; + } + else if ( input_action ) + { + if ( auto input_action_meta_data = CommonUI::GetEnhancedInputActionMetadata( input_action ) ) + { + return input_action_meta_data->NavBarPriority; + } + } - return false; - }; + return 0; + }; - auto get_navbar_priority = []( FCommonInputActionDataBase * legacy_data, const auto * input_action ) { - if ( legacy_data ) - { - return legacy_data->NavBarPriority; - } - else if ( input_action ) - { - if ( auto input_action_meta_data = CommonUI::GetEnhancedInputActionMetadata( input_action ) ) - { - return input_action_meta_data->NavBarPriority; - } - } + const auto legacy_data_a = binding_a->GetLegacyInputActionData(); + const auto legacy_data_b = binding_b->GetLegacyInputActionData(); - return 0; - }; + const UInputAction * input_action_a = nullptr; + const UInputAction * input_action_b = nullptr; - const auto legacy_data_a = binding_a->GetLegacyInputActionData(); - const auto legacy_data_b = binding_b->GetLegacyInputActionData(); + if ( CommonUI::IsEnhancedInputSupportEnabled() ) + { + input_action_a = binding_a->InputAction.Get(); + input_action_b = binding_b->InputAction.Get(); + } - const UInputAction * input_action_a = nullptr; - const UInputAction * input_action_b = nullptr; + const auto is_valid_action_a = legacy_data_a || input_action_a; + const auto is_valid_action_b = legacy_data_b || input_action_b; - if ( CommonUI::IsEnhancedInputSupportEnabled() ) - { - input_action_a = binding_a->InputAction.Get(); - input_action_b = binding_b->InputAction.Get(); - } + if ( ensureMsgf( ( is_valid_action_a && is_valid_action_b ), TEXT( "Action bindings not displayed yet -- array filter enforces they are not included" ) ) ) + { + const auto a_is_back = is_key_back_action( legacy_data_a, input_action_a ); + const auto b_is_back = is_key_back_action( legacy_data_b, input_action_b ); - const bool is_valid_action_a = legacy_data_a || input_action_a; - const bool is_valid_action_b = legacy_data_b || input_action_b; + if ( a_is_back && b_is_back ) + { + return false; + } - if ( ensureMsgf( ( is_valid_action_a && is_valid_action_b ), TEXT( "Action bindings not displayed yet -- array filter enforces they are not included" ) ) ) - { - const bool a_is_back = is_key_back_action( legacy_data_a, input_action_a ); - const bool b_is_back = is_key_back_action( legacy_data_b, input_action_b ); + const auto nav_bar_priority_a = get_navbar_priority( legacy_data_a, input_action_a ); + const auto nav_bar_priority_b = get_navbar_priority( legacy_data_b, input_action_b ); - if ( a_is_back && b_is_back ) - { - return false; - } + if ( nav_bar_priority_a != nav_bar_priority_b ) + { + return nav_bar_priority_a < nav_bar_priority_b; + } + } - const int32 nav_bar_priority_a = get_navbar_priority( legacy_data_a, input_action_a ); - const int32 nav_bar_priority_b = get_navbar_priority( legacy_data_b, input_action_b ); + return GetTypeHash( binding_a->Handle ) < GetTypeHash( binding_b->Handle ); + } - if ( nav_bar_priority_a != nav_bar_priority_b ) - { - return nav_bar_priority_a < nav_bar_priority_b; - } - } + return true; + } ); - return GetTypeHash( binding_a->Handle ) < GetTypeHash( binding_b->Handle ); - } + for ( auto binding_handle : filtered_bindings ) + { + const auto binding = FUIActionBinding::FindBinding( binding_handle ); - return true; - } ); + if ( binding->bDisplayInActionBar ) + { + FKey key; + bool is_back_action; - for ( auto binding_handle : filtered_bindings ) + if ( const auto legacy_data = binding->GetLegacyInputActionData() ) { - const auto binding = FUIActionBinding::FindBinding( binding_handle ); - - if ( binding->bDisplayInActionBar ) - { - FKey key; - bool is_back_action; - - if ( const auto legacy_data = binding->GetLegacyInputActionData() ) - { - key = legacy_data->GetInputTypeInfo( player_input_type, player_gamepad_name ).GetKey(); - is_back_action = key == EKeys::Virtual_Back || key == EKeys::Escape || key == EKeys::Android_Back; - } - else - { - const auto * input_action = binding->InputAction.Get(); - key = CommonUI::GetFirstKeyForInputType( action_router->GetLocalPlayer(), player_input_type, input_action ); - is_back_action = key == EKeys::Virtual_Back || key == EKeys::Escape || key == EKeys::Android_Back; - } + key = legacy_data->GetInputTypeInfo( player_input_type, player_gamepad_name ).GetKey(); + is_back_action = key == EKeys::Virtual_Back || key == EKeys::Escape || key == EKeys::Android_Back; + } + else + { + const auto * input_action = binding->InputAction.Get(); + key = CommonUI::GetFirstKeyForInputType( action_router->GetLocalPlayer(), player_input_type, input_action ); + is_back_action = key == EKeys::Virtual_Back || key == EKeys::Escape || key == EKeys::Android_Back; + } - auto * action_button = Cast< ICommonBoundActionButtonInterface >( CreateEntryInternal( ActionButtonClass, is_back_action ) ); + auto * action_button = Cast< ICommonBoundActionButtonInterface >( CreateEntryInternal( ActionButtonClass, is_back_action ) ); - if ( ensure( action_button ) ) - { - action_button->SetRepresentedAction( binding_handle ); - NativeOnActionButtonCreated( action_button, binding_handle ); - } - } + if ( ensure( action_button ) ) + { + action_button->SetRepresentedAction( binding_handle ); + NativeOnActionButtonCreated( action_button, binding_handle ); } } } diff --git a/Source/GameBaseFramework/Public/GBFSplitCommonBoundActionBar.h b/Source/GameBaseFramework/Public/GBFSplitCommonBoundActionBar.h index 2c4b95de..969c0562 100644 --- a/Source/GameBaseFramework/Public/GBFSplitCommonBoundActionBar.h +++ b/Source/GameBaseFramework/Public/GBFSplitCommonBoundActionBar.h @@ -32,7 +32,6 @@ class GAMEBASEFRAMEWORK_API UGBFSplitCommonBoundActionBar : public UUserWidget, protected: bool IsEntryClassValid( TSubclassOf< UUserWidget > in_entry_class ) const; void OnWidgetRebuilt() override; - void SynchronizeProperties() override; void ReleaseSlateResources( bool release_children ) override; UUserWidget * CreateEntryInternal( TSubclassOf< UUserWidget > in_entry_class, bool is_back_action ); @@ -55,17 +54,14 @@ class GAMEBASEFRAMEWORK_API UGBFSplitCommonBoundActionBar : public UUserWidget, UPROPERTY( EditAnywhere, Category = EntryLayout, meta = ( MustImplement = "/Script/CommonUI.CommonBoundActionButtonInterface" ) ) TSubclassOf< UGBFBoundActionButton > ActionButtonClass; - UPROPERTY( EditAnywhere, AdvancedDisplay, Category = Display ) - uint8 bIgnoreDuplicateActions : 1; - UPROPERTY( EditAnywhere, Category = Display ) uint8 bDisplayOwningPlayerActionsOnly : 1; UPROPERTY( BlueprintReadOnly, meta = ( AllowPrivateAccess, BindWidget ) ) - TObjectPtr< UHorizontalBox > LeftHorizontalBox; + TObjectPtr< UPanelWidget > CancelButtonContainer; UPROPERTY( BlueprintReadOnly, meta = ( AllowPrivateAccess, BindWidget ) ) - TObjectPtr< UHorizontalBox > RightHorizontalBox; + TObjectPtr< UPanelWidget > ActionButtonsContainer; UPROPERTY( Transient ) FUserWidgetPool WidgetPool; From 5a8eb1094623f5d2a7ed997b6e9dc2ffe301d375 Mon Sep 17 00:00:00 2001 From: Thibaut Spreux Date: Mon, 13 May 2024 11:54:26 +0200 Subject: [PATCH 09/10] review includes --- .../GameBaseFramework/Private/GBFSplitCommonBoundActionBar.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/GameBaseFramework/Private/GBFSplitCommonBoundActionBar.cpp b/Source/GameBaseFramework/Private/GBFSplitCommonBoundActionBar.cpp index 9ee992c5..0ccb264e 100644 --- a/Source/GameBaseFramework/Private/GBFSplitCommonBoundActionBar.cpp +++ b/Source/GameBaseFramework/Private/GBFSplitCommonBoundActionBar.cpp @@ -7,11 +7,11 @@ #include "Input/CommonUIActionRouterBase.h" #include "Input/UIActionBinding.h" #include "InputAction.h" -#include "OnlineSubsystemUtils.h" #include #include #include +#include bool bSplitActionBarIgnoreOptOut = false; #if !UE_BUILD_SHIPPING From e00a984c6ef813d3e2945a03fc8da5452f7118a1 Mon Sep 17 00:00:00 2001 From: Thibaut Spreux Date: Mon, 13 May 2024 16:57:58 +0200 Subject: [PATCH 10/10] re review --- .../Private/GBFSplitCommonBoundActionBar.cpp | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/Source/GameBaseFramework/Private/GBFSplitCommonBoundActionBar.cpp b/Source/GameBaseFramework/Private/GBFSplitCommonBoundActionBar.cpp index 0ccb264e..59e19c00 100644 --- a/Source/GameBaseFramework/Private/GBFSplitCommonBoundActionBar.cpp +++ b/Source/GameBaseFramework/Private/GBFSplitCommonBoundActionBar.cpp @@ -14,12 +14,15 @@ #include bool bSplitActionBarIgnoreOptOut = false; -#if !UE_BUILD_SHIPPING + +#if !( UE_BUILD_SHIPPING || UE_BUILD_TEST ) + static FAutoConsoleVariableRef CVarSplitActionBarIgnoreOptOut( TEXT( "SplitActionBar.IgnoreOptOut" ), bSplitActionBarIgnoreOptOut, TEXT( "If true, the Split Bound Action Bar will display bindings whether or not they are configured bDisplayInReflector" ), ECVF_Default ); + #endif UGBFSplitCommonBoundActionBar::UGBFSplitCommonBoundActionBar( const FObjectInitializer & object_initializer ) : @@ -183,8 +186,17 @@ void UGBFSplitCommonBoundActionBar::HandleDeferredDisplayUpdate() for ( const auto * local_player : sorted_players ) { - const auto * action_router = ULocalPlayer::GetSubsystem< UCommonUIActionRouterBase >( owning_local_player ); - if ( IsEntryClassValid( ActionButtonClass ) && ( local_player == owning_local_player || !bDisplayOwningPlayerActionsOnly ) && action_router != nullptr ) + if ( local_player != owning_local_player && bDisplayOwningPlayerActionsOnly ) + { + continue; + } + + if ( !IsEntryClassValid( ActionButtonClass ) ) + { + continue; + } + + if ( const auto * action_router = ULocalPlayer::GetSubsystem< UCommonUIActionRouterBase >( owning_local_player ) ) { const auto & input_subsystem = action_router->GetInputSubsystem(); const auto player_input_type = input_subsystem.GetCurrentInputType();