Demos/BeatPad/KnobBlob.cs
using System;
using Goo;
using Sandbox.UI;
using Sandbox.BeatPad;
using static Sandbox.BeatPadTokens;
namespace Sandbox;
// Pure presenter for one knob: circle body + ink drop-shadow + a rotated indicator
// stem + an uppercase label + a value readout that fades (Flash 1..0 drives readout alpha).
// Angle comes straight from the value (no spring; pointer-immediate per spec).
internal static class KnobBlob
{
public readonly record struct Props(
int Index,
string Label,
float Value, // 0..1 (for OUT this is the meter level)
float Flash, // 1 just after change -> 0
bool ReadOnly,
Action<MousePanelEvent> OnDown,
Action<MousePanelEvent> OnUp,
Action<MousePanelEvent> OnMove);
public static Container Build(Props p)
{
float angle = KnobMath.ValueToAngle(p.Value);
return new Container
{
Key = $"knob-{p.Index}",
Width = KnobSize,
Height = KnobSize + 34f, // room for label/readout below the circle (label gap + larger type)
Position = PositionMode.Relative,
FlexDirection = FlexDirection.Column,
AlignItems = Align.Center,
Children =
{
// circle cluster (fixed-size box holding shadow + body + stem)
new Container
{
Key = "body",
Width = KnobSize, Height = KnobSize,
Position = PositionMode.Relative,
OnMouseDown = p.ReadOnly ? null : p.OnDown,
OnMouseUp = p.ReadOnly ? null : p.OnUp,
OnMouseMove = p.ReadOnly ? null : p.OnMove,
Children =
{
new Container
{
Key = "shadow",
Position = PositionMode.Absolute,
Left = KnobShadow, Top = KnobShadow,
Width = Length.Percent(100), Height = Length.Percent(100),
BackgroundColor = Ink,
BorderRadius = Length.Percent(50),
PointerEvents = PointerEvents.None,
},
new Container
{
Key = "disc",
Position = PositionMode.Absolute,
Left = 0, Top = 0,
Width = Length.Percent(100), Height = Length.Percent(100),
BackgroundColor = Paper,
BorderColor = Ink,
BorderWidth = OutlineWidth,
BorderRadius = Length.Percent(50),
PointerEvents = PointerEvents.None,
},
// rotated stem wrapper (full-size, rotates about center)
new Container
{
Key = "stem-rot",
Position = PositionMode.Absolute,
Left = 0, Top = 0,
Width = Length.Percent(100), Height = Length.Percent(100),
Transform = Goo.PanelTransform.Rotate(angle),
PointerEvents = PointerEvents.None,
Children =
{
new Container
{
Key = "stem",
Position = PositionMode.Absolute,
Left = Length.Percent(50),
Top = 4f,
MarginLeft = -StemWidth * 0.5f,
Width = StemWidth,
Height = StemHeight,
BackgroundColor = Ink,
BorderRadius = 1f,
PointerEvents = PointerEvents.None,
},
},
},
},
},
new Text(p.Label)
{
Key = "label",
MarginTop = 6f, // sit the label slightly off the knob body
FontSize = 13f,
FontColor = Ink,
FontFamily = FontLabel,
TextTransform = TextTransform.Uppercase,
LetterSpacing = 1.5f,
TextAlign = TextAlign.Center,
},
new Text($"{(int)MathF.Round(p.Value * 100f)}")
{
Key = "readout",
FontSize = 9f,
FontColor = Hot.WithAlpha(p.Flash),
FontFamily = FontMono,
TextAlign = TextAlign.Center,
},
},
};
}
}