PageRenderTime 50ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/src/tools/LineTool.cs

https://bitbucket.org/tuldok89/openpdn
C# | 564 lines | 453 code | 88 blank | 23 comment | 65 complexity | 934731da46a2d1668c524d00a34fe35c MD5 | raw file
  1. /////////////////////////////////////////////////////////////////////////////////
  2. // Paint.NET //
  3. // Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. //
  4. // Portions Copyright (C) Microsoft Corporation. All Rights Reserved. //
  5. // See src/Resources/Files/License.txt for full licensing and attribution //
  6. // details. //
  7. // . //
  8. /////////////////////////////////////////////////////////////////////////////////
  9. using System;
  10. using System.Collections.Generic;
  11. using System.Drawing;
  12. using System.Drawing.Drawing2D;
  13. using System.Linq;
  14. using System.Windows.Forms;
  15. using PaintDotNet.Base;
  16. namespace PaintDotNet.Tools
  17. {
  18. internal class LineTool
  19. : ShapeTool
  20. {
  21. private const int ControlPointCount = 4;
  22. private const float FlattenConstant = 0.1f;
  23. private Cursor _lineToolCursor;
  24. private Cursor _lineToolMouseDownCursor;
  25. private readonly string _statusTextFormat = PdnResources.GetString("LineTool.StatusText.Format");
  26. private ImageResource _lineToolIcon;
  27. private MoveNubRenderer[] _moveNubs;
  28. private bool _inCurveMode;
  29. private int _draggingNubIndex = -1;
  30. private CurveType _curveType;
  31. private enum CurveType
  32. {
  33. NotDecided,
  34. Bezier,
  35. Spline
  36. }
  37. private static PointF[] LineToSpline(PointF a, PointF b, int points)
  38. {
  39. var spline = new PointF[points];
  40. for (int i = 0; i < spline.Length; ++i)
  41. {
  42. float frac = (float)i / (float)(spline.Length - 1);
  43. PointF mid = Utility.Lerp(a, b, frac);
  44. spline[i] = mid;
  45. }
  46. return spline;
  47. }
  48. protected override List<PointF> TrimShapePath(List<PointF> points)
  49. {
  50. if (_inCurveMode)
  51. {
  52. return points;
  53. }
  54. var array = new List<PointF>();
  55. if (points.Count > 0)
  56. {
  57. array.Add(points[0]);
  58. if (points.Count > 1)
  59. {
  60. array.Add(points[points.Count - 1]);
  61. }
  62. }
  63. return array;
  64. }
  65. private static void ConstrainPoints(ref PointF a, ref PointF b)
  66. {
  67. var dir = new PointF(b.X - a.X, b.Y - a.Y);
  68. double theta = Math.Atan2(dir.Y, dir.X);
  69. double len = Math.Sqrt(dir.X * dir.X + dir.Y * dir.Y);
  70. theta = Math.Round(12 * theta / Math.PI) * Math.PI / 12;
  71. b = new PointF((float)(a.X + len * Math.Cos(theta)), (float)(a.Y + len * Math.Sin(theta)));
  72. }
  73. protected override PdnGraphicsPath CreateShapePath(PointF[] points)
  74. {
  75. PdnGraphicsPath path;
  76. if (points.Length >= 4)
  77. {
  78. path = new PdnGraphicsPath();
  79. switch (_curveType)
  80. {
  81. default:
  82. path.AddCurve(points);
  83. break;
  84. case CurveType.Bezier:
  85. path.AddBezier(points[0], points[1], points[2], points[3]);
  86. break;
  87. }
  88. path.Flatten(Utility.IdentityMatrix, FlattenConstant);
  89. return path;
  90. }
  91. PointF a = points[0];
  92. PointF b = points[points.Length - 1];
  93. if (0 != (ModifierKeys & Keys.Shift) && a != b)
  94. {
  95. ConstrainPoints(ref a, ref b);
  96. }
  97. double angle = -180.0 * Math.Atan2(b.Y - a.Y, b.X - a.X) / Math.PI;
  98. MeasurementUnit units = AppWorkspace.Units;
  99. double offsetXPhysical = Document.PixelToPhysicalX(b.X - a.X, units);
  100. double offsetYPhysical = Document.PixelToPhysicalY(b.Y - a.Y, units);
  101. double offsetLengthPhysical = Math.Sqrt(offsetXPhysical * offsetXPhysical + offsetYPhysical * offsetYPhysical);
  102. string numberFormat;
  103. string unitsAbbreviation;
  104. if (units != MeasurementUnit.Pixel)
  105. {
  106. string unitsAbbreviationName = "MeasurementUnit." + units + ".Abbreviation";
  107. unitsAbbreviation = PdnResources.GetString(unitsAbbreviationName);
  108. numberFormat = "F2";
  109. }
  110. else
  111. {
  112. unitsAbbreviation = string.Empty;
  113. numberFormat = "F0";
  114. }
  115. string unitsString = PdnResources.GetString("MeasurementUnit." + units + ".Plural");
  116. string statusText = string.Format(
  117. _statusTextFormat,
  118. offsetXPhysical.ToString(numberFormat),
  119. unitsAbbreviation,
  120. offsetYPhysical.ToString(numberFormat),
  121. unitsAbbreviation,
  122. offsetLengthPhysical.ToString("F2"),
  123. unitsString,
  124. angle.ToString("F2"));
  125. SetStatus(_lineToolIcon, statusText);
  126. if (a == b)
  127. {
  128. return null;
  129. }
  130. path = new PdnGraphicsPath();
  131. PointF[] spline = LineToSpline(a, b, ControlPointCount);
  132. path.AddCurve(spline);
  133. path.Flatten(Utility.IdentityMatrix, FlattenConstant);
  134. return path;
  135. }
  136. public override PixelOffsetMode GetPixelOffsetMode()
  137. {
  138. return PixelOffsetMode.None;
  139. }
  140. protected override void OnPulse()
  141. {
  142. if (_moveNubs != null)
  143. {
  144. for (int i = 0; i < _moveNubs.Length; ++i)
  145. {
  146. if (!_moveNubs[i].Visible)
  147. {
  148. continue;
  149. }
  150. // Oscillate between 25% and 100% alpha over a period of 2 seconds
  151. // Alpha value of 100% is sustained for a large duration of this period
  152. const int period = 10000 * 2000; // 10000 ticks per ms, 2000ms per second
  153. long tick = (DateTime.Now.Ticks % period) + (i * (period / _moveNubs.Length));
  154. //double sin = Math.Sin(((double)tick / (double)period) * (2.0 * Math.PI));
  155. double sin = Math.Sin(((double)tick / 20000000) * (2.0 * Math.PI)); //Same as above, only with constant value computed
  156. // sin is [-1, +1]
  157. sin = Math.Min(0.5, sin);
  158. // sin is [-1, +0.5]
  159. sin += 1.0;
  160. // sin is [0, 1.5]
  161. sin /= 2.0;
  162. // sin is [0, 0.75]
  163. sin += 0.25;
  164. // sin is [0.25, 1]
  165. var newAlpha = (int)(sin * 255.0);
  166. int clampedAlpha = Utility.Clamp(newAlpha, 0, 255);
  167. _moveNubs[i].Alpha = clampedAlpha;
  168. }
  169. }
  170. base.OnPulse();
  171. }
  172. private const int ToggleStartCapOrdinal = 0;
  173. private const int ToggleDashOrdinal = 1;
  174. private const int ToggleEndCapOrdinal = 2;
  175. protected override bool OnWildShortcutKey(int ordinal)
  176. {
  177. switch (ordinal)
  178. {
  179. case ToggleStartCapOrdinal:
  180. AppWorkspace.Widgets.ToolConfigStrip.CyclePenStartCap();
  181. return true;
  182. case ToggleDashOrdinal:
  183. AppWorkspace.Widgets.ToolConfigStrip.CyclePenDashStyle();
  184. return true;
  185. case ToggleEndCapOrdinal:
  186. AppWorkspace.Widgets.ToolConfigStrip.CyclePenEndCap();
  187. return true;
  188. }
  189. return base.OnWildShortcutKey(ordinal);
  190. }
  191. private bool _controlKeyDown;
  192. private DateTime _controlKeyDownTime = DateTime.MinValue;
  193. private readonly TimeSpan _controlKeyDownThreshold = new TimeSpan(0, 0, 0, 0, 400);
  194. protected override void OnKeyDown(KeyEventArgs e)
  195. {
  196. switch (e.KeyCode)
  197. {
  198. case Keys.ControlKey:
  199. if (!_controlKeyDown)
  200. {
  201. _controlKeyDown = true;
  202. _controlKeyDownTime = DateTime.Now;
  203. }
  204. break;
  205. }
  206. base.OnKeyDown(e);
  207. }
  208. protected override void OnKeyUp(KeyEventArgs e)
  209. {
  210. switch (e.KeyCode)
  211. {
  212. case Keys.ControlKey:
  213. TimeSpan heldDuration = (DateTime.Now - _controlKeyDownTime);
  214. // If the user taps Ctrl, then we should toggle the visiblity of the moveNubs
  215. if (heldDuration < _controlKeyDownThreshold)
  216. {
  217. foreach (MoveNubRenderer t in _moveNubs)
  218. {
  219. t.Visible = _inCurveMode && !t.Visible;
  220. }
  221. }
  222. _controlKeyDown = false;
  223. break;
  224. }
  225. base.OnKeyUp(e); base.OnKeyUp(e);
  226. }
  227. protected override void OnKeyPress(KeyPressEventArgs e)
  228. {
  229. if (_inCurveMode)
  230. {
  231. switch (e.KeyChar)
  232. {
  233. case '\r': // Enter
  234. e.Handled = true;
  235. CommitShape();
  236. break;
  237. case (char)27: // Escape
  238. // Only recognize if the user is not pressing Ctrl.
  239. // Reason for this is that Ctrl+[ ends up being sent
  240. // to us as (char)27 as well, but the user probably
  241. // wants to use that for the decrease brush size
  242. // shortcut, not cancel :)
  243. if ((ModifierKeys & Keys.Control) == 0)
  244. {
  245. e.Handled = true;
  246. HistoryStack.StepBackward();
  247. }
  248. break;
  249. }
  250. }
  251. base.OnKeyPress(e);
  252. }
  253. protected override void OnShapeCommitting()
  254. {
  255. foreach (MoveNubRenderer t in _moveNubs)
  256. {
  257. t.Visible = false;
  258. }
  259. _inCurveMode = false;
  260. _curveType = CurveType.NotDecided;
  261. Cursor = _lineToolCursor;
  262. _draggingNubIndex = -1;
  263. DocumentWorkspace.UpdateStatusBarToToolHelpText();
  264. }
  265. protected override bool OnShapeEnd()
  266. {
  267. // init move nubs
  268. List<PointF> points = GetTrimmedShapePath();
  269. if (points.Count < 2)
  270. {
  271. return true;
  272. }
  273. var a = points[0];
  274. var b = points[points.Count - 1];
  275. if (0 != (ModifierKeys & Keys.Shift) && a != b)
  276. {
  277. ConstrainPoints(ref a, ref b);
  278. }
  279. PointF[] spline = LineToSpline(a, b, ControlPointCount);
  280. var newPoints = new List<PointF>();
  281. _inCurveMode = true;
  282. for (int i = 0; i < _moveNubs.Length; ++i)
  283. {
  284. _moveNubs[i].Location = spline[i];
  285. _moveNubs[i].Visible = true;
  286. newPoints.Add(spline[i]);
  287. }
  288. string helpText2 = PdnResources.GetString("LineTool.PreCurveHelpText");
  289. SetStatus(null, helpText2);
  290. SetShapePath(newPoints);
  291. return false;
  292. }
  293. protected override void OnStylusDown(StylusEventArgs e)
  294. {
  295. bool callBase = false;
  296. if (!_inCurveMode)
  297. {
  298. callBase = true;
  299. }
  300. else
  301. {
  302. var mousePtF = new PointF(e.Fx, e.Fy);
  303. Point mousePt = Point.Truncate(mousePtF);
  304. float minDistance = float.MaxValue;
  305. for (int i = 0; i < _moveNubs.Length; ++i)
  306. {
  307. if (!_moveNubs[i].IsPointTouching(mousePt, true)) continue;
  308. float distance = Utility.Distance(mousePtF, _moveNubs[i].Location);
  309. if (distance >= minDistance) continue;
  310. minDistance = distance;
  311. _draggingNubIndex = i;
  312. }
  313. if (_draggingNubIndex == -1)
  314. {
  315. callBase = true;
  316. }
  317. else
  318. {
  319. Cursor = HandCursorMouseDown;
  320. if (_curveType == CurveType.NotDecided)
  321. {
  322. _curveType = e.Button == MouseButtons.Right ? CurveType.Bezier : CurveType.Spline;
  323. }
  324. foreach (MoveNubRenderer t in _moveNubs)
  325. {
  326. t.Visible = false;
  327. }
  328. string helpText2 = PdnResources.GetString("LineTool.CurvingHelpText");
  329. SetStatus(null, helpText2);
  330. OnStylusMove(e);
  331. }
  332. }
  333. if (!callBase) return;
  334. base.OnStylusDown(e);
  335. Cursor = _lineToolMouseDownCursor;
  336. }
  337. protected override void OnMouseDown(MouseEventArgs e)
  338. {
  339. if (!_inCurveMode)
  340. {
  341. base.OnMouseDown(e);
  342. }
  343. }
  344. protected override void OnStylusUp(StylusEventArgs e)
  345. {
  346. if (!_inCurveMode)
  347. {
  348. base.OnStylusUp(e);
  349. }
  350. else
  351. {
  352. if (_draggingNubIndex != -1)
  353. {
  354. OnStylusMove(e);
  355. _draggingNubIndex = -1;
  356. Cursor = _lineToolCursor;
  357. foreach (MoveNubRenderer t in _moveNubs)
  358. {
  359. t.Visible = true;
  360. }
  361. }
  362. }
  363. }
  364. protected override void OnMouseUp(MouseEventArgs e)
  365. {
  366. if (!_inCurveMode)
  367. {
  368. base.OnMouseUp(e);
  369. }
  370. }
  371. protected override void OnStylusMove(StylusEventArgs e)
  372. {
  373. if (!_inCurveMode)
  374. {
  375. base.OnStylusMove(e);
  376. }
  377. else if (_draggingNubIndex != -1)
  378. {
  379. var mousePt = new PointF(e.Fx, e.Fy);
  380. _moveNubs[_draggingNubIndex].Location = mousePt;
  381. List<PointF> points = GetTrimmedShapePath();
  382. points[_draggingNubIndex] = mousePt;
  383. SetShapePath(points);
  384. }
  385. }
  386. protected override void OnMouseMove(MouseEventArgs e)
  387. {
  388. if (_draggingNubIndex != -1)
  389. {
  390. RenderShape();
  391. Update();
  392. }
  393. else
  394. {
  395. var mousePt = new Point(e.X, e.Y);
  396. bool hot = false;
  397. if (_moveNubs.Any(t => t.Visible && t.IsPointTouching(Point.Truncate(mousePt), true)))
  398. {
  399. Cursor = HandCursor;
  400. hot = true;
  401. }
  402. if (!hot)
  403. {
  404. Cursor = IsMouseDown ? _lineToolMouseDownCursor : _lineToolCursor;
  405. }
  406. }
  407. base.OnMouseMove(e);
  408. }
  409. protected override void OnActivate()
  410. {
  411. _lineToolCursor = new Cursor(PdnResources.GetResourceStream("Cursors.LineToolCursor.cur"));
  412. _lineToolMouseDownCursor = new Cursor(PdnResources.GetResourceStream("Cursors.GenericToolCursorMouseDown.cur"));
  413. Cursor = _lineToolCursor;
  414. _lineToolIcon = Image;
  415. _moveNubs = new MoveNubRenderer[ControlPointCount];
  416. for (int i = 0; i < _moveNubs.Length; ++i)
  417. {
  418. _moveNubs[i] = new MoveNubRenderer(RendererList) {Visible = false};
  419. RendererList.Add(_moveNubs[i], false);
  420. }
  421. AppEnvironment.PrimaryColorChanged += RenderShapeBecauseOfEvent;
  422. AppEnvironment.SecondaryColorChanged += RenderShapeBecauseOfEvent;
  423. AppEnvironment.AntiAliasingChanged += RenderShapeBecauseOfEvent;
  424. AppEnvironment.AlphaBlendingChanged += RenderShapeBecauseOfEvent;
  425. AppEnvironment.BrushInfoChanged += RenderShapeBecauseOfEvent;
  426. AppEnvironment.PenInfoChanged += RenderShapeBecauseOfEvent;
  427. AppWorkspace.UnitsChanged += RenderShapeBecauseOfEvent;
  428. base.OnActivate();
  429. }
  430. private void RenderShapeBecauseOfEvent(object sender, EventArgs e)
  431. {
  432. if (_inCurveMode)
  433. {
  434. RenderShape();
  435. }
  436. }
  437. protected override void OnDeactivate()
  438. {
  439. base.OnDeactivate();
  440. AppEnvironment.PrimaryColorChanged -= RenderShapeBecauseOfEvent;
  441. AppEnvironment.SecondaryColorChanged -= RenderShapeBecauseOfEvent;
  442. AppEnvironment.AntiAliasingChanged -= RenderShapeBecauseOfEvent;
  443. AppEnvironment.AlphaBlendingChanged -= RenderShapeBecauseOfEvent;
  444. AppEnvironment.BrushInfoChanged -= RenderShapeBecauseOfEvent;
  445. AppEnvironment.PenInfoChanged -= RenderShapeBecauseOfEvent;
  446. AppWorkspace.UnitsChanged -= RenderShapeBecauseOfEvent;
  447. for (int i = 0; i < _moveNubs.Length; ++i)
  448. {
  449. RendererList.Remove(_moveNubs[i]);
  450. _moveNubs[i].Dispose();
  451. _moveNubs[i] = null;
  452. }
  453. _moveNubs = null;
  454. if (_lineToolCursor != null)
  455. {
  456. _lineToolCursor.Dispose();
  457. _lineToolCursor = null;
  458. }
  459. if (_lineToolMouseDownCursor == null) return;
  460. _lineToolMouseDownCursor.Dispose();
  461. _lineToolMouseDownCursor = null;
  462. }
  463. public LineTool(DocumentWorkspace documentWorkspace)
  464. : base(documentWorkspace,
  465. PdnResources.GetImageResource("Icons.LineToolIcon.png"),
  466. PdnResources.GetString("LineTool.Name"),
  467. PdnResources.GetString("LineTool.HelpText"),
  468. ToolBarConfigItems.None | ToolBarConfigItems.PenCaps,
  469. ToolBarConfigItems.ShapeType)
  470. {
  471. ForceShapeDrawType = true;
  472. ForcedShapeDrawType = ShapeDrawType.Outline;
  473. UseDashStyle = true;
  474. }
  475. }
  476. }