// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using FlaxEditor.CustomEditors;
using FlaxEditor.CustomEditors.Elements;
using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEditor.Options
{
///
/// The input binding container.
///
[Serializable]
[HideInEditor]
[TypeConverter(typeof(InputBindingConverter))]
[CustomEditor(typeof(InputBindingEditor))]
public struct InputBinding
{
///
/// The key to bind.
///
public KeyboardKeys Key;
///
/// The first modifier ( if not used).
///
public KeyboardKeys Modifier1;
///
/// The second modifier ( if not used).
///
public KeyboardKeys Modifier2;
///
/// Initializes a new instance of the struct.
///
/// The key.
public InputBinding(KeyboardKeys key)
{
Key = key;
Modifier1 = KeyboardKeys.None;
Modifier2 = KeyboardKeys.None;
}
///
/// Initializes a new instance of the struct.
///
/// The key.
/// The first modifier.
public InputBinding(KeyboardKeys key, KeyboardKeys modifier1)
{
Key = key;
Modifier1 = modifier1;
Modifier2 = KeyboardKeys.None;
}
///
/// Initializes a new instance of the struct.
///
/// The key.
/// The first modifier.
/// The second modifier.
public InputBinding(KeyboardKeys key, KeyboardKeys modifier1, KeyboardKeys modifier2)
{
Key = key;
Modifier1 = modifier1;
Modifier2 = modifier2;
}
///
/// Parses the specified key text value.
///
/// The value.
/// The result (valid only if parsing succeed).
/// True if parsing succeed, otherwise false.
public static bool Parse(string value, out KeyboardKeys result)
{
if (string.Equals(value, "Ctrl", StringComparison.OrdinalIgnoreCase))
{
result = KeyboardKeys.Control;
return true;
}
return Enum.TryParse(value, true, out result);
}
///
/// Returns a that represents the key enum (for UI).
///
/// The key.
/// A that represents the key.
public static string ToString(KeyboardKeys key)
{
switch (key)
{
case KeyboardKeys.Control: return "Ctrl";
default: return key.ToString();
}
}
///
/// Tries the parse the input text value to the .
///
/// The value.
/// The result value (valid only if method returns true).
/// True if parsing succeed, otherwise false.
public static bool TryParse(string value, out InputBinding result)
{
result = new InputBinding();
string[] v = value.Split('+');
switch (v.Length)
{
case 3:
if (Parse(v[2], out result.Key) &&
Parse(v[1], out result.Modifier1) &&
Parse(v[0], out result.Modifier2))
return true;
break;
case 2:
if (Parse(v[1], out result.Key) &&
Parse(v[0], out result.Modifier1))
return true;
break;
case 1:
if (Parse(v[0], out result.Key))
return true;
break;
}
return false;
}
private bool ProcessModifiers(Control control)
{
return ProcessModifiers(control.Root.GetKey);
}
private bool ProcessModifiers(Window window)
{
return ProcessModifiers(window.GetKey);
}
private bool ProcessModifiers(Func getKeyFunc)
{
bool ctrlPressed = getKeyFunc(KeyboardKeys.Control);
bool shiftPressed = getKeyFunc(KeyboardKeys.Shift);
bool altPressed = getKeyFunc(KeyboardKeys.Alt);
bool mod1 = false;
if (Modifier1 == KeyboardKeys.None)
mod1 = true;
else if (Modifier1 == KeyboardKeys.Control)
{
mod1 = ctrlPressed;
ctrlPressed = false;
}
else if (Modifier1 == KeyboardKeys.Shift)
{
mod1 = shiftPressed;
shiftPressed = false;
}
else if (Modifier1 == KeyboardKeys.Alt)
{
mod1 = altPressed;
altPressed = false;
}
bool mod2 = false;
if (Modifier2 == KeyboardKeys.None)
mod2 = true;
else if (Modifier2 == KeyboardKeys.Control)
{
mod2 = ctrlPressed;
ctrlPressed = false;
}
else if (Modifier2 == KeyboardKeys.Shift)
{
mod2 = shiftPressed;
shiftPressed = false;
}
else if (Modifier2 == KeyboardKeys.Alt)
{
mod2 = altPressed;
altPressed = false;
}
// Check if any unhandled modifiers are not pressed
if (mod1 && mod2)
return !ctrlPressed && !shiftPressed && !altPressed;
return false;
}
///
/// Processes this input binding to check if state matches.
///
/// The input providing control.
/// True if input has been processed, otherwise false.
public bool Process(Control control)
{
var root = control.Root;
return root.GetKey(Key) && ProcessModifiers(control);
}
///
/// Processes this input binding to check if state matches.
///
/// The input providing control.
/// The input key.
/// True if input has been processed, otherwise false.
public bool Process(Control control, KeyboardKeys key)
{
if (key != Key)
return false;
return ProcessModifiers(control);
}
///
/// Processes this input binding to check if state matches.
///
/// The input providing window.
/// True if input has been processed, otherwise false.
public bool Process(Window window)
{
return window.GetKey(Key) && ProcessModifiers(window);
}
///
public override string ToString()
{
string result = string.Empty;
if (Modifier2 != KeyboardKeys.None)
{
result = ToString(Modifier2);
}
if (Modifier1 != KeyboardKeys.None)
{
if (result.Length != 0)
result += '+';
result += ToString(Modifier1);
}
if (Key != KeyboardKeys.None)
{
if (result.Length != 0)
result += '+';
result += ToString(Key);
}
return result;
}
}
class InputBindingConverter : TypeConverter
{
///
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
///
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return false;
}
return base.CanConvertTo(context, destinationType);
}
///
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string str)
{
InputBinding.TryParse(str, out var result);
return result;
}
return base.ConvertFrom(context, culture, value);
}
///
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (destinationType == typeof(string))
{
return ((InputBinding)value).ToString();
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
class InputBindingEditor : CustomEditor
{
private CustomElement _element;
private class InputBindingBox : TextBox
{
private InputBinding _binding;
///
protected override void OnEditBegin()
{
base.OnEditBegin();
// Reset
_text = string.Empty;
_binding = new InputBinding();
}
///
public override bool OnCharInput(char c)
{
// Skip text
return true;
}
///
public override bool OnKeyDown(KeyboardKeys key)
{
// Skip already added keys
if (_binding.Key == key || _binding.Modifier1 == key || _binding.Modifier2 == key)
return true;
switch (key)
{
// Skip
case KeyboardKeys.Spacebar: break;
// Modifiers
case KeyboardKeys.Control:
case KeyboardKeys.Shift:
if (_binding.Modifier1 == KeyboardKeys.None)
{
_binding.Modifier1 = key;
_text = _binding.ToString();
}
else if (_binding.Modifier2 == KeyboardKeys.None)
{
_binding.Modifier2 = key;
_text = _binding.ToString();
}
break;
// Keys
default:
if (_binding.Key == KeyboardKeys.None)
{
_binding.Key = key;
_text = _binding.ToString();
Defocus();
}
break;
}
return true;
}
}
///
public override DisplayStyle Style => DisplayStyle.Inline;
///
public override void Initialize(LayoutElementsContainer layout)
{
var grid = layout.CustomContainer();
var gridControl = grid.CustomControl;
gridControl.ClipChildren = false;
gridControl.Height = TextBox.DefaultHeight;
gridControl.RowFill = new[]
{
1.0f,
};
gridControl.ColumnFill = new[]
{
0.9f,
0.1f
};
_element = grid.Custom();
SetText();
_element.CustomControl.WatermarkText = "Type a binding";
_element.CustomControl.EditEnd += OnValueChanged;
var button = grid.Button("X");
button.Button.TooltipText = "Remove binding";
button.Button.Clicked += OnXButtonClicked;
}
private void OnXButtonClicked()
{
SetValue(new InputBinding());
}
private void OnValueChanged()
{
if (InputBinding.TryParse(_element.CustomControl.Text, out var value))
SetValue(value);
else
SetText();
}
private void SetText()
{
_element.CustomControl.Text = ((InputBinding)Values[0]).ToString();
}
///
public override void Refresh()
{
base.Refresh();
SetText();
}
}
///
/// The input actions processing helper that handles input bindings configuration layer.
///
public class InputActionsContainer
{
///
/// The binding.
///
public struct Binding
{
///
/// The binded options callback.
///
public Func Binder;
///
/// The action callback.
///
public Action Callback;
///
/// Initializes a new instance of the struct.
///
/// The input binding options getter (can read from editor options or use constant binding).
/// The callback to invoke on user input.
public Binding(Func binder, Action callback)
{
Binder = binder;
Callback = callback;
}
}
///
/// List of all available bindings.
///
public List Bindings;
///
/// Initializes a new instance of the class.
///
public InputActionsContainer()
{
Bindings = new List();
}
///
/// Initializes a new instance of the class.
///
/// The input bindings collection.
public InputActionsContainer(params Binding[] bindings)
{
Bindings = new List(bindings);
}
///
/// Adds the specified binding.
///
/// The input binding.
public void Add(Binding binding)
{
Bindings.Add(binding);
}
///
/// Adds the specified binding.
///
/// The input binding options getter (can read from editor options or use constant binding).
/// The callback to invoke on user input.
public void Add(Func binder, Action callback)
{
Bindings.Add(new Binding(binder, callback));
}
///
/// Adds the specified bindings.
///
/// The input bindings collection.
public void Add(params Binding[] bindings)
{
Bindings.AddRange(bindings);
}
///
/// Processes the specified key input and tries to invoke first matching callback for the current user input state.
///
/// The editor instance.
/// The input providing control.
/// The input key.
/// True if event has been handled, otherwise false.
public bool Process(Editor editor, Control control, KeyboardKeys key)
{
var root = control.Root;
var options = editor.Options.Options.Input;
for (int i = 0; i < Bindings.Count; i++)
{
var binding = Bindings[i].Binder(options);
if (binding.Process(control, key))
{
Bindings[i].Callback();
return true;
}
}
return false;
}
}
}