#nullable enable using System; using System.Collections.Generic; using System.Linq; using System.Text; using UICatalog; using UICatalog.Scenarios; /// /// Demonstrates creating and drawing regions through mouse dragging. /// [ScenarioMetadata ("Regions", "Region Tester")] [ScenarioCategory ("Mouse and Keyboard")] [ScenarioCategory ("Drawing")] public class RegionScenario : Scenario { private readonly Region _region = new (); private Point? _dragStart; private bool _isDragging; private readonly Rune? _previewFillRune = Glyphs.Stipple; private RegionDrawStyles _drawStyle; private RegionOp _regionOp; public override void Main () { Application.Init (); Window app = new () { Title = GetQuitKeyAndName (), TabStop = TabBehavior.TabGroup }; app.Padding!.Thickness = new (1); var tools = new ToolsView { Title = "Tools", X = Pos.AnchorEnd (), Y = 2 }; tools.CurrentAttribute = app.GetAttributeForRole (VisualRole.HotNormal); tools.SetStyle += b => { _drawStyle = (RegionDrawStyles)b; app.SetNeedsDraw (); }; tools.RegionOpChanged += (s, e) => { _regionOp = e; }; //tools.AddLayer += () => canvas.AddLayer (); app.Add (tools); // Add drag handling to window app.MouseEvent += (s, e) => { if (e.Flags.HasFlag (MouseFlags.Button1Pressed)) { if (!e.Flags.HasFlag (MouseFlags.ReportMousePosition)) { // Start drag _dragStart = e.ScreenPosition; _isDragging = true; } else { // Drag if (_isDragging && _dragStart.HasValue) { app.SetNeedsDraw (); } } } if (e.Flags.HasFlag (MouseFlags.Button1Released)) { if (_isDragging && _dragStart.HasValue) { // Add the new region AddRectangleFromPoints (_dragStart.Value, e.ScreenPosition, _regionOp); _isDragging = false; _dragStart = null; } app.SetNeedsDraw (); } }; // Draw the regions app.DrawingContent += (s, e) => { // Draw all regions with single line style //_region.FillRectangles (_attribute.Value, _fillRune); switch (_drawStyle) { case RegionDrawStyles.FillOnly: _region.FillRectangles (tools.CurrentAttribute!.Value, _previewFillRune); break; case RegionDrawStyles.InnerBoundaries: _region.DrawBoundaries (app.LineCanvas, LineStyle.Single, tools.CurrentAttribute); _region.FillRectangles (tools.CurrentAttribute!.Value, (Rune)' '); break; case RegionDrawStyles.OuterBoundary: _region.DrawOuterBoundary (app.LineCanvas, LineStyle.Single, tools.CurrentAttribute); _region.FillRectangles (tools.CurrentAttribute!.Value, (Rune)' '); break; } // If currently dragging, draw preview rectangle if (_isDragging && _dragStart.HasValue) { Point currentMousePos = Application.GetLastMousePosition ()!.Value; Rectangle previewRect = GetRectFromPoints (_dragStart.Value, currentMousePos); var previewRegion = new Region (previewRect); previewRegion.FillRectangles (tools.CurrentAttribute!.Value, (Rune)' '); previewRegion.DrawBoundaries ( app.LineCanvas, LineStyle.Dashed, new ( tools.CurrentAttribute!.Value.Foreground.GetBrighterColor (), tools.CurrentAttribute!.Value.Background)); } }; Application.Run (app); // Clean up app.Dispose (); Application.Shutdown (); } private void AddRectangleFromPoints (Point start, Point end, RegionOp op) { Rectangle rect = GetRectFromPoints (start, end); var region = new Region (rect); _region.Combine (region, op); // Or RegionOp.MinimalUnion if you want minimal rectangles } private Rectangle GetRectFromPoints (Point start, Point end) { int left = Math.Min (start.X, end.X); int top = Math.Min (start.Y, end.Y); int right = Math.Max (start.X, end.X); int bottom = Math.Max (start.Y, end.Y); // Ensure minimum width and height of 1 int width = Math.Max (1, right - left + 1); int height = Math.Max (1, bottom - top + 1); return new (left, top, width, height); } } public enum RegionDrawStyles { FillOnly = 0, InnerBoundaries = 1, OuterBoundary = 2 } public class ToolsView : Window { //private Button _addLayerBtn; private readonly AttributeView _attributeView = new (); private RadioGroup? _stylePicker; private RegionOpSelector? _regionOpSelector; public Attribute? CurrentAttribute { get => _attributeView.Value; set => _attributeView.Value = value; } public ToolsView () { BorderStyle = LineStyle.Dotted; Border!.Thickness = new (1, 2, 1, 1); Height = Dim.Auto (); Width = Dim.Auto (); } //public event Action AddLayer; public override void BeginInit () { base.BeginInit (); _attributeView.ValueChanged += (s, e) => AttributeChanged?.Invoke (this, e); _stylePicker = new () { Width = Dim.Fill (), X = 0, Y = Pos.Bottom (_attributeView) + 1, RadioLabels = Enum.GetNames ().Select (n => n = "_" + n).ToArray () }; _stylePicker.BorderStyle = LineStyle.Single; _stylePicker.Border!.Thickness = new (0, 1, 0, 0); _stylePicker.Title = "Draw Style"; _stylePicker.SelectedItemChanged += (s, a) => { SetStyle?.Invoke ((LineStyle)a.SelectedItem!); }; _stylePicker.SelectedItem = (int)RegionDrawStyles.FillOnly; _regionOpSelector = new () { X = 0, Y = Pos.Bottom (_stylePicker) + 1 }; _regionOpSelector.SelectedItemChanged += (s, a) => { RegionOpChanged?.Invoke (this, a); }; _regionOpSelector.SelectedItem = RegionOp.MinimalUnion; //_addLayerBtn = new () { Text = "New Layer", X = Pos.Center (), Y = Pos.Bottom (_stylePicker) }; //_addLayerBtn.Accepting += (s, a) => AddLayer?.Invoke (); Add (_attributeView, _stylePicker, _regionOpSelector); //, _addLayerBtn); } public event EventHandler? AttributeChanged; public event EventHandler? RegionOpChanged; public event Action? SetStyle; } public class RegionOpSelector : View { private readonly RadioGroup _radioGroup; public RegionOpSelector () { Width = Dim.Auto (); Height = Dim.Auto (); BorderStyle = LineStyle.Single; Border!.Thickness = new (0, 1, 0, 0); Title = "RegionOp"; _radioGroup = new () { X = 0, Y = 0, RadioLabels = Enum.GetNames ().Select (n => n = "_" + n).ToArray () }; _radioGroup.SelectedItemChanged += (s, a) => { SelectedItemChanged?.Invoke (this, (RegionOp)a.SelectedItem!); }; Add (_radioGroup); } public event EventHandler? SelectedItemChanged; public RegionOp SelectedItem { get => (RegionOp)_radioGroup.SelectedItem; set => _radioGroup.SelectedItem = (int)value; } } public class AttributeView : View { public event EventHandler? ValueChanged; private Attribute? _value; public Attribute? Value { get => _value; set { _value = value; ValueChanged?.Invoke (this, value); } } private static readonly HashSet<(int, int)> _foregroundPoints = [ (0, 0), (1, 0), (2, 0), (0, 1), (1, 1), (2, 1) ]; private static readonly HashSet<(int, int)> _backgroundPoints = [ (3, 1), (1, 2), (2, 2), (3, 2) ]; public AttributeView () { Width = Dim.Fill (); Height = 4; BorderStyle = LineStyle.Single; Border!.Thickness = new (0, 1, 0, 0); Title = "Attribute"; } /// protected override bool OnDrawingContent () { Color fg = Value?.Foreground ?? Color.Black; Color bg = Value?.Background ?? Color.Black; bool isTransparentFg = fg == GetAttributeForRole (VisualRole.Normal).Background; bool isTransparentBg = bg == GetAttributeForRole (VisualRole.Normal).Background; SetAttribute (new (fg, isTransparentFg ? Color.Gray : fg)); // Square of foreground color foreach ((int, int) point in _foregroundPoints) { // Make pattern like this when it is same color as background of control /*▓▒ ▒▓*/ Rune rune; if (isTransparentFg) { rune = (Rune)(point.Item1 % 2 == point.Item2 % 2 ? '▓' : '▒'); } else { rune = (Rune)'█'; } AddRune (point.Item1, point.Item2, rune); } SetAttribute (new (bg, isTransparentBg ? Color.Gray : bg)); // Square of background color foreach ((int, int) point in _backgroundPoints) { // Make pattern like this when it is same color as background of control /*▓▒ ▒▓*/ Rune rune; if (isTransparentBg) { rune = (Rune)(point.Item1 % 2 == point.Item2 % 2 ? '▓' : '▒'); } else { rune = (Rune)'█'; } AddRune (point.Item1, point.Item2, rune); } return true; } /// protected override bool OnMouseEvent (MouseEventArgs mouseEvent) { if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Clicked)) { if (IsForegroundPoint (mouseEvent.Position.X, mouseEvent.Position.Y)) { ClickedInForeground (); } else if (IsBackgroundPoint (mouseEvent.Position.X, mouseEvent.Position.Y)) { ClickedInBackground (); } } mouseEvent.Handled = true; return mouseEvent.Handled; } private bool IsForegroundPoint (int x, int y) { return _foregroundPoints.Contains ((x, y)); } private bool IsBackgroundPoint (int x, int y) { return _backgroundPoints.Contains ((x, y)); } private void ClickedInBackground () { if (LineDrawing.PromptForColor ("Background", Value!.Value.Background, out Color newColor)) { Value = new (Value!.Value.Foreground, newColor); SetNeedsDraw (); } } private void ClickedInForeground () { if (LineDrawing.PromptForColor ("Foreground", Value!.Value.Foreground, out Color newColor)) { Value = new (newColor, Value!.Value.Background); SetNeedsDraw (); } } }