Skip to content

SNaBorderedWindow: self-contained drag via real layout position#20

Merged
SodiumZH merged 5 commits into
mainfrom
copilot/create-bordered-window-widget
Feb 24, 2026
Merged

SNaBorderedWindow: self-contained drag via real layout position#20
SodiumZH merged 5 commits into
mainfrom
copilot/create-bordered-window-widget

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Feb 23, 2026

Top-border drag was silently ignored, then fixed with SetRenderTransform — which only shifts pixels. Layout geometry stays put, so all child widget hit-boxes remain at the original position regardless of where the window visually appears.

Changes

Real position tracking (WindowPosition)

  • Replaced CurrentRenderOffset/DragStartRenderOffset with WindowPosition/DragStartWindowPosition (FVector2D, zero-initialised).
  • Added public GetWindowPosition() const for callers to read the live position.

Outer canvas with TAttribute-bound slot

RebuildLayout now wraps the inner SBox/Canvas tree in a widget-owned OuterCanvas:

SAssignNew(OuterCanvas, SCanvas)
    + SCanvas::Slot()
      .Position(TAttribute<FVector2D>::CreateSP(this, &SNaBorderedWindow::GetWindowPosition))
      .Size(TotalSize)
      [ SNew(SBox)... [ Canvas ] ];

ChildSlot [ OuterCanvas ];

The slot position is a live delegate — re-evaluated by ArrangeChildren on every layout pass.

Drag move path

WindowPosition = DragStartWindowPosition + Delta;
OuterCanvas->Invalidate(EInvalidateWidgetReason::Layout);

Invalidate(Layout) triggers re-arrangement; Slate re-queries the TAttribute, places the entire subtree (9-patch + ContentWidget) at the new real layout position, and child hit-boxes follow correctly.

Original prompt

Implement SNaBorderedWindow Widget

Overview

Create a fully functional bordered window widget for UE5 Slate that supports dragging and resizing with 9-part image composition (similar to 9-slice scaling pattern).

Requirements

1. Core Architecture

  • UE5 Slate widget inheriting from SCompoundWidget
  • 9-part image composition:
    • 1 main body (center)
    • 4 borders (top, bottom, left, right)
    • 4 corners (top-left, top-right, bottom-left, bottom-right)

2. Size Parameters Structure

Create a configuration struct FNaBorderedWindowParams:

USTRUCT(BlueprintType)
struct FNaBorderedWindowParams {
    GENERATED_BODY()
    
    // Main body size (center rectangle)
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    FVector2D BodySize = FVector2D(200.f, 150.f);
    
    // Border widths for each direction
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    float BorderTop = 8.f;
    
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    float BorderBottom = 8.f;
    
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    float BorderLeft = 8.f;
    
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    float BorderRight = 8.f;
    
    // Images for all 9 parts
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    UObject* ImageCenter = nullptr;
    
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    UObject* ImageTop = nullptr;
    
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    UObject* ImageBottom = nullptr;
    
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    UObject* ImageLeft = nullptr;
    
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    UObject* ImageRight = nullptr;
    
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    UObject* ImageTopLeft = nullptr;
    
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    UObject* ImageTopRight = nullptr;
    
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    UObject* ImageBottomLeft = nullptr;
    
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    UObject* ImageBottomRight = nullptr;
};

3. Widget Structure

File locations:

  • Header: Plugins/NaWidgets/Source/NaWidgets/Public/Widgets/SNaBorderedWindow.h
  • Implementation: Plugins/NaWidgets/Source/NaWidgets/Private/Widgets/SNaBorderedWindow.cpp

Class definition:

class NAWIDGETS_API SNaBorderedWindow : public SCompoundWidget
{
public:
    SLATE_BEGIN_ARGS(SNaBorderedWindow)
    {
        _Params = nullptr;
        _MinBodySize = FVector2D(50.f, 50.f);
        _MaxBodySize = FVector2D(1000.f, 1000.f);
    }
    
