123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473 |
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using Terminal.Gui;
- namespace UICatalog.Scenarios;
- public interface ITool
- {
- void OnMouseEvent (DrawingArea area, MouseEventArgs mouseEvent);
- }
- internal class DrawLineTool : ITool
- {
- private StraightLine _currentLine;
- public LineStyle LineStyle { get; set; } = LineStyle.Single;
- /// <inheritdoc/>
- public void OnMouseEvent (DrawingArea area, MouseEventArgs mouseEvent)
- {
- if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed))
- {
- if (_currentLine == null)
- {
- // Mouse pressed down
- _currentLine = new (
- mouseEvent.Position,
- 0,
- Orientation.Vertical,
- LineStyle,
- area.CurrentAttribute
- );
- area.CurrentLayer.AddLine (_currentLine);
- }
- else
- {
- // Mouse dragged
- Point start = _currentLine.Start;
- Point 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;
- area.CurrentLayer.ClearCache ();
- area.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 = area.Layers.IndexOf (area.CurrentLayer);
- area.Layers.Remove (area.CurrentLayer);
- area.CurrentLayer = new (
- area.CurrentLayer.Lines.Exclude (
- _currentLine.Start,
- _currentLine.Length,
- _currentLine.Orientation
- )
- );
- area.Layers.Insert (idx, area.CurrentLayer);
- }
- _currentLine = null;
- area.ClearUndo ();
- area.SetNeedsDisplay ();
- }
- }
- mouseEvent.Handled = true;
- }
- }
- [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 += (s, e) => canvas.SetAttribute (e);
- tools.SetStyle += b => canvas.CurrentTool = new DrawLineTool { LineStyle = b };
- tools.AddLayer += () => canvas.AddLayer ();
- win.Add (canvas);
- win.Add (tools);
- tools.CurrentColor = canvas.GetNormalColor ();
- canvas.CurrentAttribute = tools.CurrentColor;
- win.KeyDown += (s, e) => { e.Handled = canvas.NewKeyDownEvent (e); };
- Application.Run (win);
- win.Dispose ();
- Application.Shutdown ();
- }
- public static bool PromptForColor (string title, Color current, out Color newColor)
- {
- var accept = false;
- var d = new Dialog
- {
- Title = title,
- Width = Application.Force16Colors ? 35 : Dim.Auto (DimAutoStyle.Auto, Dim.Percent (80), Dim.Percent (90)),
- Height = 10
- };
- var btnOk = new Button
- {
- X = Pos.Center () - 5,
- Y = Application.Force16Colors ? 6 : 4,
- Text = "Ok",
- Width = Dim.Auto (),
- IsDefault = true
- };
- btnOk.Accepting += (s, e) =>
- {
- accept = true;
- e.Cancel = true;
- Application.RequestStop ();
- };
- var btnCancel = new Button
- {
- X = Pos.Center () + 5,
- Y = 4,
- Text = "Cancel",
- Width = Dim.Auto ()
- };
- btnCancel.Accepting += (s, e) =>
- {
- e.Cancel = true;
- Application.RequestStop ();
- };
- d.Add (btnOk);
- d.Add (btnCancel);
- d.AddButton (btnOk);
- d.AddButton (btnCancel);
- View cp;
- if (Application.Force16Colors)
- {
- cp = new ColorPicker16
- {
- SelectedColor = current.GetClosestNamedColor16 (),
- Width = Dim.Fill ()
- };
- }
- else
- {
- cp = new ColorPicker
- {
- SelectedColor = current,
- Width = Dim.Fill (),
- Style = new () { ShowColorName = true, ShowTextFields = true }
- };
- ((ColorPicker)cp).ApplyStyleChanges ();
- }
- d.Add (cp);
- Application.Run (d);
- d.Dispose ();
- newColor = Application.Force16Colors ? ((ColorPicker16)cp).SelectedColor : ((ColorPicker)cp).SelectedColor;
- return accept;
- }
- }
- public class ToolsView : Window
- {
- private Button _addLayerBtn;
- private readonly AttributeView _colors;
- private RadioGroup _stylePicker;
- public Attribute CurrentColor
- {
- get => _colors.Value;
- set => _colors.Value = value;
- }
- public ToolsView ()
- {
- BorderStyle = LineStyle.Dotted;
- Border.Thickness = new (1, 2, 1, 1);
- Initialized += ToolsView_Initialized;
- _colors = new ();
- }
- public event Action AddLayer;
- public override void BeginInit ()
- {
- base.BeginInit ();
- _colors.ValueChanged += (s, e) => ColorChanged?.Invoke (this, e);
- _stylePicker = new()
- {
- X = 0, Y = Pos.Bottom (_colors), RadioLabels = Enum.GetNames (typeof (LineStyle)).ToArray ()
- };
- _stylePicker.SelectedItemChanged += (s, a) => { SetStyle?.Invoke ((LineStyle)a.SelectedItem); };
- _stylePicker.SelectedItem = 1;
- _addLayerBtn = new() { Text = "New Layer", X = Pos.Center (), Y = Pos.Bottom (_stylePicker) };
- _addLayerBtn.Accepting += (s, a) => AddLayer?.Invoke ();
- Add (_colors, _stylePicker, _addLayerBtn);
- }
- public event EventHandler<Attribute> ColorChanged;
- public event Action<LineStyle> SetStyle;
- private void ToolsView_Initialized (object sender, EventArgs e)
- {
- Width = Math.Max (_colors.Frame.Width, _stylePicker.Frame.Width) + GetAdornmentsThickness ().Horizontal;
- Height = _colors.Frame.Height + _stylePicker.Frame.Height + _addLayerBtn.Frame.Height + GetAdornmentsThickness ().Vertical;
- }
- }
- public class DrawingArea : View
- {
- public readonly List<LineCanvas> Layers = new ();
- private readonly Stack<StraightLine> _undoHistory = new ();
- public Attribute CurrentAttribute { get; set; }
- public LineCanvas CurrentLayer { get; set; }
- public ITool CurrentTool { get; set; } = new DrawLineTool ();
- public DrawingArea () { AddLayer (); }
- protected override bool OnDrawingContent (Rectangle viewport)
- {
- foreach (LineCanvas canvas in Layers)
- {
- foreach (KeyValuePair<Point, Cell?> c in canvas.GetCellMap ())
- {
- if (c.Value is { })
- {
- 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);
- }
- }
- }
- // TODO: This is a hack to work around overlapped views not drawing correctly.
- // without this the toolbox disappears
- SuperView?.SetNeedsLayout();
- return true;
- }
- //// BUGBUG: Why is this not handled by a key binding???
- protected 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 (MouseEventArgs mouseEvent)
- {
- CurrentTool.OnMouseEvent (this, mouseEvent);
- return mouseEvent.Handled;
- }
- internal void AddLayer ()
- {
- CurrentLayer = new ();
- Layers.Add (CurrentLayer);
- }
- internal void SetAttribute (Attribute a) { CurrentAttribute = a; }
- public void ClearUndo () { _undoHistory.Clear (); }
- }
- public class AttributeView : View
- {
- public event EventHandler<Attribute> ValueChanged;
- private Attribute _value;
- public Attribute Value
- {
- get => _value;
- set
- {
- _value = value;
- ValueChanged?.Invoke (this, value);
- }
- }
- private static readonly HashSet<(int, int)> ForegroundPoints = new()
- {
- (0, 0), (1, 0), (2, 0),
- (0, 1), (1, 1), (2, 1)
- };
- private static readonly HashSet<(int, int)> BackgroundPoints = new()
- {
- (3, 1),
- (1, 2), (2, 2), (3, 2)
- };
- public AttributeView ()
- {
- Width = 4;
- Height = 3;
- }
- /// <inheritdoc/>
- protected override bool OnDrawingContent (Rectangle viewport)
- {
- Color fg = Value.Foreground;
- Color bg = Value.Background;
- bool isTransparentFg = fg == GetNormalColor ().Background;
- bool isTransparentBg = bg == GetNormalColor ().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;
- }
- /// <inheritdoc/>
- 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.Background, out Color newColor))
- {
- Value = new (Value.Foreground, newColor);
- SetNeedsDisplay ();
- }
- }
- private void ClickedInForeground ()
- {
- if (LineDrawing.PromptForColor ("Foreground", Value.Foreground, out Color newColor))
- {
- Value = new (newColor, Value.Background);
- SetNeedsDisplay ();
- }
- }
- }
|