Files
secretsplash/Editor/Inspectors/PSXUIEditors.cs
2026-03-25 12:25:48 +01:00

447 lines
20 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using UnityEngine;
using UnityEditor;
using SplashEdit.RuntimeCode;
namespace SplashEdit.EditorCode
{
// ─── Scene Preview Gizmos ───
// These draw filled rectangles in the Scene view for WYSIWYG UI preview.
public static class PSXUIGizmos
{
[DrawGizmo(GizmoType.NonSelected | GizmoType.Selected)]
static void DrawBoxGizmo(PSXUIBox box, GizmoType gizmoType)
{
RectTransform rt = box.GetComponent<RectTransform>();
if (rt == null) return;
Vector3[] corners = new Vector3[4];
rt.GetWorldCorners(corners);
Color fill = box.BoxColor;
fill.a = (gizmoType & GizmoType.Selected) != 0 ? 0.8f : 0.5f;
Color border = Color.white;
border.a = (gizmoType & GizmoType.Selected) != 0 ? 1f : 0.4f;
Handles.DrawSolidRectangleWithOutline(corners, fill, border);
}
[DrawGizmo(GizmoType.NonSelected | GizmoType.Selected)]
static void DrawImageGizmo(PSXUIImage image, GizmoType gizmoType)
{
RectTransform rt = image.GetComponent<RectTransform>();
if (rt == null) return;
Vector3[] corners = new Vector3[4];
rt.GetWorldCorners(corners);
bool selected = (gizmoType & GizmoType.Selected) != 0;
// Draw texture preview if available
if (image.SourceTexture != null)
{
Color tint = image.TintColor;
tint.a = selected ? 0.9f : 0.6f;
Handles.DrawSolidRectangleWithOutline(corners, tint * 0.3f, tint);
// Draw texture in GUI overlay
Handles.BeginGUI();
Vector2 min = HandleUtility.WorldToGUIPoint(corners[0]);
Vector2 max = HandleUtility.WorldToGUIPoint(corners[2]);
Rect screenRect = new Rect(
Mathf.Min(min.x, max.x), Mathf.Min(min.y, max.y),
Mathf.Abs(max.x - min.x), Mathf.Abs(max.y - min.y));
if (screenRect.width > 2 && screenRect.height > 2)
{
GUI.color = new Color(tint.r, tint.g, tint.b, selected ? 0.9f : 0.5f);
GUI.DrawTexture(screenRect, image.SourceTexture, ScaleMode.StretchToFill);
GUI.color = Color.white;
}
Handles.EndGUI();
}
else
{
Color fill = new Color(0.4f, 0.4f, 0.8f, selected ? 0.4f : 0.2f);
Handles.DrawSolidRectangleWithOutline(corners, fill, Color.cyan);
}
}
[DrawGizmo(GizmoType.NonSelected | GizmoType.Selected)]
static void DrawTextGizmo(PSXUIText text, GizmoType gizmoType)
{
RectTransform rt = text.GetComponent<RectTransform>();
if (rt == null) return;
Vector3[] corners = new Vector3[4];
rt.GetWorldCorners(corners);
bool selected = (gizmoType & GizmoType.Selected) != 0;
Color border = text.TextColor;
border.a = selected ? 1f : 0.5f;
Color fill = new Color(0, 0, 0, selected ? 0.3f : 0.1f);
Handles.DrawSolidRectangleWithOutline(corners, fill, border);
// Draw text preview at actual PSX glyph size.
// On the PS1, chainprintf renders each glyph top-left aligned at the
// element's position — no centering. Mimic that here.
string label = string.IsNullOrEmpty(text.DefaultText) ? "[empty]" : text.DefaultText;
PSXFontAsset font = text.GetEffectiveFont();
int glyphW = font != null ? font.GlyphWidth : 8;
int glyphH = font != null ? font.GlyphHeight : 16;
Handles.BeginGUI();
// Convert top-left corner (corners[1] in Unity is top-left after GetWorldCorners)
Vector2 topLeft = HandleUtility.WorldToGUIPoint(corners[1]);
Vector2 botRight = HandleUtility.WorldToGUIPoint(corners[3]);
// Calculate pixel scale: how many screen pixels represent one PSX pixel
float rectWorldW = Vector3.Distance(corners[0], corners[3]);
float rectScreenW = Mathf.Abs(botRight.x - topLeft.x);
float rectW = rt.rect.width;
float psxPixelScale = (rectW > 0.01f) ? rectScreenW / rectW : 1f;
float charScreenW = glyphW * psxPixelScale;
float charScreenH = glyphH * psxPixelScale;
int fontSize = Mathf.Clamp(Mathf.RoundToInt(charScreenH * 0.75f), 6, 72);
GUIStyle style = new GUIStyle(EditorStyles.label);
style.normal.textColor = text.TextColor;
style.alignment = TextAnchor.UpperLeft;
style.fontSize = fontSize;
style.wordWrap = false;
style.clipping = TextClipping.Clip;
style.padding = new RectOffset(0, 0, 0, 0);
// Render position = top-left of the rect (matching PSX chainprintf behavior)
float guiW = Mathf.Abs(botRight.x - topLeft.x);
float guiH = Mathf.Abs(botRight.y - topLeft.y);
Rect guiRect = new Rect(
Mathf.Min(topLeft.x, botRight.x),
Mathf.Min(topLeft.y, botRight.y),
guiW, guiH);
GUI.color = new Color(text.TextColor.r, text.TextColor.g, text.TextColor.b, selected ? 1f : 0.7f);
GUI.Label(guiRect, label, style);
GUI.color = Color.white;
Handles.EndGUI();
}
[DrawGizmo(GizmoType.NonSelected | GizmoType.Selected)]
static void DrawProgressBarGizmo(PSXUIProgressBar bar, GizmoType gizmoType)
{
RectTransform rt = bar.GetComponent<RectTransform>();
if (rt == null) return;
Vector3[] corners = new Vector3[4];
rt.GetWorldCorners(corners);
bool selected = (gizmoType & GizmoType.Selected) != 0;
// Background
Color bgColor = bar.BackgroundColor;
bgColor.a = selected ? 0.8f : 0.5f;
Handles.DrawSolidRectangleWithOutline(corners, bgColor, Color.white * (selected ? 1f : 0.4f));
// Fill portion
float t = bar.InitialValue / 100f;
if (t > 0.001f)
{
Vector3[] fillCorners = new Vector3[4];
fillCorners[0] = corners[0]; // bottom-left
fillCorners[1] = corners[1]; // top-left
fillCorners[2] = Vector3.Lerp(corners[1], corners[2], t); // top-right (partial)
fillCorners[3] = Vector3.Lerp(corners[0], corners[3], t); // bottom-right (partial)
Color fillColor = bar.FillColor;
fillColor.a = selected ? 0.9f : 0.6f;
Handles.DrawSolidRectangleWithOutline(fillCorners, fillColor, Color.clear);
}
}
[DrawGizmo(GizmoType.NonSelected | GizmoType.Selected)]
static void DrawCanvasGizmo(PSXCanvas canvas, GizmoType gizmoType)
{
RectTransform rt = canvas.GetComponent<RectTransform>();
if (rt == null) return;
Vector3[] corners = new Vector3[4];
rt.GetWorldCorners(corners);
bool selected = (gizmoType & GizmoType.Selected) != 0;
Color border = selected ? Color.yellow : new Color(1, 1, 0, 0.3f);
Handles.DrawSolidRectangleWithOutline(corners, Color.clear, border);
// Label
if (selected)
{
Vector2 res = PSXCanvas.PSXResolution;
Vector3 topMid = (corners[1] + corners[2]) * 0.5f;
string label = $"PSX Canvas: {canvas.CanvasName} ({res.x}x{res.y})";
GUIStyle style = new GUIStyle(EditorStyles.boldLabel);
style.normal.textColor = Color.yellow;
Handles.Label(topMid, label, style);
}
}
}
/// <summary>
/// Custom inspector for PSXCanvas component.
/// Shows canvas name, visibility, sort order, font, and a summary of child elements.
/// </summary>
[CustomEditor(typeof(PSXCanvas))]
public class PSXCanvasEditor : Editor
{
public override void OnInspectorGUI()
{
serializedObject.Update();
Vector2 res = PSXCanvas.PSXResolution;
EditorGUILayout.LabelField($"PSX Canvas ({res.x}x{res.y})", EditorStyles.boldLabel);
EditorGUILayout.Space(4);
EditorGUILayout.PropertyField(serializedObject.FindProperty("canvasName"), new GUIContent("Canvas Name",
"Name used from Lua: UI.FindCanvas(\"name\"). Max 24 chars."));
EditorGUILayout.PropertyField(serializedObject.FindProperty("startVisible"), new GUIContent("Start Visible",
"Whether the canvas is visible when the scene loads."));
EditorGUILayout.PropertyField(serializedObject.FindProperty("sortOrder"), new GUIContent("Sort Order",
"Render priority (0 = back, 255 = front)."));
EditorGUILayout.PropertyField(serializedObject.FindProperty("defaultFont"), new GUIContent("Default Font",
"Default custom font for text elements. If empty, uses built-in system font (8x16)."));
EditorGUILayout.Space(4);
// Force Canvas configuration button
if (GUILayout.Button($"Reset Canvas to {res.x}x{res.y}"))
{
PSXCanvas.InvalidateResolutionCache();
((PSXCanvas)target).ConfigureCanvas();
}
EditorGUILayout.Space(4);
// Element summary
PSXCanvas canvas = (PSXCanvas)target;
int imageCount = canvas.GetComponentsInChildren<PSXUIImage>(true).Length;
int boxCount = canvas.GetComponentsInChildren<PSXUIBox>(true).Length;
int textCount = canvas.GetComponentsInChildren<PSXUIText>(true).Length;
int progressCount = canvas.GetComponentsInChildren<PSXUIProgressBar>(true).Length;
int total = imageCount + boxCount + textCount + progressCount;
EditorGUILayout.HelpBox(
$"Elements: {total} total\n" +
$" Images: {imageCount} | Boxes: {boxCount}\n" +
$" Texts: {textCount} | Progress Bars: {progressCount}",
total > 128 ? MessageType.Warning : MessageType.Info);
if (total > 128)
EditorGUILayout.HelpBox("PS1 UI system supports max 128 elements total across all canvases.", MessageType.Error);
serializedObject.ApplyModifiedProperties();
}
}
/// <summary>
/// Custom inspector for PSXUIImage component.
/// </summary>
[CustomEditor(typeof(PSXUIImage))]
public class PSXUIImageEditor : Editor
{
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUILayout.LabelField("PSX UI Image", EditorStyles.boldLabel);
EditorGUILayout.Space(4);
EditorGUILayout.PropertyField(serializedObject.FindProperty("elementName"), new GUIContent("Element Name",
"Name used from Lua: UI.FindElement(canvas, \"name\"). Max 24 chars."));
EditorGUILayout.PropertyField(serializedObject.FindProperty("sourceTexture"), new GUIContent("Source Texture",
"Texture to quantize and pack into VRAM."));
EditorGUILayout.PropertyField(serializedObject.FindProperty("bitDepth"), new GUIContent("Bit Depth",
"VRAM storage depth. 4-bit = 16 colors, 8-bit = 256 colors, 16-bit = direct color."));
EditorGUILayout.PropertyField(serializedObject.FindProperty("tintColor"), new GUIContent("Tint Color",
"Color multiply applied to the image (white = no tint)."));
EditorGUILayout.PropertyField(serializedObject.FindProperty("startVisible"));
// Texture size warning
PSXUIImage img = (PSXUIImage)target;
if (img.SourceTexture != null)
{
if (img.SourceTexture.width > 256 || img.SourceTexture.height > 256)
EditorGUILayout.HelpBox("Texture exceeds 256×256. It will be resized during export.", MessageType.Warning);
}
serializedObject.ApplyModifiedProperties();
}
}
/// <summary>
/// Custom inspector for PSXUIBox component.
/// </summary>
[CustomEditor(typeof(PSXUIBox))]
public class PSXUIBoxEditor : Editor
{
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUILayout.LabelField("PSX UI Box", EditorStyles.boldLabel);
EditorGUILayout.Space(4);
EditorGUILayout.PropertyField(serializedObject.FindProperty("elementName"));
EditorGUILayout.PropertyField(serializedObject.FindProperty("boxColor"), new GUIContent("Box Color",
"Solid fill color rendered as a FastFill primitive."));
EditorGUILayout.PropertyField(serializedObject.FindProperty("startVisible"));
serializedObject.ApplyModifiedProperties();
}
}
/// <summary>
/// Custom inspector for PSXUIText component.
/// </summary>
[CustomEditor(typeof(PSXUIText))]
public class PSXUITextEditor : Editor
{
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUILayout.LabelField("PSX UI Text", EditorStyles.boldLabel);
EditorGUILayout.Space(4);
EditorGUILayout.PropertyField(serializedObject.FindProperty("elementName"));
EditorGUILayout.PropertyField(serializedObject.FindProperty("defaultText"), new GUIContent("Default Text",
"Initial text content. Max 63 chars. Change at runtime via UI.SetText()."));
EditorGUILayout.PropertyField(serializedObject.FindProperty("textColor"), new GUIContent("Text Color",
"Text render color."));
EditorGUILayout.PropertyField(serializedObject.FindProperty("fontOverride"), new GUIContent("Font Override",
"Custom font for this text element. If empty, uses the canvas default font or built-in system font (8x16)."));
EditorGUILayout.PropertyField(serializedObject.FindProperty("startVisible"));
// Character count
PSXUIText txt = (PSXUIText)target;
if (!string.IsNullOrEmpty(txt.DefaultText) && txt.DefaultText.Length > 63)
EditorGUILayout.HelpBox("Text exceeds 63 characters and will be truncated.", MessageType.Warning);
// Font info
PSXFontAsset font = txt.GetEffectiveFont();
if (font != null)
{
EditorGUILayout.HelpBox(
$"Font: {font.name} ({font.GlyphWidth}x{font.GlyphHeight} glyphs)",
MessageType.Info);
}
else
{
EditorGUILayout.HelpBox("Using built-in system font (8x16 glyphs).", MessageType.Info);
}
serializedObject.ApplyModifiedProperties();
}
}
/// <summary>
/// Custom inspector for PSXUIProgressBar component.
/// </summary>
[CustomEditor(typeof(PSXUIProgressBar))]
public class PSXUIProgressBarEditor : Editor
{
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUILayout.LabelField("PSX UI Progress Bar", EditorStyles.boldLabel);
EditorGUILayout.Space(4);
EditorGUILayout.PropertyField(serializedObject.FindProperty("elementName"));
EditorGUILayout.PropertyField(serializedObject.FindProperty("backgroundColor"), new GUIContent("Background Color",
"Color shown behind the fill bar."));
EditorGUILayout.PropertyField(serializedObject.FindProperty("fillColor"), new GUIContent("Fill Color",
"Color of the progress fill."));
EditorGUILayout.PropertyField(serializedObject.FindProperty("initialValue"), new GUIContent("Initial Value",
"Starting progress (0-100). Change via UI.SetProgress()."));
EditorGUILayout.PropertyField(serializedObject.FindProperty("startVisible"));
// Preview bar
Rect r = EditorGUILayout.GetControlRect(false, 16);
PSXUIProgressBar bar = (PSXUIProgressBar)target;
EditorGUI.DrawRect(r, bar.BackgroundColor);
Rect fill = r;
fill.width *= bar.InitialValue / 100f;
EditorGUI.DrawRect(fill, bar.FillColor);
serializedObject.ApplyModifiedProperties();
}
}
/// <summary>
/// Custom inspector for PSXFontAsset ScriptableObject.
/// Shows font metrics, auto-conversion from TTF/OTF, and a preview of the glyph layout.
/// </summary>
[CustomEditor(typeof(PSXFontAsset))]
public class PSXFontAssetEditor : Editor
{
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUILayout.LabelField("PSX Font Asset", EditorStyles.boldLabel);
EditorGUILayout.Space(4);
// Source font (TTF/OTF)
EditorGUILayout.LabelField("Auto-Convert from Font", EditorStyles.miniBoldLabel);
EditorGUILayout.PropertyField(serializedObject.FindProperty("sourceFont"), new GUIContent("Source Font (TTF/OTF)",
"Assign a Unity Font (TrueType/OpenType). Click 'Generate Bitmap' to rasterize it."));
EditorGUILayout.PropertyField(serializedObject.FindProperty("fontSize"), new GUIContent("Font Size",
"Pixel height for rasterization. Determines glyph cell height."));
PSXFontAsset font = (PSXFontAsset)target;
if (font.SourceFont != null)
{
if (GUILayout.Button("Generate Bitmap from Font"))
{
font.GenerateBitmapFromFont();
}
if (font.FontTexture == null)
EditorGUILayout.HelpBox("Click 'Generate Bitmap' to create the font texture.", MessageType.Info);
}
EditorGUILayout.Space(8);
// Manual bitmap
EditorGUILayout.LabelField("Manual Bitmap Source", EditorStyles.miniBoldLabel);
EditorGUILayout.PropertyField(serializedObject.FindProperty("fontTexture"), new GUIContent("Font Texture",
"256px wide bitmap. Glyphs in ASCII order from 0x20 (space). " +
"Transparent = background, opaque = foreground."));
EditorGUILayout.Space(4);
// Glyph metrics
EditorGUILayout.LabelField("Glyph Metrics", EditorStyles.miniBoldLabel);
EditorGUILayout.PropertyField(serializedObject.FindProperty("glyphWidth"), new GUIContent("Glyph Width",
"Width of each glyph cell in pixels."));
EditorGUILayout.PropertyField(serializedObject.FindProperty("glyphHeight"), new GUIContent("Glyph Height",
"Height of each glyph cell in pixels."));
EditorGUILayout.Space(4);
int glyphsPerRow = font.GlyphsPerRow;
int rowCount = font.RowCount;
int totalH = font.TextureHeight;
int vramBytes = totalH * 128; // 128 bytes per row at 4bpp 256px
EditorGUILayout.HelpBox(
$"Layout: {glyphsPerRow} glyphs/row, {rowCount} rows\n" +
$"Texture: 256 x {totalH} pixels (4bpp)\n" +
$"VRAM: {vramBytes} bytes ({vramBytes / 1024f:F1} KB)\n" +
$"Glyph size: {font.GlyphWidth} x {font.GlyphHeight}",
MessageType.Info);
if (font.FontTexture != null)
{
if (font.FontTexture.width != 256)
EditorGUILayout.HelpBox($"Font texture must be 256 pixels wide (currently {font.FontTexture.width}).", MessageType.Error);
// Show preview
Rect previewRect = EditorGUILayout.GetControlRect(false, 64);
GUI.DrawTexture(previewRect, font.FontTexture, ScaleMode.ScaleToFit);
}
serializedObject.ApplyModifiedProperties();
}
}
}