using System; using System.Collections.Generic; using System.Linq; using Terminal.Gui; namespace UICatalog.Scenarios; [ScenarioMetadata ("Line Drawing", "Demonstrates LineCanvas.")] [ScenarioCategory ("Controls")] [ScenarioCategory ("Drawing")] public class LineDrawing : Scenario { public override void Main () { Application.Init (); var win = new Window { Title = GetQuitKeyAndName () }; var canvas = new DrawingArea { X = 0, Y = 0, Width = Dim.Fill (), Height = Dim.Fill () }; var tools = new ToolsView { Title = "Tools", X = Pos.Right (canvas) - 20, Y = 2 }; tools.ColorChanged += c => canvas.SetColor (c); tools.SetStyle += b => canvas.LineStyle = b; tools.AddLayer += () => canvas.AddLayer (); win.Add (canvas); win.Add (tools); win.KeyDown += (s, e) => { e.Handled = canvas.OnKeyDown (e); }; Application.Run (win); win.Dispose (); Application.Shutdown (); } private class DrawingArea : View { private readonly List _layers = new (); private readonly Stack _undoHistory = new (); private Color _currentColor = new (Color.White); private LineCanvas _currentLayer; private StraightLine _currentLine; public DrawingArea () { AddLayer (); } public LineStyle LineStyle { get; set; } public override void OnDrawContentComplete (Rectangle viewport) { base.OnDrawContentComplete (viewport); foreach (LineCanvas canvas in _layers) { foreach (KeyValuePair c in canvas.GetCellMap ()) { if (c.Value is { }) { Driver.SetAttribute (c.Value.Value.Attribute ?? ColorScheme.Normal); // TODO: #2616 - Support combining sequences that don't normalize AddRune (c.Key.X, c.Key.Y, c.Value.Value.Rune); } } } } //// BUGBUG: Why is this not handled by a key binding??? public override bool OnKeyDown (Key e) { // BUGBUG: These should be implemented with key bindings if (e.KeyCode == (KeyCode.Z | KeyCode.CtrlMask)) { StraightLine pop = _currentLayer.RemoveLastLine (); if (pop != null) { _undoHistory.Push (pop); SetNeedsDisplay (); return true; } } if (e.KeyCode == (KeyCode.Y | KeyCode.CtrlMask)) { if (_undoHistory.Any ()) { StraightLine pop = _undoHistory.Pop (); _currentLayer.AddLine (pop); SetNeedsDisplay (); return true; } } return false; } protected override bool OnMouseEvent (MouseEvent mouseEvent) { if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed)) { if (_currentLine == null) { // Mouse pressed down _currentLine = new StraightLine ( mouseEvent.Position, 0, Orientation.Vertical, LineStyle, new Attribute (_currentColor, GetNormalColor ().Background) ); _currentLayer.AddLine (_currentLine); } else { // Mouse dragged Point start = _currentLine.Start; var end = mouseEvent.Position; var orientation = Orientation.Vertical; int length = end.Y - start.Y; // if line is wider than it is tall switch to horizontal if (Math.Abs (start.X - end.X) > Math.Abs (start.Y - end.Y)) { orientation = Orientation.Horizontal; length = end.X - start.X; } if (length > 0) { length++; } else { length--; } _currentLine.Length = length; _currentLine.Orientation = orientation; _currentLayer.ClearCache (); SetNeedsDisplay (); } } else { // Mouse released if (_currentLine != null) { if (_currentLine.Length == 0) { _currentLine.Length = 1; } if (_currentLine.Style == LineStyle.None) { // Treat none as eraser int idx = _layers.IndexOf (_currentLayer); _layers.Remove (_currentLayer); _currentLayer = new LineCanvas ( _currentLayer.Lines.Exclude ( _currentLine.Start, _currentLine.Length, _currentLine.Orientation ) ); _layers.Insert (idx, _currentLayer); } _currentLine = null; _undoHistory.Clear (); SetNeedsDisplay (); } } return base.OnMouseEvent (mouseEvent); } internal void AddLayer () { _currentLayer = new LineCanvas (); _layers.Add (_currentLayer); } internal void SetColor (Color c) { _currentColor = c; } } private class ToolsView : Window { private Button _addLayerBtn; private ColorPicker _colorPicker; private RadioGroup _stylePicker; public ToolsView () { BorderStyle = LineStyle.Dotted; Border.Thickness = new Thickness (1, 2, 1, 1); Initialized += ToolsView_Initialized; } public event Action AddLayer; public override void BeginInit () { base.BeginInit (); _colorPicker = new ColorPicker { X = 0, Y = 0, BoxHeight = 1, BoxWidth = 2 }; _colorPicker.ColorChanged += (s, a) => ColorChanged?.Invoke (a.Color); _stylePicker = new RadioGroup { X = 0, Y = Pos.Bottom (_colorPicker), RadioLabels = Enum.GetNames (typeof (LineStyle)).ToArray () }; _stylePicker.SelectedItemChanged += (s, a) => { SetStyle?.Invoke ((LineStyle)a.SelectedItem); }; _stylePicker.SelectedItem = 1; _addLayerBtn = new Button { Text = "New Layer", X = Pos.Center (), Y = Pos.Bottom (_stylePicker) }; _addLayerBtn.Accept += (s, a) => AddLayer?.Invoke (); Add (_colorPicker, _stylePicker, _addLayerBtn); } public event Action ColorChanged; public event Action SetStyle; private void ToolsView_Initialized (object sender, EventArgs e) { LayoutSubviews (); Width = Math.Max (_colorPicker.Frame.Width, _stylePicker.Frame.Width) + GetAdornmentsThickness ().Horizontal; Height = _colorPicker.Frame.Height + _stylePicker.Frame.Height + _addLayerBtn.Frame.Height + GetAdornmentsThickness ().Vertical; SuperView.LayoutSubviews (); } } }