diff --git a/Source/StevesUEHelpers/Private/StevesGameSubsystem.cpp b/Source/StevesUEHelpers/Private/StevesGameSubsystem.cpp index e7f4a37..c864010 100644 --- a/Source/StevesUEHelpers/Private/StevesGameSubsystem.cpp +++ b/Source/StevesUEHelpers/Private/StevesGameSubsystem.cpp @@ -35,6 +35,7 @@ void UStevesGameSubsystem::CreateInputDetector() FSlateApplication::Get().RegisterInputPreProcessor(InputDetector); InputDetector->OnInputModeChanged.BindUObject(this, &UStevesGameSubsystem::OnInputDetectorModeChanged); + InputDetector->OnButtonInputModeChanged.BindUObject(this, &UStevesGameSubsystem::OnButtonInputDetectorModeChanged); } } @@ -122,6 +123,13 @@ void UStevesGameSubsystem::OnInputDetectorModeChanged(int PlayerIndex, EInputMod OnInputModeChanged.Broadcast(PlayerIndex, NewMode); } +void UStevesGameSubsystem::OnButtonInputDetectorModeChanged(int PlayerIndex, EInputMode NewMode) +{ + // This is specifically for button changes; if this is a different main input mode it will also be registered in OnInputDetectorModeChanged + // Just relay this one + OnButtonInputModeChanged.Broadcast(PlayerIndex, NewMode); +} + FFocusSystem* UStevesGameSubsystem::GetFocusSystem() { return &FocusSystem; @@ -160,11 +168,13 @@ UPaperSprite* UStevesGameSubsystem::GetInputImageSpriteFromAction(const FName& N GS_TempActionMap.Empty(); Settings->GetActionMappingByName(Name, GS_TempActionMap); - // For default, prefer keyboard for buttons + // For default, prefer latest press keyboard/mouse for buttons if (DevicePreference == EInputImageDevicePreference::Auto) - DevicePreference = EInputImageDevicePreference::Gamepad_Keyboard_Mouse; + DevicePreference = EInputImageDevicePreference::Gamepad_Keyboard_Mouse_Button; - const auto Preferred = GetPreferedActionOrAxisMapping(GS_TempActionMap, Name, DevicePreference, LastInputWasGamePad(PlayerIdx)); + const EInputMode LastInput = GetLastInputModeUsed(PlayerIdx); + const EInputMode LastButtonInput = GetLastInputButtonPressed(PlayerIdx); + const auto Preferred = GetPreferedActionOrAxisMapping(GS_TempActionMap, Name, DevicePreference, LastInput, LastButtonInput); if (Preferred) { return GetInputImageSpriteFromKey(Preferred->Key, PlayerIdx, Theme); @@ -186,7 +196,9 @@ UPaperSprite* UStevesGameSubsystem::GetInputImageSpriteFromAxis(const FName& Nam if (DevicePreference == EInputImageDevicePreference::Auto) DevicePreference = EInputImageDevicePreference::Gamepad_Mouse_Keyboard; - const auto Preferred = GetPreferedActionOrAxisMapping(GS_TempAxisMap, Name, DevicePreference, LastInputWasGamePad(PlayerIdx)); + const EInputMode LastInput = GetLastInputModeUsed(PlayerIdx); + const EInputMode LastButtonInput = GetLastInputButtonPressed(PlayerIdx); + const auto Preferred = GetPreferedActionOrAxisMapping(GS_TempAxisMap, Name, DevicePreference, LastInput, LastButtonInput); if (Preferred) { return GetInputImageSpriteFromKey(Preferred->Key, PlayerIdx, Theme); @@ -281,7 +293,8 @@ bool UStevesGameSubsystem::FInputModeDetector::ShouldProcessInputEvents() const UStevesGameSubsystem::FInputModeDetector::FInputModeDetector() { // 4 local players should be plenty usually (will expand if necessary) - LastInputModeByPlayer.Init(EInputMode::Mouse, 4); + LastInputModeByPlayer.Init(DefaultInputMode, 4); + LastButtonPressByPlayer.Init(DefaultButtonInputMode, 4); } bool UStevesGameSubsystem::FInputModeDetector::HandleKeyDownEvent(FSlateApplication& SlateApp, const FKeyEvent& InKeyEvent) @@ -302,7 +315,7 @@ bool UStevesGameSubsystem::FInputModeDetector::HandleAnalogInputEvent(FSlateAppl if (ShouldProcessInputEvents()) { if (InAnalogInputEvent.GetAnalogValue() > GamepadAxisThreshold) - SetMode(InAnalogInputEvent.GetUserIndex(), EInputMode::Gamepad); + SetMode(InAnalogInputEvent.GetUserIndex(), EInputMode::Gamepad, false); } // Don't consume @@ -316,7 +329,7 @@ bool UStevesGameSubsystem::FInputModeDetector::HandleMouseMoveEvent(FSlateApplic FVector2D Dist = MouseEvent.GetScreenSpacePosition() - MouseEvent.GetLastScreenSpacePosition(); if (FMath::Abs(Dist.X) > MouseMoveThreshold || FMath::Abs(Dist.Y) > MouseMoveThreshold) { - SetMode(MouseEvent.GetUserIndex(), EInputMode::Mouse); + SetMode(MouseEvent.GetUserIndex(), EInputMode::Mouse, false); } } // Don't consume @@ -328,7 +341,7 @@ bool UStevesGameSubsystem::FInputModeDetector::HandleMouseButtonDownEvent(FSlate if (ShouldProcessInputEvents()) { // We don't care which button - SetMode(MouseEvent.GetUserIndex(), EInputMode::Mouse); + SetMode(MouseEvent.GetUserIndex(), EInputMode::Mouse, true); } // Don't consume @@ -340,7 +353,7 @@ bool UStevesGameSubsystem::FInputModeDetector::HandleMouseWheelOrGestureEvent(FS { if (ShouldProcessInputEvents()) { - SetMode(InWheelEvent.GetUserIndex(), EInputMode::Mouse); + SetMode(InWheelEvent.GetUserIndex(), EInputMode::Mouse, false); } // Don't consume @@ -356,38 +369,96 @@ EInputMode UStevesGameSubsystem::FInputModeDetector::GetLastInputMode(int Player return DefaultInputMode; } +EInputMode UStevesGameSubsystem::FInputModeDetector::GetLastButtonInputMode(int PlayerIndex) +{ + if (PlayerIndex >= 0 && PlayerIndex < LastButtonPressByPlayer.Num()) + return LastButtonPressByPlayer[PlayerIndex]; + + // Assume default if never told + return DefaultButtonInputMode; +} void UStevesGameSubsystem::FInputModeDetector::ProcessKeyOrButton(int PlayerIndex, FKey Key) { if (Key.IsGamepadKey()) { - SetMode(PlayerIndex, EInputMode::Gamepad); + SetMode(PlayerIndex, EInputMode::Gamepad, IsAGamepadButton(Key)); } else if (Key.IsMouseButton()) { // Assuming mice don't have analog buttons! - SetMode(PlayerIndex, EInputMode::Mouse); + SetMode(PlayerIndex, EInputMode::Mouse, true); } else { // We assume anything that's not mouse and not gamepad is a keyboard // Assuming keyboards don't have analog buttons! - SetMode(PlayerIndex, EInputMode::Keyboard); + SetMode(PlayerIndex, EInputMode::Keyboard, true); } } - -void UStevesGameSubsystem::FInputModeDetector::SetMode(int PlayerIndex, EInputMode NewMode) + +bool UStevesGameSubsystem::FInputModeDetector::IsAGamepadButton(const FKey& Key) { + + // Key.IsButtonAxis() returns true for some thumbstick movement events, because the axis type is EInputAxisType::Button for + // some reason. That means you get button events for thumbstick movements, which is super dumb. + // See core engine InputCoreTypes.cpp for the stick axes which are defined FKeyDetails::GamepadKey | FKeyDetails::ButtonAxis + // This is for some kind of virtual input but it's a nasty hack, omit them + return Key.IsGamepadKey() && + Key != EKeys::Gamepad_LeftStick_Up && + Key != EKeys::Gamepad_LeftStick_Down && + Key != EKeys::Gamepad_LeftStick_Left && + Key != EKeys::Gamepad_LeftStick_Right && + Key != EKeys::Gamepad_RightStick_Up && + Key != EKeys::Gamepad_RightStick_Down && + Key != EKeys::Gamepad_RightStick_Left && + Key != EKeys::Gamepad_RightStick_Right; + +} + +void UStevesGameSubsystem::FInputModeDetector::SetMode(int PlayerIndex, EInputMode NewMode, bool bIsButton) +{ + bool bButtonChanged = false; + bool bMainChanged = false; + + if (bIsButton) + { + if (NewMode != EInputMode::Unknown && NewMode != GetLastButtonInputMode(PlayerIndex)) + { + if (PlayerIndex >= LastButtonPressByPlayer.Num()) + LastButtonPressByPlayer.SetNum(PlayerIndex + 1); + + LastButtonPressByPlayer[PlayerIndex] = NewMode; + + bButtonChanged = true; + } + } + // Whether it's a button or not it can affect the main input mode if (NewMode != EInputMode::Unknown && NewMode != GetLastInputMode(PlayerIndex)) { if (PlayerIndex >= LastInputModeByPlayer.Num()) LastInputModeByPlayer.SetNum(PlayerIndex + 1); LastInputModeByPlayer[PlayerIndex] = NewMode; + + bMainChanged = true; + } + + // Raise events at the end once all state has changed + if (bButtonChanged) + { + // ReSharper disable once CppExpressionWithoutSideEffects + OnButtonInputModeChanged.ExecuteIfBound(PlayerIndex, NewMode); + //UE_LOG(LogStevesUEHelpers, Display, TEXT("Button mode for player %d changed: %s"), PlayerIndex, *UEnum::GetValueAsString(NewMode)); + } + if (bMainChanged) + { + // ReSharper disable once CppExpressionWithoutSideEffects OnInputModeChanged.ExecuteIfBound(PlayerIndex, NewMode); //UE_LOG(LogStevesUEHelpers, Display, TEXT("Input mode for player %d changed: %s"), PlayerIndex, *UEnum::GetValueAsString(NewMode)); } + } //PRAGMA_ENABLE_OPTIMIZATION \ No newline at end of file diff --git a/Source/StevesUEHelpers/Public/StevesGameSubsystem.h b/Source/StevesUEHelpers/Public/StevesGameSubsystem.h index 70e924c..f0b66cc 100644 --- a/Source/StevesUEHelpers/Public/StevesGameSubsystem.h +++ b/Source/StevesUEHelpers/Public/StevesGameSubsystem.h @@ -64,8 +64,10 @@ protected: { protected: TArray LastInputModeByPlayer; + TArray LastButtonPressByPlayer; const EInputMode DefaultInputMode = EInputMode::Mouse; + const EInputMode DefaultButtonInputMode = EInputMode::Keyboard; const float MouseMoveThreshold = 1; const float GamepadAxisThreshold = 0.2; @@ -74,8 +76,10 @@ protected: /// Whether this detector should ignore events (e.g. because the application is in the background) bool bIgnoreEvents = false; - // Single delegate caller, owner should propagate if they want (this isn't a UObject) + /// Event raised when main input mode changes for any reason FInternalInputModeChanged OnInputModeChanged; + /// Event raised when button input mode changes only + FInternalInputModeChanged OnButtonInputModeChanged; FInputModeDetector(); @@ -88,14 +92,18 @@ protected: virtual bool HandleMouseWheelOrGestureEvent(FSlateApplication& SlateApp, const FPointerEvent& InWheelEvent, const FPointerEvent* InGestureEvent) override; + /// Get the last input mode from any kind of input EInputMode GetLastInputMode(int PlayerIndex = 0); + /// Get the last input mode from button inputs (ignores axis changes, good for detecting if keyboard or mouse buttons are being used) + EInputMode GetLastButtonInputMode(int PlayerIndex = 0); // Needed but unused virtual void Tick(const float DeltaTime, FSlateApplication& SlateApp, TSharedRef Cursor) override {} protected: + static bool IsAGamepadButton(const FKey& Key); void ProcessKeyOrButton(int PlayerIndex, FKey Key); - void SetMode(int PlayerIndex, EInputMode NewMode); + void SetMode(int PlayerIndex, EInputMode NewMode, bool bIsButton); }; protected: @@ -122,6 +130,7 @@ protected: // Called by detector void OnInputDetectorModeChanged(int PlayerIndex, EInputMode NewMode); + void OnButtonInputDetectorModeChanged(int PlayerIndex, EInputMode NewMode); TSoftObjectPtr GetGamepadImages(int PlayerIndex, const UUiTheme* Theme); @@ -129,17 +138,27 @@ protected: public: - /// Event raised when input mode changed between gamepad and keyboard / mouse + /// Event raised when main input mode changed between gamepad and keyboard / mouse (for any of axis / button events) UPROPERTY(BlueprintAssignable) FOnInputModeChanged OnInputModeChanged; - + + /// Event raised when the last button input changed between gamepad / keyboard / mouse + /// This can happen at a different time to OnInputModeChanged, e.g. if that was triggered by a mouse move, but the + /// last button pressed was still keyboard, you'd get this event later + UPROPERTY(BlueprintAssignable) + FOnInputModeChanged OnButtonInputModeChanged; + /// Event raised when the game window's foreground status changes UPROPERTY(BlueprintAssignable) FOnWindowForegroundChanged OnWindowForegroundChanged; + /// Gets the device where the most recent input event of any kind happened UFUNCTION(BlueprintCallable) EInputMode GetLastInputModeUsed(int PlayerIndex = 0) const { return InputDetector->GetLastInputMode(PlayerIndex); } - + /// Gets the device where the most recent button press happened + UFUNCTION(BlueprintCallable) + EInputMode GetLastInputButtonPressed(int PlayerIndex = 0) const { return InputDetector->GetLastButtonInputMode(PlayerIndex); } + UFUNCTION(BlueprintCallable) bool LastInputWasGamePad(int PlayerIndex = 0) const { return GetLastInputModeUsed(PlayerIndex) == EInputMode::Gamepad; }