FpsUI/Widgets/HealthWidget.cs

UI component for the FPS health bar. HealthView is a stateless presenter that builds the track with fill, ghost chip, numeric display and flash overlays. HealthWidget is a ScreenPanel widget that owns a HealthModel and theme, exposes Damage/Heal methods, ticks the model each frame, and builds the UI root.

File Access
using System;
using Goo;
using Sandbox;
using Sandbox.UI;

namespace Goo.FpsUI;

// Stateless presenter: the health bar with ghost chip, flashes, and low-HP pulse.
static class HealthView
{
    public const float TrackW = 320f, TrackH = 24f;

    public static Container Build( HealthModel m, FpsTheme t )
    {
        float frac = m.ShownFraction, ghost = m.GhostFraction;
        Color grade = Color.Lerp( t.Warn, t.Good, Math.Clamp( frac, 0f, 1f ) );
        float pulse = m.Critical ? 0.10f + 0.14f * (MathF.Sin( Time.Now * 7f ) * 0.5f + 0.5f) : 0f;

        var track = new Container
        {
            Key = "hpTrack", Position = PositionMode.Relative,
            Width = TrackW, Height = TrackH, BackgroundColor = t.TrackBg,
            BorderRadius = t.Radius, Overflow = OverflowMode.Hidden,
            Children =
            {
                Parts.FillRect( "ghost", ghost, Color.White.WithAlpha( 0.5f ), t.Radius ),
                Parts.FillRect( "fill", frac, grade, t.Radius ),
            },
        };
        if ( pulse > 0f )               track.Children.Add( Parts.Overlay( "warn", t.Warn, pulse, t.Radius ) );
        if ( m.HealFlash > 0.001f )     track.Children.Add( Parts.Overlay( "heal", t.Good, m.HealFlash * 0.7f, t.Radius ) );
        if ( m.HitFlash  > 0.001f )     track.Children.Add( Parts.Overlay( "hit", Color.White, m.HitFlash * 0.9f, t.Radius ) );
        track.Children.Add( new Container
        {
            Key = "num", Position = PositionMode.Absolute, Top = 0, Left = 0,
            Width = Length.Percent( 100 ), Height = Length.Percent( 100 ),
            JustifyContent = Justify.Center, AlignItems = Align.Center,
            Children = { new Text( $"{m.Number}/{(int)m.MaxHealth}" )
                { FontFamily = t.FontFamily, FontSize = 14f, FontWeight = t.WeightBold, FontColor = Color.White } },
        } );
        return track;
    }
}

// Standalone health bar. Drop on a ScreenPanel GameObject, call Damage/Heal from game code.
public sealed partial class HealthWidget : GooPanel<Container>
{
    [Property, Range( 1f, 1000f )] public float MaxHealth { get; set; } = 100f; // full-bar value

    readonly HealthModel _m = new();
    readonly FpsTheme _t = new();
    bool _booted;

    void Boot() { _m.MaxHealth = MaxHealth; _m.Reset(); _booted = true; }

    public void Damage( float amount ) => _m.Damage( amount ); // apply damage from your game
    public void Heal( float amount ) => _m.Heal( amount );     // apply healing from your game

    // Demo-only seam: implemented in FpsDemo.cs, compiles out when that file is deleted.
    partial void StepDemo( float dt, ref bool active );

    protected override bool Tick( float dt )
    {
        if ( !_booted ) Boot();
        bool demo = false;
        StepDemo( dt, ref demo );
        bool moving = _m.Tick( dt );
        return demo || moving || _m.Critical;
    }

    protected override Container Build()
    {
        if ( !_booted ) Boot();
        var root = Parts.Root( "fpsHealth" );
        root.Children.Add( Parts.Anchor( "a", Parts.Corner.BottomLeft, _t.Margin, Parts.Panel( "bg", _t, HealthView.Build( _m, _t ) ) ) );
        return root;
    }
}