RegionScenario.cs 12 KB

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