Skip to content

Instantly share code, notes, and snippets.

@kg
Created March 10, 2023 01:44
Show Gist options
  • Save kg/6a6ba42d5019b546858a2b18751de019 to your computer and use it in GitHub Desktop.
Save kg/6a6ba42d5019b546858a2b18751de019 to your computer and use it in GitHub Desktop.
internal class VectorEditModal : ModalDialog<VectorEditArguments, Vector4> {
public readonly Game Game;
private readonly Func<IGlyphSource> GlyphSourceProvider,
GlyphSourceProviderSmall;
public Canvas Orb;
public Vector4 Value;
private Vector4? ValueRange;
private float UserScale = 1.0f, ValueIncrement = 0.1f;
public VectorEditModal (Game game, VectorEditArguments parameters) : base(parameters) {
Game = game;
GlyphSourceProvider = Game.GetFontProvider(0.8f, Game.FontStyle.Regular);
GlyphSourceProviderSmall = Game.GetFontProvider(0.7f, Game.FontStyle.Regular);
DynamicContents = VectorEditor_Content;
Title = "Edit Vector";
CancelResult = parameters.DefaultValue;
SetValue(parameters.DefaultValue, false);
if (Parameters.MinValues.HasValue && Parameters.MaxValues.HasValue) {
var valueRange = Parameters.MaxValues.Value - Parameters.MinValues.Value;
ValueRange = valueRange;
var avg = (Math.Abs(valueRange.X) + Math.Abs(valueRange.Y) +
Math.Abs(valueRange.Z) + Math.Abs(valueRange.W)) / 4f;
if (avg >= 5000)
ValueIncrement = 10.0f;
else if (avg >= 1000)
ValueIncrement = 5.0f;
else if (avg >= 100)
ValueIncrement = 1.0f;
else if (avg >= 10)
ValueIncrement = 0.1f;
else
ValueIncrement = 0.05f;
}
BackgroundFadeLevel = 0.2f;
}
private void VectorEditor_Content (ref ContainerBuilder builder) {
const float maxWidth = 150f,
orbSize = 128f;
builder.DefaultGlyphSourceProvider = GlyphSourceProvider;
var cflags = ControlFlags.Container_Prevent_Crush | ControlFlags.Container_Row | ControlFlags.Container_Break_Allow | ControlFlags.Container_Align_Start;
builder.New<Canvas>()
.SetSize(orbSize, orbSize)
.StoreInstance(ref Orb);
if (!Orb.HasPaintHandler) {
Orb.OnPaint += Orb_Paint;
Orb.AddEventListener<MouseEventArgs>(null, Orb_OnMouseEvent);
}
ContainerBuilder columns = builder.NewGroup(containerFlags: cflags),
leftCol = columns.NewGroup(layoutFlags: ControlFlags.Layout_Fill, containerFlags: cflags),
rightCol = columns.NewGroup(layoutFlags: ControlFlags.Layout_Fill, containerFlags: cflags);
rightCol.New<ParameterEditor<float>>(tooltip: "Value for the operator buttons to the right")
.SetDescription("rhs")
.SetRange<float>(-4f, 16f, false)
.SetMaximumSize(width: maxWidth)
.SetIncrement(0.1f)
.SetForceBreak(true)
.Value(ref UserScale, out bool userScaleChanged);
if (rightCol.Text<Button>("+", tooltip: "Add")
.GetEvent(UIEvents.Click))
ScaleValue(UserScale);
if (rightCol.Text<Button>("×", tooltip: "Multiply")
.GetEvent(UIEvents.Click))
ScaleValue(UserScale);
if (rightCol.Text<Button>("÷", tooltip: "Divide")
.GetEvent(UIEvents.Click))
ScaleValue(1.0f / UserScale);
if (rightCol.Text<Button>("0", tooltip: "Set value to 0")
.SetForceBreak(true)
.GetEvent(UIEvents.Click))
SetValue(Vector4.Zero, true);
if (rightCol.Text<Button>("1", tooltip: "Set value to 1")
.GetEvent(UIEvents.Click))
SetValue(Vector4.One, true);
if (rightCol.Text<Button>("Normalize", tooltip: "Normalize the vector")
.GetEvent(UIEvents.Click))
NormalizeValue();
bool changedX, changedY, changedZ = false, changedW = false;
leftCol.New<ParameterEditor<float>>()
.SetForceBreak(true)
.SetDescription("X")
.SetMaximumSize(width: maxWidth)
.SetRange(Parameters.MinValues?.X, Parameters.MaxValues?.X, false)
.SetIncrement(ValueIncrement)
.Value(ref Value.X, out changedX);
leftCol.New<ParameterEditor<float>>()
.SetForceBreak(true)
.SetDescription("Y")
.SetMaximumSize(width: maxWidth)
.SetRange(Parameters.MinValues?.Y, Parameters.MaxValues?.Y, false)
.SetIncrement(ValueIncrement)
.Value(ref Value.Y, out changedY);
if (Parameters.ElementCount > 2)
leftCol.New<ParameterEditor<float>>()
.SetForceBreak(true)
.SetDescription("Z")
.SetMaximumSize(width: maxWidth)
.SetRange(Parameters.MinValues?.Z, Parameters.MaxValues?.Z, false)
.SetIncrement(ValueIncrement)
.Value(ref Value.Z, out changedZ);
if (Parameters.ElementCount > 3)
leftCol.New<ParameterEditor<float>>()
.SetForceBreak(true)
.SetDescription("W")
.SetMaximumSize(width: maxWidth)
.SetRange(Parameters.MinValues?.W, Parameters.MaxValues?.W, false)
.SetIncrement(ValueIncrement)
.Value(ref Value.W, out changedW);
leftCol.Spacer(true);
// FIXME: On manual change, expand min/max values so the orb is more useful
if (changedX || changedY || changedZ || changedW)
SetValue(Value, true);
var buttons = builder.NewGroup(containerFlags: ControlFlags.Container_Row | ControlFlags.Container_Align_Middle);
buttons.Properties.SetForceBreak(true);
AcceptControl = buttons.Text<Button>("OK");
CancelControl = buttons.Text<Button>("Cancel");
}
private void Orb_OnMouseEvent (IEventInfo<MouseEventArgs> e, MouseEventArgs arguments) {
if ((arguments.Buttons & MouseButtons.Left) == default)
return;
Vector4 minValue = Parameters.MinValues ?? new Vector4(-1),
maxValue = Parameters.MaxValues ?? new Vector4(1),
newValue = new Vector4(
Arithmetic.Lerp(minValue.X, maxValue.X, arguments.LocalPosition.X / arguments.ContentBox.Width),
Arithmetic.Lerp(minValue.Y, maxValue.Y, arguments.LocalPosition.Y / arguments.ContentBox.Height),
0f, 0f
);
SetValue(newValue, true);
e.Consume();
}
private void Orb_Paint (ref UIOperationContext context, ref ImperativeRenderer renderer, DecorationSettings settings) {
settings.ContentBox.SnapAndInset(out var a, out var b);
renderer.RasterizeRectangle(a, b, 3f, Color.Black * 0.2f);
renderer.Layer += 1;
var r = (Math.Min(b.X - a.X, b.Y - a.Y) / 2f) - 1.5f;
var center = (a + b) / 2f;
renderer.RasterizeEllipse(center, new Vector2(1.5f), Color.White * 0.5f);
renderer.RasterizeEllipse(center, new Vector2(r), 1.5f, Color.Transparent, Color.Transparent, Color.White * 0.5f);
// TODO: ValueRange
var pos = new Vector3(Value.X, Value.Y, Value.Z);
var norm = pos;
norm.Normalize();
Vector4 minValue = Parameters.MinValues ?? new Vector4(-1),
maxValue = Parameters.MaxValues ?? new Vector4(1),
range = maxValue - minValue;
Vector2 norm2 = new Vector2(
Arithmetic.Lerp(a.X + 2f, b.X - 2f, (norm.X + 1) / 2f),
Arithmetic.Lerp(a.Y + 2f, b.Y - 2f, (norm.Y + 1) / 2f)
),
pos2 = new Vector2(
Arithmetic.Lerp(a.X, b.X, Arithmetic.Saturate((pos.X - minValue.X) / range.X)),
Arithmetic.Lerp(a.Y, b.Y, Arithmetic.Saturate((pos.Y - minValue.Y) / range.Y))
);
var radius2 = Arithmetic.Clamp(1.0f + norm.Z, 0.2f, 2.0f);
renderer.RasterizeLineSegment(center, norm2, 1.0f, radius2, 0f, Color.White * 0.5f, Color.White * 0.7f, Color.Transparent);
renderer.Layer += 1;
renderer.RasterizeEllipse(pos2, new Vector2(3.33f), 1.33f, Color.White, Color.White, Color.Black);
}
protected Vector2 V2 => new Vector2(Value.X, Value.Y);
protected Vector3 V3 => new Vector3(Value.X, Value.Y, Value.Z);
private void NormalizeValue () {
switch (Parameters.ElementCount) {
case 2:
var temp = V2;
temp.Normalize();
Value = new Vector4(temp, 0, 0);
break;
case 3:
var temp3 = V3;
temp3.Normalize();
Value = new Vector4(temp3, 0);
break;
default:
Value.Normalize();
break;
}
SetValue(Value, true);
}
private void ScaleValue (float scale) {
SetValue(Value * scale, true);
}
public void SetValue (Vector4 value, bool fireEvent) {
if (Parameters.ElementCount < 4)
value.W = 0;
if (Parameters.ElementCount < 3)
value.Z = 0;
Value = value;
AcceptResult = value;
if (fireEvent && (Parameters.OnValueChanged != null))
Parameters.OnValueChanged(AcceptResult);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment