RegionScenario.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. #nullable enable
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Text;
  6. using UICatalog;
  7. using UICatalog.Scenarios;
  8. /// <summary>
  9. /// Demonstrates creating and drawing regions through mouse dragging.
  10. /// </summary>
  11. [ScenarioMetadata ("Regions", "Region Tester")]
  12. [ScenarioCategory ("Mouse and Keyboard")]
  13. [ScenarioCategory ("Drawing")]
  14. public class RegionScenario : Scenario
  15. {
  16. private readonly Region _region = new ();
  17. private Point? _dragStart;
  18. private bool _isDragging;
  19. private readonly Rune? _previewFillRune = Glyphs.Stipple;
  20. private RegionDrawStyles _drawStyle;
  21. private RegionOp _regionOp;
  22. public override void Main ()
  23. {
  24. Application.Init ();
  25. Window app = new ()
  26. {
  27. Title = GetQuitKeyAndName (),
  28. TabStop = TabBehavior.TabGroup
  29. };
  30. app.Padding!.Thickness = new (1);
  31. var tools = new ToolsView { Title = "Tools", X = Pos.AnchorEnd (), Y = 2 };
  32. tools.CurrentAttribute = app.GetAttributeForRole (VisualRole.HotNormal);
  33. tools.SetStyle += b =>
  34. {
  35. _drawStyle = (RegionDrawStyles)b;
  36. app.SetNeedsDraw ();
  37. };
  38. tools.RegionOpChanged += (s, e) => { _regionOp = e; };
  39. //tools.AddLayer += () => canvas.AddLayer ();
  40. app.Add (tools);
  41. // Add drag handling to window
  42. app.MouseEvent += (s, e) =>
  43. {
  44. if (e.Flags.HasFlag (MouseFlags.Button1Pressed))
  45. {
  46. if (!e.Flags.HasFlag (MouseFlags.ReportMousePosition))
  47. { // Start drag
  48. _dragStart = e.ScreenPosition;
  49. _isDragging = true;
  50. }
  51. else
  52. {
  53. // Drag
  54. if (_isDragging && _dragStart.HasValue)
  55. {
  56. app.SetNeedsDraw ();
  57. }
  58. }
  59. }
  60. if (e.Flags.HasFlag (MouseFlags.Button1Released))
  61. {
  62. if (_isDragging && _dragStart.HasValue)
  63. {
  64. // Add the new region
  65. AddRectangleFromPoints (_dragStart.Value, e.ScreenPosition, _regionOp);
  66. _isDragging = false;
  67. _dragStart = null;
  68. }
  69. app.SetNeedsDraw ();
  70. }
  71. };
  72. // Draw the regions
  73. app.DrawingContent += (s, e) =>
  74. {
  75. // Draw all regions with single line style
  76. //_region.FillRectangles (_attribute.Value, _fillRune);
  77. switch (_drawStyle)
  78. {
  79. case RegionDrawStyles.FillOnly:
  80. _region.FillRectangles (tools.CurrentAttribute!.Value, _previewFillRune);
  81. break;
  82. case RegionDrawStyles.InnerBoundaries:
  83. _region.DrawBoundaries (app.LineCanvas, LineStyle.Single, tools.CurrentAttribute);
  84. _region.FillRectangles (tools.CurrentAttribute!.Value, (Rune)' ');
  85. break;
  86. case RegionDrawStyles.OuterBoundary:
  87. _region.DrawOuterBoundary (app.LineCanvas, LineStyle.Single, tools.CurrentAttribute);
  88. _region.FillRectangles (tools.CurrentAttribute!.Value, (Rune)' ');
  89. break;
  90. }
  91. // If currently dragging, draw preview rectangle
  92. if (_isDragging && _dragStart.HasValue)
  93. {
  94. Point currentMousePos = Application.GetLastMousePosition ()!.Value;
  95. Rectangle previewRect = GetRectFromPoints (_dragStart.Value, currentMousePos);
  96. var previewRegion = new Region (previewRect);
  97. previewRegion.FillRectangles (tools.CurrentAttribute!.Value, (Rune)' ');
  98. previewRegion.DrawBoundaries (
  99. app.LineCanvas,
  100. LineStyle.Dashed,
  101. new (
  102. tools.CurrentAttribute!.Value.Foreground.GetBrighterColor (),
  103. tools.CurrentAttribute!.Value.Background));
  104. }
  105. };
  106. Application.Run (app);
  107. // Clean up
  108. app.Dispose ();
  109. Application.Shutdown ();
  110. }
  111. private void AddRectangleFromPoints (Point start, Point end, RegionOp op)
  112. {
  113. Rectangle rect = GetRectFromPoints (start, end);
  114. var region = new Region (rect);
  115. _region.Combine (region, op); // Or RegionOp.MinimalUnion if you want minimal rectangles
  116. }
  117. private Rectangle GetRectFromPoints (Point start, Point end)
  118. {
  119. int left = Math.Min (start.X, end.X);
  120. int top = Math.Min (start.Y, end.Y);
  121. int right = Math.Max (start.X, end.X);
  122. int bottom = Math.Max (start.Y, end.Y);
  123. // Ensure minimum width and height of 1
  124. int width = Math.Max (1, right - left + 1);
  125. int height = Math.Max (1, bottom - top + 1);
  126. return new (left, top, width, height);
  127. }
  128. }
  129. public enum RegionDrawStyles
  130. {
  131. FillOnly = 0,
  132. InnerBoundaries = 1,
  133. OuterBoundary = 2
  134. }
  135. public class ToolsView : Window
  136. {
  137. //private Button _addLayerBtn;
  138. private readonly AttributeView _attributeView = new ();
  139. private RadioGroup? _stylePicker;
  140. private RegionOpSelector? _regionOpSelector;
  141. public Attribute? CurrentAttribute
  142. {
  143. get => _attributeView.Value;
  144. set => _attributeView.Value = value;
  145. }
  146. public ToolsView ()
  147. {
  148. BorderStyle = LineStyle.Dotted;
  149. Border!.Thickness = new (1, 2, 1, 1);
  150. Height = Dim.Auto ();
  151. Width = Dim.Auto ();
  152. }
  153. //public event Action AddLayer;
  154. public override void BeginInit ()
  155. {
  156. base.BeginInit ();
  157. _attributeView.ValueChanged += (s, e) => AttributeChanged?.Invoke (this, e);
  158. _stylePicker = new ()
  159. {
  160. Width = Dim.Fill (),
  161. X = 0, Y = Pos.Bottom (_attributeView) + 1, RadioLabels = Enum.GetNames<RegionDrawStyles> ().Select (n => n = "_" + n).ToArray ()
  162. };
  163. _stylePicker.BorderStyle = LineStyle.Single;
  164. _stylePicker.Border!.Thickness = new (0, 1, 0, 0);
  165. _stylePicker.Title = "Draw Style";
  166. _stylePicker.SelectedItemChanged += (s, a) => { SetStyle?.Invoke ((LineStyle)a.SelectedItem!); };
  167. _stylePicker.SelectedItem = (int)RegionDrawStyles.FillOnly;
  168. _regionOpSelector = new ()
  169. {
  170. X = 0,
  171. Y = Pos.Bottom (_stylePicker) + 1
  172. };
  173. _regionOpSelector.SelectedItemChanged += (s, a) => { RegionOpChanged?.Invoke (this, a); };
  174. _regionOpSelector.SelectedItem = RegionOp.MinimalUnion;
  175. //_addLayerBtn = new () { Text = "New Layer", X = Pos.Center (), Y = Pos.Bottom (_stylePicker) };
  176. //_addLayerBtn.Accepting += (s, a) => AddLayer?.Invoke ();
  177. Add (_attributeView, _stylePicker, _regionOpSelector); //, _addLayerBtn);
  178. }
  179. public event EventHandler<Attribute?>? AttributeChanged;
  180. public event EventHandler<RegionOp>? RegionOpChanged;
  181. public event Action<LineStyle>? SetStyle;
  182. }
  183. public class RegionOpSelector : View
  184. {
  185. private readonly RadioGroup _radioGroup;
  186. public RegionOpSelector ()
  187. {
  188. Width = Dim.Auto ();
  189. Height = Dim.Auto ();
  190. BorderStyle = LineStyle.Single;
  191. Border!.Thickness = new (0, 1, 0, 0);
  192. Title = "RegionOp";
  193. _radioGroup = new ()
  194. {
  195. X = 0,
  196. Y = 0,
  197. RadioLabels = Enum.GetNames<RegionOp> ().Select (n => n = "_" + n).ToArray ()
  198. };
  199. _radioGroup.SelectedItemChanged += (s, a) => { SelectedItemChanged?.Invoke (this, (RegionOp)a.SelectedItem!); };
  200. Add (_radioGroup);
  201. }
  202. public event EventHandler<RegionOp>? SelectedItemChanged;
  203. public RegionOp SelectedItem
  204. {
  205. get => (RegionOp)_radioGroup.SelectedItem;
  206. set => _radioGroup.SelectedItem = (int)value;
  207. }
  208. }
  209. public class AttributeView : View
  210. {
  211. public event EventHandler<Attribute?>? ValueChanged;
  212. private Attribute? _value;
  213. public Attribute? Value
  214. {
  215. get => _value;
  216. set
  217. {
  218. _value = value;
  219. ValueChanged?.Invoke (this, value);
  220. }
  221. }
  222. private static readonly HashSet<(int, int)> _foregroundPoints =
  223. [
  224. (0, 0), (1, 0), (2, 0),
  225. (0, 1), (1, 1), (2, 1)
  226. ];
  227. private static readonly HashSet<(int, int)> _backgroundPoints =
  228. [
  229. (3, 1),
  230. (1, 2), (2, 2), (3, 2)
  231. ];
  232. public AttributeView ()
  233. {
  234. Width = Dim.Fill ();
  235. Height = 4;
  236. BorderStyle = LineStyle.Single;
  237. Border!.Thickness = new (0, 1, 0, 0);
  238. Title = "Attribute";
  239. }
  240. /// <inheritdoc/>
  241. protected override bool OnDrawingContent ()
  242. {
  243. Color fg = Value?.Foreground ?? Color.Black;
  244. Color bg = Value?.Background ?? Color.Black;
  245. bool isTransparentFg = fg == GetAttributeForRole (VisualRole.Normal).Background;
  246. bool isTransparentBg = bg == GetAttributeForRole (VisualRole.Normal).Background;
  247. SetAttribute (new (fg, isTransparentFg ? Color.Gray : fg));
  248. // Square of foreground color
  249. foreach ((int, int) point in _foregroundPoints)
  250. {
  251. // Make pattern like this when it is same color as background of control
  252. /*▓▒
  253. ▒▓*/
  254. Rune rune;
  255. if (isTransparentFg)
  256. {
  257. rune = (Rune)(point.Item1 % 2 == point.Item2 % 2 ? '▓' : '▒');
  258. }
  259. else
  260. {
  261. rune = (Rune)'█';
  262. }
  263. AddRune (point.Item1, point.Item2, rune);
  264. }
  265. SetAttribute (new (bg, isTransparentBg ? Color.Gray : bg));
  266. // Square of background color
  267. foreach ((int, int) point in _backgroundPoints)
  268. {
  269. // Make pattern like this when it is same color as background of control
  270. /*▓▒
  271. ▒▓*/
  272. Rune rune;
  273. if (isTransparentBg)
  274. {
  275. rune = (Rune)(point.Item1 % 2 == point.Item2 % 2 ? '▓' : '▒');
  276. }
  277. else
  278. {
  279. rune = (Rune)'█';
  280. }
  281. AddRune (point.Item1, point.Item2, rune);
  282. }
  283. return true;
  284. }
  285. /// <inheritdoc/>
  286. protected override bool OnMouseEvent (MouseEventArgs mouseEvent)
  287. {
  288. if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Clicked))
  289. {
  290. if (IsForegroundPoint (mouseEvent.Position.X, mouseEvent.Position.Y))
  291. {
  292. ClickedInForeground ();
  293. }
  294. else if (IsBackgroundPoint (mouseEvent.Position.X, mouseEvent.Position.Y))
  295. {
  296. ClickedInBackground ();
  297. }
  298. }
  299. mouseEvent.Handled = true;
  300. return mouseEvent.Handled;
  301. }
  302. private bool IsForegroundPoint (int x, int y) { return _foregroundPoints.Contains ((x, y)); }
  303. private bool IsBackgroundPoint (int x, int y) { return _backgroundPoints.Contains ((x, y)); }
  304. private void ClickedInBackground ()
  305. {
  306. if (LineDrawing.PromptForColor ("Background", Value!.Value.Background, out Color newColor))
  307. {
  308. Value = new (Value!.Value.Foreground, newColor);
  309. SetNeedsDraw ();
  310. }
  311. }
  312. private void ClickedInForeground ()
  313. {
  314. if (LineDrawing.PromptForColor ("Foreground", Value!.Value.Foreground, out Color newColor))
  315. {
  316. Value = new (newColor, Value!.Value.Background);
  317. SetNeedsDraw ();
  318. }
  319. }
  320. }