LineDrawing.cs 14 KB

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