Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Plugins/NaWidgets/Source/NaWidgets/NaWidgets.Build.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ public NaWidgets(ReadOnlyTargetRules Target) : base(Target)
"CoreUObject",
"Engine",
"Slate",
"SlateCore"
"SlateCore",
"UMG"
// ... add private dependencies that you statically link with here ...
}
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// By Sodium

#include "Widgets/WindowWidgets/NaBorderedWindow.h"

TSharedRef<SWidget> UNaBorderedWindow::RebuildWidget()
{
TSharedPtr<SWidget> ContentSlate;
if (Content)
ContentSlate = Content->TakeWidget();

SAssignNew(BorderedWindow, SNaBorderedWindow)
.Params(&Params)
.MinBodySize(MinBodySize)
.MaxBodySize(MaxBodySize)
.Content(ContentSlate);

return BorderedWindow.ToSharedRef();
}

void UNaBorderedWindow::ReleaseSlateResources(bool bReleaseChildren)
{
Super::ReleaseSlateResources(bReleaseChildren);
BorderedWindow.Reset();
}

void UNaBorderedWindow::SynchronizeProperties()
{
Super::SynchronizeProperties();
if (BorderedWindow.IsValid())
{
BorderedWindow->UpdateImages(Params);
BorderedWindow->SetBorderWidths(Params.BorderTop, Params.BorderBottom, Params.BorderLeft, Params.BorderRight);
BorderedWindow->SetBodySize(Params.BodySize);
}
}

const FText UNaBorderedWindow::GetPaletteCategory()
{
return FText::FromString(TEXT("NaWidgets"));
}

void UNaBorderedWindow::SetBodySize(FVector2D NewSize)
{
Params.BodySize = NewSize;
if (BorderedWindow.IsValid())
BorderedWindow->SetBodySize(NewSize);
}

void UNaBorderedWindow::SetBorderWidths(float Top, float Bottom, float Left, float Right)
{
Params.BorderTop = Top;
Params.BorderBottom = Bottom;
Params.BorderLeft = Left;
Params.BorderRight = Right;
if (BorderedWindow.IsValid())
BorderedWindow->SetBorderWidths(Top, Bottom, Left, Right);
}

