/src/tools/LineTool.cs
C# | 564 lines | 453 code | 88 blank | 23 comment | 65 complexity | 934731da46a2d1668c524d00a34fe35c MD5 | raw file
- /////////////////////////////////////////////////////////////////////////////////
- // Paint.NET //
- // Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. //
- // Portions Copyright (C) Microsoft Corporation. All Rights Reserved. //
- // See src/Resources/Files/License.txt for full licensing and attribution //
- // details. //
- // . //
- /////////////////////////////////////////////////////////////////////////////////
-
- using System;
- using System.Collections.Generic;
- using System.Drawing;
- using System.Drawing.Drawing2D;
- using System.Linq;
- using System.Windows.Forms;
- using PaintDotNet.Base;
-
- namespace PaintDotNet.Tools
- {
- internal class LineTool
- : ShapeTool
- {
- private const int ControlPointCount = 4;
- private const float FlattenConstant = 0.1f;
- private Cursor _lineToolCursor;
- private Cursor _lineToolMouseDownCursor;
- private readonly string _statusTextFormat = PdnResources.GetString("LineTool.StatusText.Format");
- private ImageResource _lineToolIcon;
- private MoveNubRenderer[] _moveNubs;
- private bool _inCurveMode;
- private int _draggingNubIndex = -1;
- private CurveType _curveType;
-
- private enum CurveType
- {
- NotDecided,
- Bezier,
- Spline
- }
-
- private static PointF[] LineToSpline(PointF a, PointF b, int points)
- {
- var spline = new PointF[points];
-
- for (int i = 0; i < spline.Length; ++i)
- {
- float frac = (float)i / (float)(spline.Length - 1);
- PointF mid = Utility.Lerp(a, b, frac);
- spline[i] = mid;
- }
-
- return spline;
- }
-
- protected override List<PointF> TrimShapePath(List<PointF> points)
- {
- if (_inCurveMode)
- {
- return points;
- }
- var array = new List<PointF>();
-
- if (points.Count > 0)
- {
- array.Add(points[0]);
-
- if (points.Count > 1)
- {
- array.Add(points[points.Count - 1]);
- }
- }
-
- return array;
- }
-
- private static void ConstrainPoints(ref PointF a, ref PointF b)
- {
- var dir = new PointF(b.X - a.X, b.Y - a.Y);
- double theta = Math.Atan2(dir.Y, dir.X);
- double len = Math.Sqrt(dir.X * dir.X + dir.Y * dir.Y);
-
- theta = Math.Round(12 * theta / Math.PI) * Math.PI / 12;
- b = new PointF((float)(a.X + len * Math.Cos(theta)), (float)(a.Y + len * Math.Sin(theta)));
- }
-
- protected override PdnGraphicsPath CreateShapePath(PointF[] points)
- {
- PdnGraphicsPath path;
- if (points.Length >= 4)
- {
- path = new PdnGraphicsPath();
-
- switch (_curveType)
- {
- default:
- path.AddCurve(points);
- break;
-
- case CurveType.Bezier:
- path.AddBezier(points[0], points[1], points[2], points[3]);
- break;
- }
-
- path.Flatten(Utility.IdentityMatrix, FlattenConstant);
- return path;
- }
- PointF a = points[0];
- PointF b = points[points.Length - 1];
-
- if (0 != (ModifierKeys & Keys.Shift) && a != b)
- {
- ConstrainPoints(ref a, ref b);
- }
-
- double angle = -180.0 * Math.Atan2(b.Y - a.Y, b.X - a.X) / Math.PI;
- MeasurementUnit units = AppWorkspace.Units;
- double offsetXPhysical = Document.PixelToPhysicalX(b.X - a.X, units);
- double offsetYPhysical = Document.PixelToPhysicalY(b.Y - a.Y, units);
- double offsetLengthPhysical = Math.Sqrt(offsetXPhysical * offsetXPhysical + offsetYPhysical * offsetYPhysical);
-
- string numberFormat;
- string unitsAbbreviation;
-
- if (units != MeasurementUnit.Pixel)
- {
- string unitsAbbreviationName = "MeasurementUnit." + units + ".Abbreviation";
- unitsAbbreviation = PdnResources.GetString(unitsAbbreviationName);
- numberFormat = "F2";
- }
- else
- {
- unitsAbbreviation = string.Empty;
- numberFormat = "F0";
- }
-
- string unitsString = PdnResources.GetString("MeasurementUnit." + units + ".Plural");
-
- string statusText = string.Format(
- _statusTextFormat,
- offsetXPhysical.ToString(numberFormat),
- unitsAbbreviation,
- offsetYPhysical.ToString(numberFormat),
- unitsAbbreviation,
- offsetLengthPhysical.ToString("F2"),
- unitsString,
- angle.ToString("F2"));
-
- SetStatus(_lineToolIcon, statusText);
-
- if (a == b)
- {
- return null;
- }
- path = new PdnGraphicsPath();
- PointF[] spline = LineToSpline(a, b, ControlPointCount);
- path.AddCurve(spline);
- path.Flatten(Utility.IdentityMatrix, FlattenConstant);
- return path;
- }
-
- public override PixelOffsetMode GetPixelOffsetMode()
- {
- return PixelOffsetMode.None;
- }
-
- protected override void OnPulse()
- {
- if (_moveNubs != null)
- {
- for (int i = 0; i < _moveNubs.Length; ++i)
- {
- if (!_moveNubs[i].Visible)
- {
- continue;
- }
-
- // Oscillate between 25% and 100% alpha over a period of 2 seconds
- // Alpha value of 100% is sustained for a large duration of this period
- const int period = 10000 * 2000; // 10000 ticks per ms, 2000ms per second
- long tick = (DateTime.Now.Ticks % period) + (i * (period / _moveNubs.Length));
- //double sin = Math.Sin(((double)tick / (double)period) * (2.0 * Math.PI));
- double sin = Math.Sin(((double)tick / 20000000) * (2.0 * Math.PI)); //Same as above, only with constant value computed
- // sin is [-1, +1]
-
- sin = Math.Min(0.5, sin);
- // sin is [-1, +0.5]
-
- sin += 1.0;
- // sin is [0, 1.5]
-
- sin /= 2.0;
- // sin is [0, 0.75]
-
- sin += 0.25;
- // sin is [0.25, 1]
-
- var newAlpha = (int)(sin * 255.0);
- int clampedAlpha = Utility.Clamp(newAlpha, 0, 255);
- _moveNubs[i].Alpha = clampedAlpha;
- }
- }
-
- base.OnPulse();
- }
-
- private const int ToggleStartCapOrdinal = 0;
- private const int ToggleDashOrdinal = 1;
- private const int ToggleEndCapOrdinal = 2;
-
- protected override bool OnWildShortcutKey(int ordinal)
- {
- switch (ordinal)
- {
- case ToggleStartCapOrdinal:
- AppWorkspace.Widgets.ToolConfigStrip.CyclePenStartCap();
- return true;
-
- case ToggleDashOrdinal:
- AppWorkspace.Widgets.ToolConfigStrip.CyclePenDashStyle();
- return true;
-
- case ToggleEndCapOrdinal:
- AppWorkspace.Widgets.ToolConfigStrip.CyclePenEndCap();
- return true;
- }
-
- return base.OnWildShortcutKey(ordinal);
- }
-
- private bool _controlKeyDown;
- private DateTime _controlKeyDownTime = DateTime.MinValue;
- private readonly TimeSpan _controlKeyDownThreshold = new TimeSpan(0, 0, 0, 0, 400);
-
- protected override void OnKeyDown(KeyEventArgs e)
- {
- switch (e.KeyCode)
- {
- case Keys.ControlKey:
- if (!_controlKeyDown)
- {
- _controlKeyDown = true;
- _controlKeyDownTime = DateTime.Now;
- }
-
- break;
- }
-
- base.OnKeyDown(e);
- }
-
- protected override void OnKeyUp(KeyEventArgs e)
- {
- switch (e.KeyCode)
- {
- case Keys.ControlKey:
- TimeSpan heldDuration = (DateTime.Now - _controlKeyDownTime);
-
- // If the user taps Ctrl, then we should toggle the visiblity of the moveNubs
- if (heldDuration < _controlKeyDownThreshold)
- {
- foreach (MoveNubRenderer t in _moveNubs)
- {
- t.Visible = _inCurveMode && !t.Visible;
- }
- }
-
- _controlKeyDown = false;
- break;
- }
-
- base.OnKeyUp(e); base.OnKeyUp(e);
- }
-
- protected override void OnKeyPress(KeyPressEventArgs e)
- {
- if (_inCurveMode)
- {
- switch (e.KeyChar)
- {
- case '\r': // Enter
- e.Handled = true;
- CommitShape();
- break;
-
- case (char)27: // Escape
- // Only recognize if the user is not pressing Ctrl.
- // Reason for this is that Ctrl+[ ends up being sent
- // to us as (char)27 as well, but the user probably
- // wants to use that for the decrease brush size
- // shortcut, not cancel :)
- if ((ModifierKeys & Keys.Control) == 0)
- {
- e.Handled = true;
- HistoryStack.StepBackward();
- }
- break;
- }
- }
-
- base.OnKeyPress(e);
- }
-
- protected override void OnShapeCommitting()
- {
- foreach (MoveNubRenderer t in _moveNubs)
- {
- t.Visible = false;
- }
-
- _inCurveMode = false;
- _curveType = CurveType.NotDecided;
- Cursor = _lineToolCursor;
- _draggingNubIndex = -1;
-
- DocumentWorkspace.UpdateStatusBarToToolHelpText();
- }
-
- protected override bool OnShapeEnd()
- {
- // init move nubs
- List<PointF> points = GetTrimmedShapePath();
-
- if (points.Count < 2)
- {
- return true;
- }
- var a = points[0];
- var b = points[points.Count - 1];
-
- if (0 != (ModifierKeys & Keys.Shift) && a != b)
- {
- ConstrainPoints(ref a, ref b);
- }
-
- PointF[] spline = LineToSpline(a, b, ControlPointCount);
- var newPoints = new List<PointF>();
-
- _inCurveMode = true;
- for (int i = 0; i < _moveNubs.Length; ++i)
- {
- _moveNubs[i].Location = spline[i];
- _moveNubs[i].Visible = true;
- newPoints.Add(spline[i]);
- }
-
- string helpText2 = PdnResources.GetString("LineTool.PreCurveHelpText");
- SetStatus(null, helpText2);
- SetShapePath(newPoints);
- return false;
- }
-
- protected override void OnStylusDown(StylusEventArgs e)
- {
- bool callBase = false;
-
- if (!_inCurveMode)
- {
- callBase = true;
- }
- else
- {
- var mousePtF = new PointF(e.Fx, e.Fy);
- Point mousePt = Point.Truncate(mousePtF);
- float minDistance = float.MaxValue;
-
- for (int i = 0; i < _moveNubs.Length; ++i)
- {
- if (!_moveNubs[i].IsPointTouching(mousePt, true)) continue;
- float distance = Utility.Distance(mousePtF, _moveNubs[i].Location);
-
- if (distance >= minDistance) continue;
- minDistance = distance;
- _draggingNubIndex = i;
- }
-
- if (_draggingNubIndex == -1)
- {
- callBase = true;
- }
- else
- {
- Cursor = HandCursorMouseDown;
-
- if (_curveType == CurveType.NotDecided)
- {
- _curveType = e.Button == MouseButtons.Right ? CurveType.Bezier : CurveType.Spline;
- }
-
- foreach (MoveNubRenderer t in _moveNubs)
- {
- t.Visible = false;
- }
-
- string helpText2 = PdnResources.GetString("LineTool.CurvingHelpText");
- SetStatus(null, helpText2);
- OnStylusMove(e);
- }
- }
-
- if (!callBase) return;
- base.OnStylusDown(e);
- Cursor = _lineToolMouseDownCursor;
- }
-
- protected override void OnMouseDown(MouseEventArgs e)
- {
- if (!_inCurveMode)
- {
- base.OnMouseDown(e);
- }
- }
-
- protected override void OnStylusUp(StylusEventArgs e)
- {
- if (!_inCurveMode)
- {
- base.OnStylusUp(e);
- }
- else
- {
- if (_draggingNubIndex != -1)
- {
- OnStylusMove(e);
- _draggingNubIndex = -1;
- Cursor = _lineToolCursor;
-
- foreach (MoveNubRenderer t in _moveNubs)
- {
- t.Visible = true;
- }
- }
- }
- }
-
- protected override void OnMouseUp(MouseEventArgs e)
- {
- if (!_inCurveMode)
- {
- base.OnMouseUp(e);
- }
- }
-
- protected override void OnStylusMove(StylusEventArgs e)
- {
- if (!_inCurveMode)
- {
- base.OnStylusMove(e);
- }
- else if (_draggingNubIndex != -1)
- {
- var mousePt = new PointF(e.Fx, e.Fy);
- _moveNubs[_draggingNubIndex].Location = mousePt;
- List<PointF> points = GetTrimmedShapePath();
- points[_draggingNubIndex] = mousePt;
- SetShapePath(points);
- }
- }
-
- protected override void OnMouseMove(MouseEventArgs e)
- {
- if (_draggingNubIndex != -1)
- {
- RenderShape();
- Update();
- }
- else
- {
- var mousePt = new Point(e.X, e.Y);
- bool hot = false;
-
- if (_moveNubs.Any(t => t.Visible && t.IsPointTouching(Point.Truncate(mousePt), true)))
- {
- Cursor = HandCursor;
- hot = true;
- }
-
- if (!hot)
- {
- Cursor = IsMouseDown ? _lineToolMouseDownCursor : _lineToolCursor;
- }
- }
-
- base.OnMouseMove(e);
- }
-
- protected override void OnActivate()
- {
- _lineToolCursor = new Cursor(PdnResources.GetResourceStream("Cursors.LineToolCursor.cur"));
- _lineToolMouseDownCursor = new Cursor(PdnResources.GetResourceStream("Cursors.GenericToolCursorMouseDown.cur"));
- Cursor = _lineToolCursor;
- _lineToolIcon = Image;
-
- _moveNubs = new MoveNubRenderer[ControlPointCount];
- for (int i = 0; i < _moveNubs.Length; ++i)
- {
- _moveNubs[i] = new MoveNubRenderer(RendererList) {Visible = false};
- RendererList.Add(_moveNubs[i], false);
- }
-
- AppEnvironment.PrimaryColorChanged += RenderShapeBecauseOfEvent;
- AppEnvironment.SecondaryColorChanged += RenderShapeBecauseOfEvent;
- AppEnvironment.AntiAliasingChanged += RenderShapeBecauseOfEvent;
- AppEnvironment.AlphaBlendingChanged += RenderShapeBecauseOfEvent;
- AppEnvironment.BrushInfoChanged += RenderShapeBecauseOfEvent;
- AppEnvironment.PenInfoChanged += RenderShapeBecauseOfEvent;
- AppWorkspace.UnitsChanged += RenderShapeBecauseOfEvent;
-
- base.OnActivate();
- }
-
- private void RenderShapeBecauseOfEvent(object sender, EventArgs e)
- {
- if (_inCurveMode)
- {
- RenderShape();
- }
- }
-
- protected override void OnDeactivate()
- {
- base.OnDeactivate();
-
- AppEnvironment.PrimaryColorChanged -= RenderShapeBecauseOfEvent;
- AppEnvironment.SecondaryColorChanged -= RenderShapeBecauseOfEvent;
- AppEnvironment.AntiAliasingChanged -= RenderShapeBecauseOfEvent;
- AppEnvironment.AlphaBlendingChanged -= RenderShapeBecauseOfEvent;
- AppEnvironment.BrushInfoChanged -= RenderShapeBecauseOfEvent;
- AppEnvironment.PenInfoChanged -= RenderShapeBecauseOfEvent;
- AppWorkspace.UnitsChanged -= RenderShapeBecauseOfEvent;
-
- for (int i = 0; i < _moveNubs.Length; ++i)
- {
- RendererList.Remove(_moveNubs[i]);
- _moveNubs[i].Dispose();
- _moveNubs[i] = null;
- }
-
- _moveNubs = null;
-
- if (_lineToolCursor != null)
- {
- _lineToolCursor.Dispose();
- _lineToolCursor = null;
- }
-
- if (_lineToolMouseDownCursor == null) return;
- _lineToolMouseDownCursor.Dispose();
- _lineToolMouseDownCursor = null;
- }
-
- public LineTool(DocumentWorkspace documentWorkspace)
- : base(documentWorkspace,
- PdnResources.GetImageResource("Icons.LineToolIcon.png"),
- PdnResources.GetString("LineTool.Name"),
- PdnResources.GetString("LineTool.HelpText"),
- ToolBarConfigItems.None | ToolBarConfigItems.PenCaps,
- ToolBarConfigItems.ShapeType)
- {
- ForceShapeDrawType = true;
- ForcedShapeDrawType = ShapeDrawType.Outline;
- UseDashStyle = true;
- }
- }
- }