using System;
using System.Linq;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Headless.XUnit;
using Avalonia.VisualTree;
using IFSEngine.Animation;
using IFSEngine.Model;
using Xunit;
namespace IFSRenderer.AvaloniaShell.Tests;
/// <summary>
/// The keyframe type dropdown, as a user reaches it.
/// </summary>
/// <remarks>
/// Half of what was reported - "I got no control over it" - was never a missing feature. The
/// interpolation controls existed, hidden until a keyframe is selected, and on Windows they are
/// behind a right-click on the dope diamond. So the test that matters is not that the model
/// supports a type, but that SELECTING A KEYFRAME PUTS THE CONTROL ON SCREEN.
/// </remarks>
public class KeyframeEditorUiTests
{
private static (AnimationPanel Panel, Channel Channel, CurveCanvas Curve) Panel()
{
var fractal = new IFS();
foreach ((double t, double v) in new[] { (3.0, 0.0), (5.0, 1.0), (7.0, 1.0), (9.0, 2.0) })
fractal.Dopesheet.AddOrUpdateChannel("Focus", "Camera.FocusDistance",
TimeOnly.FromTimeSpan(TimeSpan.FromSeconds(t)), v);
var panel = new AnimationPanel();
var window = new Window { Width = 1000, Height = 600, Content = panel };
window.Show();
panel.SetFractal(fractal);
window.Measure(new Size(1000, 600));
window.Arrange(new Rect(0, 0, 1000, 600));
CurveCanvas curve = panel.GetVisualDescendants().OfType<CurveCanvas>().Single();
return (panel, fractal.Dopesheet.Channels["Camera.FocusDistance"], curve);
}
private static ComboBox TypeCombo(AnimationPanel panel) =>
panel.GetVisualDescendants().OfType<ComboBox>()
.Single(c => c.ItemsSource is not null
&& c.ItemsSource.Cast<object>().Any(i => i.ToString() == "Bezier"));
[AvaloniaFact]
public void TheKeyframeEditorIsHiddenUntilAKeyframeIsSelected()
{
(AnimationPanel panel, Channel channel, CurveCanvas curve) = Panel();
ComboBox combo = TypeCombo(panel);
Assert.Null(curve.SelectedKeyframe);
Assert.False(combo.IsEffectivelyVisible);
curve.BeginDrag(channel.Keyframes[1], new Point(0, 0));
curve.EndDrag();
Relayout(panel);
Assert.True(combo.IsEffectivelyVisible,
"selecting a keyframe did not reveal the type dropdown");
}
/// <summary>Visibility changes only take effect once the tree has been measured again.</summary>
private static void Relayout(AnimationPanel panel)
{
Window window = panel.GetVisualRoot() as Window;
window!.Measure(new Size(1000, 600));
window.Arrange(new Rect(0, 0, 1000, 600));
}
[AvaloniaFact]
public void TheDropdownOffersBlendersPresetsAndSetsTheSelectedKeyframesType()
{
(AnimationPanel panel, Channel channel, CurveCanvas curve) = Panel();
Keyframe key = channel.Keyframes[1];
curve.BeginDrag(key, new Point(0, 0));
curve.EndDrag();
ComboBox combo = TypeCombo(panel);
string[] labels = combo.ItemsSource!.Cast<object>().Select(i => i.ToString()).ToArray();
// Blender's own menu, in its own order.
Assert.Equal(
[
"Constant", "Linear", "Bezier",
"Sinusoidal", "Quadratic", "Cubic", "Quartic", "Quintic", "Exponential", "Circular",
"Back", "Bounce", "Elastic",
"Legacy",
],
labels);
// A keyed value starts on Blender's default, which is what stops the hold wobbling.
Assert.Equal(KeyframeType.Bezier, key.Type);
combo.SelectedIndex = Array.IndexOf(labels, "Constant");
Assert.Equal(KeyframeType.Constant, key.Type);
// ...and the legacy mode went with it, so upstream still reads something sane.
Assert.Equal(IFSEngine.Animation.InterpolationMode.Constant, key.InterpolationMode);
}
/// <summary>
/// The dropdown reaches the evaluator, not just the model: picking a type changes what the
/// channel is worth between its keyframes.
/// </summary>
/// <remarks>
/// Measured on the 3s..5s RAMP rather than the 5s..7s hold, because a hold between two
/// equal keys is 1.0 under Linear and under Bezier alike - it would not tell the two apart,
/// which is the entire point of the fix.
/// </remarks>
[AvaloniaFact]
public void ChangingTheTypeChangesWhatTheChannelEvaluatesTo()
{
(AnimationPanel panel, Channel channel, CurveCanvas curve) = Panel();
Keyframe ramp = channel.Keyframes[0]; // t=3, value 0, running up to t=5, value 1
curve.BeginDrag(ramp, new Point(0, 0));
curve.EndDrag();
ComboBox combo = TypeCombo(panel);
string[] labels = combo.ItemsSource!.Cast<object>().Select(i => i.ToString()).ToArray();
combo.SelectedIndex = Array.IndexOf(labels, "Linear");
Assert.Equal(0.5, channel.EvaluateAt(4), 9);
combo.SelectedIndex = Array.IndexOf(labels, "Constant");
Assert.Equal(0.0, channel.EvaluateAt(4), 9);
// Legacy hands the segment back to InterpolationMode, whatever that now says.
combo.SelectedIndex = Array.IndexOf(labels, "Legacy");
Assert.Equal(KeyframeType.Legacy, ramp.Type);
Assert.Equal(0.0, channel.EvaluateAt(4), 9);
}
}