histogram implemented
This commit is contained in:
@@ -6,12 +6,32 @@
|
||||
Title="Leerform Calibration"
|
||||
Width="1024" Height="800">
|
||||
|
||||
<Window.Styles>
|
||||
<Style Selector="Button">
|
||||
<Setter Property="Background" Value="White" />
|
||||
<Setter Property="Foreground" Value="Red" />
|
||||
<Setter Property="BorderBrush" Value="Red" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="FontWeight" Value="Bold" />
|
||||
</Style>
|
||||
<Style Selector="Button:pointerover /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="#FFEBEE" />
|
||||
<Setter Property="BorderBrush" Value="Red" />
|
||||
<Setter Property="TextBlock.Foreground" Value="Red" />
|
||||
</Style>
|
||||
<Style Selector="Button:pressed /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="#FFCDD2" />
|
||||
<Setter Property="BorderBrush" Value="Red" />
|
||||
<Setter Property="TextBlock.Foreground" Value="Red" />
|
||||
</Style>
|
||||
</Window.Styles>
|
||||
|
||||
<DockPanel>
|
||||
<!-- Toolbar -->
|
||||
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Spacing="8" Margin="8">
|
||||
<Button Content="Start Preview" Command="{Binding StartPreviewCommand}" IsEnabled="{Binding !IsPreviewRunning}" />
|
||||
<Button Content="Stop Preview" Command="{Binding StopPreviewCommand}" IsEnabled="{Binding IsPreviewRunning}" />
|
||||
<Button Content="Capture Image" Command="{Binding CaptureImageCommand}" />
|
||||
<Button Content="START PREVIEW" Command="{Binding StartPreviewCommand}" IsEnabled="{Binding !IsPreviewRunning}" />
|
||||
<Button Content="STOP PREVIEW" Command="{Binding StopPreviewCommand}" IsEnabled="{Binding IsPreviewRunning}" />
|
||||
<Button Content="CAPTURE IMAGE" Command="{Binding CaptureImageCommand}" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Status Bar -->
|
||||
@@ -29,17 +49,14 @@
|
||||
</Border>
|
||||
|
||||
<!-- Settings Panel -->
|
||||
<Border DockPanel.Dock="Right" Width="260" Padding="12" Background="White">
|
||||
<ScrollViewer>
|
||||
<StackPanel Spacing="8">
|
||||
<Border DockPanel.Dock="Right" Width="290" Padding="12" Background="White">
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Spacing="8" Margin="0,0,20,0">
|
||||
<TextBlock Text="Calibration Settings" FontWeight="Bold" FontSize="14" Foreground="Black" />
|
||||
|
||||
|
||||
<Button Content="Calibrate" Command="{Binding RunCalibrationCommand}" Margin="0,4"
|
||||
Background="Red" Foreground="White" FontWeight="Bold" />
|
||||
<Button Content="Clear Images" Command="{Binding ClearCalibrationImagesCommand}" Margin="0,4"
|
||||
Background="White" Foreground="Red" FontWeight="Bold"
|
||||
BorderBrush="Red" BorderThickness="1" />
|
||||
<Button Content="CALIBRATE" Command="{Binding RunCalibrationCommand}" Margin="0,4" />
|
||||
<Button Content="CLEAR IMAGES" Command="{Binding ClearCalibrationImagesCommand}" Margin="0,4" />
|
||||
|
||||
<Separator Margin="0,8" />
|
||||
|
||||
@@ -85,15 +102,12 @@
|
||||
|
||||
<Separator Margin="0,8" />
|
||||
|
||||
<Button Content="Set Reference" Command="{Binding SetReferenceHistogramCommand}" Margin="0,4"
|
||||
Background="#2E7D32" Foreground="White" FontWeight="Bold" HorizontalAlignment="Stretch" />
|
||||
<TextBlock Text="✓ Reference set" Foreground="LimeGreen" IsVisible="{Binding HasReferenceHistogram}" />
|
||||
<Button Content="SET REFERENCE (RESETS SPLINES)" Command="{Binding SetReferenceHistogramCommand}" Margin="0,4"
|
||||
HorizontalAlignment="Stretch" />
|
||||
<TextBlock Text="✓ Average spline set" Foreground="LimeGreen" IsVisible="{Binding HasAverageSpline}" />
|
||||
|
||||
<TextBlock Text="Tolerance" Foreground="Black" Margin="0,4,0,0" />
|
||||
<NumericUpDown Value="{Binding Tolerance}" Minimum="0" Maximum="1" Increment="0.05" FormatString="0.00" />
|
||||
|
||||
<Button Content="Test Pattern" Command="{Binding TestPatternCommand}" Margin="0,4"
|
||||
Background="#1565C0" Foreground="White" FontWeight="Bold" HorizontalAlignment="Stretch" />
|
||||
<Button Content="TEST PATTERN" Command="{Binding TestPatternCommand}" Margin="0,4"
|
||||
HorizontalAlignment="Stretch" />
|
||||
|
||||
<Separator Margin="0,8" />
|
||||
|
||||
@@ -105,8 +119,8 @@
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
<Button Content="Save Recipe" Command="{Binding SaveRecipeCommand}" Margin="0,4"
|
||||
Background="#6A1B9A" Foreground="White" FontWeight="Bold" HorizontalAlignment="Stretch" />
|
||||
<Button Content="SAVE RECIPE" Command="{Binding SaveRecipeCommand}" Margin="0,4"
|
||||
HorizontalAlignment="Stretch" />
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
|
||||
@@ -1,26 +1,43 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="using:LindtLeerformPlugin.ViewModels"
|
||||
xmlns:v="using:LindtLeerformPlugin.Views"
|
||||
x:Class="LindtLeerformPlugin.Views.CellHistogramWindow"
|
||||
x:DataType="vm:CellHistogramViewModel"
|
||||
Title="{Binding Title}"
|
||||
Width="720" Height="600"
|
||||
Width="820" Height="560"
|
||||
Background="#1A1A1A"
|
||||
WindowStartupLocation="CenterOwner">
|
||||
WindowStartupLocation="CenterOwner"
|
||||
ShowInTaskbar="False"
|
||||
CanResize="True">
|
||||
|
||||
<ScrollViewer>
|
||||
<StackPanel Spacing="12" Margin="16">
|
||||
<TextBlock Text="Reference (learned)" FontWeight="Bold" Foreground="White" FontSize="14" />
|
||||
<Border Background="#0A0A0A" Padding="4">
|
||||
<Image Source="{Binding ReferenceImage}" Stretch="Uniform" Height="220" />
|
||||
</Border>
|
||||
|
||||
<TextBlock Text="Selected cell" FontWeight="Bold" Foreground="White" FontSize="14" Margin="0,8,0,0" />
|
||||
<Border Background="#0A0A0A" Padding="4">
|
||||
<Image Source="{Binding CellImage}" Stretch="Uniform" Height="220" />
|
||||
</Border>
|
||||
|
||||
<TextBlock Text="{Binding DistanceText}" Foreground="LightGray" FontSize="13" Margin="0,8,0,0" />
|
||||
<DockPanel Margin="12">
|
||||
<StackPanel DockPanel.Dock="Top" Spacing="4" Margin="0,0,0,8">
|
||||
<TextBlock Text="Drag the gray dashed handles to edit the AVERAGE spline. Use 'Override Cell' to give this cell its own spline."
|
||||
Foreground="LightGray" FontSize="12" TextWrapping="Wrap" />
|
||||
<TextBlock Text="A cell is BAD when any histogram bar rises above its active spline."
|
||||
Foreground="#888888" FontSize="11" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" Spacing="8" Margin="0,12,0,0">
|
||||
<Button Content="Override Cell"
|
||||
Command="{Binding CreateOverrideCommand}"
|
||||
IsVisible="{Binding !HasOverride}"
|
||||
Background="#FF8C00" Foreground="Black" FontWeight="Bold" />
|
||||
<Button Content="Remove Override"
|
||||
Command="{Binding RemoveOverrideCommand}"
|
||||
IsVisible="{Binding HasOverride}"
|
||||
Background="#8B0000" Foreground="White" FontWeight="Bold" />
|
||||
<TextBlock Text="{Binding VerdictText}" Foreground="LightGray"
|
||||
VerticalAlignment="Center" FontSize="13" />
|
||||
</StackPanel>
|
||||
|
||||
<Border Background="#0A0A0A" Padding="2">
|
||||
<v:SplineHistogramEditor x:Name="Editor"
|
||||
Histogram="{Binding CellHistogram}"
|
||||
CellSpline="{Binding CellSpline, Mode=TwoWay}"
|
||||
AverageSpline="{Binding AverageSpline, Mode=TwoWay}"
|
||||
Bins="{Binding Bins}" />
|
||||
</Border>
|
||||
</DockPanel>
|
||||
</Window>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Avalonia.Controls;
|
||||
using LindtLeerformPlugin.ViewModels;
|
||||
|
||||
namespace LindtLeerformPlugin.Views;
|
||||
|
||||
@@ -7,5 +8,7 @@ public partial class CellHistogramWindow : Window
|
||||
public CellHistogramWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
Editor.SplineDragCompleted += (_, _) =>
|
||||
(DataContext as CellHistogramViewModel)?.RaiseDragCompleted();
|
||||
}
|
||||
}
|
||||
|
||||
305
Plugins/LindtLeerformPlugin/Views/SplineHistogramEditor.cs
Normal file
305
Plugins/LindtLeerformPlugin/Views/SplineHistogramEditor.cs
Normal file
@@ -0,0 +1,305 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Media;
|
||||
using LindtLeerformPlugin.Services;
|
||||
|
||||
namespace LindtLeerformPlugin.Views;
|
||||
|
||||
/// <summary>
|
||||
/// Custom Avalonia control that draws a histogram as colored bars and overlays an editable
|
||||
/// average spline (faint dashed gray) plus an optional editable cell spline (bright orange).
|
||||
/// Five control points per spline; X positions are fixed and only Y is dragged.
|
||||
/// </summary>
|
||||
public class SplineHistogramEditor : Control
|
||||
{
|
||||
public static readonly StyledProperty<float[]?> HistogramProperty =
|
||||
AvaloniaProperty.Register<SplineHistogramEditor, float[]?>(nameof(Histogram));
|
||||
|
||||
public static readonly StyledProperty<float[]?> CellSplineProperty =
|
||||
AvaloniaProperty.Register<SplineHistogramEditor, float[]?>(
|
||||
nameof(CellSpline), defaultBindingMode: Avalonia.Data.BindingMode.TwoWay);
|
||||
|
||||
public static readonly StyledProperty<float[]?> AverageSplineProperty =
|
||||
AvaloniaProperty.Register<SplineHistogramEditor, float[]?>(
|
||||
nameof(AverageSpline), defaultBindingMode: Avalonia.Data.BindingMode.TwoWay);
|
||||
|
||||
public static readonly StyledProperty<int[]?> BinsProperty =
|
||||
AvaloniaProperty.Register<SplineHistogramEditor, int[]?>(nameof(Bins));
|
||||
|
||||
public float[]? Histogram
|
||||
{
|
||||
get => GetValue(HistogramProperty);
|
||||
set => SetValue(HistogramProperty, value);
|
||||
}
|
||||
|
||||
public float[]? CellSpline
|
||||
{
|
||||
get => GetValue(CellSplineProperty);
|
||||
set => SetValue(CellSplineProperty, value);
|
||||
}
|
||||
|
||||
public float[]? AverageSpline
|
||||
{
|
||||
get => GetValue(AverageSplineProperty);
|
||||
set => SetValue(AverageSplineProperty, value);
|
||||
}
|
||||
|
||||
public int[]? Bins
|
||||
{
|
||||
get => GetValue(BinsProperty);
|
||||
set => SetValue(BinsProperty, value);
|
||||
}
|
||||
|
||||
private const double LeftPad = 12;
|
||||
private const double RightPad = 12;
|
||||
private const double TopPad = 12;
|
||||
private const double BottomPad = 18;
|
||||
private const double HitRadius = 12;
|
||||
private const double HandleRadius = 6;
|
||||
|
||||
private static readonly IBrush BackgroundBrush = new SolidColorBrush(Color.FromRgb(20, 20, 20));
|
||||
private static readonly IPen AxisPen = new Pen(new SolidColorBrush(Color.FromRgb(80, 80, 80)), 1);
|
||||
private static readonly IPen AverageSplinePen = new Pen(
|
||||
new SolidColorBrush(Color.FromRgb(170, 170, 170)), 2,
|
||||
dashStyle: new DashStyle(new double[] { 4, 4 }, 0));
|
||||
private static readonly IBrush AverageHandleFill = new SolidColorBrush(Color.FromRgb(220, 220, 220));
|
||||
private static readonly IPen AverageHandleStroke = new Pen(new SolidColorBrush(Color.FromRgb(80, 80, 80)), 1);
|
||||
private static readonly IPen CellSplinePen = new Pen(new SolidColorBrush(Color.FromRgb(255, 140, 0)), 2.5);
|
||||
private static readonly IBrush CellHandleFill = new SolidColorBrush(Color.FromRgb(255, 140, 0));
|
||||
private static readonly IPen CellHandleStroke = new Pen(Brushes.Black, 1);
|
||||
|
||||
static SplineHistogramEditor()
|
||||
{
|
||||
AffectsRender<SplineHistogramEditor>(
|
||||
HistogramProperty, CellSplineProperty, AverageSplineProperty, BinsProperty);
|
||||
}
|
||||
|
||||
public SplineHistogramEditor()
|
||||
{
|
||||
// Render() fills the bounds, and Control hit-tests its full Bounds rectangle by
|
||||
// default, so we just need to be focusable for pointer capture during drag.
|
||||
Focusable = true;
|
||||
}
|
||||
|
||||
// Cached handle pixel positions, refreshed at every Render(). Indexed [0..4].
|
||||
private readonly Point[] _avgHandlePixels = new Point[SplineCurve.KnotCount];
|
||||
private readonly Point[] _cellHandlePixels = new Point[SplineCurve.KnotCount];
|
||||
|
||||
private SplineKind _draggingKind = SplineKind.None;
|
||||
private int _draggingIndex = -1;
|
||||
|
||||
/// <summary>Raised on pointer release after a spline handle drag has completed.</summary>
|
||||
public event EventHandler? SplineDragCompleted;
|
||||
|
||||
private enum SplineKind { None, Average, Cell }
|
||||
|
||||
public override void Render(DrawingContext ctx)
|
||||
{
|
||||
var bounds = new Rect(Bounds.Size);
|
||||
ctx.FillRectangle(BackgroundBrush, bounds);
|
||||
|
||||
var plotRect = new Rect(
|
||||
LeftPad, TopPad,
|
||||
Math.Max(1, Bounds.Width - LeftPad - RightPad),
|
||||
Math.Max(1, Bounds.Height - TopPad - BottomPad));
|
||||
|
||||
DrawAxes(ctx, plotRect);
|
||||
|
||||
var hist = Histogram;
|
||||
var bins = Bins;
|
||||
var binCount = (hist?.Length) ?? (bins is { Length: 3 } ? bins[0] * bins[1] * bins[2] : 0);
|
||||
|
||||
// Fixed [0, 1] Y axis — no auto-zoom. Bins and splines are L1-normalised so 1.0
|
||||
// is the natural ceiling and the user always sees an absolute scale.
|
||||
const double displayMax = 1.0;
|
||||
|
||||
if (hist != null && hist.Length > 0 && bins is { Length: 3 })
|
||||
DrawBars(ctx, plotRect, hist, bins, displayMax);
|
||||
|
||||
if (binCount <= 0)
|
||||
binCount = 125; // safe default for spline rendering when histogram is missing
|
||||
|
||||
DrawSpline(ctx, plotRect, AverageSpline, AverageSplinePen, displayMax, binCount,
|
||||
_avgHandlePixels, AverageHandleFill, AverageHandleStroke, drawHandles: AverageSpline is { Length: SplineCurve.KnotCount });
|
||||
DrawSpline(ctx, plotRect, CellSpline, CellSplinePen, displayMax, binCount,
|
||||
_cellHandlePixels, CellHandleFill, CellHandleStroke, drawHandles: CellSpline is { Length: SplineCurve.KnotCount });
|
||||
}
|
||||
|
||||
private static void DrawAxes(DrawingContext ctx, Rect plot)
|
||||
{
|
||||
ctx.DrawLine(AxisPen, new Point(plot.Left, plot.Bottom), new Point(plot.Right, plot.Bottom));
|
||||
ctx.DrawLine(AxisPen, new Point(plot.Left, plot.Top), new Point(plot.Left, plot.Bottom));
|
||||
}
|
||||
|
||||
private static void DrawBars(DrawingContext ctx, Rect plot, float[] hist, int[] bins, double displayMax)
|
||||
{
|
||||
var totalBins = bins[0] * bins[1] * bins[2];
|
||||
if (totalBins <= 0) return;
|
||||
|
||||
var barWidth = plot.Width / totalBins;
|
||||
for (var i = 0; i < totalBins && i < hist.Length; i++)
|
||||
{
|
||||
var binB = i / (bins[1] * bins[2]);
|
||||
var binG = (i / bins[2]) % bins[1];
|
||||
var binR = i % bins[2];
|
||||
|
||||
var b = (byte)Math.Min(255, (int)((binB + 0.5) * 256 / bins[0]));
|
||||
var g = (byte)Math.Min(255, (int)((binG + 0.5) * 256 / bins[1]));
|
||||
var r = (byte)Math.Min(255, (int)((binR + 0.5) * 256 / bins[2]));
|
||||
var brush = new SolidColorBrush(Color.FromRgb(r, g, b));
|
||||
|
||||
var v = hist[i];
|
||||
if (v < 0) v = 0;
|
||||
var h = v / displayMax * plot.Height;
|
||||
if (h < 0) h = 0;
|
||||
if (h > plot.Height) h = plot.Height;
|
||||
|
||||
var x = plot.Left + i * barWidth;
|
||||
var y = plot.Bottom - h;
|
||||
ctx.FillRectangle(brush, new Rect(x, y, Math.Max(1, barWidth), h));
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawSpline(DrawingContext ctx, Rect plot, float[]? knots, IPen curvePen,
|
||||
double displayMax, int binCount, Point[] handleCache, IBrush handleFill, IPen handleStroke,
|
||||
bool drawHandles)
|
||||
{
|
||||
if (knots == null || knots.Length < 2 || displayMax <= 0)
|
||||
{
|
||||
for (var k = 0; k < handleCache.Length; k++) handleCache[k] = new Point(double.NaN, double.NaN);
|
||||
return;
|
||||
}
|
||||
|
||||
// Sample the spline along the plot width and stroke a polyline.
|
||||
var steps = Math.Max(64, (int)plot.Width);
|
||||
var geometry = new StreamGeometry();
|
||||
using (var sgc = geometry.Open())
|
||||
{
|
||||
for (var s = 0; s <= steps; s++)
|
||||
{
|
||||
var t = (double)s / steps;
|
||||
var y = SplineCurve.Evaluate(knots, t);
|
||||
var px = plot.Left + t * plot.Width;
|
||||
var py = plot.Bottom - Math.Clamp(y / displayMax, 0, 1) * plot.Height;
|
||||
var p = new Point(px, py);
|
||||
if (s == 0) sgc.BeginFigure(p, false);
|
||||
else sgc.LineTo(p);
|
||||
}
|
||||
sgc.EndFigure(false);
|
||||
}
|
||||
ctx.DrawGeometry(null, curvePen, geometry);
|
||||
|
||||
// Cache and draw handles at the knot positions.
|
||||
if (knots.Length <= handleCache.Length)
|
||||
{
|
||||
for (var k = 0; k < knots.Length; k++)
|
||||
{
|
||||
var t = knots.Length == 1 ? 0.0 : (double)k / (knots.Length - 1);
|
||||
var px = plot.Left + t * plot.Width;
|
||||
var py = plot.Bottom - Math.Clamp(knots[k] / displayMax, 0, 1) * plot.Height;
|
||||
handleCache[k] = new Point(px, py);
|
||||
|
||||
if (drawHandles)
|
||||
{
|
||||
ctx.DrawEllipse(handleFill, handleStroke, handleCache[k], HandleRadius, HandleRadius);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnPointerPressed(PointerPressedEventArgs e)
|
||||
{
|
||||
base.OnPointerPressed(e);
|
||||
var props = e.GetCurrentPoint(this).Properties;
|
||||
if (!props.IsLeftButtonPressed) return;
|
||||
|
||||
var pos = e.GetPosition(this);
|
||||
|
||||
// Cell handles take priority over average handles when both overlap.
|
||||
var hit = HitTest(pos, _cellHandlePixels, requireCellSpline: true);
|
||||
if (hit >= 0)
|
||||
{
|
||||
_draggingKind = SplineKind.Cell;
|
||||
_draggingIndex = hit;
|
||||
}
|
||||
else
|
||||
{
|
||||
hit = HitTest(pos, _avgHandlePixels, requireCellSpline: false);
|
||||
if (hit < 0) return;
|
||||
_draggingKind = SplineKind.Average;
|
||||
_draggingIndex = hit;
|
||||
}
|
||||
|
||||
e.Pointer.Capture(this);
|
||||
ApplyDrag(pos);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
protected override void OnPointerMoved(PointerEventArgs e)
|
||||
{
|
||||
base.OnPointerMoved(e);
|
||||
if (_draggingKind == SplineKind.None) return;
|
||||
var pos = e.GetPosition(this);
|
||||
ApplyDrag(pos);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
protected override void OnPointerReleased(PointerReleasedEventArgs e)
|
||||
{
|
||||
base.OnPointerReleased(e);
|
||||
if (_draggingKind == SplineKind.None) return;
|
||||
_draggingKind = SplineKind.None;
|
||||
_draggingIndex = -1;
|
||||
if (e.Pointer.Captured == this)
|
||||
e.Pointer.Capture(null);
|
||||
e.Handled = true;
|
||||
SplineDragCompleted?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private int HitTest(Point pos, Point[] handles, bool requireCellSpline)
|
||||
{
|
||||
if (requireCellSpline && CellSpline is not { Length: SplineCurve.KnotCount }) return -1;
|
||||
|
||||
for (var i = 0; i < handles.Length; i++)
|
||||
{
|
||||
var h = handles[i];
|
||||
if (double.IsNaN(h.X)) continue;
|
||||
var dx = pos.X - h.X;
|
||||
var dy = pos.Y - h.Y;
|
||||
if (dx * dx + dy * dy <= HitRadius * HitRadius)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private void ApplyDrag(Point pos)
|
||||
{
|
||||
if (_draggingIndex < 0) return;
|
||||
|
||||
var plotRect = new Rect(
|
||||
LeftPad, TopPad,
|
||||
Math.Max(1, Bounds.Width - LeftPad - RightPad),
|
||||
Math.Max(1, Bounds.Height - TopPad - BottomPad));
|
||||
|
||||
var rel = (plotRect.Bottom - pos.Y) / plotRect.Height;
|
||||
var newY = (float)Math.Clamp(rel, 0.0, 1.0);
|
||||
|
||||
if (_draggingKind == SplineKind.Average)
|
||||
{
|
||||
var current = AverageSpline;
|
||||
if (current is not { Length: SplineCurve.KnotCount }) return;
|
||||
var copy = (float[])current.Clone();
|
||||
copy[_draggingIndex] = newY;
|
||||
SetCurrentValue(AverageSplineProperty, copy);
|
||||
}
|
||||
else if (_draggingKind == SplineKind.Cell)
|
||||
{
|
||||
var current = CellSpline;
|
||||
if (current is not { Length: SplineCurve.KnotCount }) return;
|
||||
var copy = (float[])current.Clone();
|
||||
copy[_draggingIndex] = newY;
|
||||
SetCurrentValue(CellSplineProperty, copy);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user