Demos/KeystrokeVisualizerUI.cs
using System.Collections.Generic;
using Goo;
using Goo.Animation;
using Goo.Input;
using Sandbox.KeystrokeViz;
using Sandbox.UI;
namespace Sandbox;
public class KeystrokeVisualizerUI : GooPanel<Container>
{
[Property, Range( 1, 20 )] public int MaxEntries { get; set; } = 4;
[Property] public Layout.Anchor Anchor { get; set; } = Layout.Anchor.BottomLeft;
[Property, Range( 0f, 200f )] public float AnchorMargin { get; set; } = 32f;
[Property, Range( 0.1f, 10f )] public float HoldTime { get; set; } = 1.5f;
[Property, Range( 0f, 1f )] public float SlideInDuration { get; set; } = 0.1f;
[Property, Range( 0f, 2f )] public float FadeOutDuration { get; set; } = 0.20f;
[Property, Range( 0f, 1f )] public float SlideRiseFraction { get; set; } = 0.1f;
[Property, Range( 0f, 3f )] public float IdleGap { get; set; } = 1f;
[Property, Range( 16f, 96f )] public float ChipHeight { get; set; } = 64f;
[Property, Range( 0f, 32f )] public float ChipGap { get; set; } = 8f;
[Property, Range( 8f, 48f )] public float FontSize { get; set; } = 32f;
[Property, Range( 0f, 1f )] public float ChipOpacity { get; set; } = 0.8f;
[Property, Range( 1, 64 )] public int MaxTypingRunLength { get; set; } = 16;
[Property] public bool ShowHintWhenEmpty { get; set; } = true;
[Property] public bool CaptureModifierAlone { get; set; } = false;
[Property] public bool ShiftAsChord { get; set; } = true;
readonly List<Entry> _queue = new();
readonly KeyTracker _tracker = new() { ReemitHeldOnModifierRise = true };
float _now;
protected override void OnEnabled()
{
base.OnEnabled();
_queue.Clear();
_tracker.Reset();
_now = 0f;
Panel.Style.Width = Length.Percent( 100 );
Panel.Style.Height = Length.Percent( 100 );
}
protected override void OnUpdate()
{
_now += Time.Delta;
_tracker.Poll();
int countBefore = _queue.Count;
bool hadInput = _tracker.JustPressed.Count > 0;
if ( hadInput )
{
var cfg = new GroupingConfig( IdleGap, CaptureModifierAlone, ShiftAsChord, MaxTypingRunLength );
KeystrokeGrouper.Apply( _queue, _tracker.JustPressed, _tracker.Modifiers, _now, cfg );
}
KeystrokeGrouper.Sweep( _queue, _now, HoldTime, FadeOutDuration );
KeystrokeGrouper.EnforceCap( _queue, _now, MaxEntries, HoldTime );
// hadInput catches typing-run extensions that don't change count and aren't animating.
if ( hadInput || _queue.Count != countBefore || NeedsRebuild() )
Rebuild();
base.OnUpdate();
}
bool NeedsRebuild() => KeystrokeGrouper.HasAnimatingEntries( _queue, _now, SlideInDuration, HoldTime, FadeOutDuration );
protected override Container Build()
{
var r = Layout.ResolveAnchor( Anchor );
var root = Hud.Overlay() with
{
Padding = AnchorMargin,
JustifyContent = r.JustifyContent,
AlignItems = r.AlignItems,
};
if ( _queue.Count == 0 && ShowHintWhenEmpty )
{
root.Children.Add( HintBanner.Build( FontSize ) );
return root;
}
var column = new Container { FlexDirection = r.FlexDirection, Gap = ChipGap };
var anim = new ChipAnim( new AgePhase( SlideInDuration, HoldTime, FadeOutDuration ), ChipHeight, SlideRiseFraction, FontSize, ChipOpacity, r.StacksUp );
column.Children.AddRange( _queue, ( i, e ) =>
EntryChip.Build( i, e.Label, _now - e.SpawnTime, _now - e.LastInputTime, anim ) );
root.Children.Add( column );
return root;
}
}