FVector2D UNaBorderedWindow::GetBodySize() const
{
return Params.BodySize;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
// By Sodium

#include "Widgets/WindowWidgets/SNaBorderedWindow.h"
#include "SlateOptMacros.h"
#include "Widgets/Images/SImage.h"
#include "Widgets/SCanvas.h"
#include "Widgets/Layout/SBox.h"
#include "Widgets/SNullWidget.h"
#include "Styling/CoreStyle.h"

static FSlateBrush MakeBorderedWindowBrush(UObject* ResourceObj, FVector2D Size)
{
FSlateBrush Brush = *FCoreStyle::Get().GetDefaultBrush();
Brush.SetResourceObject(ResourceObj);
Brush.SetImageSize(Size);
Brush.Tiling = ESlateBrushTileType::NoTile;
return Brush;
}

BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SNaBorderedWindow::Construct(const FArguments& InArgs)
{
if (InArgs._Params.Get())
Params = *InArgs._Params.Get();
MinBodySize = InArgs._MinBodySize.Get();
MaxBodySize = InArgs._MaxBodySize.Get();
ContentWidget = InArgs._Content.Get();

// Initialize brushes from params
BrushTopLeft = MakeBorderedWindowBrush(Params.ImageTopLeft, FVector2D(Params.BorderLeft, Params.BorderTop));
BrushTop = MakeBorderedWindowBrush(Params.ImageTop, FVector2D(Params.BodySize.X, Params.BorderTop));
BrushTopRight = MakeBorderedWindowBrush(Params.ImageTopRight, FVector2D(Params.BorderRight, Params.BorderTop));
BrushLeft = MakeBorderedWindowBrush(Params.ImageLeft, FVector2D(Params.BorderLeft, Params.BodySize.Y));
BrushCenter = MakeBorderedWindowBrush(Params.ImageCenter, Params.BodySize);
BrushRight = MakeBorderedWindowBrush(Params.ImageRight, FVector2D(Params.BorderRight, Params.BodySize.Y));
BrushBottomLeft = MakeBorderedWindowBrush(Params.ImageBottomLeft, FVector2D(Params.BorderLeft, Params.BorderBottom));
BrushBottom = MakeBorderedWindowBrush(Params.ImageBottom, FVector2D(Params.BodySize.X, Params.BorderBottom));
BrushBottomRight = MakeBorderedWindowBrush(Params.ImageBottomRight, FVector2D(Params.BorderRight, Params.BorderBottom));

// Create image sub-widgets
SAssignNew(ImgTopLeft, SImage).Image(&BrushTopLeft);
SAssignNew(ImgTop, SImage).Image(&BrushTop);
SAssignNew(ImgTopRight, SImage).Image(&BrushTopRight);
SAssignNew(ImgLeft, SImage).Image(&BrushLeft);
SAssignNew(ImgCenter, SImage).Image(&BrushCenter);
SAssignNew(ImgRight, SImage).Image(&BrushRight);
SAssignNew(ImgBottomLeft, SImage).Image(&BrushBottomLeft);
SAssignNew(ImgBottom, SImage).Image(&BrushBottom);
SAssignNew(ImgBottomRight, SImage).Image(&BrushBottomRight);

RebuildLayout();
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION

void SNaBorderedWindow::RebuildLayout()
{
// Update brush sizes to match current params
BrushTopLeft.SetImageSize(FVector2D(Params.BorderLeft, Params.BorderTop));
BrushTop.SetImageSize(FVector2D(Params.BodySize.X, Params.BorderTop));
BrushTopRight.SetImageSize(FVector2D(Params.BorderRight, Params.BorderTop));
BrushLeft.SetImageSize(FVector2D(Params.BorderLeft, Params.BodySize.Y));
BrushCenter.SetImageSize(Params.BodySize);
BrushRight.SetImageSize(FVector2D(Params.BorderRight, Params.BodySize.Y));
BrushBottomLeft.SetImageSize(FVector2D(Params.BorderLeft, Params.BorderBottom));
BrushBottom.SetImageSize(FVector2D(Params.BodySize.X, Params.BorderBottom));
BrushBottomRight.SetImageSize(FVector2D(Params.BorderRight, Params.BorderBottom));

const FVector2D TotalSize(
Params.BorderLeft + Params.BodySize.X + Params.BorderRight,
Params.BorderTop + Params.BodySize.Y + Params.BorderBottom
);

// Slot positions
const FVector2D PosTopLeft (0.f, 0.f);
const FVector2D PosTop (Params.BorderLeft, 0.f);
const FVector2D PosTopRight (Params.BorderLeft + Params.BodySize.X, 0.f);
const FVector2D PosLeft (0.f, Params.BorderTop);
const FVector2D PosCenter (Params.BorderLeft, Params.BorderTop);
const FVector2D PosRight (Params.BorderLeft + Params.BodySize.X, Params.BorderTop);
const FVector2D PosBottomLeft (0.f, Params.BorderTop + Params.BodySize.Y);
const FVector2D PosBottom (Params.BorderLeft, Params.BorderTop + Params.BodySize.Y);
const FVector2D PosBottomRight(Params.BorderLeft + Params.BodySize.X, Params.BorderTop + Params.BodySize.Y);

// Slot sizes
const FVector2D SzCornerTL(Params.BorderLeft, Params.BorderTop);
const FVector2D SzTop (Params.BodySize.X, Params.BorderTop);
const FVector2D SzCornerTR(Params.BorderRight, Params.BorderTop);
const FVector2D SzLeft (Params.BorderLeft, Params.BodySize.Y);
const FVector2D SzCenter (Params.BodySize);
const FVector2D SzRight (Params.BorderRight, Params.BodySize.Y);
const FVector2D SzCornerBL(Params.BorderLeft, Params.BorderBottom);
const FVector2D SzBottom (Params.BodySize.X, Params.BorderBottom);
const FVector2D SzCornerBR(Params.BorderRight, Params.BorderBottom);

// Optional content widget placed over the center area
const TSharedRef<SWidget> CenterContent = ContentWidget.IsValid()
? ContentWidget.ToSharedRef()
: SNullWidget::NullWidget;

// Rebuild canvas with updated absolute positions/sizes
SAssignNew(Canvas, SCanvas)
+ SCanvas::Slot().Position(PosTopLeft).Size(SzCornerTL) [ImgTopLeft.ToSharedRef()]
+ SCanvas::Slot().Position(PosTop).Size(SzTop) [ImgTop.ToSharedRef()]
+ SCanvas::Slot().Position(PosTopRight).Size(SzCornerTR) [ImgTopRight.ToSharedRef()]
+ SCanvas::Slot().Position(PosLeft).Size(SzLeft) [ImgLeft.ToSharedRef()]
+ SCanvas::Slot().Position(PosCenter).Size(SzCenter) [ImgCenter.ToSharedRef()]
+ SCanvas::Slot().Position(PosRight).Size(SzRight) [ImgRight.ToSharedRef()]
+ SCanvas::Slot().Position(PosBottomLeft).Size(SzCornerBL) [ImgBottomLeft.ToSharedRef()]
+ SCanvas::Slot().Position(PosBottom).Size(SzBottom) [ImgBottom.ToSharedRef()]
+ SCanvas::Slot().Position(PosBottomRight).Size(SzCornerBR)[ImgBottomRight.ToSharedRef()]
+ SCanvas::Slot().Position(PosCenter).Size(SzCenter) [CenterContent];

// Outer canvas: its single slot's position is driven by WindowPosition so that
// dragging changes the real layout position, moving all children with it.
SAssignNew(OuterCanvas, SCanvas)
+ SCanvas::Slot()
.Position(TAttribute<FVector2D>::CreateSP(this, &SNaBorderedWindow::GetWindowPosition))
.Size(TotalSize)
[
SNew(SBox)
.WidthOverride(TotalSize.X)
.HeightOverride(TotalSize.Y)
[
Canvas.ToSharedRef()
]
];

ChildSlot
[
OuterCanvas.ToSharedRef()
];
}

FVector2D SNaBorderedWindow::ClampBodySize(FVector2D Size) const
{
return FVector2D(
FMath::Clamp(Size.X, MinBodySize.X, MaxBodySize.X),
FMath::Clamp(Size.Y, MinBodySize.Y, MaxBodySize.Y)
);
}

void SNaBorderedWindow::SetBodySize(FVector2D NewSize)
{
Params.BodySize = ClampBodySize(NewSize);
RebuildLayout();
}

void SNaBorderedWindow::SetBorderWidths(float Top, float Bottom, float Left, float Right)
{
Params.BorderTop = Top;
Params.BorderBottom = Bottom;
Params.BorderLeft = Left;
Params.BorderRight = Right;
RebuildLayout();
}

void SNaBorderedWindow::UpdateImages(const FNaBorderedWindowParams& NewParams)
{
Params = NewParams;
BrushTopLeft.SetResourceObject(Params.ImageTopLeft);
BrushTop.SetResourceObject(Params.ImageTop);
BrushTopRight.SetResourceObject(Params.ImageTopRight);
BrushLeft.SetResourceObject(Params.ImageLeft);
BrushCenter.SetResourceObject(Params.ImageCenter);
BrushRight.SetResourceObject(Params.ImageRight);
BrushBottomLeft.SetResourceObject(Params.ImageBottomLeft);
BrushBottom.SetResourceObject(Params.ImageBottom);
BrushBottomRight.SetResourceObject(Params.ImageBottomRight);
RebuildLayout();
}

FReply SNaBorderedWindow::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
if (MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton)
{
const FVector2D LocalPos = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition());
const EWindowRegion Region = GetRegionAtPosition(MyGeometry, LocalPos);

if (Region == EWindowRegion::TopBorder)
{
bIsDragging = true;
DragStartPosition = MouseEvent.GetScreenSpacePosition();
DragStartWindowPosition = WindowPosition;
return FReply::Handled().CaptureMouse(SharedThis(this));
}
else if (Region == EWindowRegion::BottomRightCorner)
{
bIsResizing = true;
DragStartPosition = LocalPos;
DragStartBodySize = Params.BodySize;
return FReply::Handled().CaptureMouse(SharedThis(this));
}
}

return FReply::Unhandled();
}

