mirror of
https://github.com/kevinporetti/UnrealImGui.git
synced 2025-01-18 08:20:32 +00:00
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.
This commit is contained in:
parent
35f2d342a0
commit
393460f330
@ -41,6 +41,7 @@ public class ImGui : ModuleRules
|
|||||||
{
|
{
|
||||||
"CoreUObject",
|
"CoreUObject",
|
||||||
"Engine",
|
"Engine",
|
||||||
|
"InputCore",
|
||||||
"Slate",
|
"Slate",
|
||||||
"SlateCore"
|
"SlateCore"
|
||||||
// ... add private dependencies that you statically link with here ...
|
// ... add private dependencies that you statically link with here ...
|
||||||
|
@ -20,6 +20,9 @@ FImGuiContextProxy::FImGuiContextProxy()
|
|||||||
unsigned char* Pixels;
|
unsigned char* Pixels;
|
||||||
IO.Fonts->GetTexDataAsRGBA32(&Pixels, nullptr, nullptr);
|
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
|
// Begin frame to complete context initialization (this is to avoid problems with other systems calling to ImGui
|
||||||
// during startup).
|
// during startup).
|
||||||
BeginFrame();
|
BeginFrame();
|
||||||
@ -30,7 +33,7 @@ FImGuiContextProxy::~FImGuiContextProxy()
|
|||||||
ImGui::Shutdown();
|
ImGui::Shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
void FImGuiContextProxy::Tick(float DeltaSeconds)
|
void FImGuiContextProxy::Tick(float DeltaSeconds, const FImGuiInputState* InputState)
|
||||||
{
|
{
|
||||||
if (bIsFrameStarted)
|
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.
|
// 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)
|
if (!bIsFrameStarted)
|
||||||
{
|
{
|
||||||
ImGuiIO& IO = ImGui::GetIO();
|
ImGuiIO& IO = ImGui::GetIO();
|
||||||
IO.DeltaTime = DeltaTime;
|
IO.DeltaTime = DeltaTime;
|
||||||
|
|
||||||
|
if (InputState)
|
||||||
|
{
|
||||||
|
ImGuiInterops::CopyInput(IO, *InputState);
|
||||||
|
}
|
||||||
|
|
||||||
ImGui::NewFrame();
|
ImGui::NewFrame();
|
||||||
|
|
||||||
bIsFrameStarted = true;
|
bIsFrameStarted = true;
|
||||||
|
@ -7,6 +7,8 @@
|
|||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
|
|
||||||
|
|
||||||
|
class FImGuiInputState;
|
||||||
|
|
||||||
// Represents a single ImGui context. All the context updates should be done through this proxy. During update it
|
// 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.
|
// 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.
|
// TODO: Add dynamically created contexts, so we can have a better support for multi-PIE.
|
||||||
@ -30,11 +32,13 @@ public:
|
|||||||
FSimpleMulticastDelegate& OnDraw() { return DrawEvent; }
|
FSimpleMulticastDelegate& OnDraw() { return DrawEvent; }
|
||||||
|
|
||||||
// Tick to advance context to the next frame.
|
// 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:
|
private:
|
||||||
|
|
||||||
void BeginFrame(float DeltaTime = 1.f / 60.f);
|
void BeginFrame(float DeltaTime = 1.f / 60.f, const FImGuiInputState* InputState = nullptr);
|
||||||
void EndFrame();
|
void EndFrame();
|
||||||
|
|
||||||
void UpdateDrawData(ImDrawData* DrawData);
|
void UpdateDrawData(ImDrawData* DrawData);
|
||||||
|
75
Source/ImGui/Private/ImGuiInputState.cpp
Normal file
75
Source/ImGui/Private/ImGuiInputState.cpp
Normal file
@ -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<ImWchar>(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];
|
||||||
|
}
|
101
Source/ImGui/Private/ImGuiInputState.h
Normal file
101
Source/ImGui/Private/ImGuiInputState.h
Normal file
@ -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<FMouseButtonsArray, uint32>;
|
||||||
|
|
||||||
|
// Pair of indices defining range in keys array.
|
||||||
|
using FKeysIndexRange = Utilities::TArrayIndexRange<FKeysArray, uint32>;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
};
|
178
Source/ImGui/Private/ImGuiInteroperability.cpp
Normal file
178
Source/ImGui/Private/ImGuiInteroperability.cpp
Normal file
@ -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<typename TArray>
|
||||||
|
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<typename TArray, typename SizeType>
|
||||||
|
void Copy(const TArray& Src, TArray& Dst, const Utilities::TArrayIndexRange<typename TArray, typename SizeType>& 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<typename TArray, typename SizeType>
|
||||||
|
void Copy(const TArray& Src, TArray& Dst, SizeType Count)
|
||||||
|
{
|
||||||
|
checkf(Count < Utilities::ArraySize<TArray>::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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -7,9 +7,56 @@
|
|||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
|
|
||||||
|
|
||||||
|
class FImGuiInputState;
|
||||||
|
|
||||||
// Utilities to help standardise operations between Unreal and ImGui.
|
// Utilities to help standardise operations between Unreal and ImGui.
|
||||||
namespace ImGuiInterops
|
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
|
// Conversions
|
||||||
//====================================================================================================
|
//====================================================================================================
|
||||||
|
@ -118,7 +118,10 @@ void FImGuiModuleManager::Tick(float DeltaSeconds)
|
|||||||
if (IsInUpdateThread())
|
if (IsInUpdateThread())
|
||||||
{
|
{
|
||||||
// Update context proxy to advance to next frame.
|
// 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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,6 +22,9 @@ public:
|
|||||||
// Get texture resources manager.
|
// Get texture resources manager.
|
||||||
FTextureManager& GetTextureManager() { return TextureManager; }
|
FTextureManager& GetTextureManager() { return TextureManager; }
|
||||||
|
|
||||||
|
// Event called right after ImGui is updated, to give other subsystems chance to react.
|
||||||
|
FSimpleMulticastDelegate& OnPostImGuiUpdate() { return PostImGuiUpdateEvent; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
FImGuiModuleManager();
|
FImGuiModuleManager();
|
||||||
@ -50,6 +53,9 @@ private:
|
|||||||
void AddWidgetToViewport(UGameViewportClient* GameViewport);
|
void AddWidgetToViewport(UGameViewportClient* GameViewport);
|
||||||
void AddWidgetToAllViewports();
|
void AddWidgetToAllViewports();
|
||||||
|
|
||||||
|
// Event that we call after ImGui is updated.
|
||||||
|
FSimpleMulticastDelegate PostImGuiUpdateEvent;
|
||||||
|
|
||||||
// Proxy controlling ImGui context.
|
// Proxy controlling ImGui context.
|
||||||
FImGuiContextProxy ContextProxy;
|
FImGuiContextProxy ContextProxy;
|
||||||
|
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
#include "SImGuiWidget.h"
|
#include "SImGuiWidget.h"
|
||||||
|
|
||||||
|
#include "ImGuiContextProxy.h"
|
||||||
|
#include "ImGuiInteroperability.h"
|
||||||
#include "ImGuiModuleManager.h"
|
#include "ImGuiModuleManager.h"
|
||||||
#include "TextureManager.h"
|
#include "TextureManager.h"
|
||||||
#include "Utilities/ScopeGuards.h"
|
#include "Utilities/ScopeGuards.h"
|
||||||
@ -14,9 +16,76 @@ void SImGuiWidget::Construct(const FArguments& InArgs)
|
|||||||
{
|
{
|
||||||
checkf(InArgs._ModuleManager, TEXT("Null Module Manager argument"));
|
checkf(InArgs._ModuleManager, TEXT("Null Module Manager argument"));
|
||||||
ModuleManager = InArgs._ModuleManager;
|
ModuleManager = InArgs._ModuleManager;
|
||||||
|
|
||||||
|
ModuleManager->OnPostImGuiUpdate().AddRaw(this, &SImGuiWidget::OnPostImGuiUpdate);
|
||||||
}
|
}
|
||||||
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
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,
|
int32 SImGuiWidget::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect,
|
||||||
FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& WidgetStyle, bool bParentEnabled) const
|
FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& WidgetStyle, bool bParentEnabled) const
|
||||||
{
|
{
|
||||||
|
@ -2,11 +2,14 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "ImGuiInputState.h"
|
||||||
|
|
||||||
#include <Widgets/SLeafWidget.h>
|
#include <Widgets/SLeafWidget.h>
|
||||||
|
|
||||||
|
|
||||||
class FImGuiModuleManager;
|
class FImGuiModuleManager;
|
||||||
|
|
||||||
// Slate widget for rendering ImGui output.
|
// Slate widget for rendering ImGui output and storing Slate inputs.
|
||||||
class SImGuiWidget : public SLeafWidget
|
class SImGuiWidget : public SLeafWidget
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -18,8 +21,36 @@ public:
|
|||||||
|
|
||||||
void Construct(const FArguments& InArgs);
|
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:
|
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 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;
|
virtual FVector2D ComputeDesiredSize(float) const override;
|
||||||
@ -28,4 +59,6 @@ private:
|
|||||||
|
|
||||||
mutable TArray<FSlateVertex> VertexBuffer;
|
mutable TArray<FSlateVertex> VertexBuffer;
|
||||||
mutable TArray<SlateIndex> IndexBuffer;
|
mutable TArray<SlateIndex> IndexBuffer;
|
||||||
|
|
||||||
|
FImGuiInputState InputState;
|
||||||
};
|
};
|
||||||
|
63
Source/ImGui/Private/Utilities/Arrays.h
Normal file
63
Source/ImGui/Private/Utilities/Arrays.h
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
// Distributed under the MIT License (MIT) (see accompanying LICENSE file)
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Range.h"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <iterator>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
|
||||||
|
// 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<class T, std::size_t N>
|
||||||
|
constexpr std::size_t GetArraySize(const T(&)[N])
|
||||||
|
{
|
||||||
|
return N;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to determine number of elements in std array.
|
||||||
|
template<class T, std::size_t N>
|
||||||
|
constexpr std::size_t GetArraySize(const std::array<T, N>&)
|
||||||
|
{
|
||||||
|
return N;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//====================================================================================================
|
||||||
|
// Traits
|
||||||
|
//====================================================================================================
|
||||||
|
|
||||||
|
template<typename TArray>
|
||||||
|
struct ArraySize;
|
||||||
|
|
||||||
|
// Struct to determine number of elements in fixed size array.
|
||||||
|
template<typename T, std::size_t N>
|
||||||
|
struct ArraySize<T[N]> : std::extent<T[N]>
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
// Struct to determine number of elements in std array.
|
||||||
|
template<typename T, std::size_t N>
|
||||||
|
struct ArraySize<std::array<T, N>> : std::tuple_size<std::array<T, N>>
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//====================================================================================================
|
||||||
|
// Ranges
|
||||||
|
//====================================================================================================
|
||||||
|
|
||||||
|
// Array indices range. Limited by 0 and array size.
|
||||||
|
template<typename TArray, typename SizeType>
|
||||||
|
using TArrayIndexRange = TBoundedRange<SizeType, 0, ArraySize<TArray>::value>;
|
||||||
|
}
|
173
Source/ImGui/Private/Utilities/Range.h
Normal file
173
Source/ImGui/Private/Utilities/Range.h
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
// Distributed under the MIT License (MIT) (see accompanying LICENSE file)
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
|
||||||
|
namespace Utilities
|
||||||
|
{
|
||||||
|
//====================================================================================================
|
||||||
|
// Range
|
||||||
|
//====================================================================================================
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
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<typename T>
|
||||||
|
const T& begin(const TRange<T>& Range)
|
||||||
|
{
|
||||||
|
return Range.GetBegin();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
const T& end(const TRange<T>& Range)
|
||||||
|
{
|
||||||
|
return Range.GetEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//====================================================================================================
|
||||||
|
// Bounded Range
|
||||||
|
//====================================================================================================
|
||||||
|
|
||||||
|
template<typename T, T BeginBound, T EndBound>
|
||||||
|
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<typename T, T BeginBound, T EndBound>
|
||||||
|
const T& begin(const TBoundedRange<T, BeginBound, EndBound>& Range)
|
||||||
|
{
|
||||||
|
return Range.GetBegin();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T, T BeginBound, T EndBound>
|
||||||
|
const T& end(const TBoundedRange<T, BeginBound, EndBound>& Range)
|
||||||
|
{
|
||||||
|
return Range.GetEnd();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user