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:
Sebastian 2017-04-22 16:38:04 +01:00
parent 35f2d342a0
commit 393460f330
13 changed files with 768 additions and 7 deletions

View File

@ -41,6 +41,7 @@ public class ImGui : ModuleRules
{
"CoreUObject",
"Engine",
"InputCore",
"Slate",
"SlateCore"
// ... add private dependencies that you statically link with here ...

View File

@ -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;

View File

@ -7,6 +7,8 @@
#include <imgui.h>
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);

View 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];
}

View 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;
};

View 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());
}
}
}

View File

@ -7,9 +7,56 @@
#include <imgui.h>
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
//====================================================================================================

View File

@ -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();
}
}

View File

@ -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;

View File

@ -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
{

View File

@ -2,11 +2,14 @@
#pragma once
#include "ImGuiInputState.h"
#include <Widgets/SLeafWidget.h>
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<FSlateVertex> VertexBuffer;
mutable TArray<SlateIndex> IndexBuffer;
FImGuiInputState InputState;
};

View 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>;
}

View 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();
}
}