FReply SNaBorderedWindow::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
if (MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton)
{
if (bIsDragging || bIsResizing)
{
bIsDragging = false;
bIsResizing = false;
return FReply::Handled().ReleaseMouseCapture();
}
}

return FReply::Unhandled();
}

FReply SNaBorderedWindow::OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
if (bIsResizing)
{
const FVector2D CurrentPos = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition());
const FVector2D Delta = CurrentPos - DragStartPosition;

FVector2D NewBodySize = DragStartBodySize + Delta;
Params.BodySize = ClampBodySize(NewBodySize);
RebuildLayout();
return FReply::Handled();
}
else if (bIsDragging)
{
const FVector2D Delta = MouseEvent.GetScreenSpacePosition() - DragStartPosition;
WindowPosition = DragStartWindowPosition + Delta;
OuterCanvas->Invalidate(EInvalidateWidgetReason::Layout);
return FReply::Handled();
}

return FReply::Unhandled();
}

SNaBorderedWindow::EWindowRegion SNaBorderedWindow::GetRegionAtPosition(
const FGeometry& MyGeometry, const FVector2D& LocalPosition) const
{
// Bottom-right corner
if (LocalPosition.X >= Params.BorderLeft + Params.BodySize.X &&
LocalPosition.Y >= Params.BorderTop + Params.BodySize.Y)
{
return EWindowRegion::BottomRightCorner;
}

// Top border (excluding corners)
if (LocalPosition.Y < Params.BorderTop &&
LocalPosition.X >= Params.BorderLeft &&
LocalPosition.X < Params.BorderLeft + Params.BodySize.X)
{
return EWindowRegion::TopBorder;
}

// Center
if (LocalPosition.X >= Params.BorderLeft &&
LocalPosition.X < Params.BorderLeft + Params.BodySize.X &&
LocalPosition.Y >= Params.BorderTop &&
LocalPosition.Y < Params.BorderTop + Params.BodySize.Y)
{
return EWindowRegion::Center;
}

return EWindowRegion::None;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// By Sodium

#pragma once

#include "CoreMinimal.h"
#include "Components/Widget.h"
#include "Widgets/WindowWidgets/SNaBorderedWindow.h"
#include "NaBorderedWindow.generated.h"

/**
* UMG wrapper for SNaBorderedWindow.
* Exposes the 9-part bordered window widget for use in UMG and Blueprints.
*/
UCLASS()
class NAWIDGETS_API UNaBorderedWindow : public UWidget
{
GENERATED_BODY()

public:
/** Image and size configuration for all 9 parts. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Appearance")
FNaBorderedWindowParams Params;

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Sizing")
FVector2D MinBodySize = FVector2D(50.f, 50.f);

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Sizing")
FVector2D MaxBodySize = FVector2D(1000.f, 1000.f);

/** Optional widget placed over the center body area. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Content")
UWidget* Content = nullptr;

UFUNCTION(BlueprintCallable, Category = "Bordered Window")
void SetBodySize(FVector2D NewSize);

UFUNCTION(BlueprintCallable, Category = "Bordered Window")
void SetBorderWidths(float Top, float Bottom, float Left, float Right);

UFUNCTION(BlueprintCallable, Category = "Bordered Window")
FVector2D GetBodySize() const;

TSharedPtr<SNaBorderedWindow> BorderedWindow;

protected:
virtual TSharedRef<SWidget> RebuildWidget() override;
virtual void ReleaseSlateResources(bool bReleaseChildren) override;
virtual void SynchronizeProperties() override;
virtual const FText GetPaletteCategory() override;
};
Loading
Loading