From 393460f3306c804bef32e1d4c3f85e5ec9818636 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sat, 22 Apr 2017 16:38:04 +0100 Subject: [PATCH] Added input support: - Added ImGui Input State to collect and store input updates. - Changed Slate ImGui Widget to handle Slate input events and store them in input state. - Changed ImGui Context Proxy to copy input state to its context. --- Source/ImGui/ImGui.Build.cs | 1 + Source/ImGui/Private/ImGuiContextProxy.cpp | 14 +- Source/ImGui/Private/ImGuiContextProxy.h | 8 +- Source/ImGui/Private/ImGuiInputState.cpp | 75 ++++++++ Source/ImGui/Private/ImGuiInputState.h | 101 ++++++++++ .../ImGui/Private/ImGuiInteroperability.cpp | 178 ++++++++++++++++++ Source/ImGui/Private/ImGuiInteroperability.h | 47 +++++ Source/ImGui/Private/ImGuiModuleManager.cpp | 5 +- Source/ImGui/Private/ImGuiModuleManager.h | 6 + Source/ImGui/Private/SImGuiWidget.cpp | 69 +++++++ Source/ImGui/Private/SImGuiWidget.h | 35 +++- Source/ImGui/Private/Utilities/Arrays.h | 63 +++++++ Source/ImGui/Private/Utilities/Range.h | 173 +++++++++++++++++ 13 files changed, 768 insertions(+), 7 deletions(-) create mode 100644 Source/ImGui/Private/ImGuiInputState.cpp create mode 100644 Source/ImGui/Private/ImGuiInputState.h create mode 100644 Source/ImGui/Private/ImGuiInteroperability.cpp create mode 100644 Source/ImGui/Private/Utilities/Arrays.h create mode 100644 Source/ImGui/Private/Utilities/Range.h diff --git a/Source/ImGui/ImGui.Build.cs b/Source/ImGui/ImGui.Build.cs index c7697d0..5aeafc2 100644 --- a/Source/ImGui/ImGui.Build.cs +++ b/Source/ImGui/ImGui.Build.cs @@ -41,6 +41,7 @@ public class ImGui : ModuleRules { "CoreUObject", "Engine", + "InputCore", "Slate", "SlateCore" // ... add private dependencies that you statically link with here ... diff --git a/Source/ImGui/Private/ImGuiContextProxy.cpp b/Source/ImGui/Private/ImGuiContextProxy.cpp index 2362b1a..7d367ec 100644 --- a/Source/ImGui/Private/ImGuiContextProxy.cpp +++ b/Source/ImGui/Private/ImGuiContextProxy.cpp @@ -20,6 +20,9 @@ FImGuiContextProxy::FImGuiContextProxy() unsigned char* Pixels; IO.Fonts->GetTexDataAsRGBA32(&Pixels, nullptr, nullptr); + // Initialize key mapping, so context can correctly interpret input state. + ImGuiInterops::SetUnrealKeyMap(IO); + // Begin frame to complete context initialization (this is to avoid problems with other systems calling to ImGui // during startup). BeginFrame(); @@ -30,7 +33,7 @@ FImGuiContextProxy::~FImGuiContextProxy() ImGui::Shutdown(); } -void FImGuiContextProxy::Tick(float DeltaSeconds) +void FImGuiContextProxy::Tick(float DeltaSeconds, const FImGuiInputState* InputState) { if (bIsFrameStarted) { @@ -46,16 +49,21 @@ void FImGuiContextProxy::Tick(float DeltaSeconds) } // Begin a new frame and set the context back to a state in which it allows to draw controls. - BeginFrame(DeltaSeconds); + BeginFrame(DeltaSeconds, InputState); } -void FImGuiContextProxy::BeginFrame(float DeltaTime) +void FImGuiContextProxy::BeginFrame(float DeltaTime, const FImGuiInputState* InputState) { if (!bIsFrameStarted) { ImGuiIO& IO = ImGui::GetIO(); IO.DeltaTime = DeltaTime; + if (InputState) + { + ImGuiInterops::CopyInput(IO, *InputState); + } + ImGui::NewFrame(); bIsFrameStarted = true; diff --git a/Source/ImGui/Private/ImGuiContextProxy.h b/Source/ImGui/Private/ImGuiContextProxy.h index 6c8c359..21035ce 100644 --- a/Source/ImGui/Private/ImGuiContextProxy.h +++ b/Source/ImGui/Private/ImGuiContextProxy.h @@ -7,6 +7,8 @@ #include +class FImGuiInputState; + // Represents a single ImGui context. All the context updates should be done through this proxy. During update it // broadcasts draw events to allow listeners draw their controls. After update it stores produced draw data. // TODO: Add dynamically created contexts, so we can have a better support for multi-PIE. @@ -30,11 +32,13 @@ public: FSimpleMulticastDelegate& OnDraw() { return DrawEvent; } // Tick to advance context to the next frame. - void Tick(float DeltaSeconds); + // @param DeltaSeconds - Time delta in seconds (will be passed to ImGui) + // @param InputState - Input state for ImGui IO or null if there is no input for this context + void Tick(float DeltaSeconds, const FImGuiInputState* InputState = nullptr); private: - void BeginFrame(float DeltaTime = 1.f / 60.f); + void BeginFrame(float DeltaTime = 1.f / 60.f, const FImGuiInputState* InputState = nullptr); void EndFrame(); void UpdateDrawData(ImDrawData* DrawData); diff --git a/Source/ImGui/Private/ImGuiInputState.cpp b/Source/ImGui/Private/ImGuiInputState.cpp new file mode 100644 index 0000000..575a856 --- /dev/null +++ b/Source/ImGui/Private/ImGuiInputState.cpp @@ -0,0 +1,75 @@ +// Distributed under the MIT License (MIT) (see accompanying LICENSE file) + +#include "ImGuiPrivatePCH.h" + +#include "ImGuiInputState.h" + + +FImGuiInputState::FImGuiInputState() +{ + ClearState(); +} + +void FImGuiInputState::AddCharacter(TCHAR Char) +{ + static_assert(sizeof(TCHAR) <= sizeof(InputCharacters[0]), "Size of elements in Input Characters buffer is smaller than size of 'TCHAR'. Possible truncation."); + + if (InputCharactersNum < Utilities::GetArraySize(InputCharacters)) + { + InputCharacters[InputCharactersNum++] = static_cast(Char); + InputCharacters[InputCharactersNum] = 0; + } +} + +void FImGuiInputState::SetKeyDown(uint32 KeyIndex, bool bIsDown) +{ + if (KeyIndex < Utilities::GetArraySize(KeysDown)) + { + if (KeysDown[KeyIndex] != bIsDown) + { + KeysDown[KeyIndex] = bIsDown; + KeysUpdateRange.AddPosition(KeyIndex); + } + } +} + +void FImGuiInputState::SetMouseDown(uint32 MouseIndex, bool bIsDown) +{ + if (MouseIndex < Utilities::GetArraySize(MouseButtonsDown)) + { + if (MouseButtonsDown[MouseIndex] != bIsDown) + { + MouseButtonsDown[MouseIndex] = bIsDown; + MouseButtonsUpdateRange.AddPosition(MouseIndex); + } + } +} + +void FImGuiInputState::ClearState() +{ + ClearCharacters(); + + using std::fill; + fill(KeysDown, &KeysDown[Utilities::GetArraySize(KeysDown)], false); + fill(MouseButtonsDown, &MouseButtonsDown[Utilities::GetArraySize(MouseButtonsDown)], false); + + // Fully expanding dirty parts of both arrays, to inform about the change. + KeysUpdateRange.SetFull(); + MouseButtonsUpdateRange.SetFull(); +} + +void FImGuiInputState::ClearUpdateState() +{ + ClearCharacters(); + + KeysUpdateRange.SetEmpty(); + MouseButtonsUpdateRange.SetEmpty(); + + MouseWheelDelta = 0.f; +} + +void FImGuiInputState::ClearCharacters() +{ + InputCharactersNum = 0; + InputCharacters[0]; +} diff --git a/Source/ImGui/Private/ImGuiInputState.h b/Source/ImGui/Private/ImGuiInputState.h new file mode 100644 index 0000000..62b90a4 --- /dev/null +++ b/Source/ImGui/Private/ImGuiInputState.h @@ -0,0 +1,101 @@ +// Distributed under the MIT License (MIT) (see accompanying LICENSE file) + +#pragma once + +#include "ImGuiInteroperability.h" +#include "Utilities/Arrays.h" + + +// Collects and stores input state and updates for ImGui IO. +class FImGuiInputState +{ +public: + + // Characters buffer. + using FCharactersBuffer = ImGuiInterops::ImGuiTypes::FInputCharactersBuffer; + + // Array for mouse button states. + using FMouseButtonsArray = ImGuiInterops::ImGuiTypes::FMouseButtonsArray; + + // Array for key states. + using FKeysArray = ImGuiInterops::ImGuiTypes::FKeysArray; + + // Pair of indices defining range in mouse buttons array. + using FMouseButtonsIndexRange = Utilities::TArrayIndexRange; + + // Pair of indices defining range in keys array. + using FKeysIndexRange = Utilities::TArrayIndexRange; + + // Create empty state with whole range instance with the whole update state marked as dirty. + FImGuiInputState(); + + // Get reference to input characters buffer. + const FCharactersBuffer& GetCharacters() const { return InputCharacters; } + + // Get number of characters in input characters buffer. + int32 GetCharactersNum() const { return InputCharactersNum; } + + // Add a character to the characters buffer. We can store and send to ImGui up to 16 characters per frame. Any + // character beyond that limit will be discarded. + // @param Char - Character to add + void AddCharacter(TCHAR Char); + + // Get reference to the array with key down states. + const FKeysArray& GetKeys() const { return KeysDown; } + + // Get possibly empty range of indices bounding dirty part of the keys array. + const FKeysIndexRange& GetKeysUpdateRange() const { return KeysUpdateRange; } + + // Change state of the key in the keys array and expand range bounding dirty part of the array. + // @param KeyIndex - Index of the key + // @param bIsDown - True, if key is down + void SetKeyDown(uint32 KeyIndex, bool bIsDown); + + // Get reference to the array with mouse button down states. + const FMouseButtonsArray& GetMouseButtons() const { return MouseButtonsDown; } + + // Get possibly empty range of indices bounding dirty part of the mouse buttons array. + const FMouseButtonsIndexRange& GetMouseButtonsUpdateRange() const { return MouseButtonsUpdateRange; } + + // Change state of the button in the mouse buttons array and expand range bounding dirty part of the array. + // @param MouseIndex - Index of the mouse button + // @param bIsDown - True, if button is down + void SetMouseDown(uint32 MouseIndex, bool IsDown); + + // Get mouse wheel delta accumulated during the last frame. + float GetMouseWheelDelta() const { return MouseWheelDelta; } + + // Add mouse wheel delta. + // @param DeltaValue - Mouse wheel delta to add + void AddMouseWheelDelta(float DeltaValue) { MouseWheelDelta += DeltaValue; } + + // Get the current mouse position. + const FVector2D& GetMousePosition() const { return MousePosition; } + + // Set mouse position. + // @param Position - New mouse position + void SetMousePosition(const FVector2D& Position) { MousePosition = Position; } + + // Clear state and mark as dirty. + void ClearState(); + + // Clear part of the state that is meant to be updated in every frame like: accumulators, buffers and information + // about dirty parts of keys or mouse buttons arrays. + void ClearUpdateState(); + +private: + + void ClearCharacters(); + + FVector2D MousePosition = FVector2D::ZeroVector; + float MouseWheelDelta = 0.f; + + FMouseButtonsArray MouseButtonsDown; + FMouseButtonsIndexRange MouseButtonsUpdateRange; + + FCharactersBuffer InputCharacters; + uint32 InputCharactersNum = 0; + + FKeysArray KeysDown; + FKeysIndexRange KeysUpdateRange; +}; diff --git a/Source/ImGui/Private/ImGuiInteroperability.cpp b/Source/ImGui/Private/ImGuiInteroperability.cpp new file mode 100644 index 0000000..656ec50 --- /dev/null +++ b/Source/ImGui/Private/ImGuiInteroperability.cpp @@ -0,0 +1,178 @@ +// Distributed under the MIT License (MIT) (see accompanying LICENSE file) + +#include "ImGuiPrivatePCH.h" + +#include "ImGuiInteroperability.h" +#include "ImGuiInputState.h" +#include "Utilities/Arrays.h" + + +namespace +{ + //==================================================================================================== + // Copying Utilities + //==================================================================================================== + + // Copy all elements from source to destination array of the same size. + template + void Copy(const TArray& Src, TArray& Dst) + { + using std::copy; + using std::begin; + using std::end; + copy(begin(Src), end(Src), begin(Dst)); + } + + // Copy subrange of source array to destination array of the same size. + template + void Copy(const TArray& Src, TArray& Dst, const Utilities::TArrayIndexRange& Range) + { + using std::copy; + using std::begin; + copy(begin(Src) + Range.GetBegin(), begin(Src) + Range.GetEnd(), begin(Dst) + Range.GetBegin()); + } + + // Copy number of elements from the beginning of source array to the beginning of destination array of the same size. + template + void Copy(const TArray& Src, TArray& Dst, SizeType Count) + { + checkf(Count < Utilities::ArraySize::value, TEXT("Number of copied elements is larger than array size.")); + + using std::copy; + using std::begin; + copy(begin(Src), begin(Src) + Count, begin(Dst)); + } +} + +namespace ImGuiInterops +{ + //==================================================================================================== + // Input Mapping + //==================================================================================================== + + void SetUnrealKeyMap(ImGuiIO& IO) + { + struct FUnrealToImGuiMapping + { + FUnrealToImGuiMapping() + { + KeyMap[ImGuiKey_Tab] = GetKeyIndex(EKeys::Tab); + KeyMap[ImGuiKey_LeftArrow] = GetKeyIndex(EKeys::Left); + KeyMap[ImGuiKey_RightArrow] = GetKeyIndex(EKeys::Right); + KeyMap[ImGuiKey_UpArrow] = GetKeyIndex(EKeys::Up); + KeyMap[ImGuiKey_DownArrow] = GetKeyIndex(EKeys::Down); + KeyMap[ImGuiKey_PageUp] = GetKeyIndex(EKeys::PageUp); + KeyMap[ImGuiKey_PageDown] = GetKeyIndex(EKeys::PageDown); + KeyMap[ImGuiKey_Home] = GetKeyIndex(EKeys::Home); + KeyMap[ImGuiKey_End] = GetKeyIndex(EKeys::End); + KeyMap[ImGuiKey_Delete] = GetKeyIndex(EKeys::Delete); + KeyMap[ImGuiKey_Backspace] = GetKeyIndex(EKeys::BackSpace); + KeyMap[ImGuiKey_Enter] = GetKeyIndex(EKeys::Enter); + KeyMap[ImGuiKey_Escape] = GetKeyIndex(EKeys::Escape); + KeyMap[ImGuiKey_A] = GetKeyIndex(EKeys::A); + KeyMap[ImGuiKey_C] = GetKeyIndex(EKeys::C); + KeyMap[ImGuiKey_V] = GetKeyIndex(EKeys::V); + KeyMap[ImGuiKey_X] = GetKeyIndex(EKeys::X); + KeyMap[ImGuiKey_Y] = GetKeyIndex(EKeys::Y); + KeyMap[ImGuiKey_Z] = GetKeyIndex(EKeys::Z); + } + + ImGuiTypes::FKeyMap KeyMap; + }; + + static const FUnrealToImGuiMapping Mapping; + + Copy(Mapping.KeyMap, IO.KeyMap); + } + + uint32 GetKeyIndex(const FKey& Key) + { + const uint32* pKeyCode = nullptr; + const uint32* pCharCode = nullptr; + + FInputKeyManager::Get().GetCodesFromKey(Key, pKeyCode, pCharCode); + + if (pKeyCode) + { + return *pKeyCode; + } + + if (pCharCode) + { + return *pCharCode; + } + + checkf(false, TEXT("Couldn't find a Key Code for key '%s'. Expecting that all keys should have a Key Code."), *Key.GetDisplayName().ToString()); + + return -1; + } + + uint32 GetMouseIndex(const FKey& MouseButton) + { + if (MouseButton == EKeys::LeftMouseButton) + { + return 0; + } + else if (MouseButton == EKeys::MiddleMouseButton) + { + return 2; + } + else if (MouseButton == EKeys::RightMouseButton) + { + return 1; + } + else if (MouseButton == EKeys::ThumbMouseButton) + { + return 3; + } + else if (MouseButton == EKeys::ThumbMouseButton2) + { + return 4; + } + + return -1; + } + + //==================================================================================================== + // Input State Copying + //==================================================================================================== + + void CopyInput(ImGuiIO& IO, const FImGuiInputState& InputState) + { + static const uint32 LeftControl = GetKeyIndex(EKeys::LeftControl); + static const uint32 RightControl = GetKeyIndex(EKeys::RightControl); + static const uint32 LeftShift = GetKeyIndex(EKeys::LeftShift); + static const uint32 RightShift = GetKeyIndex(EKeys::RightShift); + static const uint32 LeftAlt = GetKeyIndex(EKeys::LeftAlt); + static const uint32 RightAlt = GetKeyIndex(EKeys::RightAlt); + + // Copy mouse position. + IO.MousePos.x = InputState.GetMousePosition().X; + IO.MousePos.y = InputState.GetMousePosition().Y; + + // Copy mouse wheel delta. + IO.MouseWheel += InputState.GetMouseWheelDelta(); + + // Copy key modifiers. + IO.KeyCtrl = InputState.GetKeys()[LeftControl] || InputState.GetKeys()[RightControl]; + IO.KeyShift = InputState.GetKeys()[LeftShift] || InputState.GetKeys()[RightShift]; + IO.KeyAlt = InputState.GetKeys()[LeftAlt] || InputState.GetKeys()[RightAlt]; + IO.KeySuper = false; + + // Copy buffers. + if (!InputState.GetKeysUpdateRange().IsEmpty()) + { + Copy(InputState.GetKeys(), IO.KeysDown, InputState.GetKeysUpdateRange()); + } + + if (!InputState.GetMouseButtonsUpdateRange().IsEmpty()) + { + Copy(InputState.GetMouseButtons(), IO.MouseDown, InputState.GetMouseButtonsUpdateRange()); + } + + if (InputState.GetCharactersNum() > 0) + { + Copy(InputState.GetCharacters(), IO.InputCharacters, InputState.GetCharactersNum()); + } + } +} diff --git a/Source/ImGui/Private/ImGuiInteroperability.h b/Source/ImGui/Private/ImGuiInteroperability.h index 9a98266..ccc896b 100644 --- a/Source/ImGui/Private/ImGuiInteroperability.h +++ b/Source/ImGui/Private/ImGuiInteroperability.h @@ -7,9 +7,56 @@ #include +class FImGuiInputState; + // Utilities to help standardise operations between Unreal and ImGui. namespace ImGuiInterops { + //==================================================================================================== + // ImGui Types + //==================================================================================================== + + namespace ImGuiTypes + { + using FMouseButtonsArray = decltype(ImGuiIO::MouseDown); + using FKeysArray = decltype(ImGuiIO::KeysDown); + + using FInputCharactersBuffer = decltype(ImGuiIO::InputCharacters); + + using FKeyMap = decltype(ImGuiIO::KeyMap); + } + + + //==================================================================================================== + // Input Mapping + //==================================================================================================== + + // Set in ImGui IO mapping to recognize indices generated from Unreal input events. + void SetUnrealKeyMap(ImGuiIO& IO); + + // Map FKey to index in keys buffer. + uint32 GetKeyIndex(const FKey& Key); + + // Map key event to index in keys buffer. + uint32 GetKeyIndex(const FKeyEvent& KeyEvent) { return KeyEvent.GetKeyCode(); } + + // Map mouse FKey to index in mouse buttons buffer. + uint32 GetMouseIndex(const FKey& MouseButton); + + // Map pointer event to index in mouse buttons buffer. + uint32 GetMouseIndex(const FPointerEvent& MouseEvent) { return GetMouseIndex(MouseEvent.GetEffectingButton()); } + + + //==================================================================================================== + // Input State Copying + //==================================================================================================== + + // Copy input to ImGui IO. + // @param IO - Target ImGui IO + // @param InputState - Input state to copy + void CopyInput(ImGuiIO& IO, const FImGuiInputState& InputState); + + //==================================================================================================== // Conversions //==================================================================================================== diff --git a/Source/ImGui/Private/ImGuiModuleManager.cpp b/Source/ImGui/Private/ImGuiModuleManager.cpp index 20d6e2d..e496946 100644 --- a/Source/ImGui/Private/ImGuiModuleManager.cpp +++ b/Source/ImGui/Private/ImGuiModuleManager.cpp @@ -118,7 +118,10 @@ void FImGuiModuleManager::Tick(float DeltaSeconds) if (IsInUpdateThread()) { // Update context proxy to advance to next frame. - ContextProxy.Tick(DeltaSeconds); + ContextProxy.Tick(DeltaSeconds, ViewportWidget.IsValid() ? &ViewportWidget->GetInputState() : nullptr); + + // Inform that we finished updating ImGui, so other subsystems can react. + PostImGuiUpdateEvent.Broadcast(); } } diff --git a/Source/ImGui/Private/ImGuiModuleManager.h b/Source/ImGui/Private/ImGuiModuleManager.h index 1071199..fade0ea 100644 --- a/Source/ImGui/Private/ImGuiModuleManager.h +++ b/Source/ImGui/Private/ImGuiModuleManager.h @@ -22,6 +22,9 @@ public: // Get texture resources manager. FTextureManager& GetTextureManager() { return TextureManager; } + // Event called right after ImGui is updated, to give other subsystems chance to react. + FSimpleMulticastDelegate& OnPostImGuiUpdate() { return PostImGuiUpdateEvent; } + private: FImGuiModuleManager(); @@ -50,6 +53,9 @@ private: void AddWidgetToViewport(UGameViewportClient* GameViewport); void AddWidgetToAllViewports(); + // Event that we call after ImGui is updated. + FSimpleMulticastDelegate PostImGuiUpdateEvent; + // Proxy controlling ImGui context. FImGuiContextProxy ContextProxy; diff --git a/Source/ImGui/Private/SImGuiWidget.cpp b/Source/ImGui/Private/SImGuiWidget.cpp index 3316333..eca6bd0 100644 --- a/Source/ImGui/Private/SImGuiWidget.cpp +++ b/Source/ImGui/Private/SImGuiWidget.cpp @@ -4,6 +4,8 @@ #include "SImGuiWidget.h" +#include "ImGuiContextProxy.h" +#include "ImGuiInteroperability.h" #include "ImGuiModuleManager.h" #include "TextureManager.h" #include "Utilities/ScopeGuards.h" @@ -14,9 +16,76 @@ void SImGuiWidget::Construct(const FArguments& InArgs) { checkf(InArgs._ModuleManager, TEXT("Null Module Manager argument")); ModuleManager = InArgs._ModuleManager; + + ModuleManager->OnPostImGuiUpdate().AddRaw(this, &SImGuiWidget::OnPostImGuiUpdate); } END_SLATE_FUNCTION_BUILD_OPTIMIZATION +SImGuiWidget::~SImGuiWidget() +{ + ModuleManager->OnPostImGuiUpdate().RemoveAll(this); +} + +FReply SImGuiWidget::OnKeyChar(const FGeometry& MyGeometry, const FCharacterEvent& CharacterEvent) +{ + InputState.AddCharacter(CharacterEvent.GetCharacter()); + return FReply::Handled(); +} + +FReply SImGuiWidget::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& KeyEvent) +{ + InputState.SetKeyDown(ImGuiInterops::GetKeyIndex(KeyEvent), true); + + // If this is tilde key then let input through and release the focus to allow console to process it. + if (KeyEvent.GetKey() == EKeys::Tilde) + { + return FReply::Unhandled(); + } + + return FReply::Handled(); +} + +FReply SImGuiWidget::OnKeyUp(const FGeometry& MyGeometry, const FKeyEvent& KeyEvent) +{ + InputState.SetKeyDown(ImGuiInterops::GetKeyIndex(KeyEvent), false); + return FReply::Handled(); +} + +FReply SImGuiWidget::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) +{ + InputState.SetMouseDown(ImGuiInterops::GetMouseIndex(MouseEvent), true); + return FReply::Handled(); +} + +FReply SImGuiWidget::OnMouseButtonDoubleClick(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) +{ + InputState.SetMouseDown(ImGuiInterops::GetMouseIndex(MouseEvent), true); + return FReply::Handled(); +} + +FReply SImGuiWidget::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) +{ + InputState.SetMouseDown(ImGuiInterops::GetMouseIndex(MouseEvent), false); + return FReply::Handled(); +} + +FReply SImGuiWidget::OnMouseWheel(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) +{ + InputState.AddMouseWheelDelta(MouseEvent.GetWheelDelta()); + return FReply::Handled(); +} + +FReply SImGuiWidget::OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) +{ + InputState.SetMousePosition(MouseEvent.GetScreenSpacePosition() - MyGeometry.AbsolutePosition); + return FReply::Handled(); +} + +void SImGuiWidget::OnPostImGuiUpdate() +{ + InputState.ClearUpdateState(); +} + int32 SImGuiWidget::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& WidgetStyle, bool bParentEnabled) const { diff --git a/Source/ImGui/Private/SImGuiWidget.h b/Source/ImGui/Private/SImGuiWidget.h index 23d6bae..c6a775b 100644 --- a/Source/ImGui/Private/SImGuiWidget.h +++ b/Source/ImGui/Private/SImGuiWidget.h @@ -2,11 +2,14 @@ #pragma once +#include "ImGuiInputState.h" + #include + class FImGuiModuleManager; -// Slate widget for rendering ImGui output. +// Slate widget for rendering ImGui output and storing Slate inputs. class SImGuiWidget : public SLeafWidget { public: @@ -18,8 +21,36 @@ public: void Construct(const FArguments& InArgs); + ~SImGuiWidget(); + + const FImGuiInputState& GetInputState() const { return InputState; } + + //---------------------------------------------------------------------------------------------------- + // SWidget overrides + //---------------------------------------------------------------------------------------------------- + + virtual bool SupportsKeyboardFocus() const override { return true; } + + virtual FReply OnKeyChar(const FGeometry& MyGeometry, const FCharacterEvent& CharacterEvent) override; + + virtual FReply OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& KeyEvent) override; + + virtual FReply OnKeyUp(const FGeometry& MyGeometry, const FKeyEvent& KeyEvent) override; + + virtual FReply OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; + + virtual FReply OnMouseButtonDoubleClick(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; + + virtual FReply OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; + + virtual FReply OnMouseWheel(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; + + virtual FReply OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; + private: + void OnPostImGuiUpdate(); + virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& WidgetStyle, bool bParentEnabled) const override; virtual FVector2D ComputeDesiredSize(float) const override; @@ -28,4 +59,6 @@ private: mutable TArray VertexBuffer; mutable TArray IndexBuffer; + + FImGuiInputState InputState; }; diff --git a/Source/ImGui/Private/Utilities/Arrays.h b/Source/ImGui/Private/Utilities/Arrays.h new file mode 100644 index 0000000..9b461ad --- /dev/null +++ b/Source/ImGui/Private/Utilities/Arrays.h @@ -0,0 +1,63 @@ +// Distributed under the MIT License (MIT) (see accompanying LICENSE file) + +#pragma once + +#include "Range.h" + +#include +#include +#include + + +// Utilities to work with one-dimensional, statically bound arrays. Code relying on those utilities should work without +// modifications with fixed-sized arrays (currently used in ImGui) and with standard arrays. + +namespace Utilities +{ + //==================================================================================================== + // Helper functions + //==================================================================================================== + + // Function to determine number of elements in fixed size array. + template + constexpr std::size_t GetArraySize(const T(&)[N]) + { + return N; + } + + // Function to determine number of elements in std array. + template + constexpr std::size_t GetArraySize(const std::array&) + { + return N; + } + + + //==================================================================================================== + // Traits + //==================================================================================================== + + template + struct ArraySize; + + // Struct to determine number of elements in fixed size array. + template + struct ArraySize : std::extent + { + }; + + // Struct to determine number of elements in std array. + template + struct ArraySize> : std::tuple_size> + { + }; + + + //==================================================================================================== + // Ranges + //==================================================================================================== + + // Array indices range. Limited by 0 and array size. + template + using TArrayIndexRange = TBoundedRange::value>; +} diff --git a/Source/ImGui/Private/Utilities/Range.h b/Source/ImGui/Private/Utilities/Range.h new file mode 100644 index 0000000..09323f3 --- /dev/null +++ b/Source/ImGui/Private/Utilities/Range.h @@ -0,0 +1,173 @@ +// Distributed under the MIT License (MIT) (see accompanying LICENSE file) + +#pragma once + +#include + + +namespace Utilities +{ + //==================================================================================================== + // Range + //==================================================================================================== + + template + class TRange + { + public: + + TRange() {} + + TRange(const T& RangeBegin, const T& RangeEnd) { SetRange(RangeBegin, RangeEnd); } + + const T& GetBegin() const { return Begin; } + const T& GetEnd() const { return End; } + + bool IsEmpty() const { return Begin == End; } + + void SetEmpty() { Begin = End = T(); } + + void SetRange(const T& RangeBegin, const T& RangeEnd) + { + checkf(RangeBegin <= RangeEnd, TEXT("Invalid arguments: RangeBegin > RangeEnd.")); + Begin = RangeBegin; + End = RangeEnd; + } + + void AddPosition(const T& Position) + { + AddRangeUnchecked(Position, Position + 1); + } + + void AddRange(const T& RangeBegin, const T& RangeEnd) + { + checkf(RangeBegin <= RangeEnd, TEXT("Invalid arguments: RangeBegin > RangeEnd.")); + AddRangeUnchecked(RangeBegin, RangeEnd); + } + + private: + + void AddRangeUnchecked(const T& RangeBegin, const T& RangeEnd) + { + if (IsEmpty()) + { + Begin = RangeBegin; + End = RangeEnd; + } + else + { + if (Begin > RangeBegin) + { + Begin = RangeBegin; + } + + if (End < RangeEnd) + { + End = RangeEnd; + } + } + } + + T Begin = T(); + T End = T(); + }; + + + // Enable range-based loops + + template + const T& begin(const TRange& Range) + { + return Range.GetBegin(); + } + + template + const T& end(const TRange& Range) + { + return Range.GetEnd(); + } + + + //==================================================================================================== + // Bounded Range + //==================================================================================================== + + template + class TBoundedRange + { + public: + + constexpr const T& GetLowerBound() const { return BeginBound; } + constexpr const T& GetUpperBound() const { return EndBound; } + + const T& GetBegin() const { return Begin; } + const T& GetEnd() const { return End; } + + bool IsEmpty() const { return Begin == End; } + + void SetEmpty() { Begin = End = BeginBound; } + + void SetFull() + { + Begin = BeginBound; + End = EndBound; + } + + void AddPosition(const T& Position) + { + checkf(Position >= BeginBound && Position < EndBound, TEXT("Position out of range.")); + + AddRangeUnchecked(Position, Position + 1); + } + + void AddRange(const T& RangeBegin, const T& RangeEnd) + { + checkf(RangeBegin <= RangeEnd, TEXT("Invalid arguments: RangeBegin > MaxPosition.")); + checkf(RangeBegin >= BeginBound, TEXT("RangeBegin out of range.")); + checkf(RangeBegin <= EndBound, TEXT("RangeEnd out of range.")); + + AddRangeUnchecked(RangeBegin, RangeEnd); + } + + private: + + void AddRangeUnchecked(const T& RangeBegin, const T& RangeEnd) + { + if (IsEmpty()) + { + Begin = RangeBegin; + End = RangeEnd; + } + else + { + if (Begin > RangeBegin) + { + Begin = RangeBegin; + } + + if (End < RangeEnd) + { + End = RangeEnd; + } + } + } + + T Begin = EndBound; + T End = BeginBound; + }; + + + // Enable range-based loops + + template + const T& begin(const TBoundedRange& Range) + { + return Range.GetBegin(); + } + + template + const T& end(const TBoundedRange& Range) + { + return Range.GetEnd(); + } +}