using System;
using System.Collections.Generic;
namespace DanroJump.UI.ArcadeInput
{
///
/// Набор правил для ввода строки автоматным управлением: джойстик плюс одна кнопка.
///
public readonly struct ArcadeStringInputProfile
{
public const string PhoneSymbols = " +0123456789()-";
public const string ShortTextSymbols = " 0123456789+-.,:/()ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдеёжзийклмнопрстуфхцчшщъыьэюя";
public ArcadeStringInputProfile(string allowedSymbols, int maxLength)
{
AllowedSymbols = NormalizeSymbols(allowedSymbols);
MaxLength = Math.Max(1, maxLength);
}
public string AllowedSymbols { get; }
public int MaxLength { get; }
public static ArcadeStringInputProfile Phone(int maxLength = 18)
{
return new ArcadeStringInputProfile(PhoneSymbols, maxLength);
}
public static ArcadeStringInputProfile ShortText(int maxLength = 12)
{
return new ArcadeStringInputProfile(ShortTextSymbols, maxLength);
}
public static ArcadeStringInputProfile Text(int maxLength = 28)
{
return new ArcadeStringInputProfile(ShortTextSymbols, maxLength);
}
private static string NormalizeSymbols(string symbols)
{
if (string.IsNullOrEmpty(symbols))
{
return " ";
}
var seen = new HashSet();
var result = new List(symbols.Length);
foreach (var symbol in symbols)
{
if (seen.Add(symbol))
{
result.Add(symbol);
}
}
return result.Count > 0 ? new string(result.ToArray()) : " ";
}
}
public enum ArcadeStringInputSelectionKind
{
Character,
Action
}
public enum ArcadeStringInputAction
{
Confirm,
Delete,
Cancel
}
public enum ArcadeStringInputSubmitResult
{
None,
Confirm,
Cancel
}
///
/// Чистая модель посимвольного ввода для автоматов без физической клавиатуры.
///
public sealed class ArcadeStringInputState
{
public const char BlankCharacter = ' ';
private static readonly ArcadeStringInputAction[] ActionOrder =
{
ArcadeStringInputAction.Confirm,
ArcadeStringInputAction.Delete,
ArcadeStringInputAction.Cancel
};
private readonly char[] slots;
private readonly char[] symbols;
private int selectedSlot;
private int selectedActionIndex;
private ArcadeStringInputSelectionKind selectionKind;
public ArcadeStringInputState(ArcadeStringInputProfile profile, string initialValue)
{
MaxLength = Math.Max(1, profile.MaxLength);
symbols = profile.AllowedSymbols.ToCharArray();
if (symbols.Length == 0)
{
symbols = new[] { BlankCharacter };
}
slots = new char[MaxLength];
for (var index = 0; index < slots.Length; index++)
{
slots[index] = BlankCharacter;
}
SetInitialValue(initialValue);
}
public int MaxLength { get; }
public int SelectedSlot => selectedSlot;
public ArcadeStringInputSelectionKind SelectionKind => selectionKind;
public ArcadeStringInputAction SelectedAction => ActionOrder[selectedActionIndex];
public string Value => new string(slots).TrimEnd();
public char GetSlot(int index)
{
return index >= 0 && index < slots.Length ? slots[index] : BlankCharacter;
}
public bool IsSlotSelected(int index)
{
return selectionKind == ArcadeStringInputSelectionKind.Character && selectedSlot == index;
}
public bool IsActionSelected(ArcadeStringInputAction action)
{
return selectionKind == ArcadeStringInputSelectionKind.Action && SelectedAction == action;
}
public void MoveLeft()
{
if (selectionKind == ArcadeStringInputSelectionKind.Action)
{
if (selectedActionIndex > 0)
{
selectedActionIndex--;
return;
}
selectionKind = ArcadeStringInputSelectionKind.Character;
selectedSlot = MaxLength - 1;
return;
}
if (selectedSlot <= 0)
{
selectionKind = ArcadeStringInputSelectionKind.Action;
selectedActionIndex = ActionOrder.Length - 1;
return;
}
selectedSlot--;
}
public void MoveRight()
{
if (selectionKind == ArcadeStringInputSelectionKind.Action)
{
if (selectedActionIndex < ActionOrder.Length - 1)
{
selectedActionIndex++;
return;
}
selectionKind = ArcadeStringInputSelectionKind.Character;
selectedSlot = 0;
return;
}
if (selectedSlot >= MaxLength - 1)
{
selectionKind = ArcadeStringInputSelectionKind.Action;
selectedActionIndex = 0;
return;
}
selectedSlot++;
}
public void MoveUp()
{
if (selectionKind == ArcadeStringInputSelectionKind.Action)
{
selectedActionIndex = Wrap(selectedActionIndex - 1, ActionOrder.Length);
return;
}
CycleSelectedCharacter(-1);
}
public void MoveDown()
{
if (selectionKind == ArcadeStringInputSelectionKind.Action)
{
selectedActionIndex = Wrap(selectedActionIndex + 1, ActionOrder.Length);
return;
}
CycleSelectedCharacter(1);
}
public ArcadeStringInputSubmitResult Submit()
{
if (selectionKind == ArcadeStringInputSelectionKind.Character)
{
MoveRight();
return ArcadeStringInputSubmitResult.None;
}
switch (SelectedAction)
{
case ArcadeStringInputAction.Confirm:
return ArcadeStringInputSubmitResult.Confirm;
case ArcadeStringInputAction.Cancel:
return ArcadeStringInputSubmitResult.Cancel;
case ArcadeStringInputAction.Delete:
DeleteCurrentCharacter();
return ArcadeStringInputSubmitResult.None;
default:
return ArcadeStringInputSubmitResult.None;
}
}
public void DeleteCurrentCharacter()
{
slots[selectedSlot] = BlankCharacter;
selectionKind = ArcadeStringInputSelectionKind.Character;
}
private void SetInitialValue(string initialValue)
{
if (string.IsNullOrEmpty(initialValue))
{
return;
}
var writeIndex = 0;
foreach (var symbol in initialValue)
{
if (writeIndex >= slots.Length)
{
break;
}
if (Array.IndexOf(symbols, symbol) < 0)
{
continue;
}
slots[writeIndex] = symbol;
writeIndex++;
}
}
private void CycleSelectedCharacter(int direction)
{
var symbolIndex = Array.IndexOf(symbols, slots[selectedSlot]);
if (symbolIndex < 0)
{
symbolIndex = 0;
}
slots[selectedSlot] = symbols[Wrap(symbolIndex + direction, symbols.Length)];
}
private static int Wrap(int value, int length)
{
if (length <= 0)
{
return 0;
}
var wrapped = value % length;
return wrapped < 0 ? wrapped + length : wrapped;
}
}
}