Skip to content

Commit

Permalink
Made Arrow immutable (#1170)
Browse files Browse the repository at this point in the history
  • Loading branch information
Lehonti authored Dec 10, 2024
1 parent 2fe2802 commit c6f56f1
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 83 deletions.
68 changes: 35 additions & 33 deletions Pinta.Tools/Editable/EditEngines/ArrowedEditEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ public abstract class ArrowedEditEngine : BaseEditEngine
private Gtk.SpinButton? arrow_length_offset;
private Gtk.Label? arrow_length_offset_label;

private readonly Arrow previous_settings_1 = new ();
private readonly Arrow previous_settings_2 = new ();
private Arrow previous_settings_1 = new ();
private Arrow previous_settings_2 = new ();

// NRT - These are all set by HandleBuildToolBar
private ISettingsService settings = null!;
Expand Down Expand Up @@ -117,9 +117,9 @@ private void ArrowEnabledToggled (bool arrow1)
return;

if (arrow1)
activeEngine.Arrow1.Show = ArrowOneEnabled;
activeEngine.Arrow1 = activeEngine.Arrow1 with { Show = ArrowOneEnabled };
else
activeEngine.Arrow2.Show = ArrowTwoEnabled;
activeEngine.Arrow2 = activeEngine.Arrow2 with { Show = ArrowTwoEnabled };

DrawActiveShape (false, false, true, false, false);

Expand Down Expand Up @@ -158,16 +158,21 @@ protected void setNewArrowSettings (LineCurveSeriesEngine newEngine)
if (show_arrow_one_box == null)
return;

newEngine.Arrow1.Show = ArrowOneEnabled;
newEngine.Arrow2.Show = ArrowTwoEnabled;

newEngine.Arrow1.ArrowSize = ArrowSize.Value;
newEngine.Arrow1.AngleOffset = ArrowAngleOffset.Value;
newEngine.Arrow1.LengthOffset = ArrowLengthOffset.Value;

newEngine.Arrow2.ArrowSize = newEngine.Arrow1.ArrowSize;
newEngine.Arrow2.AngleOffset = newEngine.Arrow1.AngleOffset;
newEngine.Arrow2.LengthOffset = newEngine.Arrow1.LengthOffset;
double newArrowSize = ArrowSize.Value;
double newAngleOffset = ArrowAngleOffset.Value;
double newLengthOffset = ArrowLengthOffset.Value;

newEngine.Arrow1 = new (
Show: ArrowOneEnabled,
ArrowSize: newArrowSize,
AngleOffset: newAngleOffset,
LengthOffset: newLengthOffset);

newEngine.Arrow2 = new (
Show: ArrowTwoEnabled,
ArrowSize: newArrowSize,
AngleOffset: newAngleOffset,
LengthOffset: newLengthOffset);
}


