using System; using System.Collections; using System.Collections.Generic; using System.Linq; using Ink.Runtime; using TMPro; using UniRx; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.UI; using UnityEngine.UI.Extensions; using Unity.Mathematics; using UnityEngine.EventSystems; using static Unity.Mathematics.math; public class InputDisplayLayout : MonoBehaviour { public RectTransform UnassignedBindingsGroup; public RectTransform AssignedBindingsGroup; public InputDisplayButton MouseLeft; public InputDisplayButton MouseRight; public InputDisplayButton MouseMiddle; public InputDisplayButton MouseForward; public InputDisplayButton MouseBack; public VerticalLayoutGroup RowContainer; public TextAsset LayoutFile; public Prototype LabelPrototype; public Prototype LinePrototype; //public UILineRendererList LineRenderer; public Prototype RowPrototype; public Prototype RowSpacerPrototype; public int KeySize = 64; public Color DefaultColor; public Color HighlightColor; public float FillBrightness = .5f; public float FillAlpha; public float FillMultiplier = .25f; public float InactiveMultiplier = .333f; public int PlacementSearchArea = 4; public float TestPadding = 4; public float BoxLineExpand = -1; public float GlobalHue = .333f; public float Saturation; public float GlobalHueRange = .1f; private Canvas _canvas; private InputLayout _inputLayout; private ActionMapping _dragAction; private ButtonMapping _originalButton; private ButtonMapping _previewButton; private ActionMapping _previewOriginalAction; private bool _previewOriginallyActionBar; public InputActionAsset Input { get; set; } private class ButtonMapping { public InputAction TestAction; public IBindableButton Button; public InputDisplayButton DisplayButton; public ActionMapping ActionMapping; public UILineRenderer LabelLine; public bool IsActionBarButton; public Rect ButtonRect => DisplayButton.GetComponent().ScreenSpaceRect(); } private class ActionMapping { public string Name; public InputBinding Binding; public InputAction Action; public InputDisplayLabel Label; public float Hue; public List ButtonMappings = new List(); public Rect LabelRect => Label.GetComponent().ScreenSpaceRect(); } private Dictionary _bindButtons = new Dictionary(); private List _buttonMappings = new List(); private List _actionMappings = new List(); void Start() { _canvas = transform.root.GetComponent(); _inputLayout = ParseJson(LayoutFile.text); DisplayLayout(_inputLayout); _buttonMappings.Add(MapMouseButton(MouseLeft, "/leftButton")); _buttonMappings.Add(MapMouseButton(MouseRight, "/rightButton")); _buttonMappings.Add(MapMouseButton(MouseMiddle, "/middleButton")); _buttonMappings.Add(MapMouseButton(MouseForward, "/forwardButton")); _buttonMappings.Add(MapMouseButton(MouseBack, "/backButton")); Canvas.ForceUpdateCanvases(); foreach (var buttonMapping in _buttonMappings) { _bindButtons[buttonMapping.Button.InputSystemPath] = buttonMapping; buttonMapping.TestAction = new InputAction(binding: buttonMapping.Button.InputSystemPath); buttonMapping.TestAction.started += context => { buttonMapping.DisplayButton.Outline.color = HighlightColor; buttonMapping.DisplayButton.Outline.gameObject.SetActive(true); var fillColor = HighlightColor; fillColor *= FillBrightness; fillColor.a = FillAlpha; buttonMapping.DisplayButton.Fill.color = fillColor; }; buttonMapping.TestAction.canceled += context => AssignColor(buttonMapping); buttonMapping.TestAction.Enable(); } foreach(var actionBarInput in ActionGameManager.PlayerSettings.InputSettings.ActionBarInputs) { if (!_bindButtons.ContainsKey(actionBarInput)) Debug.LogError($"Unable to find input button for \"{actionBarInput}\""); else { _bindButtons[actionBarInput].IsActionBarButton = true; AssignColor(_bindButtons[actionBarInput]); } } if(Input==null) { var input = new AetheriaInput(); Input = input.asset; } ProcessActions(Input); // input.Enable(); // input.Global.Interact.performed += context => Debug.Log($"Interact Performed! Time = {((int) (Time.time * 1000)).ToString()}ms"); Observable.NextFrame().Subscribe(_ => { foreach (var actionMapping in _actionMappings) { actionMapping.Label = CreateLabel(actionMapping); PlaceLabel(actionMapping); } RegisterMouseCallbacks(); }); //StartCoroutine(AssociateInputKeys(_inputLayout)); } private ButtonMapping MapMouseButton(InputDisplayButton button, string path) { var mapping = new ButtonMapping { Button = new InputLayoutMouseButton {Path = path}, DisplayButton = button }; AssignUnboundColor(mapping.DisplayButton); return mapping; } private void ProcessActions(InputActionAsset input) { var bindableActions = input.Where(a => a.actionMap.name != "UI" && (a.type==InputActionType.Button || a.bindings.Any(b=>b.isComposite))).ToArray(); for (var i = 0; i < bindableActions.Length; i++) { var action = bindableActions[i]; foreach (var binding in action.bindings) { if (_bindButtons.ContainsKey(binding.effectivePath)) { var name = action.name.Replace(' ', '\n'); if (binding.isPartOfComposite) name = $"{name} {binding.name}"; var actionMapping = new ActionMapping { Name = name, Binding = binding, Action = action, Hue = action.actionMap.name == "Global" ? GlobalHue : frac(GlobalHue + GlobalHueRange + (float) i / (bindableActions.Length - 1) * (1 - GlobalHueRange * 2)) }; _actionMappings.Add(actionMapping); var buttonMapping = _bindButtons[binding.effectivePath]; buttonMapping.ActionMapping = actionMapping; AssignColor(buttonMapping); actionMapping.ButtonMappings.Add(buttonMapping); } } } } private void AssignColor(ButtonMapping buttonMapping) { if (buttonMapping.IsActionBarButton) { var outlineColor = Color.white; var fillColor = Color.white * FillBrightness; fillColor.a = FillAlpha; buttonMapping.DisplayButton.Fill.color = fillColor; buttonMapping.DisplayButton.Outline.gameObject.SetActive(true); buttonMapping.DisplayButton.Outline.color = outlineColor; if (buttonMapping.DisplayButton is InputDisplayKey key) key.MainLabel.color = key.AltLabel.color = outlineColor; } else if(buttonMapping.ActionMapping!=null) { var outlineColor = Color.HSVToRGB(buttonMapping.ActionMapping.Hue, Saturation, 1); var fillColor = Color.HSVToRGB(buttonMapping.ActionMapping.Hue, 1, FillBrightness); fillColor.a = FillAlpha; buttonMapping.DisplayButton.Fill.color = fillColor; buttonMapping.DisplayButton.Outline.gameObject.SetActive(true); buttonMapping.DisplayButton.Outline.color = outlineColor; if (buttonMapping.DisplayButton is InputDisplayKey key) key.MainLabel.color = key.AltLabel.color = outlineColor; } else AssignUnboundColor(buttonMapping.DisplayButton); } private void AssignUnboundColor(InputDisplayButton button, bool bindable = true) { button.Outline.gameObject.SetActive(false); var outlineColor = DefaultColor; outlineColor.a *= InactiveMultiplier; var fillColor = DefaultColor * FillBrightness; fillColor *= FillBrightness; if(!bindable) fillColor *= InactiveMultiplier; fillColor.a = FillAlpha; button.Fill.color = fillColor; if (button is InputDisplayKey key) key.MainLabel.color = key.AltLabel.color = Color.Lerp(outlineColor, fillColor, .5f); } private InputDisplayLabel CreateLabel(ActionMapping actionMapping) { var label = LabelPrototype.Instantiate(); label.Label.color = Color.HSVToRGB(actionMapping.Hue, Saturation, 1); actionMapping.Label = label; label.Label.text = actionMapping.Name; return label; } private void PlaceLabel(ActionMapping actionMapping) { if(actionMapping.ButtonMappings.Any()) { var labelRect = actionMapping.Label.Label.rectTransform; LayoutRebuilder.ForceRebuildLayoutImmediate(labelRect); var rect = FindLabelPosition(actionMapping.ButtonMappings.First().DisplayButton.GetComponent(), labelRect); //actionMapping.LabelRect = rect; actionMapping.Label.transform.parent = AssignedBindingsGroup; actionMapping.Label.transform.position = rect.center; } else { actionMapping.Label.transform.parent = UnassignedBindingsGroup; } foreach(var buttonMapping in actionMapping.ButtonMappings) ConnectButtonToLabel(buttonMapping); } private void ConnectButtonToLabel(ButtonMapping buttonMapping) { if (buttonMapping.ActionMapping != null) { var keyBounds = buttonMapping.DisplayButton.Outline.rectTransform.GetBounds(BoxLineExpand); var labelPoint = (Vector2) buttonMapping.ActionMapping.Label.Label.rectTransform.GetBounds().ClosestPoint(keyBounds.center); var keyPoint = (Vector2) keyBounds.ClosestPoint(labelPoint); labelPoint /= _canvas.scaleFactor; keyPoint /= _canvas.scaleFactor; buttonMapping.LabelLine ??= LinePrototype.Instantiate(); buttonMapping.LabelLine.color = buttonMapping.ActionMapping.Label.Label.color; buttonMapping.LabelLine.Points = new[] {keyPoint, labelPoint}; } else { buttonMapping.LabelLine?.GetComponent().ReturnToPool(); buttonMapping.LabelLine = null; } } private Rect FindLabelPosition(RectTransform key, RectTransform label) { var keyRect = key.ScreenSpaceRect(); var labelRect = label.ScreenSpaceRect(TestPadding*_canvas.scaleFactor); var localRects = Overlap(key.ScreenSpaceRect(PlacementSearchArea * KeySize * _canvas.scaleFactor)); var centroid = localRects.Aggregate(Vector2.zero, (v, r) => v + r.center) / localRects.Length; var keyCenter = keyRect.center; var dir = (keyCenter - centroid).normalized; for (int dist = KeySize; dist < PlacementSearchArea * KeySize; dist++) { for (float theta = 0; theta < PI * 2; theta += PI / 32) { for (int s = -1; s <= 1; s += 2) { var testPos = keyCenter + dir.Rotate(theta * s) * dist * _canvas.scaleFactor; var testRect = new Rect(testPos - labelRect.size / 2, labelRect.size); if(!localRects.Any(r => r.Overlaps(testRect))) { return testRect; } } } } return default; } private ActionMapping[] OverlappingLabels(Rect rect) => _actionMappings .Where(a => a.LabelRect.Overlaps(rect)).ToArray(); private Rect[] Overlap(Rect rect) => _buttonMappings .Where(b=>b.IsActionBarButton || b.ActionMapping!=null) .Select(b=>b.ButtonRect) .Concat(_actionMappings .Where(a=>a.Label && a.LabelRect.width > .1f) .Select(a=>a.LabelRect)) .Where(r => r.Overlaps(rect)).ToArray(); public void DisplayLayout(InputLayout layout) { foreach (var row in layout.Rows) { if (row is InputLayoutKeyRow keyRow) { var displayRow = RowPrototype.Instantiate(); foreach (var column in keyRow.Columns) { if (column is InputLayoutColumnSpacer) { displayRow.KeySpacerPrototype.Instantiate().preferredWidth = KeySize * column.Width; } else if (column is InputLayoutKey) { var displayKey = displayRow.KeyPrototype.Instantiate(); displayKey.LayoutElement.preferredHeight = KeySize; displayKey.LayoutElement.preferredWidth = KeySize * column.Width; displayKey.Outline.gameObject.SetActive(false); displayKey.Outline.color = DefaultColor; AssignUnboundColor(displayKey, column is InputLayoutBindableKey); if (column is InputLayoutBindableKey key) { var buttonMapping = new ButtonMapping { Button = key, DisplayButton = displayKey }; _bindButtons[key.InputSystemPath] = buttonMapping; _buttonMappings.Add(buttonMapping); displayKey.MainLabel.text = key.MainLabel; displayKey.AltLabel.text = key.AltLabel; if (column is InputLayoutMultiRowKey multiRowKey) { displayKey.Outline.rectTransform.anchorMin = displayKey.MainLabel.rectTransform.anchorMin = displayKey.AltLabel.rectTransform.anchorMin = displayKey.Fill.rectTransform.anchorMin = Vector2.down * (multiRowKey.Height - 1); } } else { displayKey.MainLabel.text = ""; displayKey.AltLabel.text = ""; } } else { throw new ArgumentOutOfRangeException(nameof(column)); } } } else if (row is InputLayoutRowSpacer rowSpacer) { RowSpacerPrototype.Instantiate().preferredHeight = KeySize * rowSpacer.Height; } else { throw new ArgumentOutOfRangeException(nameof(row)); } } } public InputLayout ParseJson(string layout) { var rows = new List(); var nextWidth = 1f; var nextHeight = 1; foreach (var rowValue in SimpleJson.TextToArray(layout)) { if (!(rowValue is List parsedRow)) throw new FormatException("Unexpected keyboard layout JSON format: expected a row array."); var row = new InputLayoutKeyRow(); var columns = new List(); foreach (var columnValue in parsedRow) { switch (columnValue) { case Dictionary options: ApplyLayoutOptions(options, rows, columns, ref nextWidth, ref nextHeight); break; case string label: columns.Add(CreateLayoutKey(label, ref nextWidth, ref nextHeight)); break; default: throw new FormatException("Unexpected keyboard layout JSON format: expected a key label or options object."); } } row.Columns = columns.ToArray(); rows.Add(row); } return new InputLayout {Rows = rows.ToArray()}; } private static void ApplyLayoutOptions( Dictionary options, ICollection rows, ICollection columns, ref float nextWidth, ref int nextHeight) { foreach (var option in options) { switch (option.Key) { case "a": break; case "w": nextWidth = Convert.ToSingle(option.Value); break; case "h": nextHeight = Convert.ToInt32(option.Value); break; case "y": rows.Add(new InputLayoutRowSpacer {Height = Convert.ToSingle(option.Value)}); break; case "x": columns.Add(new InputLayoutColumnSpacer {Width = Convert.ToSingle(option.Value)}); break; } } } private static InputLayoutKey CreateLayoutKey(string label, ref float nextWidth, ref int nextHeight) { InputLayoutKey key; var trimmedLabel = label.Trim(); if (!string.IsNullOrEmpty(trimmedLabel)) { var labels = trimmedLabel.Split('\n'); key = nextHeight != 1 ? new InputLayoutMultiRowKey {Height = nextHeight} : new InputLayoutBindableKey(); nextHeight = 1; ((InputLayoutBindableKey) key).MainLabel = labels.Length == 2 ? labels[1] : trimmedLabel; ((InputLayoutBindableKey) key).AltLabel = labels.Length == 2 ? labels[0] : ""; } else { key = new InputLayoutKey(); } key.Width = nextWidth; nextWidth = 1f; return key; } private IEnumerator AssociateInputKeys() { var path = ""; var keyPress = new InputAction(binding: "//