2
0

LineDrawing.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. namespace UICatalog.Scenarios;
  6. public interface ITool
  7. {
  8. void OnMouseEvent (DrawingArea area, MouseEventArgs mouseEvent);
  9. }
  10. internal class DrawLineTool : ITool
  11. {
  12. private StraightLine _currentLine;
  13. public LineStyle LineStyle { get; set; } = LineStyle.Single;
  14. /// <inheritdoc/>
  15. public void OnMouseEvent (DrawingArea area, MouseEventArgs mouseEvent)
  16. {
  17. if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed))
  18. {
  19. if (_currentLine == null)
  20. {
  21. // Mouse pressed down
  22. _currentLine = new (
  23. mouseEvent.Position,
  24. 0,
  25. Orientation.Vertical,
  26. LineStyle,
  27. area.CurrentAttribute
  28. );
  29. area.CurrentLayer.AddLine (_currentLine);
  30. }
  31. else
  32. {
  33. // Mouse dragged
  34. Point start = _currentLine.Start;
  35. Point end = mouseEvent.Position;
  36. var orientation = Orientation.Vertical;
  37. int length = end.Y - start.Y;
  38. // if line is wider than it is tall switch to horizontal
  39. if (Math.Abs (start.X - end.X) > Math.Abs (start.Y - end.Y))
  40. {
  41. orientation = Orientation.Horizontal;
  42. length = end.X - start.X;
  43. }
  44. if (length > 0)
  45. {
  46. length++;
  47. }
  48. else
  49. {
  50. length--;
  51. }
  52. _currentLine.Length = length;
  53. _currentLine.Orientation = orientation;
  54. area.CurrentLayer.ClearCache ();
  55. area.SetNeedsDraw ();
  56. }
  57. }
  58. else
  59. {
  60. // Mouse released
  61. if (_currentLine != null)
  62. {
  63. if (_currentLine.Length == 0)
  64. {
  65. _currentLine.Length = 1;
  66. }
  67. if (_currentLine.Style == LineStyle.None)
  68. {
  69. // Treat none as eraser
  70. int idx = area.Layers.IndexOf (area.CurrentLayer);
  71. area.Layers.Remove (area.CurrentLayer);
  72. area.CurrentLayer = new (
  73. area.CurrentLayer.Lines.Exclude (
  74. _currentLine.Start,
  75. _currentLine.Length,
  76. _currentLine.Orientation
  77. )
  78. );
  79. area.Layers.Insert (idx, area.CurrentLayer);
  80. }
  81. _currentLine = null;
  82. area.ClearUndo ();
  83. area.SetNeedsDraw ();
  84. }
  85. }
  86. mouseEvent.Handled = true;
  87. }
  88. }
  89. [ScenarioMetadata ("Line Drawing", "Demonstrates LineCanvas.")]
  90. [ScenarioCategory ("Controls")]
  91. [ScenarioCategory ("Drawing")]
  92. public class LineDrawing : Scenario
  93. {
  94. public override void Main ()
  95. {
  96. Application.Init ();
  97. var win = new Window { Title = GetQuitKeyAndName () };
  98. var canvas = new DrawingArea { X = 0, Y = 0, Width = Dim.Fill (), Height = Dim.Fill () };
  99. var tools = new ToolsView { Title = "Tools", X = Pos.Right (canvas) - 20, Y = 2 };
  100. tools.ColorChanged += (s, e) => canvas.SetCurrentAttribute (e);
  101. tools.SetStyle += b => canvas.CurrentTool = new DrawLineTool { LineStyle = b };
  102. tools.AddLayer += () => canvas.AddLayer ();
  103. win.Add (canvas);
  104. win.Add (tools);
  105. tools.CurrentColor = canvas.GetAttributeForRole (VisualRole.Normal);
  106. canvas.CurrentAttribute = tools.CurrentColor;
  107. win.KeyDown += (s, e) => { e.Handled = canvas.NewKeyDownEvent (e); };
  108. Application.Run (win);
  109. win.Dispose ();
  110. Application.Shutdown ();
  111. }
  112. public static bool PromptForColor (string title, Color current, out Color newColor)
  113. {
  114. var accept = false;
  115. var d = new Dialog
  116. {
  117. Title = title,
  118. Width = Application.Force16Colors ? 35 : Dim.Auto (DimAutoStyle.Auto, Dim.Percent (80), Dim.Percent (90)),
  119. Height = 10
  120. };
  121. var btnOk = new Button
  122. {
  123. X = Pos.Center () - 5,
  124. Y = Application.Force16Colors ? 6 : 4,
  125. Text = "Ok",
  126. Width = Dim.Auto (),
  127. IsDefault = true
  128. };
  129. btnOk.Accepting += (s, e) =>
  130. {
  131. accept = true;
  132. e.Handled = true;
  133. Application.RequestStop ();
  134. };
  135. var btnCancel = new Button
  136. {
  137. X = Pos.Center () + 5,
  138. Y = 4,
  139. Text = "Cancel",
  140. Width = Dim.Auto ()
  141. };
  142. btnCancel.Accepting += (s, e) =>
  143. {
  144. e.Handled = true;
  145. Application.RequestStop ();
  146. };
  147. d.Add (btnOk);
  148. d.Add (btnCancel);
  149. d.AddButton (btnOk);
  150. d.AddButton (btnCancel);
  151. View cp;
  152. if (Application.Force16Colors)
  153. {
  154. cp = new ColorPicker16
  155. {
  156. SelectedColor = current.GetClosestNamedColor16 (),
  157. Width = Dim.Fill ()
  158. };
  159. }
  160. else
  161. {
  162. cp = new ColorPicker
  163. {
  164. SelectedColor = current,
  165. Width = Dim.Fill (),
  166. Style = new () { ShowColorName = true, ShowTextFields = true }
  167. };
  168. ((ColorPicker)cp).ApplyStyleChanges ();
  169. }
  170. d.Add (cp);
  171. Application.Run (d);
  172. d.Dispose ();
  173. newColor = Application.Force16Colors ? ((ColorPicker16)cp).SelectedColor : ((ColorPicker)cp).SelectedColor;
  174. return accept;
  175. }
  176. }
  177. public class ToolsView : Window
  178. {
  179. private Button _addLayerBtn;
  180. private readonly AttributeView _colors;
  181. private OptionSelector<LineStyle> _stylePicker;
  182. public Attribute CurrentColor
  183. {
  184. get => _colors.Value;
  185. set => _colors.Value = value;
  186. }
  187. public ToolsView ()
  188. {
  189. BorderStyle = LineStyle.Dotted;
  190. Border.Thickness = new (1, 2, 1, 1);
  191. Initialized += ToolsView_Initialized;
  192. _colors = new ();
  193. }
  194. public event Action AddLayer;
  195. public override void BeginInit ()
  196. {
  197. base.BeginInit ();
  198. _colors.ValueChanged += (s, e) => ColorChanged?.Invoke (this, e);
  199. _stylePicker = new ()
  200. {
  201. X = 0, Y = Pos.Bottom (_colors), AssignHotKeys = true
  202. };
  203. _stylePicker.ValueChanged += (s, a) =>
  204. {
  205. if (a.Value is { })
  206. {
  207. SetStyle?.Invoke ((LineStyle)a.Value);
  208. }
  209. };
  210. _stylePicker.Value = LineStyle.Single;
  211. _addLayerBtn = new () { Text = "New Layer", X = Pos.Center (), Y = Pos.Bottom (_stylePicker) };
  212. _addLayerBtn.Accepting += (s, a) => AddLayer?.Invoke ();
  213. Add (_colors, _stylePicker, _addLayerBtn);
  214. }
  215. public event EventHandler<Attribute> ColorChanged;
  216. public event Action<LineStyle> SetStyle;
  217. private void ToolsView_Initialized (object sender, EventArgs e)
  218. {
  219. Width = Math.Max (_colors.Frame.Width, _stylePicker.Frame.Width) + GetAdornmentsThickness ().Horizontal;
  220. Height = _colors.Frame.Height + _stylePicker.Frame.Height + _addLayerBtn.Frame.Height + GetAdornmentsThickness ().Vertical;
  221. }
  222. }
  223. public class DrawingArea : View
  224. {
  225. public readonly List<LineCanvas> Layers = new ();
  226. private readonly Stack<StraightLine> _undoHistory = new ();
  227. public Attribute CurrentAttribute { get; set; }
  228. public LineCanvas CurrentLayer { get; set; }
  229. public ITool CurrentTool { get; set; } = new DrawLineTool ();
  230. public DrawingArea () { AddLayer (); }
  231. protected override bool OnDrawingContent ()
  232. {
  233. foreach (LineCanvas canvas in Layers)
  234. {
  235. foreach (KeyValuePair<Point, Cell?> c in canvas.GetCellMap ())
  236. {
  237. if (c.Value is { })
  238. {
  239. SetCurrentAttribute (c.Value.Value.Attribute ?? GetAttributeForRole (VisualRole.Normal));
  240. // TODO: #2616 - Support combining sequences that don't normalize
  241. AddStr (c.Key.X, c.Key.Y, c.Value.Value.Grapheme);
  242. }
  243. }
  244. }
  245. // TODO: This is a hack to work around overlapped views not drawing correctly.
  246. // without this the toolbox disappears
  247. SuperView?.SetNeedsLayout ();
  248. return true;
  249. }
  250. //// BUGBUG: Why is this not handled by a key binding???
  251. protected override bool OnKeyDown (Key e)
  252. {
  253. // BUGBUG: These should be implemented with key bindings
  254. if (e.KeyCode == (KeyCode.Z | KeyCode.CtrlMask))
  255. {
  256. StraightLine pop = CurrentLayer.RemoveLastLine ();
  257. if (pop != null)
  258. {
  259. _undoHistory.Push (pop);
  260. SetNeedsDraw ();
  261. return true;
  262. }
  263. }
  264. if (e.KeyCode == (KeyCode.Y | KeyCode.CtrlMask))
  265. {
  266. if (_undoHistory.Any ())
  267. {
  268. StraightLine pop = _undoHistory.Pop ();
  269. CurrentLayer.AddLine (pop);
  270. SetNeedsDraw ();
  271. return true;
  272. }
  273. }
  274. return false;
  275. }
  276. protected override bool OnMouseEvent (MouseEventArgs mouseEvent)
  277. {
  278. CurrentTool.OnMouseEvent (this, mouseEvent);
  279. return mouseEvent.Handled;
  280. }
  281. internal void AddLayer ()
  282. {
  283. CurrentLayer = new ();
  284. Layers.Add (CurrentLayer);
  285. }
  286. internal void SetCurrentAttribute (Attribute a) { CurrentAttribute = a; }
  287. public void ClearUndo () { _undoHistory.Clear (); }
  288. }
  289. public class AttributeView : View
  290. {
  291. public event EventHandler<Attribute> ValueChanged;
  292. private Attribute _value;
  293. public Attribute Value
  294. {
  295. get => _value;
  296. set
  297. {
  298. _value = value;
  299. ValueChanged?.Invoke (this, value);
  300. }
  301. }
  302. private static readonly HashSet<(int, int)> ForegroundPoints = new ()
  303. {
  304. (0, 0), (1, 0), (2, 0),
  305. (0, 1), (1, 1), (2, 1)
  306. };
  307. private static readonly HashSet<(int, int)> BackgroundPoints = new ()
  308. {
  309. (3, 1),
  310. (1, 2), (2, 2), (3, 2)
  311. };
  312. public AttributeView ()
  313. {
  314. Width = 4;
  315. Height = 3;
  316. }
  317. /// <inheritdoc/>
  318. protected override bool OnDrawingContent ()
  319. {
  320. Color fg = Value.Foreground;
  321. Color bg = Value.Background;
  322. bool isTransparentFg = fg == GetAttributeForRole (VisualRole.Normal).Background;
  323. bool isTransparentBg = bg == GetAttributeForRole (VisualRole.Normal).Background;
  324. SetAttribute (new (fg, isTransparentFg ? Color.Gray : fg));
  325. // Square of foreground color
  326. foreach ((int, int) point in ForegroundPoints)
  327. {
  328. // Make pattern like this when it is same color as background of control
  329. /*▓▒
  330. ▒▓*/
  331. Rune rune;
  332. if (isTransparentFg)
  333. {
  334. rune = (Rune)(point.Item1 % 2 == point.Item2 % 2 ? '▓' : '▒');
  335. }
  336. else
  337. {
  338. rune = (Rune)'█';
  339. }
  340. AddRune (point.Item1, point.Item2, rune);
  341. }
  342. SetAttribute (new (bg, isTransparentBg ? Color.Gray : bg));
  343. // Square of background color
  344. foreach ((int, int) point in BackgroundPoints)
  345. {
  346. // Make pattern like this when it is same color as background of control
  347. /*▓▒
  348. ▒▓*/
  349. Rune rune;
  350. if (isTransparentBg)
  351. {
  352. rune = (Rune)(point.Item1 % 2 == point.Item2 % 2 ? '▓' : '▒');
  353. }
  354. else
  355. {
  356. rune = (Rune)'█';
  357. }
  358. AddRune (point.Item1, point.Item2, rune);
  359. }
  360. return true;
  361. }
  362. /// <inheritdoc/>
  363. protected override bool OnMouseEvent (MouseEventArgs mouseEvent)
  364. {
  365. if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Clicked))
  366. {
  367. if (IsForegroundPoint (mouseEvent.Position.X, mouseEvent.Position.Y))
  368. {
  369. ClickedInForeground ();
  370. }
  371. else if (IsBackgroundPoint (mouseEvent.Position.X, mouseEvent.Position.Y))
  372. {
  373. ClickedInBackground ();
  374. }
  375. mouseEvent.Handled = true;
  376. }
  377. return mouseEvent.Handled;
  378. }
  379. private bool IsForegroundPoint (int x, int y) { return ForegroundPoints.Contains ((x, y)); }
  380. private bool IsBackgroundPoint (int x, int y) { return BackgroundPoints.Contains ((x, y)); }
  381. private void ClickedInBackground ()
  382. {
  383. if (LineDrawing.PromptForColor ("Background", Value.Background, out Color newColor))
  384. {
  385. Value = new (Value.Foreground, newColor, Value.Style);
  386. SetNeedsDraw ();
  387. }
  388. }
  389. private void ClickedInForeground ()
  390. {
  391. if (LineDrawing.PromptForColor ("Foreground", Value.Foreground, out Color newColor))
  392. {
  393. Value = new (newColor, Value.Background);
  394. SetNeedsDraw ();
  395. }
  396. }
  397. }