feat: Initial commit

This commit is contained in:
2025-02-14 16:06:00 +08:00
commit da75a84e02
1056 changed files with 163517 additions and 0 deletions

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ad96c636bfa80024f8427b7755b56639
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,113 @@
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace Cryville.Common.Unity.UI {
/// <summary>
/// A <see cref="ILayoutElement" /> that takes the length of one axis to compute the preferred length of the other axis with respect to a aspect ratio.
/// </summary>
[AddComponentMenu("Layout/Aspect Ratio Layout Element")]
[ExecuteAlways]
public class AspectRatioLayoutElement : UIBehaviour, ILayoutElement {
[SerializeField]
[Tooltip("The aspect ratio. Width divided by height.")]
private float m_aspectRatio = 1;
/// <summary>
/// The aspect ratio. Width divided by height.
/// </summary>
public float AspectRatio {
get { return m_aspectRatio; }
set { SetProperty(ref m_aspectRatio, value); }
}
[SerializeField]
[Tooltip("Whether to compute the length of the y axis.")]
private bool m_isVertical = false;
/// <summary>
/// Whether to compute the length of the y axis.
/// </summary>
public bool IsVertical {
get { return m_isVertical; }
set { SetProperty(ref m_isVertical, value); }
}
private void SetProperty<T>(ref T prop, T value) {
if (Equals(prop, value)) return;
prop = value;
SetDirty();
}
/// <inheritdoc />
public float minWidth {
get {
return m_isVertical ? -1 : (transform as RectTransform).rect.height * m_aspectRatio;
}
}
/// <inheritdoc />
public float preferredWidth { get { return minWidth; } }
/// <inheritdoc />
public float flexibleWidth { get { return -1; } }
/// <inheritdoc />
public float minHeight {
get {
return m_isVertical ? (transform as RectTransform).rect.width / m_aspectRatio : -1;
}
}
/// <inheritdoc />
public float preferredHeight { get { return minHeight; } }
/// <inheritdoc />
public float flexibleHeight { get { return -1; } }
/// <inheritdoc />
public int layoutPriority { get { return 1; } }
/// <inheritdoc />
public void CalculateLayoutInputHorizontal() { }
/// <inheritdoc />
public void CalculateLayoutInputVertical() { }
protected override void OnEnable() {
base.OnEnable();
SetDirty();
}
protected override void OnBeforeTransformParentChanged() {
base.OnBeforeTransformParentChanged();
SetDirty();
}
protected override void OnTransformParentChanged() {
base.OnTransformParentChanged();
SetDirty();
}
protected override void OnDidApplyAnimationProperties() {
base.OnDidApplyAnimationProperties();
SetDirty();
}
#if UNITY_EDITOR
protected override void OnValidate() {
base.OnValidate();
SetDirty();
}
#endif
protected override void OnDisable() {
SetDirty();
base.OnDisable();
}
protected override void OnRectTransformDimensionsChange() {
base.OnRectTransformDimensionsChange();
SetDirty();
}
bool _delayedSetDirty;
private void SetDirty() {
if (!IsActive()) return;
if (CanvasUpdateRegistry.IsRebuildingLayout()) _delayedSetDirty = true;
else LayoutRebuilder.MarkLayoutForRebuild(transform as RectTransform);
}
void Update() {
if (!_delayedSetDirty) return;
_delayedSetDirty = false;
LayoutRebuilder.MarkLayoutForRebuild(transform as RectTransform);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 96276a3ffc8f32c4fbb7035a6fb28e74
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,24 @@
using UnityEngine;
namespace Cryville.Common.Unity.UI {
/// <summary>
/// A <see cref="DockLayoutGroup" /> that sets the aspect ratio of the docking element.
/// </summary>
[AddComponentMenu("Layout/Dock Aspect Ratio Layout Group")]
public sealed class DockAspectRatioLayoutGroup : DockLayoutGroup {
[SerializeField]
[Tooltip("The aspect ratio of the docking element.")]
private float m_dockAspectRatio = 1;
/// <summary>
/// The aspect ratio of the docking element.
/// </summary>
public float DockAspectRatio {
get { return m_dockAspectRatio; }
set { base.SetProperty(ref m_dockAspectRatio, value); }
}
protected override float GetDockElementSize(Vector2 groupSize) {
return groupSize.y * m_dockAspectRatio;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9023f24d2fe04fe45bf66a9d029591c1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,136 @@
using UnityEngine;
using UnityEngine.UI;
namespace Cryville.Common.Unity.UI {
/// <summary>
/// A <see cref="LayoutGroup" /> that docks its first child element to one side.
/// </summary>
public abstract class DockLayoutGroup : LayoutGroup {
/// <summary>
/// The dock side.
/// </summary>
public enum Side {
/// <summary>
/// Top.
/// </summary>
Top = 0,
/// <summary>
/// Right.
/// </summary>
Right = 1,
/// <summary>
/// Bottom.
/// </summary>
Bottom = 2,
/// <summary>
/// Left.
/// </summary>
Left = 3,
}
[SerializeField]
[Tooltip("The docking side of the first child element.")]
private Side m_side;
/// <summary>
/// The docking side of the first child element.
/// </summary>
public Side DockSide {
get { return m_side; }
set { SetProperty(ref m_side, value); }
}
[SerializeField]
[Tooltip("The slide index. The children slide along the cross axis.")]
private float m_slideIndex;
/// <summary>
/// The slide index. The children slide along the axis.
/// </summary>
public float SlideIndex {
get { return m_slideIndex; }
set { SetProperty(ref m_slideIndex, value); }
}
/// <inheritdoc />
public sealed override void CalculateLayoutInputHorizontal() { base.CalculateLayoutInputHorizontal(); CalcAlongAxis(0); }
/// <inheritdoc />
public sealed override void CalculateLayoutInputVertical() { CalcAlongAxis(1); }
/// <inheritdoc />
public sealed override void SetLayoutHorizontal() { SetChildrenAlongAxis(0); }
/// <inheritdoc />
public sealed override void SetLayoutVertical() { SetChildrenAlongAxis(1); }
private void CalcAlongAxis(int axis) {
int isHorizontal = (int)m_side & 1;
if ((isHorizontal ^ axis) == 1) {
SetLayoutInputForAxis(0, 0, 1, axis); // TODO
}
else {
float padding = isHorizontal == 0 ? m_Padding.horizontal : m_Padding.vertical;
float totalMin = 0, totalPreferred = 0, totalFlexible = 0;
for (int i = 0; i < rectChildren.Count; i++) {
GetChildSizes(rectChildren[i], axis, out float min, out float preferred, out float flexible);
if (min > totalMin) totalMin = min;
if (preferred > totalPreferred) totalPreferred = preferred;
if (flexible > totalFlexible) totalFlexible = flexible;
}
SetLayoutInputForAxis(totalMin + padding, totalPreferred + padding, totalFlexible, axis);
}
}
private void GetChildSizes(RectTransform child, int axis, out float min, out float preferred, out float flexible) {
min = LayoutUtility.GetMinSize(child, axis);
preferred = LayoutUtility.GetPreferredSize(child, axis);
flexible = LayoutUtility.GetFlexibleSize(child, axis);
}
private float GetSlidePosition(float groupHeight, float dockHeight) {
bool d = Mathf.FloorToInt(m_slideIndex - Mathf.Floor(m_slideIndex / 2) * 2) == 0;
int l = Mathf.FloorToInt(m_slideIndex / 2);
float p = m_slideIndex - Mathf.Floor(m_slideIndex);
if (d) return l * groupHeight + p * dockHeight;
else return l * groupHeight + dockHeight + p * (groupHeight - dockHeight);
}
private void SetChildrenAlongAxis(int axis) {
int isHorizontal = (int)m_side & 1;
bool isReversed = m_side == Side.Right || m_side == Side.Bottom;
var rect = rectTransform.rect;
if ((isHorizontal ^ axis) == 1) {
float p0 = isHorizontal == 1 ? m_Padding.left : m_Padding.top;
float p1 = isHorizontal == 1 ? m_Padding.right : m_Padding.bottom;
var gs = rect.size - new Vector2(m_Padding.horizontal, m_Padding.vertical);
if (isHorizontal == 0) gs = new Vector2(gs.y, gs.x);
if (rectChildren.Count == 1) {
SetChildAlongAxis(rectChildren[0], axis, p0, gs.x);
}
else {
float s1 = GetDockElementSize(gs);
float s0 = GetSlidePosition(gs.x, s1);
float a1 = (isHorizontal == 0 ? rect.height : rect.width) - p0 - p1;
for (int i = 0; i < rectChildren.Count; i++) {
var c = rectChildren[i];
bool d = i % 2 == 0;
int l = i / 2;
if (isReversed)
SetChildAlongAxis(c, axis, (d ? a1 - s1 + p0 : p0) - a1 * l + s0, d ? s1 : a1 - s1);
else
SetChildAlongAxis(c, axis, (d ? p0 : s1 + p0) - s0 + a1 * l, d ? s1 : a1 - s1);
}
}
}
else {
float p0 = isHorizontal == 0 ? m_Padding.left : m_Padding.top;
float p1 = isHorizontal == 0 ? m_Padding.right : m_Padding.bottom;
var height = (isHorizontal == 1 ? rect.height : rect.width) - p0 - p1;
for (int i = 0; i < rectChildren.Count; i++) {
SetChildAlongAxis(rectChildren[i], axis, p0, height);
}
}
}
/// <summary>
/// Gets the length of the first child element along the axis.
/// </summary>
/// <param name="groupSize">The size of the layout group.</param>
/// <returns></returns>
protected abstract float GetDockElementSize(Vector2 groupSize);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fb94861327965934cb429511060d45fb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,24 @@
using UnityEngine;
namespace Cryville.Common.Unity.UI {
/// <summary>
/// A <see cref="DockLayoutGroup" /> that sets the occupied ratio of the docking element.
/// </summary>
[AddComponentMenu("Layout/Dock Occupied Ratio Layout Group")]
public sealed class DockOccupiedRatioLayoutGroup : DockLayoutGroup {
[SerializeField]
[Tooltip("The occupied ratio of the docking element.")]
private float m_dockOccupiedRatio = 1;
/// <summary>
/// The occupied ratio of the docking element.
/// </summary>
public float DockOccupiedRatio {
get { return m_dockOccupiedRatio; }
set { base.SetProperty(ref m_dockOccupiedRatio, value); }
}
protected override float GetDockElementSize(Vector2 groupSize) {
return groupSize.x * m_dockOccupiedRatio;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0bbf3330df0a07e46bcdf0b3c4879ecf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,97 @@
using System;
using UnityEngine;
using UnityEngine.UI;
namespace Cryville.Common.Unity.UI {
[AddComponentMenu("Layout/Flow Layout Group")]
public class FlowLayoutGroup : LayoutGroup {
[SerializeField] Vector2 m_spacing;
public Vector2 Spacing {
get { return m_spacing; }
set { SetProperty(ref m_spacing, value); }
}
[SerializeField][Range(0, 1)] float m_itemAlignmentRatio;
public float ItemAlignmentRatio {
get { return m_itemAlignmentRatio; }
set { SetProperty(ref m_itemAlignmentRatio, value); }
}
[SerializeField][Range(0, 1)] float m_itemAlignmentStretchingRatio;
public float ItemAlignmentStretchingRatio {
get { return m_itemAlignmentStretchingRatio; }
set { SetProperty(ref m_itemAlignmentStretchingRatio, value); }
}
/// <inheritdoc />
public sealed override void CalculateLayoutInputHorizontal() { base.CalculateLayoutInputHorizontal(); CalcAlongAxis(0); }
/// <inheritdoc />
public sealed override void CalculateLayoutInputVertical() { CalcAlongAxis(1); }
/// <inheritdoc />
public sealed override void SetLayoutHorizontal() { SetChildrenAlongAxis(0); }
/// <inheritdoc />
public sealed override void SetLayoutVertical() { SetChildrenAlongAxis(1); }
void CalcAlongAxis(int axis) {
if (axis == 0) {
SetLayoutInputForAxis(0, 0, 1, axis); // TODO
}
else {
float width = rectTransform.rect.width - padding.horizontal;
float x = 0, y = padding.top;
float lineHeight = 0;
for (int i = 0; i < rectChildren.Count; i++) {
RectTransform child = rectChildren[i];
float minWidth = LayoutUtility.GetMinWidth(child);
float childWidth = Math.Max(minWidth, Math.Min(width, LayoutUtility.GetPreferredWidth(child)));
float childHeight = LayoutUtility.GetPreferredHeight(child);
if (childWidth > width - x) {
x = 0;
if (i > 0) y += lineHeight + m_spacing.y;
lineHeight = 0;
}
x += childWidth + m_spacing.x;
if (childHeight > lineHeight) lineHeight = childHeight;
}
SetLayoutInputForAxis(y + lineHeight, y + lineHeight, 0, 1);
}
}
void SetChildrenAlongAxis(int axis) {
float width = rectTransform.rect.width - padding.horizontal;
float x = 0, y = padding.top;
float lineHeight = 0;
int firstItemIndexOfLine = 0;
for (int i = 0; i < rectChildren.Count; i++) {
RectTransform child = rectChildren[i];
float minWidth = LayoutUtility.GetMinWidth(child);
float childWidth = Math.Max(minWidth, Math.Min(width, LayoutUtility.GetPreferredWidth(child)));
float childHeight = LayoutUtility.GetPreferredHeight(child);
if (childWidth > width - x) {
AlignItemsInLine(firstItemIndexOfLine, i, y, lineHeight);
x = 0;
if (i > 0) y += lineHeight + m_spacing.y;
lineHeight = 0;
firstItemIndexOfLine = i;
}
SetChildAlongAxis(child, 0, x + padding.left, childWidth);
SetChildAlongAxis(child, 1, y, childHeight);
x += childWidth + m_spacing.x;
if (childHeight > lineHeight) lineHeight = childHeight;
}
AlignItemsInLine(firstItemIndexOfLine, rectChildren.Count, y, lineHeight);
}
void AlignItemsInLine(int startIndex, int endIndex, float y, float lineHeight) {
for (int i = startIndex; i < endIndex; i++) {
RectTransform child = rectChildren[i];
float childHeight = LayoutUtility.GetPreferredHeight(child) * (1 - m_itemAlignmentStretchingRatio) + lineHeight * m_itemAlignmentStretchingRatio;
SetChildAlongAxis(child, 1, y + (lineHeight - childHeight) * m_itemAlignmentRatio, childHeight);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1ce1debad67438245a6e00e503975beb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,94 @@
using UnityEngine;
using UnityEngine.UI;
namespace Cryville.Common.Unity.UI {
[AddComponentMenu("Layout/Single Layout Group")]
[ExecuteAlways]
public class SingleLayoutGroup : LayoutGroup {
[SerializeField]
protected bool m_ChildForceExpandWidth = true;
[SerializeField]
protected bool m_ChildForceExpandHeight = true;
[SerializeField]
protected bool m_ChildControlWidth = true;
[SerializeField]
protected bool m_ChildControlHeight = true;
[SerializeField]
protected bool m_ChildScaleWidth;
[SerializeField]
protected bool m_ChildScaleHeight;
public override void CalculateLayoutInputHorizontal() {
base.CalculateLayoutInputHorizontal();
CalcAlongAxis(0);
}
public override void CalculateLayoutInputVertical() { CalcAlongAxis(1); }
public override void SetLayoutHorizontal() { SetChildrenAlongAxis(0); }
public override void SetLayoutVertical() { SetChildrenAlongAxis(1); }
protected void CalcAlongAxis(int axis) {
float combinedPadding = (axis == 0) ? padding.horizontal : padding.vertical;
bool controlSize = (axis == 0) ? m_ChildControlWidth : m_ChildControlHeight;
bool useScale = (axis == 0) ? m_ChildScaleWidth : m_ChildScaleHeight;
bool childForceExpandSize = (axis == 0) ? m_ChildForceExpandWidth : m_ChildForceExpandHeight;
float totalMin = combinedPadding;
float totalPreferred = combinedPadding;
float totalFlexible = 0f;
RectTransform child = rectChildren[0];
GetChildSizes(child, axis, controlSize, childForceExpandSize, out var min, out var preferred, out var flexible);
if (useScale) {
float scaleFactor = child.localScale[axis];
min *= scaleFactor;
preferred *= scaleFactor;
flexible *= scaleFactor;
}
totalMin = Mathf.Max(min + combinedPadding, totalMin);
totalPreferred = Mathf.Max(preferred + combinedPadding, totalPreferred);
totalFlexible = Mathf.Max(flexible, totalFlexible);
totalPreferred = Mathf.Max(totalMin, totalPreferred);
SetLayoutInputForAxis(totalMin, totalPreferred, totalFlexible, axis);
}
protected void SetChildrenAlongAxis(int axis) {
float size = rectTransform.rect.size[axis];
bool controlSize = (axis == 0) ? m_ChildControlWidth : m_ChildControlHeight;
bool useScale = (axis == 0) ? m_ChildScaleWidth : m_ChildScaleHeight;
bool childForceExpandSize = (axis == 0) ? m_ChildForceExpandWidth : m_ChildForceExpandHeight;
float alignmentOnAxis = GetAlignmentOnAxis(axis);
float innerSize = size - ((axis == 0) ? padding.horizontal : padding.vertical);
RectTransform child = rectChildren[0];
GetChildSizes(child, axis, controlSize, childForceExpandSize, out var min2, out var preferred2, out var flexible2);
float scaleFactor2 = useScale ? child.localScale[axis] : 1f;
float requiredSpace = Mathf.Clamp(innerSize, min2, (flexible2 > 0f) ? size : preferred2);
float startOffset = GetStartOffset(axis, requiredSpace * scaleFactor2);
if (controlSize) {
SetChildAlongAxisWithScale(child, axis, startOffset, requiredSpace, scaleFactor2);
}
else {
float offsetInCell2 = (requiredSpace - child.sizeDelta[axis]) * alignmentOnAxis;
SetChildAlongAxisWithScale(child, axis, startOffset + offsetInCell2, scaleFactor2);
}
}
void GetChildSizes(RectTransform child, int axis, bool controlSize, bool childForceExpand, out float min, out float preferred, out float flexible) {
if (!controlSize) {
min = child.sizeDelta[axis];
preferred = min;
flexible = 0f;
}
else {
min = LayoutUtility.GetMinSize(child, axis);
preferred = LayoutUtility.GetPreferredSize(child, axis);
flexible = LayoutUtility.GetFlexibleSize(child, axis);
}
if (childForceExpand) {
flexible = Mathf.Max(flexible, 1f);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 21255a16576b76f4280a765b43a4ae1c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,80 @@
using Cryville.Common.Font;
using Cryville.Culture;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using TMPro;
using UnityEngine;
using UnityEngine.TextCore.LowLevel;
using UnityEngine.TextCore.Text;
namespace Cryville.Common.Unity.UI {
[RequireComponent(typeof(TMP_Text))]
public class TMPLocalizedText : MonoBehaviour {
public static Shader DefaultShader;
public static FontMatcher FontMatcher;
public static int MaxFallbackCount = 4;
static readonly Dictionary<CultureInfo, FontAsset> _cachedFonts = new();
[SerializeField]
Shader m_shader;
public TMP_Text Text { get; private set; }
void Awake() {
Text = GetComponent<TMP_Text>();
}
public void SetText(string text, CultureInfo culture = null) {
Text.text = text;
SetCulture(culture ?? CultureInfo.CurrentCulture);
}
void SetCulture(CultureInfo culture) {
if (FontMatcher == null) return;
string cultureName = culture.Name;
if (string.IsNullOrEmpty(cultureName)) cultureName = CultureInfo.CurrentCulture.Name;
if (!_cachedFonts.TryGetValue(culture, out var font)) {
foreach (var typeface in FontMatcher.MatchLanguage(new LanguageId(cultureName), true)) {
try {
var ifont = CreateFontAsset(typeface.File.FullName, typeface.IndexInFile);
if (m_shader) ifont.material.shader = m_shader;
else if (DefaultShader) ifont.material.shader = DefaultShader;
if (font == null) {
font = ifont;
if (MaxFallbackCount <= 0) break;
}
else {
font.fallbackFontAssetTable ??= new List<FontAsset>();
font.fallbackFontAssetTable.Add(ifont);
if (font.fallbackFontAssetTable.Count >= MaxFallbackCount) break;
}
}
catch (Exception) { }
}
_cachedFonts.Add(culture, font);
}
Text.font = font;
}
static MethodInfo _methodCreateFontAsset;
static readonly object[] _paramsCreateFontAsset = new object[] { null, null, 90, 9, GlyphRenderMode.SDFAA, 1024, 1024, Type.Missing, Type.Missing };
static FontAsset CreateFontAsset(string path, int index) {
if (_methodCreateFontAsset == null) {
_methodCreateFontAsset = typeof(FontAsset).GetMethod(
"CreateFontAsset", BindingFlags.Static | BindingFlags.NonPublic, null,
new Type[] {
typeof(string), typeof(int), typeof(int), typeof(int),
typeof(GlyphRenderMode), typeof(int), typeof(int),
typeof(AtlasPopulationMode), typeof(bool)
},
null
);
}
_paramsCreateFontAsset[0] = path;
_paramsCreateFontAsset[1] = index;
return (FontAsset)_methodCreateFontAsset.Invoke(null, _paramsCreateFontAsset);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c03870a7d4386e846be005a0ac36e987
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: