Demos/Compass/CompassView.cs
using System;
using Goo;
using Sandbox;
using Sandbox.UI;
namespace Sandbox.Compass;
// Scrolling compass strip. Reads look-yaw each Tick; emits the band content (host anchors it).
public sealed class CompassView
{
const float WindowDeg = 120f;
const float StepDeg = 15f;
const float BandWidth = 520f;
const float BandHeight = 40f;
const float TopPx = 24f;
const float YawEpsilon = 0.05f;
// Tunable: s&box yaw is CCW-positive; sign so turning right scrolls the strip left.
const float DirectionSign = -1f;
// Tunable: world yaw that should read as North.
const float NorthOffsetDeg = 0f;
float _heading = float.NaN;
bool _dirty = true;
void Invalidate() => _dirty = true;
public bool Tick( Scene? scene, float dt )
{
var cam = scene?.Camera;
if ( cam is not null )
{
float raw = cam.WorldRotation.Angles().yaw;
float yaw = CompassMath.Normalize360( raw * DirectionSign - NorthOffsetDeg );
if ( float.IsNaN( _heading ) ||
MathF.Abs( CompassMath.SignedDelta( _heading, yaw ) ) > YawEpsilon )
{
_heading = yaw;
Invalidate();
}
}
bool d = _dirty; _dirty = false; return d;
}
public Container Build()
{
var band = new Container
{
Position = PositionMode.Relative,
Top = TopPx,
Width = BandWidth,
Height = BandHeight,
BackgroundColor = Color.Black.WithAlpha( 0.35f ),
BorderRadius = 6f,
Overflow = OverflowMode.Visible,
PointerEvents = PointerEvents.None,
};
if ( float.IsNaN( _heading ) )
return band;
float halfWidth = BandWidth * 0.5f;
foreach ( var mark in CompassMath.VisibleMarks( _heading, WindowDeg, StepDeg ) )
{
float x = halfWidth + mark.XNorm * halfWidth;
float opacity = 1f - MathF.Abs( mark.XNorm );
band.Children.Add( MarkView.Build( mark, x, BandHeight, opacity ) );
}
// Fixed center caret marking current heading.
band.Children.Add( new Container
{
Key = "caret",
Position = PositionMode.Absolute,
Top = -6f,
Left = halfWidth - 6f,
Width = 12f,
Height = 12f,
JustifyContent = Justify.Center,
AlignItems = Align.Center,
FontColor = Color.White,
Children = { new Text( "▼" ) },
} );
return band;
}
}