RegionScenario.cs 13 KB

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