Editor UI widget that displays and manages a TileMap layer list. It draws header and rows, shows drag handles, visibility and collision toggles, handles mouse input for selecting, dragging/reordering layers, and toggling visibility/collisions.
using Editor;
using Sandbox;
using System;
using Saandy.Tilemapper;
namespace Saandy.Editor.Tilemapper;
public sealed class TilemapLayerListWidget : Widget
{
private const float HeaderHeight = 30.0f;
private const float RowHeight = 36.0f;
private const float Padding = 8.0f;
private const float ButtonSize = 22.0f;
private const float ButtonGap = 6.0f;
private const float DragHandleWidth = 24.0f;
private const float DragStartDistance = 5.0f;
private enum PressAction
{
None,
SelectOrDrag,
Visibility,
Collision
}
private TileMap _tilemap;
private int _hoveredIndex = -1;
private int _pressedIndex = -1;
private int _dragIndex = -1;
private Vector2 _pressPosition;
private bool _isDragging;
private PressAction _pressAction;
public TilemapLayerListWidget( Widget parent ) : base( parent )
{
MouseTracking = true;
HorizontalSizeMode = SizeMode.Flexible;
VerticalSizeMode = SizeMode.CanGrow;
MinimumHeight = 190;
}
public void SetTilemap( TileMap tilemap )
{
_tilemap = tilemap;
RefreshSize();
Update();
}
private void RefreshSize()
{
int count = _tilemap?.LayerCount ?? 1;
MinimumHeight = Math.Max( 120.0f, HeaderHeight + count * RowHeight + Padding );
}
protected override void OnMouseMove( MouseEvent e )
{
base.OnMouseMove( e );
_hoveredIndex = GetLayerIndexAtPosition( e.LocalPosition );
if ( _tilemap != null && _tilemap.IsValid() && _pressedIndex >= 0 && IsLeftButtonDown( e ) )
{
if ( _pressAction == PressAction.SelectOrDrag )
{
Vector2 delta = e.LocalPosition - _pressPosition;
bool movedFarEnough = MathF.Abs( delta.x ) >= DragStartDistance || MathF.Abs( delta.y ) >= DragStartDistance;
if ( !_isDragging && movedFarEnough )
{
_isDragging = true;
_dragIndex = _pressedIndex;
}
if ( _isDragging )
{
int targetIndex = GetLayerIndexAtPosition( e.LocalPosition );
if ( targetIndex >= 0 && targetIndex < _tilemap.LayerCount && targetIndex != _dragIndex )
{
_tilemap.MoveLayer( _dragIndex, targetIndex );
_dragIndex = targetIndex;
_pressedIndex = targetIndex;
RefreshSize();
}
e.Accepted = true;
}
}
}
Update();
}
protected override void OnMouseLeave()
{
base.OnMouseLeave();
_hoveredIndex = -1;
Update();
}
protected override void OnMousePress( MouseEvent e )
{
base.OnMousePress( e );
if ( _tilemap == null || !_tilemap.IsValid() || !e.LeftMouseButton )
return;
int index = GetLayerIndexAtPosition( e.LocalPosition );
if ( index < 0 || index >= _tilemap.LayerCount )
return;
_pressedIndex = index;
_dragIndex = -1;
_isDragging = false;
_pressPosition = e.LocalPosition;
if ( GetVisibilityButtonRect( index ).IsInside( e.LocalPosition ) )
{
_pressAction = PressAction.Visibility;
e.Accepted = true;
Update();
return;
}
if ( GetCollisionButtonRect( index ).IsInside( e.LocalPosition ) )
{
_pressAction = PressAction.Collision;
e.Accepted = true;
Update();
return;
}
_pressAction = PressAction.SelectOrDrag;
_tilemap.SetActiveLayer( index );
e.Accepted = true;
Update();
}
protected override void OnMouseReleased( MouseEvent e )
{
base.OnMouseReleased( e );
if ( _tilemap == null || !_tilemap.IsValid() )
{
ResetPressState();
Update();
return;
}
int releaseIndex = GetLayerIndexAtPosition( e.LocalPosition );
if ( !_isDragging && _pressedIndex >= 0 && releaseIndex == _pressedIndex )
{
var layer = _tilemap.GetLayer( _pressedIndex );
switch ( _pressAction )
{
case PressAction.Visibility:
if ( layer != null )
_tilemap.SetLayerVisible( _pressedIndex, !layer.IsVisible );
break;
case PressAction.Collision:
if ( layer != null )
_tilemap.SetLayerCollisionsEnabled( _pressedIndex, !layer.CollisionsEnabled );
break;
case PressAction.SelectOrDrag:
_tilemap.SetActiveLayer( _pressedIndex );
break;
}
}
ResetPressState();
e.Accepted = true;
Update();
}
private void ResetPressState()
{
_pressedIndex = -1;
_dragIndex = -1;
_isDragging = false;
_pressAction = PressAction.None;
}
protected override void OnPaint()
{
base.OnPaint();
Paint.ClearPen();
Paint.SetBrush( Color.Black.WithAlpha( 0.18f ) );
Paint.DrawRect( LocalRect );
DrawHeader();
if ( _tilemap == null || !_tilemap.IsValid() )
{
Paint.SetPen( Color.White.WithAlpha( 0.7f ) );
Paint.DrawText( new Rect( Padding, HeaderHeight + Padding, LocalRect.Width - Padding * 2.0f, 40.0f ), "No TileMap selected", TextFlag.LeftTop );
return;
}
for ( int i = 0; i < _tilemap.LayerCount; i++ )
{
DrawLayerRow( i );
}
}
private void DrawHeader()
{
Rect headerRect = new Rect( 0.0f, 0.0f, LocalRect.Width, HeaderHeight );
Paint.ClearPen();
Paint.SetBrush( Color.Black.WithAlpha( 0.30f ) );
Paint.DrawRect( headerRect );
Paint.SetPen( Color.White );
Paint.DrawText( new Rect( Padding, 0.0f, LocalRect.Width - Padding * 2.0f, HeaderHeight ), "Layers (0 = top / closest)", TextFlag.LeftCenter );
}
private void DrawLayerRow( int index )
{
var layer = _tilemap.GetLayer( index );
if ( layer == null )
return;
Rect row = GetLayerRowRect( index );
bool active = index == _tilemap.ActiveLayerIndex;
bool hovered = index == _hoveredIndex;
bool dragging = index == _dragIndex && _isDragging;
bool visible = layer.IsVisible;
float layerAlpha = visible ? 1.0f : 0.42f;
Paint.ClearPen();
Paint.SetBrush( active ? Color.FromBytes( 70, 125, 210 ).WithAlpha( 0.70f * layerAlpha ) : hovered ? Color.White.WithAlpha( 0.08f * layerAlpha ) : Color.White.WithAlpha( 0.035f * layerAlpha ) );
Paint.DrawRect( row.Shrink( 2.0f ) );
if ( dragging )
{
Paint.SetPen( Color.White.WithAlpha( 0.80f ) );
Paint.DrawRect( row.Shrink( 2.0f ) );
}
DrawDragHandle( index, visible );
DrawVisibilityButton( index, layer );
DrawCollisionButton( index, layer );
DrawLayerLabel( index, layer, active, visible );
}
private void DrawDragHandle( int index, bool visible )
{
Rect row = GetLayerRowRect( index );
Rect handle = new Rect( Padding, row.Top, DragHandleWidth, RowHeight );
Paint.SetPen( Color.White.WithAlpha( visible ? 0.45f : 0.20f ) );
Paint.DrawText( handle, "☰", TextFlag.Center );
}
private void DrawVisibilityButton( int index, TileMap.TileLayer layer )
{
Rect button = GetVisibilityButtonRect( index );
bool pressed = _pressedIndex == index && _pressAction == PressAction.Visibility;
Paint.ClearPen();
Paint.SetBrush( layer.IsVisible ? Color.FromBytes( 70, 130, 210 ).WithAlpha( pressed ? 0.95f : 0.75f ) : Color.Black.WithAlpha( pressed ? 0.65f : 0.40f ) );
Paint.DrawRect( button );
Paint.SetPen( Color.White.WithAlpha( layer.IsVisible ? 1.0f : 0.45f ) );
Paint.DrawText( button, layer.IsVisible ? "👁" : "—", TextFlag.Center );
}
private void DrawCollisionButton( int index, TileMap.TileLayer layer )
{
Rect button = GetCollisionButtonRect( index );
bool pressed = _pressedIndex == index && _pressAction == PressAction.Collision;
bool collisionActuallyEnabled = layer.IsVisible && layer.CollisionsEnabled;
Paint.ClearPen();
Paint.SetBrush( collisionActuallyEnabled ? Color.FromBytes( 90, 190, 110 ).WithAlpha( pressed ? 1.0f : 0.88f ) : Color.Black.WithAlpha( pressed ? 0.65f : 0.35f ) );
Paint.DrawRect( button );
Paint.SetPen( Color.White.WithAlpha( collisionActuallyEnabled ? 1.0f : 0.35f ) );
Paint.DrawText( button, layer.CollisionsEnabled ? "C" : "", TextFlag.Center );
}
private void DrawLayerLabel( int index, TileMap.TileLayer layer, bool active, bool visible )
{
Rect row = GetLayerRowRect( index );
Rect visibility = GetVisibilityButtonRect( index );
float labelLeft = Padding + DragHandleWidth + 4.0f;
float labelRight = visibility.Left - ButtonGap;
Paint.SetPen( Color.White.WithAlpha( visible ? active ? 1.0f : 0.85f : 0.35f ) );
string label = $"{index}: {layer.Name}";
Paint.DrawText( new Rect( labelLeft, row.Top, Math.Max( 0.0f, labelRight - labelLeft ), RowHeight ), label, TextFlag.LeftCenter );
}
private int GetLayerIndexAtPosition( Vector2 position )
{
if ( position.y < HeaderHeight )
return -1;
int index = (int)MathF.Floor( (position.y - HeaderHeight) / RowHeight );
if ( _tilemap == null || index < 0 || index >= _tilemap.LayerCount )
return -1;
return index;
}
private Rect GetLayerRowRect( int index )
{
return new Rect( 0.0f, HeaderHeight + index * RowHeight, LocalRect.Width, RowHeight );
}
private Rect GetVisibilityButtonRect( int index )
{
Rect row = GetLayerRowRect( index );
return new Rect(
LocalRect.Width - Padding - ButtonSize,
row.Top + (RowHeight - ButtonSize) * 0.5f,
ButtonSize,
ButtonSize
);
}
private Rect GetCollisionButtonRect( int index )
{
Rect visibility = GetVisibilityButtonRect( index );
Rect row = GetLayerRowRect( index );
return new Rect(
visibility.Left - ButtonGap - ButtonSize,
row.Top + (RowHeight - ButtonSize) * 0.5f,
ButtonSize,
ButtonSize
);
}
private static bool IsLeftButtonDown( MouseEvent e )
{
return e.LeftMouseButton || (e.ButtonState & MouseButtons.Left) == MouseButtons.Left;
}
}