#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;
namespace AIGame.EditorTools
{
/// Common 标准控件的九宫格导入规则,基于 Doc/UI_Normal_Set.md。
public static class UiSpriteNineSliceRules
{
private const string CommonRoot = "Assets/Bundles/UI/Texture/Common/";
private const string JournalRoot = "Assets/Bundles/UI/Texture/Journal/";
private static readonly HashSet JournalControls = new HashSet(StringComparer.OrdinalIgnoreCase)
{
"journal_card", "journal_hero", "journal_gold_card", "journal_primary_normal", "journal_primary_pressed",
"journal_secondary_normal", "journal_secondary_pressed", "journal_disabled", "journal_progress_bg", "journal_progress_fill"
};
// W,H,L,R,T,B:设计尺寸与边界;实际切图按尺寸换算,并包含 2px 透明留边。
private static readonly Dictionary Rules = new Dictionary(StringComparer.OrdinalIgnoreCase)
{
{ "ui_panel_main", new[] { 480, 256, 40, 40, 40, 40 } },
{ "ui_panel_popup", new[] { 360, 240, 36, 36, 36, 36 } },
{ "ui_panel_content", new[] { 320, 200, 28, 28, 28, 28 } },
{ "ui_panel_header", new[] { 400, 80, 32, 32, 24, 24 } },
{ "btn_primary_normal", new[] { 224, 88, 32, 32, 24, 24 } },
{ "btn_primary_pressed", new[] { 224, 88, 32, 32, 24, 24 } },
{ "btn_primary_disabled", new[] { 224, 88, 32, 32, 24, 24 } },
{ "btn_secondary_normal", new[] { 224, 88, 32, 32, 24, 24 } },
{ "btn_secondary_pressed", new[] { 224, 88, 32, 32, 24, 24 } },
{ "btn_small_normal", new[] { 160, 64, 24, 24, 18, 18 } },
{ "btn_small_pressed", new[] { 160, 64, 24, 24, 18, 18 } },
{ "scrollbar_track_v", new[] { 28, 160, 8, 8, 16, 16 } },
{ "scrollbar_thumb_v", new[] { 28, 80, 8, 8, 16, 16 } },
{ "input_bg_normal", new[] { 360, 72, 28, 28, 20, 20 } },
{ "input_bg_focus", new[] { 360, 72, 28, 28, 20, 20 } },
{ "ui_list_item_normal", new[] { 320, 72, 20, 20, 16, 16 } },
{ "ui_list_item_selected", new[] { 320, 72, 20, 20, 16, 16 } },
{ "tab_active", new[] { 200, 72, 28, 28, 20, 20 } },
{ "tab_inactive", new[] { 200, 72, 28, 28, 20, 20 } },
{ "progress_bg", new[] { 400, 32, 16, 16, 10, 10 } },
{ "progress_fill", new[] { 400, 32, 16, 16, 10, 10 } },
{ "slider_track", new[] { 320, 28, 14, 14, 8, 8 } },
{ "slider_fill", new[] { 320, 28, 14, 14, 8, 8 } },
{ "toast_bg", new[] { 280, 80, 28, 28, 20, 20 } },
{ "toggle_bg_off", new[] { 112, 56, 28, 28, 18, 18 } },
{ "toggle_bg_on", new[] { 112, 56, 28, 28, 18, 18 } }
};
public static bool IsStandardControl(string path)
{
if (string.IsNullOrEmpty(path)) return false;
path = path.Replace('\\', '/');
if (string.Equals(Path.GetDirectoryName(path)?.Replace('\\', '/'), JournalRoot.TrimEnd('/'), StringComparison.OrdinalIgnoreCase))
return JournalControls.Contains(Path.GetFileNameWithoutExtension(path));
return path.StartsWith(CommonRoot, StringComparison.OrdinalIgnoreCase)
&& string.Equals(Path.GetDirectoryName(path)?.Replace('\\', '/'), CommonRoot.TrimEnd('/'), StringComparison.OrdinalIgnoreCase)
&& Rules.ContainsKey(Path.GetFileNameWithoutExtension(path));
}
public static Vector4 GetBorder(string path, int width, int height)
{
if (JournalControls.Contains(Path.GetFileNameWithoutExtension(path)))
return new Vector4(30f * width / 128, 32f * height / 128, 30f * width / 128, 28f * height / 128);
int[] rule = Rules[Path.GetFileNameWithoutExtension(path)];
float sx = Mathf.Max(1, width - 4) / (float)rule[0];
float sy = Mathf.Max(1, height - 4) / (float)rule[1];
// Unity Vector4 顺序为 L,B,R,T,与文档 L,R,T,B 不同。
float maxX = Mathf.Max(1, (width - 1) / 2);
float maxY = Mathf.Max(1, (height - 1) / 2);
return new Vector4(
Mathf.Clamp(Mathf.Round(rule[2] * sx) + 2, 1, maxX),
Mathf.Clamp(Mathf.Round(rule[5] * sy) + 2, 1, maxY),
Mathf.Clamp(Mathf.Round(rule[3] * sx) + 2, 1, maxX),
Mathf.Clamp(Mathf.Round(rule[4] * sy) + 2, 1, maxY));
}
public static bool Configure(TextureImporter importer)
{
if (!IsStandardControl(importer.assetPath)) return false;
importer.GetSourceTextureWidthAndHeight(out int width, out int height);
Vector4 border = GetBorder(importer.assetPath, width, height);
var settings = new TextureImporterSettings();
importer.ReadTextureSettings(settings);
bool changed = importer.textureType != TextureImporterType.Sprite
|| importer.spriteImportMode != SpriteImportMode.Single
|| settings.spriteMeshType != SpriteMeshType.FullRect
|| importer.spriteBorder != border;
if (!changed) return false;
importer.textureType = TextureImporterType.Sprite;
importer.spriteImportMode = SpriteImportMode.Single;
importer.ReadTextureSettings(settings);
settings.spriteMeshType = SpriteMeshType.FullRect;
importer.SetTextureSettings(settings);
importer.spriteBorder = border;
return true;
}
[MenuItem("Tools/UI/Apply Standard Control Nine-Slice")]
public static void ApplyFromMenu()
{
Debug.Log("[UIGen] 九宫格更新: " + ApplyExisting() + " 张标准控件图。");
}
public static int ApplyExisting()
{
if (EditorApplication.isPlayingOrWillChangePlaymode)
throw new InvalidOperationException("Apply UI sprite borders in Edit Mode.");
int changed = 0;
foreach (string name in Rules.Keys)
{
string path = CommonRoot + name + ".png";
var importer = AssetImporter.GetAtPath(path) as TextureImporter;
if (importer == null || !Configure(importer)) continue;
importer.SaveAndReimport();
changed++;
}
foreach (string name in JournalControls)
{
var importer = AssetImporter.GetAtPath(JournalRoot + name + ".png") as TextureImporter;
if (importer == null || !Configure(importer)) continue;
importer.SaveAndReimport();
changed++;
}
return changed;
}
}
public sealed class UiSpriteNineSlicePostprocessor : AssetPostprocessor
{
private void OnPreprocessTexture()
{
UiSpriteNineSliceRules.Configure((TextureImporter)assetImporter);
}
}
}
#endif