terminalCode Example
Drag-and-drop system with static instance pattern
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?