#if UNITY_EDITOR #nullable enable using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; namespace io.github.hatayama.uLoopMCP { public class SimulateKeyboardOverlay : MonoBehaviour { public const float CONTAINER_BACKGROUND_ALPHA = 0.8f; private const float PRESS_DISPLAY_DURATION = 0.5f; private const float FADE_DURATION = 0.2f; private const int FONT_SIZE = 48; private const int GLYPH_FONT_SIZE = 64; private static readonly Color ContainerBackgroundColor = new Color(0.15f, 0.15f, 0.15f, CONTAINER_BACKGROUND_ALPHA); [SerializeField] private GameObject _container = null!; [SerializeField] private Image _containerImage = null!; private readonly List _badgePool = new(); private readonly List _cachedKeyNames = new(); private readonly List _displayKeys = new(); private void Awake() { Debug.Assert(_container != null, "_container must be assigned in prefab"); Debug.Assert(_containerImage != null, "_containerImage must be assigned in prefab"); // Prefab keeps preview badges for editor inspection, but runtime badges must start empty. for (int i = _container!.transform.childCount - 1; i >= 0; i--) { DestroyImmediate(_container.transform.GetChild(i).gameObject); } _container.SetActive(false); } private void LateUpdate() { bool hasHeldKeys = SimulateKeyboardOverlayState.HeldKeys.Count > 0; string? pressKey = SimulateKeyboardOverlayState.PressKey; bool isPressHeld = SimulateKeyboardOverlayState.IsPressHeld; float pressElapsedSinceRelease = 0f; if (pressKey != null && !isPressHeld) { pressElapsedSinceRelease = Time.realtimeSinceStartup - SimulateKeyboardOverlayState.PressReleasedTime; if (pressElapsedSinceRelease > PRESS_DISPLAY_DURATION + FADE_DURATION) { SimulateKeyboardOverlayState.ClearPress(); pressKey = null; } } if (!hasHeldKeys && pressKey == null) { SetBadgeCount(0); _container.SetActive(false); return; } _displayKeys.Clear(); IReadOnlyList heldKeys = SimulateKeyboardOverlayState.HeldKeys; for (int i = 0; i < heldKeys.Count; i++) { _displayKeys.Add(heldKeys[i]); } if (pressKey != null && !_displayKeys.Contains(pressKey)) { _displayKeys.Add(pressKey); } SetBadgeCount(_displayKeys.Count); _container.SetActive(true); float maxAlpha = 0f; for (int i = 0; i < _displayKeys.Count; i++) { float alpha = GetBadgeAlpha(_displayKeys[i], pressKey, isPressHeld, pressElapsedSinceRelease); UpdateBadge(_badgePool[i], i, _displayKeys[i], alpha); if (alpha > maxAlpha) { maxAlpha = alpha; } } _containerImage.color = new Color( ContainerBackgroundColor.r, ContainerBackgroundColor.g, ContainerBackgroundColor.b, ContainerBackgroundColor.a * maxAlpha); } private void SetBadgeCount(int count) { while (_badgePool.Count < count) { _badgePool.Add(CreateBadge()); _cachedKeyNames.Add(""); } for (int i = 0; i < _badgePool.Count; i++) { _badgePool[i].Root.SetActive(i < count); } } private BadgeEntry CreateBadge() { GameObject badge = new GameObject("KeyBadge"); badge.transform.SetParent(_container.transform, false); badge.AddComponent(); Image bg = badge.AddComponent(); bg.color = new Color(0f, 0f, 0f, 0f); bg.raycastTarget = false; LayoutElement layoutElement = badge.AddComponent(); GameObject textGo = new GameObject("KeyText"); textGo.transform.SetParent(badge.transform, false); RectTransform textRect = textGo.AddComponent(); textRect.anchorMin = Vector2.zero; textRect.anchorMax = Vector2.one; textRect.offsetMin = Vector2.zero; textRect.offsetMax = Vector2.zero; Text text = textGo.AddComponent(); text.font = Resources.GetBuiltinResource("LegacyRuntime.ttf"); text.fontSize = FONT_SIZE; text.fontStyle = FontStyle.Bold; text.color = Color.white; text.alignment = TextAnchor.MiddleCenter; text.horizontalOverflow = HorizontalWrapMode.Overflow; text.raycastTarget = false; // Thicken glyph strokes so Unicode symbols don't look too thin Outline outline = textGo.AddComponent(); outline.effectColor = Color.white; outline.effectDistance = new Vector2(1f, -1f); return new BadgeEntry(badge, bg, text, layoutElement); } private void UpdateBadge(BadgeEntry badge, int index, string keyName, float alpha) { badge.Text.color = new Color(1f, 1f, 1f, alpha); if (_cachedKeyNames[index] == keyName) { return; } _cachedKeyNames[index] = keyName; string symbol = KeySymbolMap.GetSymbol(keyName); badge.Text.text = symbol; badge.Text.fontSize = KeySymbolMap.IsGlyphSymbol(symbol) ? GLYPH_FONT_SIZE : FONT_SIZE; TextGenerationSettings settings = badge.Text.GetGenerationSettings(new Vector2(float.MaxValue, float.MaxValue)); float preferredWidth = badge.Text.cachedTextGeneratorForLayout.GetPreferredWidth(symbol, settings); float preferredHeight = badge.Text.cachedTextGeneratorForLayout.GetPreferredHeight(symbol, settings); badge.Layout.preferredWidth = Mathf.Max(preferredWidth / badge.Text.pixelsPerUnit, preferredHeight / badge.Text.pixelsPerUnit); badge.Layout.preferredHeight = preferredHeight / badge.Text.pixelsPerUnit; } private static float GetBadgeAlpha( string keyName, string? pressKey, bool isPressHeld, float pressElapsedSinceRelease) { if (pressKey == null || keyName != pressKey || isPressHeld || pressElapsedSinceRelease <= PRESS_DISPLAY_DURATION) { return 1f; } float fadeT = Mathf.Clamp01((pressElapsedSinceRelease - PRESS_DISPLAY_DURATION) / FADE_DURATION); return 1f - fadeT; } private readonly struct BadgeEntry { public readonly GameObject Root; public readonly Image Background; public readonly Text Text; public readonly LayoutElement Layout; public BadgeEntry(GameObject root, Image background, Text text, LayoutElement layout) { Root = root; Background = background; Text = text; Layout = layout; } } } } #endif