From c6f56f1b9408e8c51217c1634e3708b50ca0acb4 Mon Sep 17 00:00:00 2001 From: Lehonti Ramos <17771375+Lehonti@users.noreply.github.com> Date: Tue, 10 Dec 2024 04:16:06 +0100 Subject: [PATCH] Made `Arrow` immutable (#1170) --- .../Editable/EditEngines/ArrowedEditEngine.cs | 68 ++++++++++--------- Pinta.Tools/Editable/Shapes/Arrow.cs | 50 +++++++------- .../Editable/Shapes/LineCurveSeriesEngine.cs | 62 ++++++++++------- 3 files changed, 97 insertions(+), 83 deletions(-) diff --git a/Pinta.Tools/Editable/EditEngines/ArrowedEditEngine.cs b/Pinta.Tools/Editable/EditEngines/ArrowedEditEngine.cs index ae4f56ccad..38858e0a0c 100644 --- a/Pinta.Tools/Editable/EditEngines/ArrowedEditEngine.cs +++ b/Pinta.Tools/Editable/EditEngines/ArrowedEditEngine.cs @@ -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!; @@ -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); @@ -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); } @@ -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. } @@ -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 (); }; @@ -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 (); }; @@ -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 (); }; diff --git a/Pinta.Tools/Editable/Shapes/Arrow.cs b/Pinta.Tools/Editable/Shapes/Arrow.cs index 7e0043c009..8311a9deb3 100644 --- a/Pinta.Tools/Editable/Shapes/Arrow.cs +++ b/Pinta.Tools/Editable/Shapes/Arrow.cs @@ -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; - /// - /// Returns a clone of the Arrow. - /// - /// A clone of the Arrow. - public Arrow Clone () - => new () { - Show = Show, - ArrowSize = ArrowSize, - AngleOffset = AngleOffset, - LengthOffset = LengthOffset, - }; - /// /// Draws the arrow. /// /// The drawing context. /// The end point of a shape. /// The point right before the end point. - 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; @@ -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. @@ -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, diff --git a/Pinta.Tools/Editable/Shapes/LineCurveSeriesEngine.cs b/Pinta.Tools/Editable/Shapes/LineCurveSeriesEngine.cs index ca2b11ffe0..72c7ead20c 100644 --- a/Pinta.Tools/Editable/Shapes/LineCurveSeriesEngine.cs +++ b/Pinta.Tools/Editable/Shapes/LineCurveSeriesEngine.cs @@ -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; } /// /// Create a new LineCurveSeriesEngine. @@ -47,24 +47,41 @@ public sealed class LineCurveSeriesEngine : ShapeEngine /// The fill color for the shape. /// The width of the outline of the shape. /// Defines the edge of the line drawn. - 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); } /// @@ -79,7 +96,7 @@ public override void GeneratePoints (int brush_width) return; } - List generatedPoints = new List (); + List generatedPoints = new (); //Generate tangents for each of the smaller cubic Bezier curves that make up each segment of the resulting curve. @@ -87,11 +104,10 @@ public override void GeneratePoints (int brush_width) //control point's tension and the following control point's tension. //Stores all of the tangent values. - List bezierTangents = new List (); + List bezierTangents = new (); int pointCount = ControlPoints.Count - 1; double pointCountDouble = pointCount; - double tensionForPoint; //Calculate the first tangent. if (Closed) { @@ -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), @@ -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, @@ -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, @@ -182,7 +195,7 @@ public override void GeneratePoints (int brush_width) private static IEnumerable 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) { @@ -211,9 +224,10 @@ private static IEnumerable 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); } }