terminalCode Example

Drag-and-drop system with static instance pattern

calendar_today May 12, 2026 schedule ~1 min read person patrickjr verified 50

Drag-and-Drop System with Static Instance Pattern

This example shows a drag-and-drop system using a static instance pattern for global state management and visual feedback.

CSHARP
@using Sandbox;
@using Sandbox.UI;
@inherits PanelComponent
@namespace Sandbox

<root>
    @if (Current is not null)
    {
        var mouse = Mouse.Position;
        var left = mouse.x * Panel.ScaleFromScreen - 32;
        var top = mouse.y * Panel.ScaleFromScreen - 32;
        var iconStyle = string.IsNullOrEmpty(Current.Icon) ? "" : $"background-image: url({Current.Icon}); ";
        <div class="dragging" style="@(iconStyle)left: @(left)px; top: @(top)px;" @ref="DragVisual">
            @if (string.IsNullOrEmpty(Current.Icon))
            {
                <div class="drag-title">@Current.Title</div>
            }
        </div>
    }
</root>

@code
{
    public static DragHandler Instance { get; private set; }

    /// <summary>
    /// The data currently being dragged, or null.
    /// </summary>
    public static DragData Current { get; private set; }

    /// <summary>
    /// True if something is actively being dragged.
    /// </summary>
    public static bool IsDragging => Current is not null;

    Panel DragVisual { get; set; }
    private RootPanel _rootPanel;

    protected override void OnStart()
    {
        Instance = this;
    }

    protected override void OnDestroy()
    {
        if (Instance == this)
            Instance = null;
    }

    /// <summary>
    /// Start dragging with the given data.
    /// </summary>
    public static void StartDragging(DragData data)
    {
        Current = data;
        data.Source.UserData = data;

        Instance?.StateHasChanged();
    }

    /// <summary>
    /// Stop dragging and clear all state.
    /// </summary>
    public static void StopDragging()
    {
        if (!IsDragging) return;

        Current = null;
        Instance?.StateHasChanged();
    }

    protected override void OnUpdate()
    {
        if (!IsDragging) return;

        if (!_rootPanel.WantsMouseInput())
        {
            StopDragging();
            return;
        }

        if (DragVisual is not null)
        {
            var mouse = Mouse.Position;
            DragVisual.Style.Left = Length.Pixels(mouse.x * Panel.ScaleFromScreen - 32);
            DragVisual.Style.Top = Length.Pixels(mouse.y * Panel.ScaleFromScreen - 32);
        }
    }

    protected override void OnTreeBuilt()
    {
        _rootPanel = Panel.FindRootPanel();
    }

    protected override int BuildHash() => HashCode.Combine(Current);
}

Key Features

  • Static Instance Pattern: Single DragHandler instance manages global drag state
  • Static State: Current and IsDragging are static for easy access from anywhere
  • StartDragging/StopDragging: Static methods to control drag lifecycle
  • Visual Feedback: Shows icon or title following mouse cursor
  • ScaleFromScreen: Correctly positions visual across different UI scales
  • Auto-cancel: Stops dragging if root panel no longer wants mouse input
  • BuildHash Optimization: Only rebuilds when drag state changes
Was this helpful?