205 lines
7.4 KiB
C#
205 lines
7.4 KiB
C#
#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<BadgeEntry> _badgePool = new();
|
|
private readonly List<string> _cachedKeyNames = new();
|
|
private readonly List<string> _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<string> 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<RectTransform>();
|
|
|
|
Image bg = badge.AddComponent<Image>();
|
|
bg.color = new Color(0f, 0f, 0f, 0f);
|
|
bg.raycastTarget = false;
|
|
|
|
LayoutElement layoutElement = badge.AddComponent<LayoutElement>();
|
|
|
|
GameObject textGo = new GameObject("KeyText");
|
|
textGo.transform.SetParent(badge.transform, false);
|
|
|
|
RectTransform textRect = textGo.AddComponent<RectTransform>();
|
|
textRect.anchorMin = Vector2.zero;
|
|
textRect.anchorMax = Vector2.one;
|
|
textRect.offsetMin = Vector2.zero;
|
|
textRect.offsetMax = Vector2.zero;
|
|
|
|
Text text = textGo.AddComponent<Text>();
|
|
text.font = Resources.GetBuiltinResource<Font>("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>();
|
|
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
|