Expand Down Expand Up @@ -211,12 +216,14 @@ protected override void RecallPreviousSettings ()
protected override void StorePreviousSettings ()
{
if (show_arrow_one_box != null) {
previous_settings_1.Show = ArrowOneEnabled;
previous_settings_2.Show = ArrowTwoEnabled;

previous_settings_1.ArrowSize = ArrowSize.Value;
previous_settings_1.AngleOffset = ArrowAngleOffset.Value;
previous_settings_1.LengthOffset = ArrowLengthOffset.Value;
previous_settings_1 = new Arrow (
Show: ArrowOneEnabled,
ArrowSize: ArrowSize.Value,
AngleOffset: ArrowAngleOffset.Value,
LengthOffset: ArrowLengthOffset.Value);

previous_settings_2 = previous_settings_2 with { Show = ArrowTwoEnabled };

//Other Arrow2 settings are unnecessary since they are the same as Arrow1's.
}
Expand Down Expand Up @@ -293,11 +300,10 @@ private Gtk.SpinButton CreateArrowSize ()
Gtk.SpinButton result = GtkExtensions.CreateToolBarSpinButton (1, 100, 1, settings.GetSetting (ARROW_SIZE_SETTING (tool_prefix), 10));
result.OnValueChanged += (o, e) => {
var activeEngine = (LineCurveSeriesEngine?) ActiveShapeEngine;
if (activeEngine == null)
return;
if (activeEngine == null) return;
var size = result.Value;
activeEngine.Arrow1.ArrowSize = size;
activeEngine.Arrow2.ArrowSize = size;
activeEngine.Arrow1 = activeEngine.Arrow1 with { ArrowSize = size };
activeEngine.Arrow2 = activeEngine.Arrow2 with { ArrowSize = size };
DrawActiveShape (false, false, true, false, false);
StorePreviousSettings ();
};
Expand All @@ -315,11 +321,10 @@ private Gtk.SpinButton CreateArrowAngleOffset ()
Gtk.SpinButton result = GtkExtensions.CreateToolBarSpinButton (-89, 89, 1, settings.GetSetting (ARROW_ANGLE_SETTING (tool_prefix), 15));
result.OnValueChanged += (o, e) => {
var activeEngine = (LineCurveSeriesEngine?) ActiveShapeEngine;
if (activeEngine == null)
return;
if (activeEngine == null) return;
var angle = result.Value;
activeEngine.Arrow1.AngleOffset = angle;
activeEngine.Arrow2.AngleOffset = angle;
activeEngine.Arrow1 = activeEngine.Arrow1 with { AngleOffset = angle };
activeEngine.Arrow2 = activeEngine.Arrow2 with { AngleOffset = angle };
DrawActiveShape (false, false, true, false, false);
StorePreviousSettings ();
};
Expand All @@ -337,13 +342,10 @@ private Gtk.SpinButton CreateArrowLengthOffset ()
Gtk.SpinButton result = GtkExtensions.CreateToolBarSpinButton (-100, 100, 1, settings.GetSetting (ARROW_LENGTH_SETTING (tool_prefix), 10));
result.OnValueChanged += (o, e) => {
var activeEngine = (LineCurveSeriesEngine?) ActiveShapeEngine;

if (activeEngine == null)
return;

if (activeEngine == null) return;
var length = result.Value;
activeEngine.Arrow1.LengthOffset = length;
activeEngine.Arrow2.LengthOffset = length;
activeEngine.Arrow1 = activeEngine.Arrow1 with { LengthOffset = length };
activeEngine.Arrow2 = activeEngine.Arrow2 with { LengthOffset = length };
DrawActiveShape (false, false, true, false, false);
StorePreviousSettings ();
};
Expand Down
50 changes: 24 additions & 26 deletions Pinta.Tools/Editable/Shapes/Arrow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,35 +30,34 @@

namespace Pinta.Tools;

public sealed class Arrow
public readonly record struct Arrow (
bool Show,
double ArrowSize,
double AngleOffset,
double LengthOffset)
{
public bool Show { get; internal set; } = false;
public double ArrowSize { get; internal set; } = 10d;
public double AngleOffset { get; internal set; } = 15d;
public double LengthOffset { get; internal set; } = 10d;
public Arrow ()
: this (false, 10d, 15d, 10d)
{ }
}

public static class ArrowExtensions
{
private const double RadiansToDegrees = Math.PI / 180d;
private const double InvRadiansToDegrees = 180d / Math.PI;

/// <summary>
/// Returns a clone of the Arrow.
/// </summary>
/// <returns>A clone of the Arrow.</returns>
public Arrow Clone ()
=> new () {
Show = Show,
ArrowSize = ArrowSize,
AngleOffset = AngleOffset,
LengthOffset = LengthOffset,
};

/// <summary>
/// Draws the arrow.
/// </summary>
/// <param name="g">The drawing context.</param>
/// <param name="endPoint">The end point of a shape.</param>
/// <param name="almostEndPoint">The point right before the end point.</param>
public RectangleD Draw (Context g, Color outlineColor, PointD endPoint, PointD almostEndPoint)
public static RectangleD Draw (
this in Arrow arrow,
Context g,
Color outlineColor,
PointD endPoint,
PointD almostEndPoint)
{
//First, calculate the ending angle.
double endingAngle = Math.Atan (Math.Abs (endPoint.Y - almostEndPoint.Y) / Math.Abs (endPoint.X - almostEndPoint.X)) * InvRadiansToDegrees;
Expand All @@ -82,16 +81,16 @@ public RectangleD Draw (Context g, Color outlineColor, PointD endPoint, PointD a
endPoint,

new PointD (
endPoint.X + Math.Cos ((endingAngle + 270 + AngleOffset) * RadiansToDegrees) * ArrowSize,
endPoint.Y + Math.Sin ((endingAngle + 270 + AngleOffset) * RadiansToDegrees) * ArrowSize * -1d),
endPoint.X + Math.Cos ((endingAngle + 270 + arrow.AngleOffset) * RadiansToDegrees) * arrow.ArrowSize,
endPoint.Y + Math.Sin ((endingAngle + 270 + arrow.AngleOffset) * RadiansToDegrees) * arrow.ArrowSize * -1d),

new PointD (
endPoint.X + Math.Cos ((endingAngle + 180) * RadiansToDegrees) * (ArrowSize + LengthOffset),
endPoint.Y + Math.Sin ((endingAngle + 180) * RadiansToDegrees) * (ArrowSize + LengthOffset) * -1d),
endPoint.X + Math.Cos ((endingAngle + 180) * RadiansToDegrees) * (arrow.ArrowSize + arrow.LengthOffset),
endPoint.Y + Math.Sin ((endingAngle + 180) * RadiansToDegrees) * (arrow.ArrowSize + arrow.LengthOffset) * -1d),

new PointD (
endPoint.X + Math.Cos ((endingAngle + 90 - AngleOffset) * RadiansToDegrees) * ArrowSize,
endPoint.Y + Math.Sin ((endingAngle + 90 - AngleOffset) * RadiansToDegrees) * ArrowSize * -1d),
endPoint.X + Math.Cos ((endingAngle + 90 - arrow.AngleOffset) * RadiansToDegrees) * arrow.ArrowSize,
endPoint.Y + Math.Sin ((endingAngle + 90 - arrow.AngleOffset) * RadiansToDegrees) * arrow.ArrowSize * -1d),
};

