Tooling
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Styling/SlateBrush.h"
|
||||
#include "UObject/StrongObjectPtr.h"
|
||||
#include "Widgets/SLeafWidget.h"
|
||||
#include "World/WorldMapProjection.h"
|
||||
|
||||
class UTexture2D;
|
||||
class UWorldMapDefinition;
|
||||
class UWorldMapSubsystem;
|
||||
|
||||
/** A place on the ground was clicked, in world centimetres. XY only; the map is a plan and knows no Z. */
|
||||
DECLARE_DELEGATE_OneParam(FOnWorldMapClicked, FVector2D /* WorldCm */);
|
||||
|
||||
/**
|
||||
* The world map. All of it: the drawing, the panning, the zooming, the markers and the scale bar.
|
||||
*
|
||||
* It is a Slate widget rather than a UUserWidget because it has two hosts that have nothing else in common -
|
||||
* UWorldMapWidget puts it in UMG for the game, and the editor's World Map tab hosts it directly - and a Slate
|
||||
* widget is the one shape both can take. The editor tab in particular has no UWorld, so anything that needed
|
||||
* one to exist could not be shared.
|
||||
*
|
||||
* It draws a rectangle of a picture, and the whole trick is that the picture and the world are the same
|
||||
* rectangle (see FWorldMapProjection). There is no scene capture and no render target anywhere near this.
|
||||
*/
|
||||
class SALTY_API SWorldMap : public SLeafWidget
|
||||
{
|
||||
public:
|
||||
SLATE_BEGIN_ARGS(SWorldMap)
|
||||
: _Definition(nullptr)
|
||||
, _Layer(NAME_None)
|
||||
, _ShowGrid(true)
|
||||
, _ShowScaleBar(true)
|
||||
, _ShowMarkers(true)
|
||||
, _ShowLocalPlayer(true)
|
||||
, _AllowInput(true)
|
||||
{}
|
||||
/** What to draw. Null draws the empty state and says so rather than drawing nothing. */
|
||||
SLATE_ARGUMENT(UWorldMapDefinition*, Definition)
|
||||
/** Which layer; NAME_None takes the definition's default. */
|
||||
SLATE_ARGUMENT(FName, Layer)
|
||||
SLATE_ARGUMENT(bool, ShowGrid)
|
||||
SLATE_ARGUMENT(bool, ShowScaleBar)
|
||||
SLATE_ARGUMENT(bool, ShowMarkers)
|
||||
SLATE_ARGUMENT(bool, ShowLocalPlayer)
|
||||
/** False makes it a picture: no pan, no zoom, no click. For a minimap. */
|
||||
SLATE_ARGUMENT(bool, AllowInput)
|
||||
/** Where registered markers and the local player come from. Absent in the editor tab, which has neither. */
|
||||
SLATE_ARGUMENT(TWeakObjectPtr<UWorldMapSubsystem>, MarkerSource)
|
||||
SLATE_EVENT(FOnWorldMapClicked, OnClicked)
|
||||
SLATE_END_ARGS()
|
||||
|
||||
void Construct(const FArguments& InArgs);
|
||||
|
||||
void SetDefinition(UWorldMapDefinition* InDefinition);
|
||||
UWorldMapDefinition* GetDefinition() const { return Definition.Get(); }
|
||||
|
||||
void SetMarkerSource(UWorldMapSubsystem* InSource);
|
||||
|
||||
/** An unknown id is ignored rather than blanking the map, so a stale saved setting is survivable. */
|
||||
void SetLayer(FName InLayer);
|
||||
FName GetLayer() const { return Layer; }
|
||||
void NextLayer();
|
||||
|
||||
void SetShowGrid(bool bValue) { bShowGrid = bValue; }
|
||||
void SetShowScaleBar(bool bValue) { bShowScaleBar = bValue; }
|
||||
void SetShowMarkers(bool bValue) { bShowMarkers = bValue; }
|
||||
void SetShowLocalPlayer(bool bValue) { bShowLocalPlayer = bValue; }
|
||||
void SetAllowInput(bool bValue) { bAllowInput = bValue; }
|
||||
|
||||
/** Centre the view on a place, in world centimetres. Does not change the zoom. */
|
||||
void FocusOnWorld(const FVector2D& WorldCm);
|
||||
FVector2D GetViewCentreWorldCm() const;
|
||||
|
||||
/**
|
||||
* Keep the view centred on the local player as it moves. Panning by hand turns this off, because a map that
|
||||
* snaps back the moment you let go of it cannot be read.
|
||||
*/
|
||||
void SetFollowLocalPlayer(bool bValue) { bFollowLocalPlayer = bValue; }
|
||||
bool IsFollowingLocalPlayer() const { return bFollowLocalPlayer; }
|
||||
|
||||
/**
|
||||
* Go back to the player and start following again. The way out of the corner panning puts you in: having
|
||||
* dropped the follow to look somewhere else, there was otherwise no way back to it but closing the map.
|
||||
* Does nothing when there is no local body, which is the honest answer rather than jumping to the origin.
|
||||
*/
|
||||
bool FocusOnLocalPlayer();
|
||||
|
||||
/** Ground metres to one screen pixel. Smaller is closer in. */
|
||||
void SetMetresPerPixel(double InMetresPerPixel);
|
||||
double GetMetresPerPixel() const { return MetresPerPixel; }
|
||||
|
||||
/** Zoom back out until the whole world fits, and centre it. */
|
||||
void ZoomToFit();
|
||||
|
||||
// SWidget
|
||||
/** The first fit needs a size, which a widget has only after it has been laid out; following the player
|
||||
* needs to happen whether or not anything else changed. Both live here rather than in OnPaint, which is
|
||||
* const and has no business moving the view. */
|
||||
virtual void Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) override;
|
||||
virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect,
|
||||
FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override;
|
||||
virtual FVector2D ComputeDesiredSize(float LayoutScaleMultiplier) const override;
|
||||
virtual FReply OnMouseButtonDown(const FGeometry& Geometry, const FPointerEvent& Event) override;
|
||||
virtual FReply OnMouseButtonUp(const FGeometry& Geometry, const FPointerEvent& Event) override;
|
||||
virtual FReply OnMouseMove(const FGeometry& Geometry, const FPointerEvent& Event) override;
|
||||
virtual FReply OnMouseWheel(const FGeometry& Geometry, const FPointerEvent& Event) override;
|
||||
virtual FReply OnMouseButtonDoubleClick(const FGeometry& Geometry, const FPointerEvent& Event) override;
|
||||
virtual void OnMouseLeave(const FPointerEvent& Event) override;
|
||||
virtual FCursorReply OnCursorQuery(const FGeometry& Geometry, const FPointerEvent& Event) const override;
|
||||
|
||||
private:
|
||||
/** The view, in normalised map coordinates, derived from the zoom and the widget's own size. */
|
||||
struct FView
|
||||
{
|
||||
FVector2D Centre = FVector2D(0.5, 0.5);
|
||||
double SpanU = 1.0;
|
||||
double SpanV = 1.0;
|
||||
FVector2D SizePx = FVector2D(1.0, 1.0);
|
||||
bool bValid = false;
|
||||
};
|
||||
|
||||
FView ComputeView(const FVector2D& LocalSizePx) const;
|
||||
FVector2D LocalToNormalised(const FView& View, const FVector2D& LocalPx) const;
|
||||
FVector2D NormalisedToLocal(const FView& View, const FVector2D& Normalised) const;
|
||||
|
||||
/** Fold the centre back into what can actually be seen. X wraps on a whole cylinder; Y never does. */
|
||||
void ClampCentre(const FVector2D& LocalSizePx);
|
||||
|
||||
/** Widest the map may be zoomed out at this size: the whole thing, fitted. */
|
||||
double FitMetresPerPixel(const FVector2D& LocalSizePx) const;
|
||||
|
||||
const FSlateBrush* BrushForCurrentLayer() const;
|
||||
|
||||
int32 PaintMap(const FGeometry& Geometry, const FView& View, FSlateWindowElementList& Out, int32 LayerId) const;
|
||||
int32 PaintGrid(const FGeometry& Geometry, const FView& View, FSlateWindowElementList& Out, int32 LayerId) const;
|
||||
int32 PaintMarkers(const FGeometry& Geometry, const FView& View, FSlateWindowElementList& Out, int32 LayerId) const;
|
||||
int32 PaintOneMarker(const FGeometry& Geometry, const FView& View, const struct FWorldMapMarker& Marker,
|
||||
FSlateWindowElementList& Out, int32 LayerId) const;
|
||||
int32 PaintScaleBar(const FGeometry& Geometry, const FView& View, FSlateWindowElementList& Out, int32 LayerId) const;
|
||||
int32 PaintReadout(const FGeometry& Geometry, const FView& View, FSlateWindowElementList& Out, int32 LayerId) const;
|
||||
int32 PaintEmptyState(const FGeometry& Geometry, FSlateWindowElementList& Out, int32 LayerId) const;
|
||||
|
||||
const FWorldMapProjection& GetProjection() const;
|
||||
|
||||
TStrongObjectPtr<UWorldMapDefinition> Definition;
|
||||
TWeakObjectPtr<UWorldMapSubsystem> MarkerSource;
|
||||
FOnWorldMapClicked OnClicked;
|
||||
|
||||
FName Layer;
|
||||
|
||||
/** One brush per layer, built on first use and holding the texture's only hard reference. */
|
||||
mutable TMap<FName, TSharedPtr<FSlateBrush>> LayerBrushes;
|
||||
mutable TArray<TStrongObjectPtr<UTexture2D>> LoadedTextures;
|
||||
|
||||
/** Normalised. X may be anywhere on a wrapping map and is folded when it is used. */
|
||||
FVector2D ViewCentre = FVector2D(0.5, 0.5);
|
||||
double MetresPerPixel = 0.0; // 0 means "not fitted yet"; the first paint fits it to the widget
|
||||
mutable bool bNeedsFit = true;
|
||||
|
||||
bool bShowGrid = true;
|
||||
bool bShowScaleBar = true;
|
||||
bool bShowMarkers = true;
|
||||
bool bShowLocalPlayer = true;
|
||||
bool bAllowInput = true;
|
||||
bool bFollowLocalPlayer = false;
|
||||
|
||||
bool bDragging = false;
|
||||
bool bDraggedFarEnoughToNotBeAClick = false;
|
||||
FVector2D DragLastLocal = FVector2D::ZeroVector;
|
||||
FVector2D PressLocal = FVector2D::ZeroVector;
|
||||
|
||||
/** Where the pointer is, for the readout. Unset when it is not over the map. */
|
||||
mutable TOptional<FVector2D> HoverLocal;
|
||||
};
|
||||
Reference in New Issue
Block a user