#nullable enable 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 appWindow = new () { Title = GetQuitKeyAndName (), TabStop = TabBehavior.TabGroup }; appWindow.Padding!.Thickness = new (1); var tools = new ToolsView { Title = "Tools", X = Pos.AnchorEnd (), Y = 2 }; tools.CurrentAttribute = appWindow.GetAttributeForRole (VisualRole.HotNormal); tools.SetStyle += b => { _drawStyle = b; appWindow.SetNeedsDraw (); }; tools.RegionOpChanged += (s, e) => { _regionOp = e; }; //tools.AddLayer += () => canvas.AddLayer (); appWindow.Add (tools); // Add drag handling to window appWindow.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) { appWindow.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; } appWindow.SetNeedsDraw (); } }; // Draw the regions appWindow.DrawingContent += (s, e) => { // Draw all regions with single line style //_region.FillRectangles (_attribute.Value, _fillRune); switch (_drawStyle) { case RegionDrawStyles.FillOnly: _region.FillRectangles (appWindow.App?.Driver, tools.CurrentAttribute!.Value, _previewFillRune); break; case RegionDrawStyles.InnerBoundaries: _region.DrawBoundaries (appWindow.LineCanvas, LineStyle.Single, tools.CurrentAttribute); _region.FillRectangles (appWindow.App?.Driver, tools.CurrentAttribute!.Value, (Rune)' '); break; case RegionDrawStyles.OuterBoundary: _region.DrawOuterBoundary (appWindow.LineCanvas, LineStyle.Single, tools.CurrentAttribute); _region.FillRectangles (appWindow.App?.Driver, tools.CurrentAttribute!.Value, (Rune)' '); break; } // If currently dragging, draw preview rectangle if (_isDragging && _dragStart.HasValue) { Point currentMousePos = appWindow.App!.Mouse.LastMousePosition!.Value; Rectangle previewRect = GetRectFromPoints (_dragStart.Value, currentMousePos); var previewRegion = new Region (previewRect); previewRegion.FillRectangles (appWindow.App.Driver, tools.CurrentAttribute!.Value, (Rune)' '); previewRegion.DrawBoundaries ( appWindow.LineCanvas, LineStyle.Dashed, new ( tools.CurrentAttribute!.Value.Foreground.GetBrighterColor (), tools.CurrentAttribute!.Value.Background)); } }; Application.Run (appWindow); // Clean up appWindow.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 OptionSelector? _stylePicker; private OptionSelector? _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, AssignHotKeys = true }; _stylePicker.BorderStyle = LineStyle.Single; _stylePicker.Border!.Thickness = new (0, 1, 0, 0); _stylePicker.Title = "Draw Style"; _stylePicker.ValueChanged += (s, a) => { SetStyle?.Invoke ((RegionDrawStyles)a.Value!); }; _stylePicker.Value = RegionDrawStyles.FillOnly; _regionOpSelector = new () { X = 0, Y = Pos.Bottom (_stylePicker) + 1, AssignHotKeys = true }; _regionOpSelector.ValueChanged += (s, a) => { if (a.Value is { }) { RegionOpChanged?.Invoke (this, (RegionOp)a.Value); } }; _regionOpSelector.Value = 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 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 (DrawContext? context) { 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 (); } } }