Editor/Canvas/CoordChip.cs
using System;
using Editor;
using Sandbox;
namespace Grains.RazorDesigner.Canvas;
public sealed class CoordChip : Widget
{
private const string LogPrefix = "[Grains.RazorDesigner]";
private const float Inset = 12f;
private const float ChipHeight = 24f;
private const float CornerRadius = 5f;
private const float PaddingX = 8f;
private const int FontSizePx = 11;
private static readonly Color BgColor = new( 0.078f, 0.086f, 0.110f, 0.92f ); // ~ rgba(20,22,28,0.92)
private static readonly Color BorderColor = new( 0.227f, 0.255f, 0.314f, 1f ); // ~ #3a4150
private static readonly Color FgColor = new( 0.812f, 0.835f, 0.886f, 1f ); // ~ #cfd5e2
// Widest expected coord text — used by PreferredSize lazy measurement.
private const string MeasureTemplate = "x: -9999 y: -9999";
private readonly DesignerCanvas _canvas;
private readonly CanvasViewportFrame _frame;
private string _text = "x: 0 y: 0";
private float _preferredWidth;
private int _lastGeometryHash = -1;
public CoordChip( DesignerCanvas canvas, CanvasViewportFrame frame ) : base( frame )
{
_canvas = canvas ?? throw new ArgumentNullException( nameof( canvas ) );
_frame = frame ?? throw new ArgumentNullException( nameof( frame ) );
TranslucentBackground = true;
NoSystemBackground = true;
WindowFlags = WindowFlags.FramelessWindowHint | WindowFlags.Tool;
TransparentForMouseEvents = true;
Visible = false; // hidden until first CanvasMoved.
_canvas.CanvasMoved += OnCanvasMoved;
_canvas.CanvasHoverEnded += OnHoverEnded;
Log.Info( $"{LogPrefix} CoordChip ctor (top-level frameless window)" );
}
public override void OnDestroyed()
{
_canvas.CanvasMoved -= OnCanvasMoved;
_canvas.CanvasHoverEnded -= OnHoverEnded;
base.OnDestroyed();
}
[EditorEvent.Frame]
private void TrackCanvas()
{
if ( !_canvas.IsValid() || !Visible ) return;
var pref = PreferredSize;
var canvasScreen = _canvas.ScreenPosition;
var canvasSize = _canvas.Size;
var hash = HashCode.Combine( canvasScreen, canvasSize, pref );
if ( hash == _lastGeometryHash ) return;
_lastGeometryHash = hash;
Size = pref;
Position = new Vector2(
canvasScreen.x + 12f,
canvasScreen.y + canvasSize.y - pref.y - 12f );
}
public Vector2 PreferredSize
{
get
{
EnsureWidthMeasured();
return new Vector2( _preferredWidth, ChipHeight );
}
}
private void EnsureWidthMeasured()
{
if ( _preferredWidth > 0f ) return;
Paint.SetDefaultFont( FontSizePx );
var measured = Paint.MeasureText( MeasureTemplate ).x;
_preferredWidth = measured + PaddingX * 2f;
}
private void OnCanvasMoved( Vector2 widgetPos, Sandbox.KeyboardModifiers _ )
{
if ( _canvas?.DesignerScene is not { } scene ) return;
var dpi = _canvas.DpiScale > 0.01f ? _canvas.DpiScale : 1f;
var geo = CanvasGeometry.From( scene, dpi );
var css = geo.WidgetToCss( widgetPos );
_text = $"x: {(int)MathF.Round( css.x )} y: {(int)MathF.Round( css.y )}";
if ( !Visible )
{
_lastGeometryHash = -1;
Visible = true;
}
Update();
}
private void OnHoverEnded()
{
if ( !Visible ) return;
if ( _canvas.IsValid() )
{
var cursor = (Vector2)Editor.Application.CursorPosition;
var canvasRect = new Rect( _canvas.ScreenPosition, _canvas.Size );
if ( canvasRect.IsInside( cursor ) ) return;
}
Visible = false;
Update();
}
protected override void OnPaint()
{
var rect = LocalRect;
Paint.SetBrush( BgColor );
Paint.SetPen( BorderColor );
Paint.DrawRect( rect, CornerRadius );
Paint.SetDefaultFont( FontSizePx );
Paint.SetPen( FgColor );
Paint.DrawText( rect, _text, TextFlag.Center );
}
}