RegionScenario.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  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. public Rune? _previewFillRune = Glyphs.Stipple;
  21. public Rune? _fillRune = Glyphs.Dot;
  22. private RegionDrawStyles _drawStyle;
  23. private RegionOp _regionOp;
  24. public override void Main ()
  25. {
  26. Application.Init ();
  27. Window app = new ()
  28. {
  29. Title = GetQuitKeyAndName (),
  30. TabStop = TabBehavior.TabGroup,
  31. };
  32. app.Padding.Thickness = new (1);
  33. var tools = new ToolsView { Title = "Tools", X = Pos.AnchorEnd (), Y = 2 };
  34. tools.CurrentAttribute = app.ColorScheme.HotNormal;
  35. tools.SetStyle += b =>
  36. {
  37. _drawStyle = (RegionDrawStyles)b;
  38. app.SetNeedsDraw();
  39. };
  40. tools.RegionOpChanged += (s, e) =>
  41. {
  42. _regionOp = e;
  43. };
  44. //tools.AddLayer += () => canvas.AddLayer ();
  45. app.Add (tools);
  46. // Add drag handling to window
  47. app.MouseEvent += (s, e) =>
  48. {
  49. if (e.Flags.HasFlag (MouseFlags.Button1Pressed))
  50. {
  51. if (!e.Flags.HasFlag (MouseFlags.ReportMousePosition))
  52. { // Start drag
  53. _dragStart = e.ScreenPosition;
  54. _isDragging = true;
  55. }
  56. else
  57. {
  58. // Drag
  59. if (_isDragging && _dragStart.HasValue)
  60. {
  61. app.SetNeedsDraw ();
  62. }
  63. }
  64. }
  65. if (e.Flags.HasFlag (MouseFlags.Button1Released))
  66. {
  67. if (_isDragging && _dragStart.HasValue)
  68. {
  69. // Add the new region
  70. AddRectangleFromPoints (_dragStart.Value, e.ScreenPosition, _regionOp);
  71. _isDragging = false;
  72. _dragStart = null;
  73. }
  74. app.SetNeedsDraw ();
  75. }
  76. };
  77. // Draw the regions
  78. app.DrawingContent += (s, e) =>
  79. {
  80. // Draw all regions with single line style
  81. //_region.FillRectangles (_attribute.Value, _fillRune);
  82. switch (_drawStyle)
  83. {
  84. case RegionDrawStyles.FillOnly:
  85. _region.FillRectangles (tools.CurrentAttribute!.Value, _previewFillRune);
  86. break;
  87. case RegionDrawStyles.InnerBoundaries:
  88. _region.DrawBoundaries (app.LineCanvas, LineStyle.Single, tools.CurrentAttribute);
  89. _region.FillRectangles (tools.CurrentAttribute!.Value, (Rune)' ');
  90. break;
  91. case RegionDrawStyles.OuterBoundary:
  92. _region.DrawOuterBoundary (app.LineCanvas, LineStyle.Single, tools.CurrentAttribute);
  93. _region.FillRectangles (tools.CurrentAttribute!.Value, (Rune)' ');
  94. break;
  95. }
  96. // If currently dragging, draw preview rectangle
  97. if (_isDragging && _dragStart.HasValue)
  98. {
  99. Point currentMousePos = Application.GetLastMousePosition ()!.Value;
  100. var previewRect = GetRectFromPoints (_dragStart.Value, currentMousePos);
  101. var previewRegion = new Region (previewRect);
  102. previewRegion.FillRectangles (tools.CurrentAttribute!.Value, (Rune)' ');
  103. previewRegion.DrawBoundaries (app.LineCanvas, LineStyle.Dashed, new (tools.CurrentAttribute!.Value.Foreground.GetHighlightColor(), 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. var 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 Rectangle (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 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 == GetNormalColor ().Background;
  246. bool isTransparentBg = bg == GetNormalColor ().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. }