using NStack;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Terminal.Gui {
///
/// Displays a group of labels each with a selected indicator. Only one of those can be selected at a given time.
///
public class RadioGroup : View {
int selected = -1;
int cursor;
DisplayModeLayout displayMode;
int horizontalSpace = 2;
List<(int pos, int length)> horizontal;
///
/// Initializes a new instance of the class using layout.
///
public RadioGroup () : this (radioLabels: new ustring [] { }) { }
///
/// Initializes a new instance of the class using layout.
///
/// The radio labels; an array of strings that can contain hotkeys using an underscore before the letter.
/// The index of the item to be selected, the value is clamped to the number of items.
public RadioGroup (ustring [] radioLabels, int selected = 0) : base ()
{
Initialize (Rect.Empty, radioLabels, selected);
}
///
/// Initializes a new instance of the class using layout.
///
/// Boundaries for the radio group.
/// The radio labels; an array of strings that can contain hotkeys using an underscore before the letter.
/// The index of item to be selected, the value is clamped to the number of items.
public RadioGroup (Rect rect, ustring [] radioLabels, int selected = 0) : base (rect)
{
Initialize (rect, radioLabels, selected);
}
///
/// Initializes a new instance of the class using layout.
/// The frame is computed from the provided radio labels.
///
/// The x coordinate.
/// The y coordinate.
/// The radio labels; an array of strings that can contain hotkeys using an underscore before the letter.
/// The item to be selected, the value is clamped to the number of items.
public RadioGroup (int x, int y, ustring [] radioLabels, int selected = 0) :
this (MakeRect (x, y, radioLabels != null ? radioLabels.ToList () : null), radioLabels, selected)
{ }
void Initialize (Rect rect, ustring [] radioLabels, int selected)
{
if (radioLabels == null) {
this.radioLabels = new List ();
} else {
this.radioLabels = radioLabels.ToList ();
}
this.selected = selected;
if (rect == Rect.Empty) {
SetWidthHeight (this.radioLabels);
} else {
Frame = rect;
}
CanFocus = true;
HotKeySpecifier = new Rune ('_');
// Things this view knows how to do
AddCommand (Command.LineUp, () => { MoveUp (); return true; });
AddCommand (Command.LineDown, () => { MoveDown (); return true; });
AddCommand (Command.TopHome, () => { MoveHome (); return true; });
AddCommand (Command.BottomEnd, () => { MoveEnd (); return true; });
AddCommand (Command.Accept, () => { SelectItem (); return true; });
// Default keybindings for this view
AddKeyBinding (Key.CursorUp, Command.LineUp);
AddKeyBinding (Key.CursorDown, Command.LineDown);
AddKeyBinding (Key.Home, Command.TopHome);
AddKeyBinding (Key.End, Command.BottomEnd);
AddKeyBinding (Key.Space, Command.Accept);
}
///
/// Gets or sets the for this .
///
public DisplayModeLayout DisplayMode {
get { return displayMode; }
set {
if (displayMode != value) {
displayMode = value;
SetWidthHeight (radioLabels);
SetNeedsDisplay ();
}
}
}
///
/// Gets or sets the horizontal space for this if the is
///
public int HorizontalSpace {
get { return horizontalSpace; }
set {
if (horizontalSpace != value && displayMode == DisplayModeLayout.Horizontal) {
horizontalSpace = value;
SetWidthHeight (radioLabels);
UpdateTextFormatterText ();
SetNeedsDisplay ();
}
}
}
void SetWidthHeight (List radioLabels)
{
switch (displayMode) {
case DisplayModeLayout.Vertical:
var r = MakeRect (0, 0, radioLabels);
if (IsAdded && LayoutStyle == LayoutStyle.Computed) {
Width = r.Width;
Height = radioLabels.Count;
} else {
Frame = new Rect (Frame.Location, new Size (r.Width, radioLabels.Count));
}
break;
case DisplayModeLayout.Horizontal:
CalculateHorizontalPositions ();
var length = 0;
foreach (var item in horizontal) {
length += item.length;
}
var hr = new Rect (0, 0, length, 1);
if (IsAdded && LayoutStyle == LayoutStyle.Computed) {
Width = hr.Width;
Height = 1;
} else {
Frame = new Rect (Frame.Location, new Size (hr.Width, radioLabels.Count));
}
break;
}
}
static Rect MakeRect (int x, int y, List radioLabels)
{
if (radioLabels == null) {
return new Rect (x, y, 0, 0);
}
int width = 0;
foreach (var s in radioLabels)
width = Math.Max (s.ConsoleWidth + 3, width);
return new Rect (x, y, width, radioLabels.Count);
}
List radioLabels = new List ();
///
/// The radio labels to display
///
/// The radio labels.
public ustring [] RadioLabels {
get => radioLabels.ToArray ();
set {
var prevCount = radioLabels.Count;
radioLabels = value.ToList ();
if (prevCount != radioLabels.Count) {
SetWidthHeight (radioLabels);
}
SelectedItem = 0;
cursor = 0;
SetNeedsDisplay ();
}
}
private void CalculateHorizontalPositions ()
{
if (displayMode == DisplayModeLayout.Horizontal) {
horizontal = new List<(int pos, int length)> ();
int start = 0;
int length = 0;
for (int i = 0; i < radioLabels.Count; i++) {
start += length;
length = radioLabels [i].ConsoleWidth + 2 + (i < radioLabels.Count - 1 ? horizontalSpace : 0);
horizontal.Add ((start, length));
}
}
}
//// Redraws the RadioGroup
//void Update(List newRadioLabels)
//{
// for (int i = 0; i < radioLabels.Count; i++) {
// Move(0, i);
// Driver.SetAttribute(ColorScheme.Normal);
// Driver.AddStr(ustring.Make(new string (' ', radioLabels[i].ConsoleWidth + 4)));
// }
// if (newRadioLabels.Count != radioLabels.Count) {
// SetWidthHeight(newRadioLabels);
// }
//}
///
public override void Redraw (Rect bounds)
{
Driver.SetAttribute (GetNormalColor ());
Clear ();
for (int i = 0; i < radioLabels.Count; i++) {
switch (DisplayMode) {
case DisplayModeLayout.Vertical:
Move (0, i);
break;
case DisplayModeLayout.Horizontal:
Move (horizontal [i].pos, 0);
break;
}
var rl = radioLabels [i];
Driver.SetAttribute (GetNormalColor ());
Driver.AddStr (ustring.Make (new Rune [] { i == selected ? Driver.Selected : Driver.UnSelected, ' ' }));
TextFormatter.FindHotKey (rl, HotKeySpecifier, true, out int hotPos, out Key hotKey);
if (hotPos != -1 && (hotKey != Key.Null || hotKey != Key.Unknown)) {
var rlRunes = rl.ToRunes ();
for (int j = 0; j < rlRunes.Length; j++) {
Rune rune = rlRunes [j];
if (j == hotPos && i == cursor) {
Application.Driver.SetAttribute (HasFocus ? ColorScheme.HotFocus : GetHotNormalColor ());
} else if (j == hotPos && i != cursor) {
Application.Driver.SetAttribute (GetHotNormalColor ());
} else if (HasFocus && i == cursor) {
Application.Driver.SetAttribute (ColorScheme.Focus);
}
if (rune == HotKeySpecifier && j + 1 < rlRunes.Length) {
j++;
rune = rlRunes [j];
if (i == cursor) {
Application.Driver.SetAttribute (HasFocus ? ColorScheme.HotFocus : GetHotNormalColor ());
} else if (i != cursor) {
Application.Driver.SetAttribute (GetHotNormalColor ());
}
}
Application.Driver.AddRune (rune);
Driver.SetAttribute (GetNormalColor ());
}
} else {
DrawHotString (rl, HasFocus && i == cursor, ColorScheme);
}
}
}
///
public override void PositionCursor ()
{
switch (DisplayMode) {
case DisplayModeLayout.Vertical:
Move (0, cursor);
break;
case DisplayModeLayout.Horizontal:
Move (horizontal [cursor].pos, 0);
break;
}
}
///
/// Invoked when the selected radio label has changed.
///
public event EventHandler SelectedItemChanged;
///
/// The currently selected item from the list of radio labels
///
/// The selected.
public int SelectedItem {
get => selected;
set {
OnSelectedItemChanged (value, SelectedItem);
cursor = selected;
SetNeedsDisplay ();
}
}
///
/// Allow to invoke the after their creation.
///
public void Refresh ()
{
OnSelectedItemChanged (selected, -1);
}
///
/// Called whenever the current selected item changes. Invokes the event.
///
///
///
public virtual void OnSelectedItemChanged (int selectedItem, int previousSelectedItem)
{
selected = selectedItem;
SelectedItemChanged?.Invoke (this, new SelectedItemChangedArgs (selectedItem, previousSelectedItem));
}
///
public override bool ProcessColdKey (KeyEvent kb)
{
var key = kb.KeyValue;
if (key < Char.MaxValue && Char.IsLetterOrDigit ((char)key)) {
int i = 0;
key = Char.ToUpper ((char)key);
foreach (var l in radioLabels) {
bool nextIsHot = false;
TextFormatter.FindHotKey (l, HotKeySpecifier, true, out _, out Key hotKey);
foreach (Rune c in l) {
if (c == HotKeySpecifier) {
nextIsHot = true;
} else {
if ((nextIsHot && Rune.ToUpper (c) == key) || (key == (uint)hotKey)) {
SelectedItem = i;
cursor = i;
if (!HasFocus)
SetFocus ();
return true;
}
nextIsHot = false;
}
}
i++;
}
}
return false;
}
///
public override bool ProcessKey (KeyEvent kb)
{
var result = InvokeKeybindings (kb);
if (result != null)
return (bool)result;
return base.ProcessKey (kb);
}
void SelectItem ()
{
SelectedItem = cursor;
}
void MoveEnd ()
{
cursor = Math.Max (radioLabels.Count - 1, 0);
}
void MoveHome ()
{
cursor = 0;
}
void MoveDown ()
{
if (cursor + 1 < radioLabels.Count) {
cursor++;
SetNeedsDisplay ();
} else if (cursor > 0) {
cursor = 0;
SetNeedsDisplay ();
}
}
void MoveUp ()
{
if (cursor > 0) {
cursor--;
SetNeedsDisplay ();
} else if (radioLabels.Count - 1 > 0) {
cursor = radioLabels.Count - 1;
SetNeedsDisplay ();
}
}
///
public override bool MouseEvent (MouseEvent me)
{
if (!me.Flags.HasFlag (MouseFlags.Button1Clicked)) {
return false;
}
if (!CanFocus) {
return false;
}
SetFocus ();
var pos = displayMode == DisplayModeLayout.Horizontal ? me.X : me.Y;
var rCount = displayMode == DisplayModeLayout.Horizontal ? horizontal.Last ().pos + horizontal.Last ().length : radioLabels.Count;
if (pos < rCount) {
var c = displayMode == DisplayModeLayout.Horizontal ? horizontal.FindIndex ((x) => x.pos <= me.X && x.pos + x.length - 2 >= me.X) : me.Y;
if (c > -1) {
cursor = SelectedItem = c;
SetNeedsDisplay ();
}
}
return true;
}
///
public override bool OnEnter (View view)
{
Application.Driver.SetCursorVisibility (CursorVisibility.Invisible);
return base.OnEnter (view);
}
}
///
/// Used for choose the display mode of this
///
public enum DisplayModeLayout {
///
/// Vertical mode display. It's the default.
///
Vertical,
///
/// Horizontal mode display.
///
Horizontal
}
}