123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369 |
- #nullable enable
- using System;
- namespace Terminal.Gui;
- /// <summary>
- /// True color picker using HSL
- /// </summary>
- public partial class ColorPicker : View
- {
- /// <summary>
- /// Creates a new instance of <see cref="ColorPicker"/>. Use
- /// <see cref="Style"/> to change color model. Use <see cref="SelectedColor"/>
- /// to change initial <see cref="Color"/>.
- /// </summary>
- public ColorPicker ()
- {
- CanFocus = true;
- TabStop = TabBehavior.TabStop;
- Height = Dim.Auto ();
- Width = Dim.Auto ();
- ApplyStyleChanges ();
- }
- private readonly Dictionary<IColorBar, TextField> _textFields = new ();
- private readonly ColorModelStrategy _strategy = new ();
- private TextField? _tfHex;
- private Label? _lbHex;
- private TextField? _tfName;
- private Label? _lbName;
- private Color _selectedColor = Color.Black;
- // TODO: Add interface
- private readonly IColorNameResolver _colorNameResolver = new W3CColors ();
- private List<IColorBar> _bars = new ();
- /// <summary>
- /// Rebuild the user interface to reflect the new state of <see cref="Style"/>.
- /// </summary>
- public void ApplyStyleChanges ()
- {
- Color oldValue = _selectedColor;
- DisposeOldViews ();
- var y = 0;
- const int textFieldWidth = 4;
- foreach (ColorBar bar in _strategy.CreateBars (Style.ColorModel))
- {
- bar.Y = y;
- bar.Width = Dim.Fill (Style.ShowTextFields ? textFieldWidth : 0);
- TextField? tfValue = null;
- if (Style.ShowTextFields)
- {
- tfValue = new TextField
- {
- X = Pos.AnchorEnd (textFieldWidth),
- Y = y,
- Width = textFieldWidth
- };
- tfValue.HasFocusChanged += UpdateSingleBarValueFromTextField;
- tfValue.Accepting += (s, _)=>UpdateSingleBarValueFromTextField(s);
- _textFields.Add (bar, tfValue);
- }
- y++;
- bar.ValueChanged += RebuildColorFromBar;
- _bars.Add (bar);
- Add (bar);
- if (tfValue is { })
- {
- Add (tfValue);
- }
- }
- if (Style.ShowColorName)
- {
- CreateNameField ();
- }
- CreateTextField ();
- SelectedColor = oldValue;
- SetLayoutNeeded ();
- }
- /// <summary>
- /// Fired when color is changed.
- /// </summary>
- public event EventHandler<ColorEventArgs>? ColorChanged;
- /// <inheritdoc/>
- public override void OnDrawContent (Rectangle viewport)
- {
- base.OnDrawContent (viewport);
- Attribute normal = GetNormalColor ();
- Driver?.SetAttribute (new (SelectedColor, normal.Background));
- int y = _bars.Count + (Style.ShowColorName ? 1 : 0);
- AddRune (13, y, (Rune)'■');
- }
- /// <summary>
- /// The color selected in the picker
- /// </summary>
- public Color SelectedColor
- {
- get => _selectedColor;
- set => SetSelectedColor (value, true);
- }
- /// <summary>
- /// Style settings for the color picker. After making changes ensure you call
- /// <see cref="ApplyStyleChanges"/>.
- /// </summary>
- public ColorPickerStyle Style { get; set; } = new ();
- private void CreateNameField ()
- {
- _lbName = new ()
- {
- Text = "Name:",
- X = 0,
- Y = 3
- };
- _tfName = new ()
- {
- Y = 3,
- X = 6,
- Width = 20 // width of "LightGoldenRodYellow" - the longest w3c color name
- };
- Add (_lbName);
- Add (_tfName);
- var auto = new AppendAutocomplete (_tfName);
- auto.SuggestionGenerator = new SingleWordSuggestionGenerator
- {
- AllSuggestions = _colorNameResolver.GetColorNames ().ToList ()
- };
- _tfName.Autocomplete = auto;
- _tfName.HasFocusChanged += UpdateValueFromName;
- _tfName.Accepting += (s, _) => UpdateValueFromName ();
- }
- private void CreateTextField ()
- {
- int y = _bars.Count;
- if (Style.ShowColorName)
- {
- y++;
- }
- _lbHex = new ()
- {
- Text = "Hex:",
- X = 0,
- Y = y
- };
- _tfHex = new ()
- {
- Y = y,
- X = 4,
- Width = 8,
- };
- Add (_lbHex);
- Add (_tfHex);
- _tfHex.HasFocusChanged += UpdateValueFromTextField;
- _tfHex.Accepting += (_,_)=> UpdateValueFromTextField();
- }
- private void DisposeOldViews ()
- {
- foreach (ColorBar bar in _bars.Cast<ColorBar> ())
- {
- bar.ValueChanged -= RebuildColorFromBar;
- if (_textFields.TryGetValue (bar, out TextField? tf))
- {
- Remove (tf);
- tf.Dispose ();
- }
- Remove (bar);
- bar.Dispose ();
- }
- _bars = new ();
- _textFields.Clear ();
- if (_lbHex != null)
- {
- Remove (_lbHex);
- _lbHex.Dispose ();
- _lbHex = null;
- }
- if (_tfHex != null)
- {
- Remove (_tfHex);
- _tfHex.Dispose ();
- _tfHex = null;
- }
- if (_lbName != null)
- {
- Remove (_lbName);
- _lbName.Dispose ();
- _lbName = null;
- }
- if (_tfName != null)
- {
- Remove (_tfName);
- _tfName.Dispose ();
- _tfName = null;
- }
- }
- private void RebuildColorFromBar (object? sender, EventArgs<int> e) { SetSelectedColor (_strategy.GetColorFromBars (_bars, Style.ColorModel), false); }
- private void SetSelectedColor (Color value, bool syncBars)
- {
- if (_selectedColor != value)
- {
- Color old = _selectedColor;
- _selectedColor = value;
- ColorChanged?.Invoke (
- this,
- new (value));
- }
- SyncSubViewValues (syncBars);
- }
- private void SyncSubViewValues (bool syncBars)
- {
- if (syncBars)
- {
- _strategy.SetBarsToColor (_bars, _selectedColor, Style.ColorModel);
- }
- foreach (KeyValuePair<IColorBar, TextField> kvp in _textFields)
- {
- kvp.Value.Text = kvp.Key.Value.ToString ();
- }
- var colorHex = _selectedColor.ToString ($"#{SelectedColor.R:X2}{SelectedColor.G:X2}{SelectedColor.B:X2}");
- if (_tfName != null)
- {
- _tfName.Text = _colorNameResolver.TryNameColor (_selectedColor, out string name) ? name : string.Empty;
- }
- if (_tfHex != null)
- {
- _tfHex.Text = colorHex;
- }
- SetLayoutNeeded ();
- }
- private void UpdateSingleBarValueFromTextField (object? sender, HasFocusEventArgs e)
- {
- // if the new value of Focused is true then it is an enter event so ignore
- if (e.NewValue)
- {
- return;
- }
- // it is a leave event so update
- UpdateSingleBarValueFromTextField (sender);
- }
- private void UpdateSingleBarValueFromTextField (object? sender)
- {
- foreach (KeyValuePair<IColorBar, TextField> kvp in _textFields)
- {
- if (kvp.Value == sender)
- {
- if (int.TryParse (kvp.Value.Text, out int v))
- {
- kvp.Key.Value = v;
- }
- }
- }
- }
- private void UpdateValueFromName (object? sender, HasFocusEventArgs e)
- {
- // if the new value of Focused is true then it is an enter event so ignore
- if (e.NewValue)
- {
- return;
- }
- // it is a leave event so update
- UpdateValueFromName();
- }
- private void UpdateValueFromName ()
- {
- if (_tfName == null)
- {
- return;
- }
- if (_colorNameResolver.TryParseColor (_tfName.Text, out Color newColor))
- {
- SelectedColor = newColor;
- }
- else
- {
- // value is invalid, revert the value in the text field back to current state
- SyncSubViewValues (false);
- }
- }
- private void UpdateValueFromTextField (object? sender, HasFocusEventArgs e)
- {
- // if the new value of Focused is true then it is an enter event so ignore
- if (e.NewValue)
- {
- return;
- }
- // it is a leave event so update
- UpdateValueFromTextField ();
- }
- private void UpdateValueFromTextField ()
- {
- if (_tfHex == null)
- {
- return;
- }
- if (Color.TryParse (_tfHex.Text, out Color? newColor))
- {
- SelectedColor = newColor.Value;
- }
- else
- {
- // value is invalid, revert the value in the text field back to current state
- SyncSubViewValues (false);
- }
- }
- /// <inheritdoc />
- protected override void Dispose (bool disposing)
- {
- DisposeOldViews ();
- base.Dispose (disposing);
- }
- }
|