using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using MessagePack; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using TMPro; using UniRx; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.UI; using UnityEngine.UI.Extensions; using CultMath; using UnityEngine.EventSystems; using static CultMath.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(); var path = Path.Combine(ActionGameManager.GameDataDirectory.CreateSubdirectory("KeyboardLayouts").FullName, $"{LayoutFile.name}.msgpack"); // _inputLayout = ParseJson(LayoutFile.text); _inputLayout = MessagePackSerializer.Deserialize(File.ReadAllBytes(path)); // _inputLayout = JsonConvert.DeserializeObject(File.ReadAllText(path)); // SaveLayout(); 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; var reader = new JsonTextReader(new StringReader(layout)); reader.Read(); if (reader.TokenType != JsonToken.StartArray) throw new JsonReaderException($"Unexpected JSON format in line {reader.LineNumber}:{reader.LinePosition}, expected: StartArray, received: {Enum.GetName(typeof(JsonToken), reader.TokenType)}"); while(reader.Read() && reader.TokenType != JsonToken.EndArray) { if (reader.TokenType != JsonToken.StartArray) throw new JsonReaderException($"Unexpected JSON format in line {reader.LineNumber}:{reader.LinePosition}, expected: StartArray, received: {Enum.GetName(typeof(JsonToken), reader.TokenType)}"); var row = new InputLayoutKeyRow(); var columns = new List(); while (reader.Read() && reader.TokenType != JsonToken.EndArray) { switch (reader.TokenType) { case JsonToken.StartObject: while(reader.Read() && reader.TokenType != JsonToken.EndObject) { switch (reader.Value.ToString()) { case "a": break; case "w": nextWidth = (float) (reader.ReadAsDouble() ?? 1.0); break; case "h": nextHeight = reader.ReadAsInt32() ?? 1; break; case "y": rows.Add(new InputLayoutRowSpacer{Height = (float) (reader.ReadAsDouble() ?? 0.0)}); break; case "x": columns.Add(new InputLayoutColumnSpacer{Width = (float) (reader.ReadAsDouble() ?? 0.0)}); break; } } break; case JsonToken.String: InputLayoutKey key; var v = reader.Value.ToString().Trim(); if (!string.IsNullOrEmpty(v)) { var labels = v.Split('\n'); key = nextHeight != 1 ? new InputLayoutMultiRowKey {Height = nextHeight} : new InputLayoutBindableKey(); nextHeight = 1; ((InputLayoutBindableKey) key).MainLabel = labels.Length == 2 ? labels[1] : v; ((InputLayoutBindableKey) key).AltLabel = labels.Length == 2 ? labels[0] : ""; } else key = new InputLayoutKey(); key.Width = nextWidth; columns.Add(key); nextWidth = 1f; break; default: throw new JsonReaderException($"Unexpected JSON format in line {reader.LineNumber}:{reader.LinePosition}"); } } row.Columns = columns.ToArray(); rows.Add(row); } return new InputLayout {Rows = rows.ToArray()}; } private IEnumerator AssociateInputKeys() { var path = ""; var keyPress = new InputAction(binding: "//