211 lines
15 KiB
C#
211 lines
15 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
|
|
namespace BlockOutReplica.Presentation
|
|
{
|
|
public enum OriginalTextRole { Body, Counter, Title, Button, Timer, Navigation, JourneyTitle, ButtonGreen, ButtonRed, PopupLabel }
|
|
|
|
public sealed partial class BlockOutHud
|
|
{
|
|
private static OriginalSdfFontData recoveredTypeface;
|
|
private static Font typographyFallback;
|
|
private static readonly Dictionary<string, Material> typographyMaterials = new Dictionary<string, Material>();
|
|
private static readonly HashSet<string> mirroredHalfSprites = new HashSet<string> {
|
|
"Home_Button_Green", "Home_Button_Red", "Pause_Btn_Resume", "Pause_Btn_Quit", "Pause_Btn_BG",
|
|
"Prelevel_Btn_Play", "Prelevel_Btn_Play_Bg_Default", "Journey_Reward_Card", "Prelevel_Booster_Area_Default",
|
|
"Home_Button_Bg", "Home_Button_Green_0", "Home_Settings_Button", "Home_Settings_Button_Bg",
|
|
"Prelevel_Booster_Btn_Bg_Default", "Prelevel_Booster_Btn_Blue",
|
|
"Journey_Poster_Frame", "Journey_Use_Btn", "Journey_Use_Btn_BG", "Journey_Poster_Level_BG"
|
|
};
|
|
|
|
public static bool HasOriginalTypeface => Typeface != null;
|
|
private static OriginalSdfFontData Typeface => recoveredTypeface != null ? recoveredTypeface :
|
|
(recoveredTypeface = Resources.Load<OriginalSdfFontData>("OriginalTypography/MuseoSansRounded-1000"));
|
|
|
|
public static GUIStyle CreateOriginalTextStyle(int pixelSize, OriginalTextRole role = OriginalTextRole.Body,
|
|
TextAnchor alignment = TextAnchor.MiddleCenter)
|
|
{
|
|
if (typographyFallback == null) typographyFallback = Resources.Load<Font>("OriginalTypography/Roboto-Bold");
|
|
var style = new GUIStyle(GUI.skin.label) {name = "Original.Museo." + role, font = typographyFallback,
|
|
fontSize = pixelSize, fontStyle = FontStyle.Normal, alignment = alignment, wordWrap = false,
|
|
normal = {textColor = role == OriginalTextRole.Body ? new Color(.2f, .14509805f, .63529414f) : Color.white}};
|
|
return style;
|
|
}
|
|
|
|
public static bool TryDrawOriginalText(string value, Rect rect, GUIStyle style, float outlineWidth = -1)
|
|
{
|
|
var font = Typeface;
|
|
if (font == null || font.Atlas == null || font.RenderingMaterial == null || !font.Supports(value)) return false;
|
|
if (Event.current.type != EventType.Repaint) return true;
|
|
string role = style.name.StartsWith("Original.Museo.", StringComparison.Ordinal) ? style.name.Substring("Original.Museo.".Length) : "Counter";
|
|
OriginalSdfFontData.Profile profile = font.GetProfile(role);
|
|
if (profile == null) return false;
|
|
Color face = style.normal.textColor;
|
|
Material material = TypographyMaterial(role, font.RenderingMaterial);
|
|
material.SetColor("_FaceColor", face);
|
|
material.SetColor("_OutlineColor", profile.Outline);
|
|
material.SetFloat("_FaceDilate", profile.FaceDilate);
|
|
material.SetFloat("_ScaleRatioA", profile.ScaleRatioA);
|
|
material.SetFloat("_OutlineWidth", outlineWidth < 0 ? (profile.OutlineEnabled ? profile.OutlineWidth : 0) : outlineWidth);
|
|
int size = Mathf.Max(1, style.fontSize);
|
|
string[] lines = WrapOriginalText(value, rect.width, size, style.wordWrap, font);
|
|
float lineHeight = font.LineHeight * size / font.PointSize;
|
|
float blockHeight = font.CapHeight * size / font.PointSize + Mathf.Max(0, lines.Length - 1) * lineHeight;
|
|
float y = rect.y + VerticalAlignment(style.alignment, rect.height - blockHeight);
|
|
Color tint = GUI.color * GUI.contentColor;
|
|
for (int i = 0; i < lines.Length; i++)
|
|
{
|
|
float width = font.Measure(lines[i], size);
|
|
float x = rect.x + HorizontalAlignment(style.alignment, rect.width - width);
|
|
float baseline = y + font.CapHeight * size / font.PointSize + i * lineHeight;
|
|
// Underlay is behind every title layer, not between the expanded layers and face.
|
|
if (role != "Body" && role != "Navigation" && profile.UnderlayEnabled && profile.Underlay.a > 0 &&
|
|
(profile.UnderlayDilate > 0 || profile.UnderlayOffsetX != 0 || profile.UnderlayOffsetY != 0))
|
|
{
|
|
Material shadow = TypographyMaterial(role + ".Underlay", font.RenderingMaterial);
|
|
shadow.SetColor("_FaceColor", profile.Underlay); shadow.SetColor("_OutlineColor", Color.clear);
|
|
shadow.SetFloat("_FaceDilate", profile.FaceDilate + profile.UnderlayDilate * profile.ScaleRatioC / Mathf.Max(.0001f, profile.ScaleRatioA));
|
|
shadow.SetFloat("_ScaleRatioA", profile.ScaleRatioA);
|
|
shadow.SetFloat("_OutlineWidth", 0);
|
|
DrawOriginalLine(lines[i], x + profile.UnderlayOffsetX * profile.ScaleRatioC * (font.Padding + 1) * size / font.PointSize,
|
|
baseline - profile.UnderlayOffsetY * profile.ScaleRatioC * (font.Padding + 1) * size / font.PointSize, size, font, shadow, tint);
|
|
}
|
|
if (role == "Button" || role == "ButtonGreen" || role == "ButtonRed")
|
|
{
|
|
// LevelPausePopup uses expanded colored glyphs behind a plain white atlas
|
|
// face, not PlayButtonPurpleUnderlay. Retain those actual source materials.
|
|
if (role == "ButtonRed")
|
|
DrawOriginalProfileLine(lines[i], x, baseline, size, font, "ButtonRedOuter", tint);
|
|
DrawOriginalProfileLine(lines[i], x, baseline, size, font,
|
|
role == "ButtonRed" ? "ButtonRedOutline" : "ButtonGreenOutline", tint);
|
|
}
|
|
if (role == "Title" || role == "JourneyTitle")
|
|
{
|
|
// Keep the source glyphs/dilation/scale ratios. Expanded faces need the source
|
|
// purple palette, not the material's default white: original TMP vertex tints
|
|
// were stripped from the export. Their palette composition is reconstructed.
|
|
var outerProfile = font.GetProfile(role == "JourneyTitle" ? "JourneyTitleOuter" : "TitleOuter");
|
|
var middleProfile = font.GetProfile(role == "JourneyTitle" ? "JourneyTitleWhiteStroke" : "TitleExpanded");
|
|
Color outerColor = role == "JourneyTitle" ? profile.Outline : outerProfile.Underlay;
|
|
// Do not reuse the deep-purple inner contour for the wide expanded face:
|
|
// that collapsed the title into white + a single flat dark-purple frame.
|
|
// DilateFull is the actual Pause layer binding, not a "white stroke";
|
|
// the bright tint is adapted from a distinct original palette material.
|
|
Color middleColor = role == "JourneyTitle" ? profile.Underlay : font.GetProfile("TitleBrightPalette").Face;
|
|
Material outer = TypographyMaterial(role + ".Outer", font.RenderingMaterial);
|
|
outer.SetColor("_FaceColor", outerColor); outer.SetColor("_OutlineColor", outerColor);
|
|
outer.SetFloat("_FaceDilate", outerProfile.FaceDilate); outer.SetFloat("_OutlineWidth", outerProfile.OutlineEnabled ? outerProfile.OutlineWidth : 0);
|
|
outer.SetFloat("_ScaleRatioA", outerProfile.ScaleRatioA);
|
|
DrawOriginalLine(lines[i], x, baseline, size, font, outer, tint);
|
|
Material middle = TypographyMaterial(role + ".Middle", font.RenderingMaterial);
|
|
middle.SetColor("_FaceColor", middleColor); middle.SetColor("_OutlineColor", Color.clear);
|
|
middle.SetFloat("_FaceDilate", middleProfile.FaceDilate); middle.SetFloat("_OutlineWidth", 0);
|
|
middle.SetFloat("_ScaleRatioA", middleProfile.ScaleRatioA);
|
|
DrawOriginalLine(lines[i], x, baseline, size, font, middle, tint);
|
|
}
|
|
DrawOriginalLine(lines[i], x, baseline, size, font, material, tint);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public static void DrawOriginalText(string value, Rect rect, int pixelSize, OriginalTextRole role = OriginalTextRole.Body,
|
|
TextAnchor alignment = TextAnchor.MiddleCenter, Color? color = null)
|
|
{
|
|
var style = CreateOriginalTextStyle(pixelSize, role, alignment);
|
|
if (color.HasValue) style.normal.textColor = color.Value;
|
|
if (!TryDrawOriginalText(value, rect, style)) GUI.Label(rect, value, style); // Explicit non-Museo fallback for absent Unicode.
|
|
}
|
|
|
|
public static float MeasureOriginalText(string value, float pixelSize) => Typeface == null ? 0 : Typeface.Measure(value, pixelSize);
|
|
|
|
public static void DrawOriginalTitle(string value, Rect rect, int pixelSize, bool journey = false) =>
|
|
DrawOriginalText(value, rect, pixelSize, journey ? OriginalTextRole.JourneyTitle : OriginalTextRole.Title);
|
|
|
|
private static Material TypographyMaterial(string key, Material source)
|
|
{
|
|
if (!typographyMaterials.TryGetValue(key, out Material material) || material == null)
|
|
{material = new Material(source) {hideFlags = HideFlags.HideAndDontSave}; typographyMaterials[key] = material;}
|
|
return material;
|
|
}
|
|
private static void DrawOriginalProfileLine(string line, float x, float baseline, int size,
|
|
OriginalSdfFontData font, string role, Color tint)
|
|
{
|
|
var profile = font.GetProfile(role);
|
|
if (profile == null) return;
|
|
if (profile.UnderlayEnabled)
|
|
{
|
|
Material shadow = TypographyMaterial(role + ".Underlay", font.RenderingMaterial);
|
|
shadow.SetColor("_FaceColor", profile.Underlay); shadow.SetColor("_OutlineColor", Color.clear);
|
|
shadow.SetFloat("_FaceDilate", profile.FaceDilate + profile.UnderlayDilate * profile.ScaleRatioC / Mathf.Max(.0001f, profile.ScaleRatioA));
|
|
shadow.SetFloat("_ScaleRatioA", profile.ScaleRatioA); shadow.SetFloat("_OutlineWidth", 0);
|
|
DrawOriginalLine(line, x + profile.UnderlayOffsetX * profile.ScaleRatioC * (font.Padding + 1) * size / font.PointSize,
|
|
baseline - profile.UnderlayOffsetY * profile.ScaleRatioC * (font.Padding + 1) * size / font.PointSize, size, font, shadow, tint);
|
|
}
|
|
Material material = TypographyMaterial(role, font.RenderingMaterial);
|
|
material.SetColor("_FaceColor", profile.Face); material.SetColor("_OutlineColor", profile.Outline);
|
|
material.SetFloat("_FaceDilate", profile.FaceDilate); material.SetFloat("_ScaleRatioA", profile.ScaleRatioA);
|
|
material.SetFloat("_OutlineWidth", profile.OutlineEnabled ? profile.OutlineWidth : 0);
|
|
DrawOriginalLine(line, x, baseline, size, font, material, tint);
|
|
}
|
|
private static void DrawOriginalLine(string line, float x, float baseline, int size, OriginalSdfFontData font, Material material, Color tint)
|
|
{
|
|
float scale = (float)size / font.PointSize;
|
|
foreach (char character in line)
|
|
{
|
|
if (!font.TryGetGlyph(character, out OriginalSdfFontData.Glyph glyph)) continue;
|
|
float s = scale * glyph.Scale;
|
|
if (glyph.Width > 0 && glyph.Height > 0)
|
|
{
|
|
float padding = font.Padding;
|
|
Rect target = new Rect(x + (glyph.BearingX - padding) * s, baseline - (glyph.BearingY + padding) * s,
|
|
(glyph.Rect.width + padding * 2) * s, (glyph.Rect.height + padding * 2) * s);
|
|
Rect uv = new Rect((glyph.Rect.x - padding) / font.Atlas.width, (glyph.Rect.y - padding) / font.Atlas.height,
|
|
(glyph.Rect.width + padding * 2) / font.Atlas.width, (glyph.Rect.height + padding * 2) / font.Atlas.height);
|
|
Graphics.DrawTexture(target, font.Atlas, uv, 0, 0, 0, 0, tint, material);
|
|
}
|
|
x += glyph.Advance * s;
|
|
}
|
|
}
|
|
private static string[] WrapOriginalText(string value, float width, int size, bool wrap, OriginalSdfFontData font)
|
|
{
|
|
if (!wrap) return value.Replace("\r", "").Split('\n');
|
|
var result = new List<string>();
|
|
foreach (string paragraph in value.Replace("\r", "").Split('\n'))
|
|
{
|
|
string line = "";
|
|
foreach (string word in paragraph.Split(' '))
|
|
{
|
|
string candidate = line.Length == 0 ? word : line + " " + word;
|
|
if (line.Length > 0 && font.Measure(candidate, size) > width) {result.Add(line); line = word;}
|
|
else line = candidate;
|
|
}
|
|
result.Add(line);
|
|
}
|
|
return result.ToArray();
|
|
}
|
|
private static float HorizontalAlignment(TextAnchor alignment, float extra) =>
|
|
alignment == TextAnchor.UpperRight || alignment == TextAnchor.MiddleRight || alignment == TextAnchor.LowerRight ? extra :
|
|
alignment == TextAnchor.UpperCenter || alignment == TextAnchor.MiddleCenter || alignment == TextAnchor.LowerCenter ? extra * .5f : 0;
|
|
private static float VerticalAlignment(TextAnchor alignment, float extra) =>
|
|
alignment == TextAnchor.LowerLeft || alignment == TextAnchor.LowerCenter || alignment == TextAnchor.LowerRight ? extra :
|
|
alignment == TextAnchor.MiddleLeft || alignment == TextAnchor.MiddleCenter || alignment == TextAnchor.MiddleRight ? extra * .5f : 0;
|
|
|
|
public static OriginalSpriteLayout GetOriginalSpriteLayout(Sprite sprite) => sprite == null ? OriginalSpriteLayout.Full :
|
|
mirroredHalfSprites.Contains(sprite.name) ? OriginalSpriteLayout.MirroredHalf : sprite.border == Vector4.zero ? OriginalSpriteLayout.Full : OriginalSpriteLayout.NineSlice;
|
|
|
|
/// <summary>Explicit layout mode avoids treating every atlas half-panel as a full button.</summary>
|
|
public static void DrawSlicedSprite(Sprite sprite, Rect target, OriginalSpriteLayout layout = OriginalSpriteLayout.Auto, float pixelsPerSourcePixel = 0)
|
|
{
|
|
if (sprite == null || sprite.texture == null) return;
|
|
if (layout == OriginalSpriteLayout.Auto) layout = GetOriginalSpriteLayout(sprite);
|
|
foreach (var segment in OriginalSpriteGeometry.Build(sprite.textureRect, sprite.border, target, layout, pixelsPerSourcePixel))
|
|
{
|
|
Rect source = segment.Source;
|
|
GUI.DrawTextureWithTexCoords(segment.Target, sprite.texture, new Rect(source.x / sprite.texture.width,
|
|
source.y / sprite.texture.height, source.width / sprite.texture.width, source.height / sprite.texture.height), true);
|
|
}
|
|
}
|
|
}
|
|
}
|