123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591 |
- using PaintDotNet.Measurement.Enum;
- using System;
- using System.Collections.Generic;
- using System.Drawing;
- using System.Drawing.Drawing2D;
- using System.Windows.Forms;
- namespace PaintDotNet.Measurement.Tools
- {
- public class LineTool : ShapeTool
- {
- private const int controlPointCount = 4;
- private const float flattenConstant = 0.1f;
- private Cursor lineToolCursor;
- private Cursor lineToolMouseDownCursor;
- private string statusTextFormat = PdnResources.GetString("LineTool.StatusText.Format");
- private ImageResource lineToolIcon;
- private MoveNubRenderer[] moveNubs;
- private bool inCurveMode = false;
- private int draggingNubIndex = -1;
- private CurveType curveType;
- private enum CurveType
- {
- NotDecided,
- Bezier,
- Spline
- }
- private PointF[] LineToSpline(PointF a, PointF b, int points)
- {
- PointF[] 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 (this.inCurveMode)
- {
- return points;
- }
- else
- {
- List<PointF> 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 void ConstrainPoints(ref PointF a, ref PointF b)
- {
- PointF 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)
- {
- if (points.Length >= 4)
- {
- PdnGraphicsPath path = new PdnGraphicsPath();
- switch (this.curveType)
- {
- default:
- case CurveType.Spline:
- 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;
- }
- else //if (points.Length <= 2)
- {
- 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.GetUnits();
- 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.ToString() + ".Abbreviation";
- unitsAbbreviation = PdnResources.GetString(unitsAbbreviationName);
- numberFormat = "F2";
- }
- else
- {
- unitsAbbreviation = string.Empty;
- numberFormat = "F0";
- }
- string unitsString = PdnResources.GetString("MeasurementUnit." + units.ToString() + ".Plural");
- string statusText = string.Format(
- this.statusTextFormat,
- offsetXPhysical.ToString(numberFormat),
- unitsAbbreviation,
- offsetYPhysical.ToString(numberFormat),
- unitsAbbreviation,
- offsetLengthPhysical.ToString("F2"),
- unitsString,
- angle.ToString("F2"));
- SetStatus(this.lineToolIcon, statusText);
- if (a == b)
- {
- return null;
- }
- else
- {
- PdnGraphicsPath 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 (this.moveNubs != null)
- {
- for (int i = 0; i < this.moveNubs.Length; ++i)
- {
- if (!this.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 / this.moveNubs.Length)); ;
- double sin = Math.Sin(((double)tick / (double)period) * (2.0 * Math.PI));
- // 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]
- int newAlpha = (int)(sin * 255.0);
- int clampedAlpha = Utility.Clamp(newAlpha, 0, 255);
- this.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.CyclePenStartCap();//Widgets.ToolConfigStrip.
- return true;
- case toggleDashOrdinal:
- AppWorkspace.CyclePenDashStyle();//Widgets.ToolConfigStrip.
- return true;
- case toggleEndCapOrdinal:
- AppWorkspace.CyclePenEndCap();//Widgets.ToolConfigStrip.
- return true;
- }
- return base.OnWildShortcutKey(ordinal);
- }
- private bool controlKeyDown = false;
- 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 (!this.controlKeyDown)
- {
- this.controlKeyDown = true;
- this.controlKeyDownTime = DateTime.Now;
- }
- break;
- }
- base.OnKeyDown(e);
- }
- protected override void OnKeyUp(KeyEventArgs e)
- {
- switch (e.KeyCode)
- {
- case Keys.ControlKey:
- TimeSpan heldDuration = (DateTime.Now - this.controlKeyDownTime);
- // If the user taps Ctrl, then we should toggle the visiblity of the moveNubs
- if (heldDuration < this.controlKeyDownThreshold)
- {
- for (int i = 0; i < this.moveNubs.Length; ++i)
- {
- this.moveNubs[i].Visible = this.inCurveMode && !this.moveNubs[i].Visible;
- }
- }
- this.controlKeyDown = false;
- break;
- }
- base.OnKeyUp(e); base.OnKeyUp(e);
- }
- protected override void OnKeyPress(KeyPressEventArgs e)
- {
- if (this.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()
- {
- for (int i = 0; i < this.moveNubs.Length; ++i)
- {
- this.moveNubs[i].Visible = false;
- }
- this.inCurveMode = false;
- this.curveType = CurveType.NotDecided;
- this.Cursor = this.lineToolCursor;
- this.draggingNubIndex = -1;
- DocumentWorkspace.UpdateStatusBarToToolHelpText();
- }
- protected override bool OnShapeEnd()
- {
- // init move nubs
- List<PointF> points = GetTrimmedShapePath();
- if (points.Count < 2)
- {
- return true;
- }
- else
- {
- PointF a = (PointF)points[0];
- PointF b = (PointF)points[points.Count - 1];
- if (0 != (ModifierKeys & Keys.Shift) && a != b)
- {
- ConstrainPoints(ref a, ref b);
- }
- PointF[] spline = LineToSpline(a, b, controlPointCount);
- List<PointF> newPoints = new List<PointF>();
- this.inCurveMode = true;
- for (int i = 0; i < this.moveNubs.Length; ++i)
- {
- this.moveNubs[i].Location = spline[i];
- this.moveNubs[i].Visible = true;
- newPoints.Add(spline[i]);
- }
- string helpText2 = PdnResources.GetString("LineTool.PreCurveHelpText");
- this.SetStatus(null, helpText2);
- SetShapePath(newPoints);
- return false;
- }
- }
- protected override void OnStylusDown(StylusEventArgs e)
- {
- bool callBase = false;
- if (!this.inCurveMode)
- {
- callBase = true;
- }
- else
- {
- PointF mousePtF = new PointF(e.Fx, e.Fy);
- Point mousePt = Point.Truncate(mousePtF);
- float minDistance = float.MaxValue;
- for (int i = 0; i < this.moveNubs.Length; ++i)
- {
- if (this.moveNubs[i].IsPointTouching(mousePt, true))
- {
- float distance = Utility.Distance(mousePtF, this.moveNubs[i].Location);
- if (distance < minDistance)
- {
- minDistance = distance;
- this.draggingNubIndex = i;
- }
- }
- }
- if (this.draggingNubIndex == -1)
- {
- callBase = true;
- }
- else
- {
- this.Cursor = this.handCursorMouseDown;
- if (this.curveType == CurveType.NotDecided)
- {
- if (e.Button == MouseButtons.Right)
- {
- this.curveType = CurveType.Bezier;
- }
- else
- {
- this.curveType = CurveType.Spline;
- }
- }
- for (int i = 0; i < this.moveNubs.Length; ++i)
- {
- this.moveNubs[i].Visible = false;
- }
- string helpText2 = PdnResources.GetString("LineTool.CurvingHelpText");
- SetStatus(null, helpText2);
- OnStylusMove(e);
- }
- }
- if (callBase)
- {
- base.OnStylusDown(e);
- Cursor = this.lineToolMouseDownCursor;
- }
- }
- protected override void OnMouseDown(MouseEventArgs e)
- {
- if (!this.inCurveMode)
- {
- base.OnMouseDown(e);
- }
- }
- protected override void OnStylusUp(StylusEventArgs e)
- {
- if (!this.inCurveMode)
- {
- base.OnStylusUp(e);
- }
- else
- {
- if (this.draggingNubIndex != -1)
- {
- OnStylusMove(e);
- this.draggingNubIndex = -1;
- this.Cursor = this.lineToolCursor;
- for (int i = 0; i < this.moveNubs.Length; ++i)
- {
- this.moveNubs[i].Visible = true;
- }
- }
- }
- }
- protected override void OnMouseUp(MouseEventArgs e)
- {
- if (!this.inCurveMode)
- {
- base.OnMouseUp(e);
- }
- }
- protected override void OnStylusMove(StylusEventArgs e)
- {
- if (!this.inCurveMode)
- {
- base.OnStylusMove(e);
- }
- else if (this.draggingNubIndex != -1)
- {
- PointF mousePt = new PointF(e.Fx, e.Fy);
- this.moveNubs[this.draggingNubIndex].Location = mousePt;
- List<PointF> points = GetTrimmedShapePath();
- points[this.draggingNubIndex] = mousePt;
- SetShapePath(points);
- }
- }
- protected override void OnMouseMove(MouseEventArgs e)
- {
- if (this.draggingNubIndex != -1)
- {
- RenderShape();
- Update();
- }
- else
- {
- Point mousePt = new Point(e.X, e.Y);
- bool hot = false;
- for (int i = 0; i < this.moveNubs.Length; ++i)
- {
- if (this.moveNubs[i].Visible && this.moveNubs[i].IsPointTouching(Point.Truncate(mousePt), true))
- {
- this.Cursor = this.handCursor;
- hot = true;
- break;
- }
- }
- if (!hot)
- {
- if (IsMouseDown)
- {
- Cursor = this.lineToolMouseDownCursor;
- }
- else
- {
- Cursor = this.lineToolCursor;
- }
- }
- }
- base.OnMouseMove(e);
- }
- protected override void OnActivate()
- {
- this.lineToolCursor = new Cursor(PdnResources.GetResourceStream("Cursors.LineToolCursor.cur"));
- this.lineToolMouseDownCursor = new Cursor(PdnResources.GetResourceStream("Cursors.GenericToolCursorMouseDown.cur"));
- this.Cursor = this.lineToolCursor;
- this.lineToolIcon = this.Image;
- this.moveNubs = new MoveNubRenderer[controlPointCount];
- for (int i = 0; i < this.moveNubs.Length; ++i)
- {
- this.moveNubs[i] = new MoveNubRenderer(this.RendererList);
- this.moveNubs[i].Visible = false;
- this.RendererList.Add(this.moveNubs[i], false);
- }
- AppEnvironment.PrimaryColorChanged += new EventHandler(RenderShapeBecauseOfEvent);
- AppEnvironment.SecondaryColorChanged += new EventHandler(RenderShapeBecauseOfEvent);
- AppEnvironment.AntiAliasingChanged += new EventHandler(RenderShapeBecauseOfEvent);
- AppEnvironment.AlphaBlendingChanged += new EventHandler(RenderShapeBecauseOfEvent);
- AppEnvironment.BrushInfoChanged += new EventHandler(RenderShapeBecauseOfEvent);
- AppEnvironment.PenInfoChanged += new EventHandler(RenderShapeBecauseOfEvent);
- AppWorkspace.UnitsChanged += new EventHandler(RenderShapeBecauseOfEvent);
- base.OnActivate();
- }
- private void RenderShapeBecauseOfEvent(object sender, EventArgs e)
- {
- if (this.inCurveMode)
- {
- RenderShape();
- }
- }
- protected override void OnDeactivate()
- {
- base.OnDeactivate();
- AppEnvironment.PrimaryColorChanged -= new EventHandler(RenderShapeBecauseOfEvent);
- AppEnvironment.SecondaryColorChanged -= new EventHandler(RenderShapeBecauseOfEvent);
- AppEnvironment.AntiAliasingChanged -= new EventHandler(RenderShapeBecauseOfEvent);
- AppEnvironment.AlphaBlendingChanged -= new EventHandler(RenderShapeBecauseOfEvent);
- AppEnvironment.BrushInfoChanged -= new EventHandler(RenderShapeBecauseOfEvent);
- AppEnvironment.PenInfoChanged -= new EventHandler(RenderShapeBecauseOfEvent);
- AppWorkspace.UnitsChanged -= new EventHandler(RenderShapeBecauseOfEvent);
- for (int i = 0; i < this.moveNubs.Length; ++i)
- {
- this.RendererList.Remove(this.moveNubs[i]);
- this.moveNubs[i].Dispose();
- this.moveNubs[i] = null;
- }
- this.moveNubs = null;
- if (this.lineToolCursor != null)
- {
- this.lineToolCursor.Dispose();
- this.lineToolCursor = null;
- }
- if (this.lineToolMouseDownCursor != null)
- {
- this.lineToolMouseDownCursor.Dispose();
- this.lineToolMouseDownCursor = null;
- }
- }
- public LineTool(IDocumentWorkspace documentWorkspace)
- : base(documentWorkspace,
- PdnResources.GetImageResource("Icons.LineToolIcon.png"),
- PdnResources.GetString("LineTool.Name"),
- PdnResources.GetString("LineTool.HelpText"),
- ToolBarConfigItems.None | ToolBarConfigItems.PenCaps,
- ToolBarConfigItems.ShapeType)
- {
- this.ForceShapeDrawType = true;
- this.ForcedShapeDrawType = ShapeDrawType.Outline;
- this.UseDashStyle = true;
- }
- }
- }
|