mirror of
https://github.com/kevinporetti/UnrealImGui.git
synced 2025-01-18 08:20:32 +00:00
Refactored ImGui widget and removed dependency on ImGui internal cursor data:
- Added SImGuiLayout to resets layout and house SImGuiWidget. - Module manager creates SImGuiLayout instead of SImGuiWidget (eventually it should be replaced with a higher level object, like AHUD). - Reworked ImGui canvas dragging and scaling and moved to SImGuiCanvasControl. - Removed dependency on ImGui internal cursor data. New presentation is cleaner and doesn't use cursor data. - Rendering code could be simplified after layout reset provided by SImGuiLayout. - SImGuiWidget still handles input, rendering of ImGui draw data and activation of SImGuiCanvasControl. - All widgets are in own subfolder.
This commit is contained in:
parent
6c6e766a20
commit
c144658f37
18
README.md
18
README.md
@ -40,12 +40,12 @@ Content of this repository needs to be placed in *Plugins* directory under proje
|
||||
|
||||
To use that in other modules you will need to declare it as a public or private dependency in those modules' Build.cs files:
|
||||
|
||||
```
|
||||
```C#
|
||||
PublicDependencyModuleNames.AddRange(new string[] { "ImGui" });
|
||||
```
|
||||
or
|
||||
|
||||
```
|
||||
```C#
|
||||
PrivateDependencyModuleNames.AddRange(new string[] { "ImGui" });
|
||||
```
|
||||
|
||||
@ -69,16 +69,12 @@ Self-debug functionality is based on console variable but it will be also replac
|
||||
Note: this console variable will be replaced with optional toggle command.
|
||||
|
||||
|
||||
### Canvas Map Mode
|
||||
### Canvas Control 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.
|
||||
Canvas control mode gives a possibility to navigate around ImGui canvas and control which part of it should be visible on the screen. To activate this mode press and hold `Left Shift` + `Left Alt` keys while in ImGui input mode. Leaving ImGui input mode or releasing shortcut keys will deactivate control mode. While in control mode ImGui draws canvas borders and a frame representing part of the ImGui canvas visible after leaving that mode. To control canvas scale, offset and frame use mouse wheel and dragging:
|
||||
- *Mouse Wheel* - to zoom in and out (resets to 1 after leaving control mode)
|
||||
- *Right Mouse Button* - to drag ImGui canvas/content
|
||||
- *Middle Mouse Button* - to drag frame representing part of the ImGui canvas that will be visible after leaving control mode
|
||||
|
||||
|
||||
See also
|
||||
|
@ -38,31 +38,11 @@ ImGuiContext** GImGuiContextPtrHandle = &GImGuiContextPtr;
|
||||
#include <HideWindowsPlatformTypes.h>
|
||||
#endif // PLATFORM_WINDOWS
|
||||
|
||||
|
||||
#include "ImGuiInteroperability.h"
|
||||
|
||||
|
||||
namespace ImGuiImplementation
|
||||
{
|
||||
bool GetCursorData(ImGuiMouseCursor CursorType, FVector2D& OutSize, FVector2D& OutUVMin, FVector2D& OutUVMax, FVector2D& OutOutlineUVMin, FVector2D& OutOutlineUVMax)
|
||||
{
|
||||
ImFontAtlas* FontAtlas = ImGui::GetIO().Fonts;
|
||||
ImVec2 Offset, Size, UV[4];
|
||||
if (FontAtlas && FontAtlas->GetMouseCursorTexData(CursorType, &Offset, &Size, &UV[0], &UV[2]))
|
||||
{
|
||||
using namespace ImGuiInterops;
|
||||
OutSize = ToVector2D(Size);
|
||||
OutUVMin = ToVector2D(UV[0]);
|
||||
OutUVMax = ToVector2D(UV[1]);
|
||||
OutOutlineUVMin = ToVector2D(UV[2]);
|
||||
OutOutlineUVMax = ToVector2D(UV[3]);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
ImGuiContext** GetImGuiContextHandle()
|
||||
{
|
||||
|
@ -8,9 +8,6 @@
|
||||
// Gives access to selected ImGui implementation features.
|
||||
namespace ImGuiImplementation
|
||||
{
|
||||
// Get specific cursor data.
|
||||
bool GetCursorData(ImGuiMouseCursor CursorType, FVector2D& OutSize, FVector2D& OutUVMin, FVector2D& OutUVMax, FVector2D& OutOutlineUVMin, FVector2D& OutOutlineUVMax);
|
||||
|
||||
#if WITH_EDITOR
|
||||
// Get the handle to the ImGui Context pointer.
|
||||
ImGuiContext** GetImGuiContextHandle();
|
||||
|
@ -12,6 +12,10 @@
|
||||
#include <imgui.h>
|
||||
|
||||
|
||||
// High enough z-order guarantees that ImGui output is rendered on top of the game UI.
|
||||
constexpr int32 IMGUI_WIDGET_Z_ORDER = 10000;
|
||||
|
||||
|
||||
FImGuiModuleManager::FImGuiModuleManager()
|
||||
: Commands(Properties)
|
||||
, Settings(Properties, Commands)
|
||||
@ -49,9 +53,14 @@ FImGuiModuleManager::~FImGuiModuleManager()
|
||||
// Remove still active widgets (important during hot-reloading).
|
||||
for (auto& Widget : Widgets)
|
||||
{
|
||||
if (Widget.IsValid())
|
||||
auto SharedWidget = Widget.Pin();
|
||||
if (SharedWidget.IsValid())
|
||||
{
|
||||
Widget.Pin()->Detach();
|
||||
auto& WidgetGameViewport = SharedWidget->GetGameViewport();
|
||||
if (WidgetGameViewport.IsValid())
|
||||
{
|
||||
WidgetGameViewport->RemoveViewportWidgetContent(SharedWidget.ToSharedRef());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -178,12 +187,14 @@ void FImGuiModuleManager::AddWidgetToViewport(UGameViewportClient* GameViewport)
|
||||
LoadTextures();
|
||||
|
||||
// Create and initialize the widget.
|
||||
TSharedPtr<SImGuiWidget> SharedWidget;
|
||||
SAssignNew(SharedWidget, SImGuiWidget).ModuleManager(this).GameViewport(GameViewport).ContextIndex(ContextIndex);
|
||||
TSharedPtr<SImGuiLayout> SharedWidget;
|
||||
SAssignNew(SharedWidget, SImGuiLayout).ModuleManager(this).GameViewport(GameViewport).ContextIndex(ContextIndex);
|
||||
|
||||
GameViewport->AddViewportWidgetContent(SharedWidget.ToSharedRef(), IMGUI_WIDGET_Z_ORDER);
|
||||
|
||||
// We transfer widget ownerships to viewports but we keep weak references in case we need to manually detach active
|
||||
// widgets during module shutdown (important during hot-reloading).
|
||||
if (TWeakPtr<SImGuiWidget>* Slot = Widgets.FindByPredicate([](auto& Widget) { return !Widget.IsValid(); }))
|
||||
if (TWeakPtr<SImGuiLayout>* Slot = Widgets.FindByPredicate([](auto& Widget) { return !Widget.IsValid(); }))
|
||||
{
|
||||
*Slot = SharedWidget;
|
||||
}
|
||||
|
@ -7,8 +7,8 @@
|
||||
#include "ImGuiModuleCommands.h"
|
||||
#include "ImGuiModuleProperties.h"
|
||||
#include "ImGuiModuleSettings.h"
|
||||
#include "SImGuiWidget.h"
|
||||
#include "TextureManager.h"
|
||||
#include "Widgets/SImGuiLayout.h"
|
||||
|
||||
|
||||
// Central manager that implements module logic. It initializes and controls remaining module components.
|
||||
@ -87,7 +87,7 @@ private:
|
||||
FTextureManager TextureManager;
|
||||
|
||||
// Slate widgets that we created.
|
||||
TArray<TWeakPtr<SImGuiWidget>> Widgets;
|
||||
TArray<TWeakPtr<SImGuiLayout>> Widgets;
|
||||
|
||||
FDelegateHandle TickInitializerHandle;
|
||||
FDelegateHandle TickDelegateHandle;
|
||||
|
349
Source/ImGui/Private/Widgets/SImGuiCanvasControl.cpp
Normal file
349
Source/ImGui/Private/Widgets/SImGuiCanvasControl.cpp
Normal file
@ -0,0 +1,349 @@
|
||||
// Distributed under the MIT License (MIT) (see accompanying LICENSE file)
|
||||
|
||||
#include "ImGuiPrivatePCH.h"
|
||||
|
||||
#include "SImGuiCanvasControl.h"
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
// Mouse wheel to zoom ratio - how fast zoom changes with mouse wheel delta.
|
||||
const float ZoomScrollSpeed = 0.03125f;
|
||||
|
||||
// Speed of blending out - how much zoom scale and canvas offset fades in every frame.
|
||||
const float BlendOutSpeed = 0.25f;
|
||||
|
||||
// TODO: Move to settings
|
||||
namespace Colors
|
||||
{
|
||||
const FLinearColor CanvasMargin = FColor(0, 4, 32, 64);
|
||||
const FLinearColor CanvasBorder = FColor::Black.WithAlpha(127);
|
||||
const FLinearColor CanvasBorderHighlight = FColor(16, 24, 64, 160);
|
||||
const FLinearColor FrameBorder = FColor(222, 163, 9, 128);
|
||||
const FLinearColor FrameBorderHighlight = FColor(255, 180, 10, 160);
|
||||
}
|
||||
|
||||
// Defines type of drag operation.
|
||||
enum class EDragType
|
||||
{
|
||||
Content,
|
||||
Canvas
|
||||
};
|
||||
|
||||
// Data for drag & drop operations. Calculations are made in widget where we have more straightforward access to data
|
||||
// like geometry or scale.
|
||||
class FImGuiDragDropOperation : public FDragDropOperation
|
||||
{
|
||||
public:
|
||||
|
||||
DRAG_DROP_OPERATOR_TYPE(FImGuiDragDropOperation, FDragDropOperation)
|
||||
|
||||
FImGuiDragDropOperation(const FVector2D& InPosition, const FVector2D& InOffset, EDragType InDragType)
|
||||
: StartPosition(InPosition)
|
||||
, StartOffset(InOffset)
|
||||
, DragType(InDragType)
|
||||
{
|
||||
bCreateNewWindow = false;
|
||||
MouseCursor = EMouseCursor::GrabHandClosed;
|
||||
}
|
||||
|
||||
FVector2D StartPosition;
|
||||
FVector2D StartOffset;
|
||||
EDragType DragType;
|
||||
};
|
||||
}
|
||||
|
||||
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
||||
void SImGuiCanvasControl::Construct(const FArguments& InArgs)
|
||||
{
|
||||
OnTransformChanged = InArgs._OnTransformChanged;
|
||||
UpdateVisibility();
|
||||
}
|
||||
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
||||
|
||||
void SImGuiCanvasControl::SetActive(bool bInActive)
|
||||
{
|
||||
if (bActive != bInActive)
|
||||
{
|
||||
bActive = bInActive;
|
||||
bBlendingOut = !bInActive;
|
||||
if (bInActive)
|
||||
{
|
||||
Opacity = 1.f;
|
||||
}
|
||||
UpdateVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
void SImGuiCanvasControl::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime)
|
||||
{
|
||||
Super::Tick(AllottedGeometry, InCurrentTime, InDeltaTime);
|
||||
|
||||
if (bBlendingOut)
|
||||
{
|
||||
if (FMath::IsNearlyEqual(CanvasScale, 1.f, ZoomScrollSpeed) && CanvasOffset.IsNearlyZero(1.f))
|
||||
{
|
||||
CanvasOffset = FVector2D::ZeroVector;
|
||||
CanvasScale = 1.f;
|
||||
Opacity = 0.f;
|
||||
bBlendingOut = false;
|
||||
UpdateVisibility();
|
||||
}
|
||||
else
|
||||
{
|
||||
CanvasOffset = FMath::Lerp(CanvasOffset, FVector2D::ZeroVector, BlendOutSpeed);
|
||||
CanvasScale = FMath::Lerp(CanvasScale, 1.f, BlendOutSpeed);
|
||||
Opacity = FMath::Lerp(Opacity, 0.f, BlendOutSpeed);
|
||||
}
|
||||
|
||||
UpdateRenderTransform();
|
||||
}
|
||||
}
|
||||
|
||||
FReply SImGuiCanvasControl::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
||||
{
|
||||
if (DragRequest == EDragRequest::None)
|
||||
{
|
||||
if (MouseEvent.GetEffectingButton() == EKeys::RightMouseButton)
|
||||
{
|
||||
DragRequest = EDragRequest::Content;
|
||||
return FReply::Handled().DetectDrag(SharedThis(this), EKeys::RightMouseButton).CaptureMouse(SharedThis(this));
|
||||
}
|
||||
else if (MouseEvent.GetEffectingButton() == EKeys::MiddleMouseButton)
|
||||
{
|
||||
DragRequest = EDragRequest::Canvas;
|
||||
return FReply::Handled().DetectDrag(SharedThis(this), EKeys::MiddleMouseButton).CaptureMouse(SharedThis(this));
|
||||
}
|
||||
}
|
||||
|
||||
return FReply::Unhandled();
|
||||
}
|
||||
|
||||
FReply SImGuiCanvasControl::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
||||
{
|
||||
if (MouseEvent.GetEffectingButton() == EKeys::RightMouseButton)
|
||||
{
|
||||
if (DragRequest == EDragRequest::Content)
|
||||
{
|
||||
DragRequest = EDragRequest::None;
|
||||
}
|
||||
}
|
||||
else if (MouseEvent.GetEffectingButton() == EKeys::MiddleMouseButton)
|
||||
{
|
||||
if (DragRequest == EDragRequest::Canvas)
|
||||
{
|
||||
DragRequest = EDragRequest::None;
|
||||
}
|
||||
}
|
||||
|
||||
return FReply::Unhandled();
|
||||
}
|
||||
|
||||
FReply SImGuiCanvasControl::OnMouseWheel(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
||||
{
|
||||
Zoom(MyGeometry, MouseEvent.GetWheelDelta() * ZoomScrollSpeed, MouseEvent.GetScreenSpacePosition());
|
||||
return FReply::Unhandled();
|
||||
}
|
||||
|
||||
FReply SImGuiCanvasControl::OnDragDetected(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
||||
{
|
||||
if (DragRequest == EDragRequest::Content)
|
||||
{
|
||||
return FReply::Handled()
|
||||
.BeginDragDrop(MakeShareable(new FImGuiDragDropOperation(
|
||||
MouseEvent.GetScreenSpacePosition(), ContentOffset, EDragType::Content)))
|
||||
.LockMouseToWidget(SharedThis(this));
|
||||
}
|
||||
else if (DragRequest == EDragRequest::Canvas)
|
||||
{
|
||||
return FReply::Handled()
|
||||
.BeginDragDrop(MakeShareable(new FImGuiDragDropOperation(
|
||||
MouseEvent.GetScreenSpacePosition(), CanvasOffset, EDragType::Canvas)))
|
||||
.LockMouseToWidget(SharedThis(this));
|
||||
}
|
||||
else
|
||||
{
|
||||
return FReply::Unhandled();
|
||||
}
|
||||
}
|
||||
|
||||
FReply SImGuiCanvasControl::OnDragOver(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent)
|
||||
{
|
||||
auto Operation = DragDropEvent.GetOperationAs<FImGuiDragDropOperation>();
|
||||
if (Operation.IsValid())
|
||||
{
|
||||
const FSlateRenderTransform ScreenToWidget = MyGeometry.GetAccumulatedRenderTransform().Inverse();
|
||||
const FVector2D DragDelta = ScreenToWidget.TransformVector(DragDropEvent.GetScreenSpacePosition() - Operation->StartPosition);
|
||||
|
||||
if (Operation->DragType == EDragType::Content)
|
||||
{
|
||||
// Content offset is in ImGui space, so we need to scale drag calculated in widget space.
|
||||
ContentOffset = Operation->StartOffset + DragDelta / CanvasScale;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Canvas offset is in widget space, so we can apply drag calculated in widget space directly.
|
||||
CanvasOffset = Operation->StartOffset + DragDelta;
|
||||
}
|
||||
|
||||
UpdateRenderTransform();
|
||||
|
||||
return FReply::Handled();
|
||||
}
|
||||
else
|
||||
{
|
||||
return FReply::Unhandled();
|
||||
}
|
||||
}
|
||||
|
||||
FReply SImGuiCanvasControl::SImGuiCanvasControl::OnDrop(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent)
|
||||
{
|
||||
DragRequest = EDragRequest::None;
|
||||
return FReply::Handled().ReleaseMouseLock();
|
||||
}
|
||||
|
||||
FVector2D SImGuiCanvasControl::ComputeDesiredSize(float InScale) const
|
||||
{
|
||||
return FVector2D{ 3840.f, 2160.f } * InScale;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
FORCEINLINE FMargin CalculateInset(const FSlateRect& From, const FSlateRect& To)
|
||||
{
|
||||
return { To.Left - From.Left, To.Top - From.Top, From.Right - To.Right, From.Bottom - To.Bottom };
|
||||
}
|
||||
|
||||
FORCEINLINE FLinearColor ScaleAlpha(FLinearColor Color, float Scale)
|
||||
{
|
||||
Color.A *= Scale;
|
||||
return Color;
|
||||
}
|
||||
|
||||
FORCEINLINE FVector2D Round(const FVector2D& Vec)
|
||||
{
|
||||
return FVector2D{ FMath::FloorToFloat(Vec.X), FMath::FloorToFloat(Vec.Y) };
|
||||
}
|
||||
}
|
||||
|
||||
int32 SImGuiCanvasControl::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const
|
||||
{
|
||||
const FPaintGeometry PaintGeometry = AllottedGeometry.ToPaintGeometry();
|
||||
|
||||
const FSlateRenderTransform& WidgetToScreen = AllottedGeometry.GetAccumulatedRenderTransform();
|
||||
const FSlateRenderTransform ImGuiToScreen = Transform.Concatenate(WidgetToScreen);
|
||||
|
||||
const FSlateRect CanvasRect = FSlateRect(
|
||||
ImGuiToScreen.TransformPoint(FVector2D::ZeroVector),
|
||||
ImGuiToScreen.TransformPoint(ComputeDesiredSize(1.f)));
|
||||
const FMargin CanvasMargin = CalculateInset(MyCullingRect, CanvasRect);
|
||||
|
||||
if (CanvasMargin.GetDesiredSize().SizeSquared() > 0.f)
|
||||
{
|
||||
CanvasBorderBrush.Margin = CanvasMargin;
|
||||
|
||||
const FLinearColor CanvasMarginColor = ScaleAlpha(Colors::CanvasMargin, Opacity);
|
||||
const FLinearColor CanvasBorderColor = ScaleAlpha(DragRequest == EDragRequest::Content
|
||||
? Colors::CanvasBorderHighlight : Colors::CanvasBorder, Opacity);
|
||||
|
||||
#if ENGINE_COMPATIBILITY_LEGACY_CLIPPING_API
|
||||
FSlateDrawElement::MakeBox(OutDrawElements, LayerId, PaintGeometry, &CanvasBorderBrush, MyCullingRect,
|
||||
ESlateDrawEffect::None, CanvasMarginColor);
|
||||
|
||||
FSlateDrawElement::MakeBox(OutDrawElements, LayerId, PaintGeometry, &CanvasBorderBrush,
|
||||
CanvasRect.ExtendBy(1).IntersectionWith(MyCullingRect), ESlateDrawEffect::None, CanvasBorderColor);
|
||||
|
||||
#else
|
||||
|
||||
FSlateDrawElement::MakeBox(OutDrawElements, LayerId, PaintGeometry, &CanvasBorderBrush, ESlateDrawEffect::None,
|
||||
CanvasMarginColor);
|
||||
|
||||
OutDrawElements.PushClip(FSlateClippingZone{ CanvasRect.ExtendBy(1) });
|
||||
FSlateDrawElement::MakeBox(OutDrawElements, LayerId, PaintGeometry, &CanvasBorderBrush, ESlateDrawEffect::None,
|
||||
CanvasBorderColor);
|
||||
OutDrawElements.PopClip();
|
||||
#endif
|
||||
}
|
||||
|
||||
const FSlateRect FrameRect = FSlateRect::FromPointAndExtent(
|
||||
WidgetToScreen.TransformPoint(Round(CanvasOffset)),
|
||||
Round(MyCullingRect.GetSize() * CanvasScale));
|
||||
const FMargin FrameMargin = CalculateInset(MyCullingRect, FrameRect);
|
||||
|
||||
if (FrameMargin.GetDesiredSize().SizeSquared() > 0.f)
|
||||
{
|
||||
FrameBorderBrush.Margin = FrameMargin;
|
||||
|
||||
const FLinearColor FrameBorderColor = ScaleAlpha(DragRequest == EDragRequest::Canvas
|
||||
? Colors::FrameBorderHighlight : Colors::FrameBorder, Opacity);
|
||||
|
||||
#if ENGINE_COMPATIBILITY_LEGACY_CLIPPING_API
|
||||
FSlateDrawElement::MakeBox(OutDrawElements, LayerId, PaintGeometry, &FrameBorderBrush,
|
||||
FrameRect.ExtendBy(1).IntersectionWith(MyCullingRect), ESlateDrawEffect::None, FrameBorderColor);
|
||||
#else
|
||||
OutDrawElements.PushClip(FSlateClippingZone{ FrameRect.ExtendBy(1) });
|
||||
FSlateDrawElement::MakeBox(OutDrawElements, LayerId, PaintGeometry, &FrameBorderBrush, ESlateDrawEffect::None,
|
||||
FrameBorderColor);
|
||||
OutDrawElements.PopClip();
|
||||
#endif
|
||||
}
|
||||
|
||||
return LayerId;
|
||||
}
|
||||
|
||||
void SImGuiCanvasControl::UpdateVisibility()
|
||||
{
|
||||
SetVisibility(bActive ? EVisibility::Visible : bBlendingOut ? EVisibility::HitTestInvisible : EVisibility::Hidden);
|
||||
}
|
||||
|
||||
void SImGuiCanvasControl::Zoom(const FGeometry& MyGeometry, const float Delta, const FVector2D& MousePosition)
|
||||
{
|
||||
// If blending out, then cancel.
|
||||
bBlendingOut = false;
|
||||
|
||||
float OldCanvasScale = CanvasScale;
|
||||
|
||||
// Normalize scale to make sure that it changes in fixed steps and that we don't accumulate rounding errors.
|
||||
// Normalizing before applying delta allows for scales that at the edges are not rounded to the closes step.
|
||||
CanvasScale = FMath::RoundToFloat(CanvasScale / ZoomScrollSpeed) * ZoomScrollSpeed;
|
||||
|
||||
// Update the scale.
|
||||
CanvasScale = FMath::Clamp(CanvasScale + Delta, GetMinScale(MyGeometry), 2.f);
|
||||
|
||||
// Update canvas offset to keep it fixed around pivot point.
|
||||
if (CanvasScale != OldCanvasScale && OldCanvasScale != 0.f)
|
||||
{
|
||||
// Pivot points (in screen space):
|
||||
// 1) Around mouse: MousePosition
|
||||
// 2) Fixed in top-left corner: MyGeometry.GetLayoutBoundingRect().GetTopLeft()
|
||||
// 3) Fixed in centre: MyGeometry.GetLayoutBoundingRect().GetCenter()
|
||||
const FVector2D PivotPoint = MyGeometry.GetAccumulatedRenderTransform().Inverse().TransformPoint(MousePosition);
|
||||
const FVector2D Pivot = PivotPoint - CanvasOffset;
|
||||
|
||||
CanvasOffset += Pivot * (OldCanvasScale - CanvasScale) / OldCanvasScale;
|
||||
}
|
||||
|
||||
UpdateRenderTransform();
|
||||
}
|
||||
|
||||
void SImGuiCanvasControl::UpdateRenderTransform()
|
||||
{
|
||||
const FVector2D RenderOffset = Round(ContentOffset * CanvasScale + CanvasOffset);
|
||||
Transform = FSlateRenderTransform(CanvasScale, RenderOffset);
|
||||
OnTransformChanged.ExecuteIfBound(Transform);
|
||||
}
|
||||
|
||||
float SImGuiCanvasControl::GetMinScale(const FGeometry& MyGeometry)
|
||||
{
|
||||
#if FROM_ENGINE_VERSION(4, 17)
|
||||
#define GET_BOUNDING_RECT GetLayoutBoundingRect
|
||||
#else
|
||||
#define GET_BOUNDING_RECT GetClippingRect
|
||||
#endif
|
||||
|
||||
const FVector2D DefaultCanvasSize = MyGeometry.GetAccumulatedRenderTransform().TransformVector(ComputeDesiredSize(1.f));
|
||||
const FVector2D WidgetSize = MyGeometry.GET_BOUNDING_RECT().GetSize();
|
||||
return FMath::Min(WidgetSize.X / DefaultCanvasSize.X, WidgetSize.Y / DefaultCanvasSize.Y);
|
||||
|
||||
#undef GET_BOUNDING_RECT
|
||||
}
|
107
Source/ImGui/Private/Widgets/SImGuiCanvasControl.h
Normal file
107
Source/ImGui/Private/Widgets/SImGuiCanvasControl.h
Normal file
@ -0,0 +1,107 @@
|
||||
// Distributed under the MIT License (MIT) (see accompanying LICENSE file)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ImGuiInputState.h"
|
||||
#include "ImGuiModuleDebug.h"
|
||||
#include "ImGuiModuleSettings.h"
|
||||
|
||||
#include <Widgets/SLeafWidget.h>
|
||||
|
||||
|
||||
// Widget that controls transform of ImGui canvas/space.
|
||||
// When active, additionally it shows boundaries of ImGui canvas and default visible area.
|
||||
// TODO: Bind to ImGui context or properties to dynamically read canvas size.
|
||||
// TODO: Bind to properties to allow configure colors.
|
||||
class SImGuiCanvasControl : public SLeafWidget
|
||||
{
|
||||
typedef SLeafWidget Super;
|
||||
|
||||
public:
|
||||
|
||||
DECLARE_DELEGATE_OneParam(FOnTransformChanged, const FSlateRenderTransform&);
|
||||
|
||||
SLATE_BEGIN_ARGS(SImGuiCanvasControl)
|
||||
{}
|
||||
SLATE_EVENT(FOnTransformChanged, OnTransformChanged)
|
||||
SLATE_END_ARGS()
|
||||
|
||||
void Construct(const FArguments& InArgs);
|
||||
|
||||
bool IsActive() const { return bActive; }
|
||||
void SetActive(bool bInActive);
|
||||
|
||||
const FSlateRenderTransform& GetTransform() const { return Transform; }
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
// SWidget overrides
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
|
||||
virtual void Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) override;
|
||||
|
||||
virtual FReply OnMouseButtonDown(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 OnDragDetected(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
|
||||
|
||||
virtual FReply OnDragOver(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) override;
|
||||
|
||||
virtual FReply OnDrop(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) override;
|
||||
|
||||
virtual FVector2D ComputeDesiredSize(float InScale) const override;
|
||||
|
||||
virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override;
|
||||
|
||||
private:
|
||||
|
||||
enum class EDragRequest : uint8
|
||||
{
|
||||
None,
|
||||
Content,
|
||||
Canvas
|
||||
};
|
||||
|
||||
void UpdateVisibility();
|
||||
|
||||
void Zoom(const FGeometry& MyGeometry, const float Delta, const FVector2D& MousePosition);
|
||||
|
||||
void UpdateRenderTransform();
|
||||
|
||||
float GetMinScale(const FGeometry& MyGeometry);
|
||||
|
||||
mutable FSlateBorderBrush CanvasBorderBrush = FSlateBorderBrush("SImGuiCanvasControl-CanvasBorder", FMargin(0.f, 0.f, 1.f, 1.f), FLinearColor::White);
|
||||
mutable FSlateBorderBrush FrameBorderBrush = FSlateBorderBrush("SImGuiCanvasControl-FrameBorder", FMargin(0.f, 0.f, 1.f, 1.f), FLinearColor::White);
|
||||
|
||||
FOnTransformChanged OnTransformChanged;
|
||||
|
||||
// Transform from ImGui space.
|
||||
FSlateRenderTransform Transform;
|
||||
|
||||
// Offset of the ImGui content in ImGui space.
|
||||
FVector2D ContentOffset = FVector2D::ZeroVector;
|
||||
|
||||
// Offset of the ImGui canvas in widget local space.
|
||||
FVector2D CanvasOffset = FVector2D::ZeroVector;
|
||||
|
||||
// Scale of the ImGui canvas in widget local space.
|
||||
float CanvasScale = 1.f;
|
||||
|
||||
// Opacity scaling visibility of elements during blending.
|
||||
float Opacity = 1.f;
|
||||
|
||||
// Whether this widget is active. While active, widget allows to modify transform of ImGui canvas, shows its
|
||||
// boundaries and default visible area.
|
||||
bool bActive = false;
|
||||
|
||||
// Whether we are blending out after widget was deactivated. While blending out, widget is visible but it doesn't
|
||||
// process inputs anymore.
|
||||
bool bBlendingOut = false;
|
||||
|
||||
// Request is set on mouse button press before drag operation is started. It remains valid until activating button
|
||||
// is released or until drag operation is finished or until it is replaced by alternative request.
|
||||
// Highlights are bound to requests, what means that they can also be activated before drag operation is started.
|
||||
EDragRequest DragRequest = EDragRequest::None;
|
||||
};
|
55
Source/ImGui/Private/Widgets/SImGuiLayout.cpp
Normal file
55
Source/ImGui/Private/Widgets/SImGuiLayout.cpp
Normal file
@ -0,0 +1,55 @@
|
||||
// Distributed under the MIT License (MIT) (see accompanying LICENSE file)
|
||||
|
||||
#include "ImGuiPrivatePCH.h"
|
||||
|
||||
#include "SImGuiLayout.h"
|
||||
#include "SImGuiWidget.h"
|
||||
|
||||
#include <Widgets/Layout/SConstraintCanvas.h>
|
||||
#include <Widgets/Layout/SDPIScaler.h>
|
||||
#include <Widgets/Layout/SScaleBox.h>
|
||||
|
||||
|
||||
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
||||
void SImGuiLayout::Construct(const FArguments& InArgs)
|
||||
{
|
||||
checkf(InArgs._GameViewport, TEXT("Null Game Viewport argument"));
|
||||
|
||||
GameViewport = InArgs._GameViewport;
|
||||
|
||||
// TODO: Remove instantiation of ImGui Widget outside of this class.
|
||||
|
||||
ChildSlot
|
||||
[
|
||||
// Remove accumulated scale to manually control how we draw data.
|
||||
SNew(SScaleBox)
|
||||
.IgnoreInheritedScale(true)
|
||||
.HAlign(HAlign_Fill)
|
||||
.VAlign(VAlign_Fill)
|
||||
[
|
||||
// Apply custom scale if necessary.
|
||||
// TODO: Bind to relevant parameter.
|
||||
SNew(SDPIScaler)
|
||||
.DPIScale(1.f)
|
||||
[
|
||||
SNew(SConstraintCanvas)
|
||||
+ SConstraintCanvas::Slot()
|
||||
.Anchors(FAnchors(0.f, 0.f, 1.f, 1.f))
|
||||
.AutoSize(true)
|
||||
.Offset(FMargin(1.f, 1.f, 0.f, 1.f))
|
||||
.Alignment(FVector2D::ZeroVector)
|
||||
[
|
||||
SNew(SImGuiWidget)
|
||||
.ModuleManager(InArgs._ModuleManager)
|
||||
.GameViewport(InArgs._GameViewport)
|
||||
.ContextIndex(InArgs._ContextIndex)
|
||||
#if !ENGINE_COMPATIBILITY_LEGACY_CLIPPING_API
|
||||
// To correctly clip borders. Using SScissorRectBox in older versions seems to be not necessary.
|
||||
.Clipping(EWidgetClipping::ClipToBounds)
|
||||
#endif
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
32
Source/ImGui/Private/Widgets/SImGuiLayout.h
Normal file
32
Source/ImGui/Private/Widgets/SImGuiLayout.h
Normal file
@ -0,0 +1,32 @@
|
||||
// Distributed under the MIT License (MIT) (see accompanying LICENSE file)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Widgets/SCompoundWidget.h>
|
||||
|
||||
|
||||
class FImGuiModuleManager;
|
||||
class UGameViewportClient;
|
||||
|
||||
// Layout preset for ImGui Widget.
|
||||
class SImGuiLayout : public SCompoundWidget
|
||||
{
|
||||
typedef SCompoundWidget Super;
|
||||
|
||||
public:
|
||||
|
||||
SLATE_BEGIN_ARGS(SImGuiLayout)
|
||||
{}
|
||||
SLATE_ARGUMENT(FImGuiModuleManager*, ModuleManager)
|
||||
SLATE_ARGUMENT(UGameViewportClient*, GameViewport)
|
||||
SLATE_ARGUMENT(int32, ContextIndex)
|
||||
SLATE_END_ARGS()
|
||||
|
||||
void Construct(const FArguments& InArgs);
|
||||
|
||||
const TWeakObjectPtr<UGameViewportClient>& GetGameViewport() const { return GameViewport; }
|
||||
|
||||
private:
|
||||
|
||||
TWeakObjectPtr<UGameViewportClient> GameViewport;
|
||||
};
|
@ -3,6 +3,7 @@
|
||||
#include "ImGuiPrivatePCH.h"
|
||||
|
||||
#include "SImGuiWidget.h"
|
||||
#include "SImGuiCanvasControl.h"
|
||||
|
||||
#include "ImGuiContextManager.h"
|
||||
#include "ImGuiContextProxy.h"
|
||||
@ -21,10 +22,6 @@
|
||||
#include <utility>
|
||||
|
||||
|
||||
// High enough z-order guarantees that ImGui output is rendered on top of the game UI.
|
||||
constexpr int32 IMGUI_WIDGET_Z_ORDER = 10000;
|
||||
|
||||
|
||||
#if IMGUI_WIDGET_DEBUG
|
||||
|
||||
DEFINE_LOG_CATEGORY_STATIC(LogImGuiWidget, Warning, All);
|
||||
@ -44,16 +41,6 @@ DEFINE_LOG_CATEGORY_STATIC(LogImGuiWidget, Warning, All);
|
||||
|
||||
#endif // IMGUI_WIDGET_DEBUG
|
||||
|
||||
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";
|
||||
}
|
||||
|
||||
#if IMGUI_WIDGET_DEBUG
|
||||
namespace CVars
|
||||
{
|
||||
@ -81,15 +68,11 @@ void SImGuiWidget::Construct(const FArguments& InArgs)
|
||||
GameViewport = InArgs._GameViewport;
|
||||
ContextIndex = InArgs._ContextIndex;
|
||||
|
||||
// NOTE: We could allow null game viewports (for instance to attach to non-viewport widgets) but we would need
|
||||
// to modify a few functions that assume valid viewport pointer.
|
||||
GameViewport->AddViewportWidgetContent(SharedThis(this), IMGUI_WIDGET_Z_ORDER);
|
||||
|
||||
// Disable mouse cursor over this widget as we will use ImGui to draw it.
|
||||
SetCursor(EMouseCursor::None);
|
||||
|
||||
// Sync visibility with default input enabled state.
|
||||
SetVisibilityFromInputEnabled();
|
||||
UpdateVisibility();
|
||||
|
||||
// Register to get post-update notifications, so we can clean frame updates.
|
||||
ModuleManager->OnPostImGuiUpdate().AddRaw(this, &SImGuiWidget::OnPostImGuiUpdate);
|
||||
@ -112,6 +95,13 @@ void SImGuiWidget::Construct(const FArguments& InArgs)
|
||||
|
||||
// Create ImGui Input Handler.
|
||||
CreateInputHandler(Settings.GetImGuiInputHandlerClass());
|
||||
|
||||
ChildSlot
|
||||
[
|
||||
SAssignNew(CanvasControlWidget, SImGuiCanvasControl).OnTransformChanged(this, &SImGuiWidget::SetImGuiTransform)
|
||||
];
|
||||
|
||||
ImGuiTransform = CanvasControlWidget->GetTransform();
|
||||
}
|
||||
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
||||
|
||||
@ -136,15 +126,6 @@ SImGuiWidget::~SImGuiWidget()
|
||||
ModuleManager->OnPostImGuiUpdate().RemoveAll(this);
|
||||
}
|
||||
|
||||
void SImGuiWidget::Detach()
|
||||
{
|
||||
if (GameViewport.IsValid())
|
||||
{
|
||||
GameViewport->RemoveViewportWidgetContent(SharedThis(this));
|
||||
GameViewport.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
void SImGuiWidget::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime)
|
||||
{
|
||||
Super::Tick(AllottedGeometry, InCurrentTime, InDeltaTime);
|
||||
@ -196,7 +177,7 @@ FReply SImGuiWidget::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& Key
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateCanvasMapMode(KeyEvent);
|
||||
UpdateCanvasControlMode(KeyEvent);
|
||||
|
||||
const FImGuiInputResponse Response = InputHandler->OnKeyDown(KeyEvent);
|
||||
if (Response.HasProcessingRequest())
|
||||
@ -205,7 +186,7 @@ FReply SImGuiWidget::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& Key
|
||||
CopyModifierKeys(KeyEvent);
|
||||
}
|
||||
|
||||
return WithMouseLockRequests(ToSlateReply(Response));
|
||||
return ToSlateReply(Response);
|
||||
}
|
||||
}
|
||||
|
||||
@ -227,13 +208,13 @@ FReply SImGuiWidget::OnKeyUp(const FGeometry& MyGeometry, const FKeyEvent& KeyEv
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateCanvasMapMode(KeyEvent);
|
||||
UpdateCanvasControlMode(KeyEvent);
|
||||
|
||||
// Always handle key up events to protect from leaving accidental keys not cleared in ImGui input state.
|
||||
InputState.SetKeyDown(KeyEvent, false);
|
||||
CopyModifierKeys(KeyEvent);
|
||||
|
||||
return WithMouseLockRequests(ToSlateReply(InputHandler->OnKeyUp(KeyEvent)));
|
||||
return ToSlateReply(InputHandler->OnKeyUp(KeyEvent));
|
||||
}
|
||||
}
|
||||
|
||||
@ -260,10 +241,7 @@ FReply SImGuiWidget::OnMouseButtonDown(const FGeometry& MyGeometry, const FPoint
|
||||
InputState.SetMouseDown(MouseEvent, true);
|
||||
CopyModifierKeys(MouseEvent);
|
||||
|
||||
UpdateCanvasMapMode(MouseEvent);
|
||||
UpdateCanvasDraggingConditions(MouseEvent);
|
||||
|
||||
return WithMouseLockRequests(FReply::Handled());
|
||||
return FReply::Handled();
|
||||
}
|
||||
|
||||
FReply SImGuiWidget::OnMouseButtonDoubleClick(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
||||
@ -271,10 +249,7 @@ FReply SImGuiWidget::OnMouseButtonDoubleClick(const FGeometry& MyGeometry, const
|
||||
InputState.SetMouseDown(MouseEvent, true);
|
||||
CopyModifierKeys(MouseEvent);
|
||||
|
||||
UpdateCanvasMapMode(MouseEvent);
|
||||
UpdateCanvasDraggingConditions(MouseEvent);
|
||||
|
||||
return WithMouseLockRequests(FReply::Handled());
|
||||
return FReply::Handled();
|
||||
}
|
||||
|
||||
FReply SImGuiWidget::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
||||
@ -282,21 +257,12 @@ FReply SImGuiWidget::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointer
|
||||
InputState.SetMouseDown(MouseEvent, false);
|
||||
CopyModifierKeys(MouseEvent);
|
||||
|
||||
UpdateCanvasMapMode(MouseEvent);
|
||||
|
||||
return WithMouseLockRequests(FReply::Handled());
|
||||
return 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();
|
||||
@ -304,21 +270,14 @@ FReply SImGuiWidget::OnMouseWheel(const FGeometry& MyGeometry, const FPointerEve
|
||||
|
||||
FReply SImGuiWidget::OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
||||
{
|
||||
if (bCanvasMapMode)
|
||||
{
|
||||
UpdateCanvasDragging(MyGeometry, MouseEvent);
|
||||
}
|
||||
|
||||
const FVector2D CanvasScreenSpacePosition = MyGeometry.AbsolutePosition + GetCanvasPosition(CanvasScale, CanvasOffset);
|
||||
InputState.SetMousePosition((MouseEvent.GetScreenSpacePosition() - CanvasScreenSpacePosition) / CanvasScale);
|
||||
const FSlateRenderTransform ImGuiToScreen = ImGuiTransform.Concatenate(MyGeometry.GetAccumulatedRenderTransform());
|
||||
InputState.SetMousePosition(ImGuiToScreen.Inverse().TransformPoint(MouseEvent.GetScreenSpacePosition()));
|
||||
CopyModifierKeys(MouseEvent);
|
||||
|
||||
// This event is called in every frame when we have a mouse, so we can use it to raise notifications.
|
||||
NotifyMouseEvent();
|
||||
|
||||
UpdateCanvasMapMode(MouseEvent);
|
||||
|
||||
return WithMouseLockRequests(FReply::Handled());
|
||||
return FReply::Handled();
|
||||
}
|
||||
|
||||
FReply SImGuiWidget::OnFocusReceived(const FGeometry& MyGeometry, const FFocusEvent& FocusEvent)
|
||||
@ -332,7 +291,7 @@ FReply SImGuiWidget::OnFocusReceived(const FGeometry& MyGeometry, const FFocusEv
|
||||
UpdateInputMode(true, IsDirectlyHovered());
|
||||
|
||||
FSlateApplication::Get().ResetToDefaultPointerInputSettings();
|
||||
return WithMouseLockRequests(FReply::Handled());
|
||||
return FReply::Handled();
|
||||
}
|
||||
|
||||
void SImGuiWidget::OnFocusLost(const FFocusEvent& FocusEvent)
|
||||
@ -377,11 +336,7 @@ void SImGuiWidget::OnMouseLeave(const FPointerEvent& MouseEvent)
|
||||
FCursorReply SImGuiWidget::OnCursorQuery(const FGeometry& MyGeometry, const FPointerEvent& CursorEvent) const
|
||||
{
|
||||
EMouseCursor::Type MouseCursor = EMouseCursor::None;
|
||||
if (MouseCursorOverride != EMouseCursor::None)
|
||||
{
|
||||
MouseCursor = MouseCursorOverride;
|
||||
}
|
||||
else if (!bUseSoftwareCursor)
|
||||
if (!bUseSoftwareCursor)
|
||||
{
|
||||
if (FImGuiContextProxy* ContextProxy = ModuleManager->GetContextManager().GetContextProxy(ContextIndex))
|
||||
{
|
||||
@ -433,25 +388,6 @@ void SImGuiWidget::UnregisterImGuiSettingsDelegates()
|
||||
Settings.OnUseSoftwareCursorChanged.RemoveAll(this);
|
||||
}
|
||||
|
||||
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());
|
||||
@ -472,17 +408,7 @@ bool SImGuiWidget::IsConsoleOpened() const
|
||||
return GameViewport->ViewportConsole && GameViewport->ViewportConsole->ConsoleState != NAME_None;
|
||||
}
|
||||
|
||||
void SImGuiWidget::SetMouseCursorOverride(EMouseCursor::Type InMouseCursorOverride)
|
||||
{
|
||||
if (MouseCursorOverride != InMouseCursorOverride)
|
||||
{
|
||||
MouseCursorOverride = InMouseCursorOverride;
|
||||
FSlateApplication::Get().QueryCursor();
|
||||
InputState.SetMousePointer(bUseSoftwareCursor && MouseCursorOverride == EMouseCursor::None && IsDirectlyHovered());
|
||||
}
|
||||
}
|
||||
|
||||
void SImGuiWidget::SetVisibilityFromInputEnabled()
|
||||
void SImGuiWidget::UpdateVisibility()
|
||||
{
|
||||
// If we don't use input disable hit test to make this widget invisible for cursors hit detection.
|
||||
SetVisibility(bInputEnabled ? EVisibility::Visible : EVisibility::HitTestInvisible);
|
||||
@ -560,7 +486,7 @@ void SImGuiWidget::UpdateInputEnabled()
|
||||
IMGUI_WIDGET_LOG(Log, TEXT("ImGui Widget %d - Input Enabled changed to '%s'."),
|
||||
ContextIndex, TEXT_BOOL(bInputEnabled));
|
||||
|
||||
SetVisibilityFromInputEnabled();
|
||||
UpdateVisibility();
|
||||
|
||||
if (!bInputEnabled)
|
||||
{
|
||||
@ -624,14 +550,9 @@ void SImGuiWidget::UpdateInputMode(bool bHasKeyboardFocus, bool bHasMousePointer
|
||||
InputMode = NewInputMode;
|
||||
|
||||
ClearMouseEventNotification();
|
||||
|
||||
if (InputMode != EInputMode::Full)
|
||||
{
|
||||
SetCanvasMapMode(false);
|
||||
}
|
||||
}
|
||||
|
||||
InputState.SetMousePointer(bUseSoftwareCursor && MouseCursorOverride == EMouseCursor::None && bHasMousePointer);
|
||||
InputState.SetMousePointer(bUseSoftwareCursor && bHasMousePointer);
|
||||
}
|
||||
|
||||
void SImGuiWidget::UpdateMouseStatus()
|
||||
@ -650,6 +571,11 @@ void SImGuiWidget::UpdateMouseStatus()
|
||||
}
|
||||
}
|
||||
|
||||
void SImGuiWidget::UpdateCanvasControlMode(const FInputEvent& InputEvent)
|
||||
{
|
||||
CanvasControlWidget->SetActive(InputEvent.IsLeftAltDown() && InputEvent.IsLeftShiftDown());
|
||||
}
|
||||
|
||||
void SImGuiWidget::OnPostImGuiUpdate()
|
||||
{
|
||||
if (InputMode != EInputMode::None)
|
||||
@ -657,271 +583,17 @@ 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGuiRenderTransform = ImGuiTransform;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
FORCEINLINE FVector2D Min(const FVector2D& A, const FVector2D& B)
|
||||
FORCEINLINE FSlateRenderTransform RoundTranslation(const FSlateRenderTransform& Transform)
|
||||
{
|
||||
return { FMath::Min(A.X, B.X), FMath::Min(A.Y, B.Y) };
|
||||
const FVector2D& Translation = Transform.GetTranslation();
|
||||
return FSlateRenderTransform{ Transform.GetMatrix(),
|
||||
FVector2D{ FMath::RoundToFloat(Translation.X), FMath::RoundToFloat(Translation.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_ResizeAll, 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 ENGINE_COMPATIBILITY_LEGACY_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 // ENGINE_COMPATIBILITY_LEGACY_CLIPPING_API
|
||||
}
|
||||
|
||||
int32 SImGuiWidget::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect,
|
||||
@ -933,11 +605,10 @@ int32 SImGuiWidget::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeo
|
||||
// keep frame tearing at minimum because it is executed at the very end of the frame.
|
||||
ContextProxy->Tick(FSlateApplication::Get().GetDeltaTime());
|
||||
|
||||
// Calculate offset that will transform vertex positions to screen space - rounded to avoid half pixel offsets.
|
||||
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) };
|
||||
// Calculate transform from ImGui to screen space. Rounding translation is necessary to keep it pixel-perfect
|
||||
// in older engine versions.
|
||||
const FSlateRenderTransform& WidgetToScreen = AllottedGeometry.GetAccumulatedRenderTransform();
|
||||
const FSlateRenderTransform ImGuiToScreen = RoundTranslation(ImGuiRenderTransform.Concatenate(WidgetToScreen));
|
||||
|
||||
#if ENGINE_COMPATIBILITY_LEGACY_CLIPPING_API
|
||||
// Convert clipping rectangle to format required by Slate vertex.
|
||||
@ -947,19 +618,19 @@ int32 SImGuiWidget::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeo
|
||||
for (const auto& DrawList : ContextProxy->GetDrawData())
|
||||
{
|
||||
#if ENGINE_COMPATIBILITY_LEGACY_CLIPPING_API
|
||||
DrawList.CopyVertexData(VertexBuffer, Transform, VertexClippingRect);
|
||||
DrawList.CopyVertexData(VertexBuffer, ImGuiToScreen, VertexClippingRect);
|
||||
|
||||
// Get access to the Slate scissor rectangle defined in Slate Core API, so we can customize elements drawing.
|
||||
extern SLATECORE_API TOptional<FShortRect> GSlateScissorRect;
|
||||
auto GSlateScissorRectSaver = ScopeGuards::MakeStateSaver(GSlateScissorRect);
|
||||
#else
|
||||
DrawList.CopyVertexData(VertexBuffer, Transform);
|
||||
DrawList.CopyVertexData(VertexBuffer, ImGuiToScreen);
|
||||
#endif // ENGINE_COMPATIBILITY_LEGACY_CLIPPING_API
|
||||
|
||||
int IndexBufferOffset = 0;
|
||||
for (int CommandNb = 0; CommandNb < DrawList.NumCommands(); CommandNb++)
|
||||
{
|
||||
const auto& DrawCommand = DrawList.GetCommand(CommandNb, Transform);
|
||||
const auto& DrawCommand = DrawList.GetCommand(CommandNb, ImGuiToScreen);
|
||||
|
||||
DrawList.CopyIndexData(IndexBuffer, IndexBufferOffset, DrawCommand.NumElements);
|
||||
|
||||
@ -986,104 +657,14 @@ int32 SImGuiWidget::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeo
|
||||
#endif // ENGINE_COMPATIBILITY_LEGACY_CLIPPING_API
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// In canvas map mode we need to draw additional information helping with navigation and dragging.
|
||||
if (bCanvasMapMode)
|
||||
return Super::OnPaint(Args, AllottedGeometry, MyClippingRect, OutDrawElements, LayerId, WidgetStyle, bParentEnabled);
|
||||
}
|
||||
|
||||
FVector2D SImGuiWidget::ComputeDesiredSize(float Scale) const
|
||||
{
|
||||
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 ENGINE_COMPATIBILITY_LEGACY_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 // ENGINE_COMPATIBILITY_LEGACY_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 ENGINE_COMPATIBILITY_LEGACY_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 // ENGINE_COMPATIBILITY_LEGACY_CLIPPING_API
|
||||
|
||||
// Draw a movement gizmo (using ImGui move cursor).
|
||||
FVector2D Size, UVMin, UVMax, OutlineUVMin, OutlineUVMax;
|
||||
if (ImGuiImplementation::GetCursorData(ImGuiMouseCursor_ResizeAll, 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 ENGINE_COMPATIBILITY_LEGACY_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 // ENGINE_COMPATIBILITY_LEGACY_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 ENGINE_COMPATIBILITY_LEGACY_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 // ENGINE_COMPATIBILITY_LEGACY_CLIPPING_API
|
||||
}
|
||||
}
|
||||
|
||||
return LayerId;
|
||||
}
|
||||
|
||||
FVector2D SImGuiWidget::ComputeDesiredSize(float) const
|
||||
{
|
||||
return FVector2D{ 3840.f, 2160.f };
|
||||
return FVector2D{ 3840.f, 2160.f } * Scale;
|
||||
}
|
||||
|
||||
#if IMGUI_WIDGET_DEBUG
|
@ -6,19 +6,20 @@
|
||||
#include "ImGuiModuleDebug.h"
|
||||
#include "ImGuiModuleSettings.h"
|
||||
|
||||
#include <Widgets/SLeafWidget.h>
|
||||
#include <Widgets/SCompoundWidget.h>
|
||||
|
||||
|
||||
// Hide ImGui Widget debug in non-developer mode.
|
||||
#define IMGUI_WIDGET_DEBUG IMGUI_MODULE_DEVELOPER
|
||||
|
||||
class FImGuiModuleManager;
|
||||
class SImGuiCanvasControl;
|
||||
class UImGuiInputHandler;
|
||||
|
||||
// Slate widget for rendering ImGui output and storing Slate inputs.
|
||||
class SImGuiWidget : public SLeafWidget
|
||||
class SImGuiWidget : public SCompoundWidget
|
||||
{
|
||||
typedef SLeafWidget Super;
|
||||
typedef SCompoundWidget Super;
|
||||
|
||||
public:
|
||||
|
||||
@ -39,12 +40,6 @@ public:
|
||||
// Get input state associated with this widget.
|
||||
const FImGuiInputState& GetInputState() const { return InputState; }
|
||||
|
||||
// Get the game viewport to which this widget is attached.
|
||||
const TWeakObjectPtr<UGameViewportClient>& GetGameViewport() const { return GameViewport; }
|
||||
|
||||
// Detach widget from viewport assigned during construction (effectively allowing to dispose this widget).
|
||||
void Detach();
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
// SWidget overrides
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
@ -100,18 +95,13 @@ private:
|
||||
void RegisterImGuiSettingsDelegates();
|
||||
void UnregisterImGuiSettingsDelegates();
|
||||
|
||||
// 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);
|
||||
|
||||
bool IsConsoleOpened() const;
|
||||
|
||||
void SetMouseCursorOverride(EMouseCursor::Type InMouseCursorOverride);
|
||||
|
||||
// Update visibility based on input enabled state.
|
||||
void SetVisibilityFromInputEnabled();
|
||||
void UpdateVisibility();
|
||||
|
||||
ULocalPlayer* GetLocalPlayer() const;
|
||||
void TakeFocus();
|
||||
@ -129,36 +119,16 @@ private:
|
||||
FORCEINLINE void NotifyMouseEvent() { bReceivedMouseEvent = true; }
|
||||
FORCEINLINE void ClearMouseEventNotification() { bReceivedMouseEvent = false; }
|
||||
|
||||
void UpdateCanvasControlMode(const FInputEvent& InputEvent);
|
||||
|
||||
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;
|
||||
|
||||
void SetImGuiTransform(const FSlateRenderTransform& Transform) { ImGuiTransform = Transform; }
|
||||
|
||||
#if IMGUI_WIDGET_DEBUG
|
||||
void OnDebugDraw();
|
||||
#endif // IMGUI_WIDGET_DEBUG
|
||||
@ -167,6 +137,9 @@ private:
|
||||
TWeakObjectPtr<UGameViewportClient> GameViewport;
|
||||
TWeakObjectPtr<UImGuiInputHandler> InputHandler;
|
||||
|
||||
FSlateRenderTransform ImGuiTransform;
|
||||
FSlateRenderTransform ImGuiRenderTransform;
|
||||
|
||||
mutable TArray<FSlateVertex> VertexBuffer;
|
||||
mutable TArray<SlateIndex> IndexBuffer;
|
||||
|
||||
@ -177,35 +150,10 @@ private:
|
||||
EInputMode InputMode = EInputMode::None;
|
||||
bool bInputEnabled = false;
|
||||
bool bReceivedMouseEvent = false;
|
||||
bool bMouseLock = false;
|
||||
|
||||
// Whether or not ImGui should draw its own cursor.
|
||||
bool bUseSoftwareCursor = false;
|
||||
|
||||
// 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;
|
||||
|
||||
TSharedPtr<SImGuiCanvasControl> CanvasControlWidget;
|
||||
TWeakPtr<SWidget> PreviousUserFocusedWidget;
|
||||
};
|
Loading…
Reference in New Issue
Block a user