mirror of
https://github.com/kevinporetti/UnrealImGui.git
synced 2025-01-18 08:20:32 +00:00
Added to ImGui Widget a canvas map mode that allows to drag and change scale of the ImGui canvas.
Map mode allows to reach areas of the canvas that otherwise would be inaccessible (for instance modal windows positioned in the centre of the canvas) and to modify which part should be visible by default.
This commit is contained in:
parent
d152b4193c
commit
fa32fd95e3
14
README.md
14
README.md
@ -52,7 +52,7 @@ PrivateDependencyModuleNames.AddRange(new string[] { "ImGui" });
|
||||
You should now be able to use ImGui.
|
||||
|
||||
|
||||
*Console variables:*
|
||||
### Console variables
|
||||
|
||||
- **ImGui.InputEnabled** - Enable or disable ImGui input mode. 0: disabled (default); 1: enabled, input is routed to ImGui and with a few exceptions is consumed. Note: this is going to be supported by a keyboard short-cut, but in the meantime ImGui input can be enabled/disabled using console.
|
||||
- **ImGui.DrawMouseCursor** - Whether or not mouse cursor in input mode should be drawn by ImGui. 0: disabled, hardware cursor will be used (default); 1: enabled, ImGui will take care for drawing mouse cursor.
|
||||
@ -60,6 +60,18 @@ You should now be able to use ImGui.
|
||||
- **ImGui.Debug.Widget** - Show self-debug for the widget that renders ImGui output. 0: disabled (default); 1: enabled.
|
||||
|
||||
|
||||
### Canvas Map Mode
|
||||
|
||||
When input mode is enabled, it is possible to activate *Canvas Map Mode* (better name welcomed) by pressing and holding `Left Shift` + `Left Alt` keys. In this mode it is possible to drag ImGui canvas and change its scale. It can be helpful to temporarily reach areas of canvas that otherwise would be inaccessible and to change what part of the canvas should be visible in normal mode.
|
||||
|
||||
In canvas map mode:
|
||||
- **Mouse Wheel** - to zoom in and out.
|
||||
- **Right Mouse Button** - to drag ImGui canvas (not available at maximum zoom out).
|
||||
- **Middle Mouse Button** - to drag frame that represents part of the ImGui canvas that is visible in normal mode (only available after zooming out). To start dragging mouse needs to be in the centre of that frame.
|
||||
- It is still possible to use remaining keys and gestures to use ImGui, but primary goal is to select part of the canvas visible in normal mode.
|
||||
- Releasing `Left Shift` and/or `Left Alt` key switches back to normal mode and automatically sets scale to 1.
|
||||
|
||||
|
||||
See also
|
||||
--------
|
||||
|
||||
|
@ -73,7 +73,7 @@ void FImGuiModuleManager::LoadTextures()
|
||||
checkf(FSlateApplication::IsInitialized(), TEXT("Slate should be initialized before we can create textures."));
|
||||
|
||||
// Create an empty texture at index 0. We will use it for ImGui outputs with null texture id.
|
||||
TextureManager.CreatePlainTexture(FName{ "ImGuiModule_Null" }, 2, 2, FColor::White);
|
||||
TextureManager.CreatePlainTexture(FName{ "ImGuiModule_Plain" }, 2, 2, FColor::White);
|
||||
|
||||
// Create a font atlas texture.
|
||||
ImFontAtlas* Fonts = ImGui::GetIO().Fonts;
|
||||
|
@ -6,9 +6,11 @@
|
||||
|
||||
#include "ImGuiContextManager.h"
|
||||
#include "ImGuiContextProxy.h"
|
||||
#include "ImGuiImplementation.h"
|
||||
#include "ImGuiInteroperability.h"
|
||||
#include "ImGuiModuleManager.h"
|
||||
#include "TextureManager.h"
|
||||
#include "Utilities/Arrays.h"
|
||||
#include "Utilities/ScopeGuards.h"
|
||||
|
||||
#include <Engine/Console.h>
|
||||
@ -30,6 +32,17 @@ DEFINE_LOG_CATEGORY_STATIC(LogImGuiWidget, Warning, All);
|
||||
#define TEXT_BOOL(Val) ((Val) ? TEXT("true") : TEXT("false"))
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
const FColor CanvasFrameColor = { 16, 16, 16 };
|
||||
const FColor ViewportFrameColor = { 204, 74, 10 };
|
||||
const FColor ViewportFrameHighlightColor = { 255, 110, 38 };
|
||||
|
||||
constexpr const char* PlainTextureName = "ImGuiModule_Plain";
|
||||
constexpr const char* FontAtlasTextureName = "ImGuiModule_FontAtlas";
|
||||
}
|
||||
|
||||
|
||||
namespace CVars
|
||||
{
|
||||
TAutoConsoleVariable<int> InputEnabled(TEXT("ImGui.InputEnabled"), 0,
|
||||
@ -143,13 +156,9 @@ FReply SImGuiWidget::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& Key
|
||||
InputState.SetKeyDown(ImGuiInterops::GetKeyIndex(KeyEvent), true);
|
||||
CopyModifierKeys(KeyEvent);
|
||||
|
||||
// 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();
|
||||
}
|
||||
UpdateCanvasMapMode(KeyEvent);
|
||||
|
||||
return FReply::Handled();
|
||||
return WithMouseLockRequests(FReply::Handled());
|
||||
}
|
||||
|
||||
FReply SImGuiWidget::OnKeyUp(const FGeometry& MyGeometry, const FKeyEvent& KeyEvent)
|
||||
@ -159,8 +168,10 @@ FReply SImGuiWidget::OnKeyUp(const FGeometry& MyGeometry, const FKeyEvent& KeyEv
|
||||
InputState.SetKeyDown(ImGuiInterops::GetKeyIndex(KeyEvent), false);
|
||||
CopyModifierKeys(KeyEvent);
|
||||
|
||||
UpdateCanvasMapMode(KeyEvent);
|
||||
|
||||
// If console is opened we notify key change but we also let event trough, so it can be handled by console.
|
||||
return IsConsoleOpened() ? FReply::Unhandled() : FReply::Handled();
|
||||
return IsConsoleOpened() ? FReply::Unhandled() : WithMouseLockRequests(FReply::Handled());
|
||||
}
|
||||
|
||||
FReply SImGuiWidget::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
||||
@ -168,7 +179,10 @@ FReply SImGuiWidget::OnMouseButtonDown(const FGeometry& MyGeometry, const FPoint
|
||||
InputState.SetMouseDown(ImGuiInterops::GetMouseIndex(MouseEvent), true);
|
||||
CopyModifierKeys(MouseEvent);
|
||||
|
||||
return FReply::Handled();
|
||||
UpdateCanvasMapMode(MouseEvent);
|
||||
UpdateCanvasDraggingConditions(MouseEvent);
|
||||
|
||||
return WithMouseLockRequests(FReply::Handled());
|
||||
}
|
||||
|
||||
FReply SImGuiWidget::OnMouseButtonDoubleClick(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
||||
@ -176,7 +190,10 @@ FReply SImGuiWidget::OnMouseButtonDoubleClick(const FGeometry& MyGeometry, const
|
||||
InputState.SetMouseDown(ImGuiInterops::GetMouseIndex(MouseEvent), true);
|
||||
CopyModifierKeys(MouseEvent);
|
||||
|
||||
return FReply::Handled();
|
||||
UpdateCanvasMapMode(MouseEvent);
|
||||
UpdateCanvasDraggingConditions(MouseEvent);
|
||||
|
||||
return WithMouseLockRequests(FReply::Handled());
|
||||
}
|
||||
|
||||
FReply SImGuiWidget::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
||||
@ -184,12 +201,21 @@ FReply SImGuiWidget::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointer
|
||||
InputState.SetMouseDown(ImGuiInterops::GetMouseIndex(MouseEvent), false);
|
||||
CopyModifierKeys(MouseEvent);
|
||||
|
||||
return FReply::Handled();
|
||||
UpdateCanvasMapMode(MouseEvent);
|
||||
|
||||
return WithMouseLockRequests(FReply::Handled());
|
||||
}
|
||||
|
||||
FReply SImGuiWidget::OnMouseWheel(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
||||
{
|
||||
if (bCanvasMapMode)
|
||||
{
|
||||
AddCanvasScale(MouseEvent.GetWheelDelta());
|
||||
}
|
||||
else
|
||||
{
|
||||
InputState.AddMouseWheelDelta(MouseEvent.GetWheelDelta());
|
||||
}
|
||||
CopyModifierKeys(MouseEvent);
|
||||
|
||||
return FReply::Handled();
|
||||
@ -198,7 +224,11 @@ FReply SImGuiWidget::OnMouseWheel(const FGeometry& MyGeometry, const FPointerEve
|
||||
FCursorReply SImGuiWidget::OnCursorQuery(const FGeometry& MyGeometry, const FPointerEvent& CursorEvent) const
|
||||
{
|
||||
EMouseCursor::Type MouseCursor = EMouseCursor::None;
|
||||
if (CVars::DrawMouseCursor.GetValueOnGameThread() <= 0)
|
||||
if (MouseCursorOverride != EMouseCursor::None)
|
||||
{
|
||||
MouseCursor = MouseCursorOverride;
|
||||
}
|
||||
else if (CVars::DrawMouseCursor.GetValueOnGameThread() <= 0)
|
||||
{
|
||||
if (FImGuiContextProxy* ContextProxy = ModuleManager->GetContextManager().GetContextProxy(ContextIndex))
|
||||
{
|
||||
@ -211,13 +241,21 @@ FCursorReply SImGuiWidget::OnCursorQuery(const FGeometry& MyGeometry, const FPoi
|
||||
|
||||
FReply SImGuiWidget::OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
||||
{
|
||||
InputState.SetMousePosition(MouseEvent.GetScreenSpacePosition() - MyGeometry.AbsolutePosition);
|
||||
if (bCanvasMapMode)
|
||||
{
|
||||
UpdateCanvasDragging(MyGeometry, MouseEvent);
|
||||
}
|
||||
|
||||
const FVector2D CanvasScreenSpacePosition = MyGeometry.AbsolutePosition + GetCanvasPosition(CanvasScale, CanvasOffset);
|
||||
InputState.SetMousePosition((MouseEvent.GetScreenSpacePosition() - CanvasScreenSpacePosition) / CanvasScale);
|
||||
CopyModifierKeys(MouseEvent);
|
||||
|
||||
// This event is called in every frame when we have a mouse, so we can use it to raise notifications.
|
||||
NotifyMouseEvent();
|
||||
|
||||
return FReply::Handled();
|
||||
UpdateCanvasMapMode(MouseEvent);
|
||||
|
||||
return WithMouseLockRequests(FReply::Handled());
|
||||
}
|
||||
|
||||
FReply SImGuiWidget::OnFocusReceived(const FGeometry& MyGeometry, const FFocusEvent& FocusEvent)
|
||||
@ -231,7 +269,7 @@ FReply SImGuiWidget::OnFocusReceived(const FGeometry& MyGeometry, const FFocusEv
|
||||
UpdateInputMode(true, IsDirectlyHovered());
|
||||
|
||||
FSlateApplication::Get().ResetToDefaultPointerInputSettings();
|
||||
return FReply::Handled();
|
||||
return WithMouseLockRequests(FReply::Handled());
|
||||
}
|
||||
|
||||
void SImGuiWidget::OnFocusLost(const FFocusEvent& FocusEvent)
|
||||
@ -273,6 +311,25 @@ void SImGuiWidget::OnMouseLeave(const FPointerEvent& MouseEvent)
|
||||
UpdateInputMode(HasKeyboardFocus() && GameViewport->Viewport->IsForegroundWindow(), false);
|
||||
}
|
||||
|
||||
FReply SImGuiWidget::WithMouseLockRequests(FReply&& Reply)
|
||||
{
|
||||
const bool bNeedMouseLock = bCanvasDragging || bFrameDragging;
|
||||
if (bNeedMouseLock != bMouseLock)
|
||||
{
|
||||
bMouseLock = bNeedMouseLock;
|
||||
if (bMouseLock)
|
||||
{
|
||||
Reply.LockMouseToWidget(SharedThis(this));
|
||||
}
|
||||
else
|
||||
{
|
||||
Reply.ReleaseMouseLock();
|
||||
}
|
||||
}
|
||||
|
||||
return Reply;
|
||||
}
|
||||
|
||||
void SImGuiWidget::CopyModifierKeys(const FInputEvent& InputEvent)
|
||||
{
|
||||
InputState.SetControlDown(InputEvent.IsControlDown());
|
||||
@ -314,6 +371,16 @@ bool SImGuiWidget::IgnoreKeyEvent(const FKeyEvent& KeyEvent) const
|
||||
return false;
|
||||
}
|
||||
|
||||
void SImGuiWidget::SetMouseCursorOverride(EMouseCursor::Type InMouseCursorOverride)
|
||||
{
|
||||
if (MouseCursorOverride != InMouseCursorOverride)
|
||||
{
|
||||
MouseCursorOverride = InMouseCursorOverride;
|
||||
FSlateApplication::Get().QueryCursor();
|
||||
InputState.SetMousePointer(MouseCursorOverride == EMouseCursor::None && IsDirectlyHovered() && CVars::DrawMouseCursor.GetValueOnGameThread() > 0);
|
||||
}
|
||||
}
|
||||
|
||||
void SImGuiWidget::SetVisibilityFromInputEnabled()
|
||||
{
|
||||
// If we don't use input disable hit test to make this widget invisible for cursors hit detection.
|
||||
@ -399,9 +466,14 @@ void SImGuiWidget::UpdateInputMode(bool bHasKeyboardFocus, bool bHasMousePointer
|
||||
InputMode = NewInputMode;
|
||||
|
||||
ClearMouseEventNotification();
|
||||
|
||||
if (InputMode != EInputMode::MouseAndKeyboard)
|
||||
{
|
||||
SetCanvasMapMode(false);
|
||||
}
|
||||
}
|
||||
|
||||
InputState.SetMousePointer(bHasMousePointer && CVars::DrawMouseCursor.GetValueOnGameThread() > 0);
|
||||
InputState.SetMousePointer(MouseCursorOverride == EMouseCursor::None && bHasMousePointer && CVars::DrawMouseCursor.GetValueOnGameThread() > 0);
|
||||
}
|
||||
|
||||
void SImGuiWidget::UpdateMouseStatus()
|
||||
@ -426,6 +498,272 @@ void SImGuiWidget::OnPostImGuiUpdate()
|
||||
{
|
||||
InputState.ClearUpdateState();
|
||||
}
|
||||
|
||||
// Remember values associated with input state send to ImGui, so we can use them when rendering frame output.
|
||||
ImGuiFrameCanvasScale = CanvasScale;
|
||||
ImGuiFrameCanvasOffset = CanvasOffset;
|
||||
|
||||
// Update canvas scale.
|
||||
UdateCanvasScale(FSlateApplication::Get().GetDeltaTime());
|
||||
}
|
||||
|
||||
void SImGuiWidget::UpdateCanvasMapMode(const FInputEvent& InputEvent)
|
||||
{
|
||||
SetCanvasMapMode(InputEvent.IsLeftAltDown() && InputEvent.IsLeftShiftDown());
|
||||
}
|
||||
|
||||
void SImGuiWidget::SetCanvasMapMode(bool bEnabled)
|
||||
{
|
||||
if (bEnabled != bCanvasMapMode)
|
||||
{
|
||||
bCanvasMapMode = bEnabled;
|
||||
|
||||
if (!bCanvasMapMode)
|
||||
{
|
||||
if (TargetCanvasScale != 1.f)
|
||||
{
|
||||
TargetCanvasScale = 1.f;
|
||||
}
|
||||
|
||||
bCanvasDragging = false;
|
||||
bFrameDragging = false;
|
||||
bFrameDraggingReady = false;
|
||||
SetMouseCursorOverride(EMouseCursor::None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SImGuiWidget::AddCanvasScale(float Delta)
|
||||
{
|
||||
TargetCanvasScale = FMath::Clamp(TargetCanvasScale + Delta * 0.05f, GetMinCanvasScale(), 1.f);
|
||||
}
|
||||
|
||||
void SImGuiWidget::UdateCanvasScale(float DeltaSeconds)
|
||||
{
|
||||
if (CanvasScale != TargetCanvasScale)
|
||||
{
|
||||
CanvasScale = FMath::Lerp(CanvasScale, TargetCanvasScale, DeltaSeconds * 25.f);
|
||||
|
||||
if (FMath::Abs(CanvasScale - TargetCanvasScale) < KINDA_SMALL_NUMBER)
|
||||
{
|
||||
CanvasScale = TargetCanvasScale;
|
||||
}
|
||||
|
||||
// If viewport frame is being dragged, move mouse to fix de-synchronization caused by scaling.
|
||||
if (bFrameDragging)
|
||||
{
|
||||
const FVector2D Position = GetCanvasPosition(CanvasScale, CanvasOffset) - CanvasScale * CanvasOffset + GetViewportSize() * CanvasScale * 0.5f;
|
||||
GameViewport->Viewport->SetMouse((int32)Position.X, (int32)Position.Y);
|
||||
|
||||
// Ignore next mouse movement, so this syncing doesn't change canvas offset.
|
||||
bFrameDraggingSkipMouseMove = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SImGuiWidget::UpdateCanvasDraggingConditions(const FPointerEvent& MouseEvent)
|
||||
{
|
||||
if (bCanvasMapMode)
|
||||
{
|
||||
if (MouseEvent.GetEffectingButton() == EKeys::RightMouseButton)
|
||||
{
|
||||
bCanvasDragging = !bFrameDragging && MouseEvent.IsMouseButtonDown(EKeys::RightMouseButton)
|
||||
&& CanvasScale > GetMinCanvasScale();
|
||||
}
|
||||
else if (MouseEvent.GetEffectingButton() == EKeys::MiddleMouseButton)
|
||||
{
|
||||
bFrameDragging = bFrameDraggingReady && MouseEvent.IsMouseButtonDown(EKeys::MiddleMouseButton);
|
||||
if (bFrameDragging)
|
||||
{
|
||||
bFrameDraggingReady = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
FORCEINLINE FVector2D Min(const FVector2D& A, const FVector2D& B)
|
||||
{
|
||||
return { FMath::Min(A.X, B.X), FMath::Min(A.Y, B.Y) };
|
||||
}
|
||||
|
||||
FORCEINLINE FVector2D Max(const FVector2D& A, const FVector2D& B)
|
||||
{
|
||||
return { FMath::Max(A.X, B.X), FMath::Max(A.Y, B.Y) };
|
||||
}
|
||||
|
||||
FORCEINLINE FVector2D Clamp(const FVector2D& V, const FVector2D& Min, const FVector2D& Max)
|
||||
{
|
||||
return { FMath::Clamp(V.X, Min.X, Max.X), FMath::Clamp(V.Y, Min.Y, Max.Y) };
|
||||
}
|
||||
}
|
||||
|
||||
void SImGuiWidget::UpdateCanvasDragging(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
||||
{
|
||||
// We only start on mouse button down but we handle finishing here, to make sure that we don't miss any release
|
||||
// events (possible when tabbing out etc.).
|
||||
bCanvasDragging &= MouseEvent.IsMouseButtonDown(EKeys::RightMouseButton);
|
||||
bFrameDragging &= MouseEvent.IsMouseButtonDown(EKeys::MiddleMouseButton);
|
||||
|
||||
bool bMouseLeftCanvas = false;
|
||||
|
||||
FImGuiContextProxy* ContextProxy = ModuleManager->GetContextManager().GetContextProxy(ContextIndex);
|
||||
if (ContextProxy && GameViewport.IsValid())
|
||||
{
|
||||
const FVector2D CanvasScreenSpacePosition = MyGeometry.AbsolutePosition + GetCanvasPosition(CanvasScale, CanvasOffset);
|
||||
const FVector2D CanvasScreenSpaceMax = CanvasScreenSpacePosition + ContextProxy->GetDisplaySize() * CanvasScale;
|
||||
bMouseLeftCanvas = (MouseEvent.GetScreenSpacePosition().X > CanvasScreenSpaceMax.X) || (MouseEvent.GetScreenSpacePosition().Y > CanvasScreenSpaceMax.Y);
|
||||
|
||||
if (bCanvasDragging)
|
||||
{
|
||||
CanvasOffset += MouseEvent.GetCursorDelta() / CanvasScale;
|
||||
}
|
||||
else if (bFrameDraggingSkipMouseMove)
|
||||
{
|
||||
bFrameDraggingSkipMouseMove = false;
|
||||
}
|
||||
else if (bFrameDragging)
|
||||
{
|
||||
// We can express canvas offset as a function of a viewport frame position and scale. With position and
|
||||
// mouse deltas equal we can find a ratio between canvas offset and mouse position deltas.
|
||||
const float DeltaPositionByOffset = (GetNormalizedCanvasScale(CanvasScale) - CanvasScale);
|
||||
|
||||
// Function for viewport frame positions behaves nicely when zooming but derived function for canvas offset
|
||||
// delta has singularity in 1 - which actually makes sense because dragging frame loses context when it
|
||||
// takes the whole widget area. We can handle that by preventing dragging when scale is 1.
|
||||
if (DeltaPositionByOffset < 0.f)
|
||||
{
|
||||
// We drag viewport frame in a way that it always remain in the canvas rectangle (see below). But this
|
||||
// creates a dead zone around the widget edges, and to handle that we clamp down all the mouse deltas
|
||||
// while mouse is in that zone.
|
||||
const FVector2D ViewportSizeScaled = GetViewportSize() * CanvasScale;
|
||||
const FVector2D ActiveZoneMin = CanvasScreenSpacePosition + ViewportSizeScaled * 0.5f;
|
||||
const FVector2D ActiveZoneMax = CanvasScreenSpaceMax - ViewportSizeScaled * 0.5f;
|
||||
const FVector2D MaxLimits = Max(MouseEvent.GetScreenSpacePosition() - ActiveZoneMin, FVector2D::ZeroVector);
|
||||
const FVector2D MinLimits = Min(MouseEvent.GetScreenSpacePosition() - ActiveZoneMax, FVector2D::ZeroVector);
|
||||
|
||||
CanvasOffset += Clamp(MouseEvent.GetCursorDelta(), MinLimits, MaxLimits) / FMath::Min(DeltaPositionByOffset, -0.1f);
|
||||
}
|
||||
}
|
||||
|
||||
if (bCanvasDragging || bFrameDragging)
|
||||
{
|
||||
// Clamping canvas offset keeps the whole viewport frame inside of the canvas rectangle.
|
||||
const FVector2D ViewportSize = GetViewportSize();
|
||||
const FVector2D DisplaySize = ContextProxy->GetDisplaySize();
|
||||
CanvasOffset = Clamp(CanvasOffset, -DisplaySize + ViewportSize, FVector2D::ZeroVector);
|
||||
}
|
||||
|
||||
bFrameDraggingReady = !bFrameDragging && !bCanvasDragging && CanvasScale < 1.f
|
||||
&& InFrameGrabbingRange(MouseEvent.GetScreenSpacePosition() - MyGeometry.AbsolutePosition, CanvasScale, CanvasOffset);
|
||||
}
|
||||
|
||||
const EMouseCursor::Type CursorTypeOverride = (bFrameDragging || bCanvasDragging) ? EMouseCursor::GrabHandClosed
|
||||
: (bFrameDraggingReady) ? EMouseCursor::CardinalCross
|
||||
: (bMouseLeftCanvas) ? EMouseCursor::Default
|
||||
: EMouseCursor::None;
|
||||
|
||||
SetMouseCursorOverride(CursorTypeOverride);
|
||||
}
|
||||
|
||||
float SImGuiWidget::GetMinCanvasScale() const
|
||||
{
|
||||
const FVector2D ViewportSize = GetViewportSize();
|
||||
const FVector2D CanvasSize = ModuleManager->GetContextManager().GetContextProxy(ContextIndex)->GetDisplaySize();
|
||||
return FMath::Min(ViewportSize.X / CanvasSize.X, ViewportSize.Y / CanvasSize.Y);
|
||||
}
|
||||
|
||||
float SImGuiWidget::GetNormalizedCanvasScale(float Scale) const
|
||||
{
|
||||
const float MinScale = GetMinCanvasScale();
|
||||
return (Scale - MinScale) / (1.f - MinScale);
|
||||
}
|
||||
|
||||
FVector2D SImGuiWidget::GetCanvasPosition(float Scale, const FVector2D& Offset) const
|
||||
{
|
||||
// Vast majority of calls will be with scale 1.0f.
|
||||
return (Scale == 1.f) ? Offset : Offset * GetNormalizedCanvasScale(Scale);
|
||||
}
|
||||
|
||||
bool SImGuiWidget::InFrameGrabbingRange(const FVector2D& Position, float Scale, const FVector2D& Offset) const
|
||||
{
|
||||
const FVector2D ViewportCenter = GetCanvasPosition(Scale, Offset) - Offset * Scale + GetViewportSize() * Scale * 0.5f;
|
||||
|
||||
// Get the grab range based on cursor shape.
|
||||
FVector2D Size, UVMin, UVMax, OutlineUVMin, OutlineUVMax;
|
||||
const float Range = ImGuiImplementation::GetCursorData(ImGuiMouseCursor_Move, Size, UVMin, UVMax, OutlineUVMin, OutlineUVMax)
|
||||
? Size.GetMax() * 0.5f + 5.f : 25.f;
|
||||
|
||||
return (Position - ViewportCenter).GetAbsMax() <= Range;
|
||||
}
|
||||
|
||||
FVector2D SImGuiWidget::GetViewportSize() const
|
||||
{
|
||||
FVector2D Size = FVector2D::ZeroVector;
|
||||
if (GameViewport.IsValid())
|
||||
{
|
||||
GameViewport->GetViewportSize(Size);
|
||||
}
|
||||
return Size;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
FORCEINLINE FVector2D RoundToFloat(const FVector2D& Vector)
|
||||
{
|
||||
return FVector2D{ FMath::RoundToFloat(Vector.X), FMath::RoundToFloat(Vector.Y) };
|
||||
}
|
||||
|
||||
void AddLocalRectanglePoints(TArray<FVector2D> &OutPoints, const FGeometry& AllottedGeometry, const FVector2D& AbsoluteMin, const FVector2D& AbsoluteSize)
|
||||
{
|
||||
FVector2D LocalMin = AllottedGeometry.AbsoluteToLocal(AbsoluteMin) + FVector2D::UnitVector;
|
||||
FVector2D LocalMax = AllottedGeometry.AbsoluteToLocal(AbsoluteMin + AbsoluteSize);
|
||||
OutPoints.Append({
|
||||
FVector2D(LocalMin.X, LocalMin.Y),
|
||||
FVector2D(LocalMax.X, LocalMin.Y),
|
||||
FVector2D(LocalMax.X, LocalMax.Y),
|
||||
FVector2D(LocalMin.X, LocalMax.Y),
|
||||
FVector2D(LocalMin.X, LocalMin.Y - 1.f) // -1 to close properly
|
||||
});
|
||||
}
|
||||
|
||||
#if WITH_OBSOLETE_CLIPPING_API
|
||||
void AddQuad(TArray<FSlateVertex>& OutVertexBuffer, TArray<SlateIndex>& OutIndexBuffer, const FVector2D& Position, const FVector2D& Size,
|
||||
const FVector2D& UVMin, const FVector2D& UVMax, const FColor& Color, const FSlateRotatedClipRectType& InClipRect)
|
||||
{
|
||||
|
||||
const uint32 IndexOffset = static_cast<uint32>(OutVertexBuffer.Num());
|
||||
|
||||
FVector2D Min = RoundToFloat(Position) + FVector2D::UnitVector * 0.5f;
|
||||
FVector2D Max = RoundToFloat(Position + Size) + FVector2D::UnitVector * 0.5f;
|
||||
OutVertexBuffer.Append({
|
||||
FSlateVertex({}, { Min.X, Min.Y }, { UVMin.X, UVMin.Y }, Color, InClipRect),
|
||||
FSlateVertex({}, { Max.X, Min.Y }, { UVMax.X, UVMin.Y }, Color, InClipRect),
|
||||
FSlateVertex({}, { Max.X, Max.Y }, { UVMax.X, UVMax.Y }, Color, InClipRect),
|
||||
FSlateVertex({}, { Min.X, Max.Y }, { UVMin.X, UVMax.Y }, Color, InClipRect)
|
||||
});
|
||||
|
||||
OutIndexBuffer.Append({ IndexOffset + 0U, IndexOffset + 1U, IndexOffset + 2U, IndexOffset + 0U, IndexOffset + 2U, IndexOffset + 3U });
|
||||
}
|
||||
#else
|
||||
void AddQuad(TArray<FSlateVertex>& OutVertexBuffer, TArray<SlateIndex>& OutIndexBuffer, const FVector2D& Position, const FVector2D& Size,
|
||||
const FVector2D& UVMin, const FVector2D& UVMax, const FColor& Color)
|
||||
{
|
||||
const uint32 IndexOffset = static_cast<uint32>(OutVertexBuffer.Num());
|
||||
|
||||
FVector2D Min = RoundToFloat(Position) + FVector2D::UnitVector * 0.5f;
|
||||
FVector2D Max = RoundToFloat(Position + Size) + FVector2D::UnitVector * 0.5f;
|
||||
OutVertexBuffer.Append({
|
||||
FSlateVertex::Make<ESlateVertexRounding::Disabled>({}, { Min.X, Min.Y }, { UVMin.X, UVMin.Y }, Color),
|
||||
FSlateVertex::Make<ESlateVertexRounding::Disabled>({}, { Max.X, Min.Y }, { UVMax.X, UVMin.Y }, Color),
|
||||
FSlateVertex::Make<ESlateVertexRounding::Disabled>({}, { Max.X, Max.Y }, { UVMax.X, UVMax.Y }, Color),
|
||||
FSlateVertex::Make<ESlateVertexRounding::Disabled>({}, { Min.X, Max.Y }, { UVMin.X, UVMax.Y }, Color)
|
||||
});
|
||||
|
||||
OutIndexBuffer.Append({ IndexOffset + 0U, IndexOffset + 1U, IndexOffset + 2U, IndexOffset + 0U, IndexOffset + 2U, IndexOffset + 3U });
|
||||
}
|
||||
#endif // WITH_OBSOLETE_CLIPPING_API
|
||||
}
|
||||
|
||||
int32 SImGuiWidget::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect,
|
||||
@ -438,13 +776,15 @@ int32 SImGuiWidget::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeo
|
||||
ContextProxy->Tick(FSlateApplication::Get().GetDeltaTime());
|
||||
|
||||
// Calculate offset that will transform vertex positions to screen space - rounded to avoid half pixel offsets.
|
||||
const FVector2D VertexPositionOffset{ FMath::RoundToFloat(MyClippingRect.Left), FMath::RoundToFloat(MyClippingRect.Top) };
|
||||
const FVector2D CanvasScreenSpacePosition = MyClippingRect.GetTopLeft() + GetCanvasPosition(ImGuiFrameCanvasScale, ImGuiFrameCanvasOffset);
|
||||
|
||||
// Calculate transform between ImGui canvas ans screen space (scale and then offset in Screen Space).
|
||||
const FTransform2D Transform{ ImGuiFrameCanvasScale, RoundToFloat(CanvasScreenSpacePosition) };
|
||||
|
||||
#if WITH_OBSOLETE_CLIPPING_API
|
||||
// Convert clipping rectangle to format required by Slate vertex.
|
||||
const FSlateRotatedRect VertexClippingRect{ MyClippingRect };
|
||||
|
||||
// Scale -> CanvasOffset in Screen Space
|
||||
const FTransform2D Transform{ VertexPositionOffset };
|
||||
#endif // WITH_OBSOLETE_CLIPPING_API
|
||||
|
||||
for (const auto& DrawList : ContextProxy->GetDrawData())
|
||||
{
|
||||
@ -488,6 +828,96 @@ int32 SImGuiWidget::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeo
|
||||
#endif // WITH_OBSOLETE_CLIPPING_API
|
||||
}
|
||||
}
|
||||
|
||||
// In canvas map mode we need to draw additional information helping with navigation and dragging.
|
||||
if (bCanvasMapMode)
|
||||
{
|
||||
const FVector2D ViewportSizeScaled = GetViewportSize() * ImGuiFrameCanvasScale;
|
||||
const FVector2D ViewportScreenSpacePosition = CanvasScreenSpacePosition - ImGuiFrameCanvasOffset * ImGuiFrameCanvasScale;
|
||||
|
||||
const FColor FrameColor = bFrameDraggingReady ? ViewportFrameHighlightColor : ViewportFrameColor;
|
||||
|
||||
TArray<FVector2D> Points;
|
||||
|
||||
if (ImGuiFrameCanvasScale < 1.f)
|
||||
{
|
||||
// Add a fader outside of the ImGui canvas if it is smaller than widget/viewport area.
|
||||
const FVector2D CanvasSizeScaled = ContextProxy->GetDisplaySize() * ImGuiFrameCanvasScale;
|
||||
const TextureIndex PlainTextureIndex = ModuleManager->GetTextureManager().FindTextureIndex(FName{ PlainTextureName });
|
||||
if (PlainTextureIndex != INDEX_NONE)
|
||||
{
|
||||
const FVector2D CanvasScreenSpaceMax = CanvasScreenSpacePosition + CanvasSizeScaled;
|
||||
const FVector2D WidgetScreenSpaceMax = MyClippingRect.GetBottomRight() - FVector2D::UnitVector;
|
||||
FVector2D DeadZoneScreenSpaceMin = MyClippingRect.GetTopLeft();
|
||||
if (CanvasScreenSpaceMax.X < WidgetScreenSpaceMax.X)
|
||||
{
|
||||
DeadZoneScreenSpaceMin.X = CanvasScreenSpaceMax.X;
|
||||
}
|
||||
else if(CanvasScreenSpaceMax.Y < WidgetScreenSpaceMax.Y)
|
||||
{
|
||||
DeadZoneScreenSpaceMin.Y = CanvasScreenSpaceMax.Y;
|
||||
}
|
||||
|
||||
if (!DeadZoneScreenSpaceMin.Equals(MyClippingRect.GetTopLeft()))
|
||||
{
|
||||
IndexBuffer.SetNum(0, false);
|
||||
VertexBuffer.SetNum(0, false);
|
||||
#if WITH_OBSOLETE_CLIPPING_API
|
||||
AddQuad(VertexBuffer, IndexBuffer, DeadZoneScreenSpaceMin, MyClippingRect.GetBottomRight() - DeadZoneScreenSpaceMin,
|
||||
FVector2D::ZeroVector, FVector2D::ZeroVector, CanvasFrameColor.WithAlpha(128), VertexClippingRect);
|
||||
#else
|
||||
AddQuad(VertexBuffer, IndexBuffer, DeadZoneScreenSpaceMin, MyClippingRect.GetBottomRight() - DeadZoneScreenSpaceMin,
|
||||
FVector2D::ZeroVector, FVector2D::ZeroVector, CanvasFrameColor.WithAlpha(128));
|
||||
#endif // WITH_OBSOLETE_CLIPPING_API
|
||||
|
||||
const FSlateResourceHandle& Handle = ModuleManager->GetTextureManager().GetTextureHandle(PlainTextureIndex);
|
||||
FSlateDrawElement::MakeCustomVerts(OutDrawElements, LayerId, Handle, VertexBuffer, IndexBuffer, nullptr, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw a scaled canvas border.
|
||||
AddLocalRectanglePoints(Points, AllottedGeometry, CanvasScreenSpacePosition, CanvasSizeScaled);
|
||||
#if WITH_OBSOLETE_CLIPPING_API
|
||||
FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Points, MyClippingRect,
|
||||
ESlateDrawEffect::None, FLinearColor{ CanvasFrameColor }, false);
|
||||
#else
|
||||
FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Points,
|
||||
ESlateDrawEffect::None, FLinearColor{ CanvasFrameColor }, false);
|
||||
#endif // WITH_OBSOLETE_CLIPPING_API
|
||||
|
||||
// Draw a movement gizmo (using ImGui move cursor).
|
||||
FVector2D Size, UVMin, UVMax, OutlineUVMin, OutlineUVMax;
|
||||
if (ImGuiImplementation::GetCursorData(ImGuiMouseCursor_Move, Size, UVMin, UVMax, OutlineUVMin, OutlineUVMax))
|
||||
{
|
||||
const TextureIndex FontAtlasIndex = ModuleManager->GetTextureManager().FindTextureIndex(FName{ FontAtlasTextureName });
|
||||
if (FontAtlasIndex != INDEX_NONE)
|
||||
{
|
||||
IndexBuffer.SetNum(0, false);
|
||||
VertexBuffer.SetNum(0, false);
|
||||
#if WITH_OBSOLETE_CLIPPING_API
|
||||
AddQuad(VertexBuffer, IndexBuffer, ViewportScreenSpacePosition + ViewportSizeScaled * 0.5f - Size * 0.375f, Size * 0.75f,
|
||||
UVMin, UVMax, FrameColor.WithAlpha(bCanvasDragging ? 32 : 128), VertexClippingRect);
|
||||
#else
|
||||
AddQuad(VertexBuffer, IndexBuffer, ViewportScreenSpacePosition + ViewportSizeScaled * 0.5f - Size * 0.375f, Size * 0.75f,
|
||||
UVMin, UVMax, FrameColor.WithAlpha(bCanvasDragging ? 32 : 128));
|
||||
#endif // WITH_OBSOLETE_CLIPPING_API
|
||||
const FSlateResourceHandle& Handle = ModuleManager->GetTextureManager().GetTextureHandle(FontAtlasIndex);
|
||||
FSlateDrawElement::MakeCustomVerts(OutDrawElements, LayerId, Handle, VertexBuffer, IndexBuffer, nullptr, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw frame representing area of the ImGui canvas that is visible when scale is 1.
|
||||
Points.SetNum(0, false);
|
||||
AddLocalRectanglePoints(Points, AllottedGeometry, ViewportScreenSpacePosition, ViewportSizeScaled);
|
||||
#if WITH_OBSOLETE_CLIPPING_API
|
||||
FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Points, MyClippingRect,
|
||||
ESlateDrawEffect::None, FLinearColor{ FrameColor }, false);
|
||||
#else
|
||||
FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Points,
|
||||
ESlateDrawEffect::None, FLinearColor{ FrameColor }, false);
|
||||
#endif // WITH_OBSOLETE_CLIPPING_API
|
||||
}
|
||||
}
|
||||
|
||||
return LayerId;
|
||||
|
@ -84,6 +84,9 @@ private:
|
||||
MouseAndKeyboard
|
||||
};
|
||||
|
||||
// If needed, add to event reply a mouse lock or unlock request.
|
||||
FORCEINLINE FReply WithMouseLockRequests(FReply&& Reply);
|
||||
|
||||
FORCEINLINE void CopyModifierKeys(const FInputEvent& InputEvent);
|
||||
FORCEINLINE void CopyModifierKeys(const FPointerEvent& MouseEvent);
|
||||
|
||||
@ -91,6 +94,8 @@ private:
|
||||
|
||||
bool IgnoreKeyEvent(const FKeyEvent& KeyEvent) const;
|
||||
|
||||
void SetMouseCursorOverride(EMouseCursor::Type InMouseCursorOverride);
|
||||
|
||||
// Update visibility based on input enabled state.
|
||||
void SetVisibilityFromInputEnabled();
|
||||
|
||||
@ -108,6 +113,30 @@ private:
|
||||
|
||||
void OnPostImGuiUpdate();
|
||||
|
||||
// Update canvas map mode based on input state.
|
||||
void UpdateCanvasMapMode(const FInputEvent& InputEvent);
|
||||
void SetCanvasMapMode(bool bEnabled);
|
||||
|
||||
void AddCanvasScale(float Delta);
|
||||
void UdateCanvasScale(float DeltaSeconds);
|
||||
|
||||
void UpdateCanvasDraggingConditions(const FPointerEvent& MouseEvent);
|
||||
void UpdateCanvasDragging(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent);
|
||||
|
||||
// Canvas scale in which the whole canvas is visible in the viewport. We don't scale below that value.
|
||||
float GetMinCanvasScale() const;
|
||||
|
||||
// Normalized canvas scale mapping range [MinCanvasScale..1] to [0..1].
|
||||
float GetNormalizedCanvasScale(float Scale) const;
|
||||
|
||||
// Position of the canvas origin, given the current canvas scale and offset. Uses NormalizedCanvasScale to smoothly
|
||||
// transition between showing visible canvas area at scale 1 and the whole canvas at min canvas scale.
|
||||
FVector2D GetCanvasPosition(float Scale, const FVector2D& Offset) const;
|
||||
|
||||
bool InFrameGrabbingRange(const FVector2D& Position, float Scale, const FVector2D& Offset) const;
|
||||
|
||||
FVector2D GetViewportSize() const;
|
||||
|
||||
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;
|
||||
@ -122,11 +151,37 @@ private:
|
||||
|
||||
int32 ContextIndex = 0;
|
||||
|
||||
FImGuiInputState InputState;
|
||||
|
||||
EInputMode InputMode = EInputMode::None;
|
||||
bool bInputEnabled = false;
|
||||
bool bReceivedMouseEvent = false;
|
||||
bool bMouseLock = false;
|
||||
|
||||
FImGuiInputState InputState;
|
||||
// Canvas map mode allows to zoom in/out and navigate between different parts of ImGui canvas.
|
||||
bool bCanvasMapMode = false;
|
||||
|
||||
// If enabled (only if not fully zoomed out), allows to drag ImGui canvas. Dragging canvas modifies canvas offset.
|
||||
bool bCanvasDragging = false;
|
||||
|
||||
// If enabled (only if zoomed out), allows to drag a frame that represents a visible area of the ImGui canvas.
|
||||
// Mouse deltas are converted to canvas offset by linear formula derived from GetCanvasPosition function.
|
||||
bool bFrameDragging = false;
|
||||
|
||||
// True, if mouse and input are in state that allows to start frame dragging. Used for highlighting.
|
||||
bool bFrameDraggingReady = false;
|
||||
|
||||
bool bFrameDraggingSkipMouseMove = false;
|
||||
|
||||
EMouseCursor::Type MouseCursorOverride = EMouseCursor::None;
|
||||
|
||||
float TargetCanvasScale = 1.f;
|
||||
|
||||
float CanvasScale = 1.f;
|
||||
FVector2D CanvasOffset = FVector2D::ZeroVector;
|
||||
|
||||
float ImGuiFrameCanvasScale = 1.f;
|
||||
FVector2D ImGuiFrameCanvasOffset = FVector2D::ZeroVector;
|
||||
|
||||
TWeakPtr<SWidget> PreviousUserFocusedWidget;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user