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; } } }