//Draw the arrow.
Expand All @@ -102,8 +101,7 @@ public RectangleD Draw (Context g, Color outlineColor, PointD endPoint, PointD a

PointD min = new (
X: Math.Min (Math.Min (arrowPoints[1].X, arrowPoints[2].X), arrowPoints[3].X),
Y: Math.Min (Math.Min (arrowPoints[1].Y, arrowPoints[2].Y), arrowPoints[3].Y)
);
Y: Math.Min (Math.Min (arrowPoints[1].Y, arrowPoints[2].Y), arrowPoints[3].Y));

return new RectangleD (
min.X,
Expand Down
62 changes: 38 additions & 24 deletions Pinta.Tools/Editable/Shapes/LineCurveSeriesEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ namespace Pinta.Tools;

public sealed class LineCurveSeriesEngine : ShapeEngine
{
public Arrow Arrow1 { get; }
public Arrow Arrow2 { get; }
public Arrow Arrow1 { get; internal set; }
public Arrow Arrow2 { get; internal set; }

/// <summary>
/// Create a new LineCurveSeriesEngine.
Expand All @@ -47,24 +47,41 @@ public sealed class LineCurveSeriesEngine : ShapeEngine
/// <param name="fillColor">The fill color for the shape.</param>
/// <param name="brushWidth">The width of the outline of the shape.</param>
/// <param name="lineCap">Defines the edge of the line drawn.</param>
public LineCurveSeriesEngine (UserLayer parentLayer, ReEditableLayer? drawingLayer, BaseEditEngine.ShapeTypes shapeType,
bool antialiasing, bool closed, Color outlineColor, Color fillColor, int brushWidth, LineCap lineCap) : base (parentLayer,
drawingLayer, shapeType, antialiasing, closed, outlineColor, fillColor, brushWidth, lineCap)
public LineCurveSeriesEngine (
UserLayer parentLayer,
ReEditableLayer? drawingLayer,
BaseEditEngine.ShapeTypes shapeType,
bool antialiasing,
bool closed,
Color outlineColor,
Color fillColor,
int brushWidth,
LineCap lineCap
) : base (
parentLayer,
drawingLayer,
shapeType,
antialiasing,
closed,
outlineColor,
fillColor,
brushWidth,
lineCap)
{
Arrow1 = new ();
Arrow2 = new ();
}

private LineCurveSeriesEngine (LineCurveSeriesEngine src)
: base (src)
: base (src)
{
Arrow1 = src.Arrow1.Clone ();
Arrow2 = src.Arrow2.Clone ();
Arrow1 = src.Arrow1;
Arrow2 = src.Arrow2;
}

public override ShapeEngine Clone ()
public override LineCurveSeriesEngine Clone ()
{
return new LineCurveSeriesEngine (this);
return new (this);
}

/// <summary>
Expand All @@ -79,19 +96,18 @@ public override void GeneratePoints (int brush_width)
return;
}

List<GeneratedPoint> generatedPoints = new List<GeneratedPoint> ();
List<GeneratedPoint> generatedPoints = new ();

//Generate tangents for each of the smaller cubic Bezier curves that make up each segment of the resulting curve.

//The tension calculated for each point is a gradient between the previous
//control point's tension and the following control point's tension.

//Stores all of the tangent values.
List<PointD> bezierTangents = new List<PointD> ();
List<PointD> bezierTangents = new ();

int pointCount = ControlPoints.Count - 1;
double pointCountDouble = pointCount;
double tensionForPoint;

//Calculate the first tangent.
if (Closed) {
Expand All @@ -106,8 +122,7 @@ public override void GeneratePoints (int brush_width)

//Calculate all of the middle tangents.
for (int i = 1; i < pointCount; ++i) {
tensionForPoint = ControlPoints[i].Tension * i / pointCountDouble;

double tensionForPoint = ControlPoints[i].Tension * i / pointCountDouble;
bezierTangents.Add (new PointD (
tensionForPoint *
(ControlPoints[i + 1].Position.X - ControlPoints[i - 1].Position.X),
Expand All @@ -130,13 +145,11 @@ public override void GeneratePoints (int brush_width)
(ControlPoints[pointCount].Position.Y - ControlPoints[pointCount - 1].Position.Y)));
}


int iMinusOne;

//Generate the resulting curve's points with consecutive cubic Bezier curves that
//use the given points as end points and the calculated tangents as control points.
for (int i = 1; i < ControlPoints.Count; ++i) {
iMinusOne = i - 1;

int iMinusOne = i - 1;

generatedPoints.AddRange (GenerateCubicBezierCurvePoints (
ControlPoints[iMinusOne].Position,
Expand All @@ -153,7 +166,7 @@ public override void GeneratePoints (int brush_width)
if (Closed) {
// Close the shape.

iMinusOne = ControlPoints.Count - 1;
int iMinusOne = ControlPoints.Count - 1;

generatedPoints.AddRange (GenerateCubicBezierCurvePoints (
ControlPoints[iMinusOne].Position,
Expand Down Expand Up @@ -182,7 +195,7 @@ public override void GeneratePoints (int brush_width)
private static IEnumerable<GeneratedPoint> GenerateCubicBezierCurvePoints (PointD p0, PointD p1, PointD p2, PointD p3, int cPIndex)
{
//Note: this must be low enough for mouse clicks to be properly considered on/off the curve at any given point.
double tInterval = .025d;
const double tInterval = .025d;

//t will go from 0d to 1d at the interval of tInterval.
for (double t = 0d; t < 1d + tInterval; t += tInterval) {
Expand Down Expand Up @@ -211,9 +224,10 @@ private static IEnumerable<GeneratedPoint> GenerateCubicBezierCurvePoints (Point
//This is done for both the X and Y given a value t going from 0d to 1d at a very small interval
//and given 4 points p0, p1, p2, and p3, where p0 and p3 are end points and p1 and p2 are control points.

yield return new GeneratedPoint (new PointD (
oneMinusTCubed * p0.X + oneMinusTSquaredTimesTTimesThree * p1.X + oneMinusTTimesTSquaredTimesThree * p2.X + tCubed * p3.X,
oneMinusTCubed * p0.Y + oneMinusTSquaredTimesTTimesThree * p1.Y + oneMinusTTimesTSquaredTimesThree * p2.Y + tCubed * p3.Y),
yield return new (
new PointD (
oneMinusTCubed * p0.X + oneMinusTSquaredTimesTTimesThree * p1.X + oneMinusTTimesTSquaredTimesThree * p2.X + tCubed * p3.X,
oneMinusTCubed * p0.Y + oneMinusTSquaredTimesTTimesThree * p1.Y + oneMinusTTimesTSquaredTimesThree * p2.Y + tCubed * p3.Y),
cPIndex);
}
}
Expand Down

0 comments on commit c6f56f1

Please sign in to comment.