    SLATE_ATTRIBUTE(const FNaBorderedWindowParams*, Params)
    SLATE_ATTRIBUTE(FVector2D, MinBodySize)
    SLATE_ATTRIBUTE(FVector2D, MaxBodySize)
    SLATE_ATTRIBUTE(TSharedPtr<SWidget>, Content)  // Optional content for center
    
    SLATE_END_ARGS()
    
    void Construct(const FArguments& InArgs);
    
    // Setters for runtime updates
    void SetBodySize(FVector2D NewSize);
    void SetBorderWidths(float Top, float Bottom, float Left, float Right);
    void UpdateImages(const FNaBorderedWindowParams& NewParams);
    
protected:
    // Internal structure
    TSharedPtr<SOverlay> RootOverlay;
    TSharedPtr<SCanvas> Canvas;
    
    // 9 image widgets
    TSharedPtr<SImage> ImgCenter;
    TSharedPtr<SImage> ImgTop;
    TSharedPtr<SImage> ImgBottom;
    TSharedPtr<SImage> ImgLeft;
    TSharedPtr<SImage> ImgRight;
    TSharedPtr<SImage> ImgTopLeft;
    TSharedPtr<SImage> ImgTopRight;
    TSharedPtr<SImage> ImgBottomLeft;
    TSharedPtr<SImage> ImgBottomRight;
    
    FNaBorderedWindowParams Params;
    FVector2D MinBodySize;
    FVector2D MaxBodySize;
    
    // Dragging state
    bool bIsDragging = false;
    bool bIsResizing = false;
    FVector2D DragStartPosition;
    FVector2D DragStartBodySize;
    
    // Rebuild layout after size changes
    void RebuildLayout();
    
    // Mouse event handlers
    virtual FReply OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
    virtual FReply OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
    virtual FReply OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
    
    // Helper to check if mouse is over specific region
    enum class EWindowRegion
    {
        None,
        TopBorder,
        BottomRightCorner,
        Center
    };
    
    EWindowRegion GetRegionAtPosition(const FGeometry& MyGeometry, const FVector2D& LocalPosition) const;
};

4. Drag-and-Drop Functionality

4.1 Top Border Dragging (Window Movement)

  • Trigger area: Top border (excluding corners)
  • Behavior: Move entire window by changing position in parent container
  • Constraint: Window position should be validated if parent has boundaries
  • Implementation:
    • Detect mouse down on top border
    • Track mouse movement delta
    • Update window position via parent slot if available

4.2 Bottom-Right Corner Dragging (Window Resizing)

  • Trigger area: Bottom-right corner i...

This pull request was created from Copilot chat.


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

…in Widgets/WindowWidgets

Co-authored-by: SodiumZH <46860829+SodiumZH@users.noreply.github.com>
Copilot AI changed the title [WIP] Implement SNaBorderedWindow widget for UE5 Slate Implement SNaBorderedWindow: 9-part bordered window widget with drag/resize Feb 23, 2026
…form

Co-authored-by: SodiumZH <46860829+SodiumZH@users.noreply.github.com>
Copilot AI changed the title Implement SNaBorderedWindow: 9-part bordered window widget with drag/resize SNaBorderedWindow: implement self-contained top-border drag Feb 24, 2026
… via TAttribute

Co-authored-by: SodiumZH <46860829+SodiumZH@users.noreply.github.com>
Copilot AI changed the title SNaBorderedWindow: implement self-contained top-border drag SNaBorderedWindow: self-contained drag via real layout position Feb 24, 2026
@SodiumZH SodiumZH marked this pull request as ready for review February 24, 2026 12:56
@SodiumZH SodiumZH merged commit 56936ee into main Feb 24, 2026
1 check failed
@SodiumZH SodiumZH deleted the copilot/create-bordered-window-widget branch February 24, 2026 12:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants