using NStack;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Terminal.Gui {
///
/// shows a group of radio labels, only one of those can be selected at a given time
///
public class RadioGroup : View {
int selected = -1;
int cursor;
void Init(Rect rect, ustring [] radioLabels, int selected)
{
if (radioLabels == null) {
this.radioLabels = new List();
} else {
this.radioLabels = radioLabels.ToList ();
}
this.selected = selected;
SetWidthHeight (this.radioLabels);
CanFocus = true;
}
///
/// 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 ()
{
Init (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)
{
Init (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) { }
///
/// The location of the cursor in the
///
public int Cursor {
get => cursor;
set {
if (cursor < 0 || cursor >= radioLabels.Count)
return;
cursor = value;
SetNeedsDisplay ();
}
}
void SetWidthHeight (List radioLabels)
{
var r = MakeRect(0, 0, radioLabels);
if (LayoutStyle == LayoutStyle.Computed) {
Width = r.Width;
Height = radioLabels.Count;
} else {
Frame = new Rect (Frame.Location, new Size (r.Width, radioLabels.Count));
}
}
static Rect MakeRect (int x, int y, List radioLabels)
{
int width = 0;
if (radioLabels == null) {
return new Rect (x, y, width, 0);
}
foreach (var s in radioLabels)
width = Math.Max (s.Length + 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 ();
}
}
//// 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].Length + 4)));
// }
// if (newRadioLabels.Count != radioLabels.Count) {
// SetWidthHeight(newRadioLabels);
// }
//}
///
public override void Redraw (Rect bounds)
{
Driver.SetAttribute (ColorScheme.Normal);
Clear ();
for (int i = 0; i < radioLabels.Count; i++) {
Move (0, i);
Driver.SetAttribute (ColorScheme.Normal);
Driver.AddStr (ustring.Make(new Rune[] { (i == selected ? Driver.Selected : Driver.UnSelected), ' '}));
DrawHotString (radioLabels [i], HasFocus && i == cursor, ColorScheme);
}
base.Redraw (bounds);
}
///
public override void PositionCursor ()
{
Move (0, cursor);
}
// TODO: Make this a global class
///
/// Event arguments for the SelectedItemChagned event.
///
public class SelectedItemChangedArgs : EventArgs {
///
/// Gets the index of the item that was previously selected. -1 if there was no previous selection.
///
public int PreviousSelectedItem { get; }
///
/// Gets the index of the item that is now selected. -1 if there is no selection.
///
public int SelectedItem { get; }
///
/// Initializes a new class.
///
///
///
public SelectedItemChangedArgs(int selectedItem, int previousSelectedItem)
{
PreviousSelectedItem = previousSelectedItem;
SelectedItem = selectedItem;
}
}
///
/// Invoked when the selected radio label has changed.
///
public Action SelectedItemChanged;
///
/// The currently selected item from the list of radio labels
///
/// The selected.
public int SelectedItem {
get => selected;
set {
OnSelectedItemChanged (value, SelectedItem);
SetNeedsDisplay ();
}
}
///
/// Called whenever the current selected item changes. Invokes the event.
///
///
///
public virtual void OnSelectedItemChanged (int selectedItem, int previousSelectedItem)
{
selected = selectedItem;
SelectedItemChanged?.Invoke (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;
foreach (var c in l) {
if (c == '_')
nextIsHot = true;
else {
if (nextIsHot && c == key) {
SelectedItem = i;
cursor = i;
if (!HasFocus)
SuperView.SetFocus (this);
return true;
}
nextIsHot = false;
}
}
i++;
}
}
return false;
}
///
public override bool ProcessKey (KeyEvent kb)
{
switch (kb.Key) {
case Key.CursorUp:
if (cursor > 0) {
cursor--;
SetNeedsDisplay ();
return true;
}
break;
case Key.CursorDown:
if (cursor + 1 < radioLabels.Count) {
cursor++;
SetNeedsDisplay ();
return true;
}
break;
case Key.Space:
SelectedItem = cursor;
return true;
}
return base.ProcessKey (kb);
}
///
public override bool MouseEvent (MouseEvent me)
{
if (!me.Flags.HasFlag (MouseFlags.Button1Clicked))
return false;
SuperView.SetFocus (this);
if (me.Y < radioLabels.Count) {
cursor = SelectedItem = me.Y;
SetNeedsDisplay ();
}
return true;
}
}
}