mirror of
https://github.com/All-Of-Us-Mods/MiraAPI.git
synced 2026-10-27 23:44:35 +00:00
Compare commits
3
Commits
test-fixes
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
51397d78a7 | ||
|
|
08fd56754e | ||
|
|
181013efec |
No files matched your search
@@ -5,7 +5,7 @@ namespace MiraAPI.Example;
|
||||
|
||||
public static class ExampleAssets
|
||||
{
|
||||
private static readonly Assembly _assembly = typeof(MiraAssets).Assembly;
|
||||
private static readonly Assembly _assembly = typeof(ExampleAssets).Assembly;
|
||||
|
||||
public static LoadableResourceAsset ExampleButton { get; } = new("MiraAPI.Example.Resources.ExampleButton.png", _assembly);
|
||||
public static LoadableResourceAsset CallMeetingButton { get; } = new("MiraAPI.Example.Resources.CallMeeting.png", _assembly);
|
||||
|
||||
@@ -19,7 +19,7 @@ public partial class ExamplePlugin : BasePlugin, IMiraPlugin
|
||||
{
|
||||
public ExamplePlugin()
|
||||
{
|
||||
MiraLocaleManager.Register("mira.example");
|
||||
MiraLocaleManager.Register("mira.example", typeof(ExamplePlugin).Assembly);
|
||||
}
|
||||
|
||||
public Harmony Harmony { get; } = new(Id);
|
||||
|
||||
@@ -58,13 +58,13 @@ public class CustomColor(StringNames name, Color32 mainColor, Color32 shadowColo
|
||||
|
||||
/// <inheritdoc cref="CustomColor"/>
|
||||
public CustomColor(StringNames name, Color32 mainColor)
|
||||
: this(name, mainColor, mainColor.GetShadowColor(60))
|
||||
: this(name, mainColor, mainColor.DarkenAbsolute(60))
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="CustomColor"/>
|
||||
public CustomColor(string name, Color32 mainColor)
|
||||
: this(name, mainColor, mainColor.GetShadowColor(60))
|
||||
: this(name, mainColor, mainColor.DarkenAbsolute(60))
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace MiraAPI.Colors;
|
||||
/// <param name="shadowColor">The static shadow color used when dynamic colors are disabled.</param>
|
||||
/// <param name="defaultTime">The default time that is used to automatically fill in the static colors when the latter is not provided.</param>
|
||||
public sealed class VariableColor(StringNames name, Func<float, Color> mainEvaluator, Func<float, Color>? shadowEvaluator, Color32? mainColor = null, Color32? shadowColor = null, float defaultTime = 0f)
|
||||
: CustomColor(name, mainColor ?? mainEvaluator(defaultTime), shadowColor ?? shadowEvaluator?.Invoke(defaultTime) ?? (mainColor ?? mainEvaluator(defaultTime)).GetShadowColor(60))
|
||||
: CustomColor(name, mainColor ?? mainEvaluator(defaultTime), shadowColor ?? shadowEvaluator?.Invoke(defaultTime) ?? (mainColor ?? mainEvaluator(defaultTime)).DarkenAbsolute(60))
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets a delegate that takes in a time value and returns the dynamic main color.
|
||||
@@ -26,7 +26,7 @@ public sealed class VariableColor(StringNames name, Func<float, Color> mainEvalu
|
||||
/// <summary>
|
||||
/// Gets or sets a delegate that takes in a time value and returns the dynamic shadow color.
|
||||
/// </summary>
|
||||
public Func<float, Color> ShadowEvaluator { get; set; } = shadowEvaluator ?? (t => mainEvaluator(t).GetShadowColor(0.24f));
|
||||
public Func<float, Color> ShadowEvaluator { get; set; } = shadowEvaluator ?? (t => mainEvaluator(t).DarkenAbsolute(0.24f));
|
||||
|
||||
/// <inheritdoc cref="VariableColor"/>
|
||||
public VariableColor(StringNames name, Func<float, Color> mainEvaluator, Color32? mainColor = null, float defaultTime = 0f)
|
||||
|
||||
@@ -97,7 +97,7 @@ public class ModdedNumberOptionAttribute(
|
||||
var value = opt.Value;
|
||||
|
||||
if (ValueType == typeof(int))
|
||||
return (int)Math.Round(value);
|
||||
return (int)MathF.Round(value);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ public class ModdedNumberOptionListAttribute(
|
||||
var value = opt.Value;
|
||||
|
||||
if (ValueType == typeof(int))
|
||||
return (int)Math.Round(value);
|
||||
return (int)MathF.Round(value);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ public abstract class TimedModifier : BaseModifier
|
||||
/// <returns>A string with the time remaining.</returns>
|
||||
public override string GetDescription()
|
||||
{
|
||||
return $"Remaining Time: {Math.Round(Duration - TimeRemaining, 0)}s/{Duration}s";
|
||||
return $"Remaining Time: {MathF.Round(Duration - TimeRemaining, 0)}s/{Duration}s";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.RegularExpressions;
|
||||
using AmongUs.Data;
|
||||
using HarmonyLib;
|
||||
@@ -42,7 +41,7 @@ internal static class PlayerTabPatch
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(ChatNotification), nameof(ChatNotification.Update))]
|
||||
internal static class ChatNotifRainbowPatch
|
||||
internal static class ChatNotifVariablePatch
|
||||
{
|
||||
private static readonly Regex RichTags = new(@"<[^>]*>", default, Regex.InfiniteMatchTimeout);
|
||||
|
||||
@@ -60,14 +59,13 @@ internal static class ChatNotifRainbowPatch
|
||||
var str = color.ToHtmlStringRGBA();
|
||||
var nameText = __instance.playerNameText;
|
||||
nameText.text = "<color=#" + str + ">" + RichTags.Replace(nameText.text, string.Empty);
|
||||
nameText.outlineColor = color.GetShadowColor(0.5f);
|
||||
nameText.outlineColor = color.DarkenAbsolute(0.5f);
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(HostInfoPanel), nameof(HostInfoPanel.Update))]
|
||||
internal static class RainbowLobbyInfoPanePatch
|
||||
internal static class VariableLobbyInfoPanePatch
|
||||
{
|
||||
[SuppressMessage("Style", "IDE0045:Convert to conditional expression", Justification = "Operator becomes too large.")]
|
||||
public static void Postfix(HostInfoPanel __instance)
|
||||
{
|
||||
if (!__instance.gameObject.activeInHierarchy)
|
||||
|
||||
@@ -464,7 +464,7 @@ public static class LobbyViewPanePatches
|
||||
|
||||
if (name is not (StringNames.CrewmateRolesHeader or StringNames.ImpostorRolesHeader))
|
||||
{
|
||||
var veryDarkColor = group.Color.DarkenColor(.35f);
|
||||
var veryDarkColor = group.Color.DarkenRelative(.35f);
|
||||
categoryHeaderRoleVariant.Title.color = veryDarkColor;
|
||||
categoryHeaderRoleVariant.Background.color = group.Color;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ using MiraAPI.Roles;
|
||||
using MiraAPI.Translation;
|
||||
using MiraAPI.Utilities;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using MiraAPI.Utilities.Components;
|
||||
using Reactor.Utilities;
|
||||
using Reactor.Utilities.Extensions;
|
||||
using TMPro;
|
||||
|
||||
@@ -1,12 +1,26 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Utilities;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for the <see cref="Color"/> struct.
|
||||
/// Extension methods for the <see cref="Color"/> and <see cref="Color32"/> structs.
|
||||
/// </summary>
|
||||
public static class ColorExtensions
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static float ToFloat(this byte @byte)
|
||||
{
|
||||
return @byte / 255f;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static byte ToByte(this float @float)
|
||||
{
|
||||
return (byte)MathF.Round(@float * 255f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the relative luminance of a <see cref="Color"/> (WCAG 2.1 specification). 0 is black, 1 is white.
|
||||
/// </summary>
|
||||
@@ -19,6 +33,18 @@ public static class ColorExtensions
|
||||
(0.0722f * Linearize(color.b));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the relative luminance of a <see cref="Color32"/> (WCAG 2.1 specification). 0 is black, 1 is white.
|
||||
/// </summary>
|
||||
/// <param name="color"><see cref="Color32"/> to calculate luminance for.</param>
|
||||
/// <returns>Luminance value between 0 and 1.</returns>
|
||||
public static float GetRelativeLuminance(this Color32 color)
|
||||
{
|
||||
return (0.2126f * Linearize(color.r.ToFloat())) +
|
||||
(0.7152f * Linearize(color.g.ToFloat())) +
|
||||
(0.0722f * Linearize(color.b.ToFloat()));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Linearizes an sRGB component into linear space.
|
||||
/// </summary>
|
||||
@@ -26,7 +52,7 @@ public static class ColorExtensions
|
||||
/// <returns>The linearized result.</returns>
|
||||
public static float Linearize(float c)
|
||||
{
|
||||
return c <= 0.03928f ? c / 12.92f : Mathf.Pow((c + 0.055f) / 1.055f, 2.4f);
|
||||
return c <= 0.03928f ? c / 12.92f : MathF.Pow((c + 0.055f) / 1.055f, 2.4f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -39,11 +65,27 @@ public static class ColorExtensions
|
||||
{
|
||||
var luminanceA = colorA.GetRelativeLuminance();
|
||||
var luminanceB = colorB.GetRelativeLuminance();
|
||||
return (Mathf.Max(luminanceA, luminanceB) + 0.05f) / (Mathf.Min(luminanceA, luminanceB) + 0.05f);
|
||||
|
||||
return (Math.Max(luminanceA, luminanceB) + 0.05f) / (Math.Min(luminanceA, luminanceB) + 0.05f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates an accessible alternate <see cref="Color"/> with sufficient contrast.
|
||||
/// Calculates the contrast ratio between two <see cref="Color32"/>s.
|
||||
/// </summary>
|
||||
/// <param name="colorA">First <see cref="Color32"/>.</param>
|
||||
/// <param name="colorB">Second <see cref="Color32"/>.</param>
|
||||
/// <returns>Contrast ratio between the two <see cref="Color32"/>s.</returns>
|
||||
public static float GetContrastRatio(this Color32 colorA, Color32 colorB)
|
||||
{
|
||||
var luminanceA = colorA.GetRelativeLuminance();
|
||||
var luminanceB = colorB.GetRelativeLuminance();
|
||||
|
||||
return (Math.Max(luminanceA, luminanceB) + 0.05f) / (Math.Min(luminanceA, luminanceB) + 0.05f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates an accessible alternate <see cref="Color"/> by shifting towards black or white
|
||||
/// based on which extreme offers the highest potential contrast from the original color.
|
||||
/// </summary>
|
||||
/// <param name="color">Base <see cref="Color"/>.</param>
|
||||
/// <param name="desiredRatio">Target contrast ratio (default 4.5f for AA).</param>
|
||||
@@ -57,10 +99,120 @@ public static class ColorExtensions
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the <see cref="Color"/> with the minimum contrast ratio to the target <see cref="Color"/>.
|
||||
/// Generates an accessible alternate <see cref="Color32"/> with sufficient contrast.
|
||||
/// </summary>
|
||||
/// <param name="color">Base <see cref="Color32"/>.</param>
|
||||
/// <param name="desiredRatio">Target contrast ratio (default 4.5f for AA).</param>
|
||||
/// <returns>Alternate <see cref="Color32"/> with sufficient contrast.</returns>
|
||||
public static Color32 FindAlternateColor(this Color32 color, float desiredRatio = 4.5f)
|
||||
{
|
||||
var colorF = new Color(color.r.ToFloat(), color.g.ToFloat(), color.b.ToFloat());
|
||||
var result = colorF.FindAlternateColor(desiredRatio);
|
||||
return new Color32(result.r.ToByte(), result.g.ToByte(), result.b.ToByte(), color.a);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Darkens a <see cref="Color"/> towards black until it reaches the target WCAG contrast ratio against a background.
|
||||
/// </summary>
|
||||
/// <param name="color">The foreground <see cref="Color"/> to modify.</param>
|
||||
/// <param name="background">The background <see cref="Color"/> it sits on.</param>
|
||||
/// <param name="targetRatio">Target contrast ratio (default 4.5f for AA).</param>
|
||||
/// <returns>A darkened <see cref="Color"/> meeting the contrast ratio, or black if impossible.</returns>
|
||||
public static Color DarkenToWCAG(this Color color, Color background, float targetRatio = 4.5f)
|
||||
{
|
||||
return color.GetContrastRatio(background) >= targetRatio
|
||||
? color
|
||||
: FindMinimumContrastColor(color, Color.black, targetRatio);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Darkens a <see cref="Color32"/> towards black until it reaches the target WCAG contrast ratio against a background.
|
||||
/// </summary>
|
||||
/// <param name="color">The foreground <see cref="Color32"/> to modify.</param>
|
||||
/// <param name="background">The background <see cref="Color32"/> it sits on.</param>
|
||||
/// <param name="targetRatio">Target contrast ratio (default 4.5f for AA).</param>
|
||||
/// <returns>A darkened <see cref="Color32"/> meeting the contrast ratio, or black if impossible.</returns>
|
||||
public static Color32 DarkenToWCAG(this Color32 color, Color32 background, float targetRatio = 4.5f)
|
||||
{
|
||||
var colorF = new Color(color.r.ToFloat(), color.g.ToFloat(), color.b.ToFloat(), color.a.ToFloat());
|
||||
var bgF = new Color(background.r.ToFloat(), background.g.ToFloat(), background.b.ToFloat(), background.a.ToFloat());
|
||||
|
||||
var result = colorF.DarkenToWCAG(bgF, targetRatio);
|
||||
return new Color32(result.r.ToByte(), result.g.ToByte(), result.b.ToByte(), color.a);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lightens a <see cref="Color"/> towards white until it reaches the target WCAG contrast ratio against a background.
|
||||
/// </summary>
|
||||
/// <param name="color">The foreground <see cref="Color"/> to modify.</param>
|
||||
/// <param name="background">The background <see cref="Color"/> it sits on.</param>
|
||||
/// <param name="targetRatio">Target contrast ratio (default 4.5f for AA).</param>
|
||||
/// <returns>A lightened <see cref="Color"/> meeting the contrast ratio, or white if impossible.</returns>
|
||||
public static Color LightenToWCAG(this Color color, Color background, float targetRatio = 4.5f)
|
||||
{
|
||||
return color.GetContrastRatio(background) >= targetRatio
|
||||
? color
|
||||
: FindMinimumContrastColor(color, Color.white, targetRatio);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lightens a <see cref="Color32"/> towards white until it reaches the target WCAG contrast ratio against a background.
|
||||
/// </summary>
|
||||
/// <param name="color">The foreground <see cref="Color32"/> to modify.</param>
|
||||
/// <param name="background">The background <see cref="Color32"/> it sits on.</param>
|
||||
/// <param name="targetRatio">Target contrast ratio (default 4.5f for AA).</param>
|
||||
/// <returns>A lightened <see cref="Color32"/> meeting the contrast ratio, or white if impossible.</returns>
|
||||
public static Color32 LightenToWCAG(this Color32 color, Color32 background, float targetRatio = 4.5f)
|
||||
{
|
||||
var colorF = new Color(color.r.ToFloat(), color.g.ToFloat(), color.b.ToFloat(), color.a.ToFloat());
|
||||
var bgF = new Color(background.r.ToFloat(), background.g.ToFloat(), background.b.ToFloat(), background.a.ToFloat());
|
||||
|
||||
var result = colorF.LightenToWCAG(bgF, targetRatio);
|
||||
return new Color32(result.r.ToByte(), result.g.ToByte(), result.b.ToByte(), color.a);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Automatically decides whether to lighten or darken the <see cref="Color"/> to achieve WCAG compliance
|
||||
/// based on which extreme offers the highest potential contrast against the provided background.
|
||||
/// </summary>
|
||||
/// <param name="color">The foreground <see cref="Color"/> to modify.</param>
|
||||
/// <param name="background">The background <see cref="Color"/> it sits on.</param>
|
||||
/// <param name="targetRatio">Target contrast ratio (default 4.5f for AA).</param>
|
||||
/// <returns>A modified <see cref="Color"/> meeting the WCAG contrast requirement.</returns>
|
||||
public static Color EnsureWCAGContrast(this Color color, Color background, float targetRatio = 4.5f)
|
||||
{
|
||||
if (color.GetContrastRatio(background) >= targetRatio)
|
||||
return color;
|
||||
|
||||
var blackContrast = Color.black.GetContrastRatio(background);
|
||||
var whiteContrast = Color.white.GetContrastRatio(background);
|
||||
|
||||
return blackContrast > whiteContrast
|
||||
? color.DarkenToWCAG(background, targetRatio)
|
||||
: color.LightenToWCAG(background, targetRatio);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Automatically decides whether to lighten or darken the <see cref="Color32"/> to achieve WCAG compliance.
|
||||
/// </summary>
|
||||
/// <param name="color">The foreground <see cref="Color32"/> to modify.</param>
|
||||
/// <param name="background">The background <see cref="Color32"/> it sits on.</param>
|
||||
/// <param name="targetRatio">Target contrast ratio (default 4.5f for AA).</param>
|
||||
/// <returns>A modified <see cref="Color32"/> meeting the WCAG contrast requirement.</returns>
|
||||
public static Color32 EnsureWCAGContrast(this Color32 color, Color32 background, float targetRatio = 4.5f)
|
||||
{
|
||||
var colorF = new Color(color.r.ToFloat(), color.g.ToFloat(), color.b.ToFloat(), color.a.ToFloat());
|
||||
var bgF = new Color(background.r.ToFloat(), background.g.ToFloat(), background.b.ToFloat(), background.a.ToFloat());
|
||||
|
||||
var result = colorF.EnsureWCAGContrast(bgF, targetRatio);
|
||||
return new Color32(result.r.ToByte(), result.g.ToByte(), result.b.ToByte(), color.a);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the <see cref="Color"/> with the minimum contrast ratio to the target <see cref="Color"/> using binary search.
|
||||
/// </summary>
|
||||
/// <param name="baseColor">The base <see cref="Color"/> to start from.</param>
|
||||
/// <param name="targetColor">The target <see cref="Color"/> to compare against.</param>
|
||||
/// <param name="targetColor">The target <see cref="Color"/> to approach (usually white or black).</param>
|
||||
/// <param name="desiredRatio">The desired contrast ratio.</param>
|
||||
/// <returns>The <see cref="Color"/> with the minimum contrast ratio to the target <see cref="Color"/>.</returns>
|
||||
public static Color FindMinimumContrastColor(Color baseColor, Color targetColor, float desiredRatio)
|
||||
@@ -72,18 +224,28 @@ public static class ColorExtensions
|
||||
? desiredRatio * (baseLuminance + 0.05f) - 0.05f
|
||||
: (baseLuminance + 0.05f) / desiredRatio - 0.05f;
|
||||
|
||||
if (isLightening && targetLuminance > 1f) return Color.white;
|
||||
if (!isLightening && targetLuminance < 0f) return Color.black;
|
||||
if (isLightening && targetLuminance > 1f)
|
||||
return Color.white;
|
||||
|
||||
if (!isLightening && targetLuminance < 0f)
|
||||
return Color.black;
|
||||
|
||||
float low = 0f, high = 1f;
|
||||
var bestColor = targetColor;
|
||||
|
||||
for (var i = 0; i < 10; i++)
|
||||
{
|
||||
var t = (low + high) / 2f;
|
||||
var testColor = Color.Lerp(baseColor, targetColor, t);
|
||||
|
||||
var testColor = new Color(
|
||||
baseColor.r + (targetColor.r - baseColor.r) * t,
|
||||
baseColor.g + (targetColor.g - baseColor.g) * t,
|
||||
baseColor.b + (targetColor.b - baseColor.b) * t,
|
||||
baseColor.a);
|
||||
|
||||
var testLuminance = testColor.GetRelativeLuminance();
|
||||
|
||||
if ((isLightening && testLuminance >= targetLuminance) || (!isLightening && testLuminance <= targetLuminance))
|
||||
if (isLightening ? testLuminance >= targetLuminance : testLuminance <= targetLuminance)
|
||||
{
|
||||
high = t;
|
||||
bestColor = testColor;
|
||||
@@ -96,4 +258,128 @@ public static class ColorExtensions
|
||||
|
||||
return bestColor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subtracts a flat amount from the <see cref="Color"/>'s RGB channels.
|
||||
/// </summary>
|
||||
/// <param name="color">The original color.</param>
|
||||
/// <param name="amount">The flat amount to subtract (typically 0.0 to 1.0).</param>
|
||||
/// <returns>A uniformly darkened <see cref="Color"/>.</returns>
|
||||
public static Color DarkenAbsolute(this Color color, float amount)
|
||||
{
|
||||
return new Color(
|
||||
Math.Max(0f, color.r - amount),
|
||||
Math.Max(0f, color.g - amount),
|
||||
Math.Max(0f, color.b - amount),
|
||||
color.a);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subtracts a flat byte amount from the <see cref="Color32"/>'s RGB channels.
|
||||
/// </summary>
|
||||
/// <param name="color">The original <see cref="Color32"/>.</param>
|
||||
/// <param name="amount">The flat amount to subtract (0 to 255).</param>
|
||||
/// <returns>A uniformly darkened <see cref="Color32"/>.</returns>
|
||||
public static Color32 DarkenAbsolute(this Color32 color, byte amount)
|
||||
{
|
||||
return new Color32(
|
||||
(byte)Math.Max(0, color.r - amount),
|
||||
(byte)Math.Max(0, color.g - amount),
|
||||
(byte)Math.Max(0, color.b - amount),
|
||||
color.a);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a flat amount to the <see cref="Color"/>'s RGB channels.
|
||||
/// </summary>
|
||||
/// <param name="color">The original color.</param>
|
||||
/// <param name="amount">The flat amount to add (typically 0.0 to 1.0).</param>
|
||||
/// <returns>A uniformly lightened <see cref="Color"/>.</returns>
|
||||
public static Color LightenAbsolute(this Color color, float amount)
|
||||
{
|
||||
return new Color(
|
||||
Math.Min(1f, color.r + amount),
|
||||
Math.Min(1f, color.g + amount),
|
||||
Math.Min(1f, color.b + amount),
|
||||
color.a);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a flat byte amount to the <see cref="Color32"/>'s RGB channels.
|
||||
/// </summary>
|
||||
/// <param name="color">The original <see cref="Color32"/>.</param>
|
||||
/// <param name="amount">The flat amount to add (0 to 255).</param>
|
||||
/// <returns>A uniformly lightened <see cref="Color32"/>.</returns>
|
||||
public static Color32 LightenAbsolute(this Color32 color, byte amount)
|
||||
{
|
||||
return new Color32(
|
||||
(byte)Math.Min(255, color.r + amount),
|
||||
(byte)Math.Min(255, color.g + amount),
|
||||
(byte)Math.Min(255, color.b + amount),
|
||||
color.a);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Darkens the <see cref="Color"/> multiplicatively, preserving the ratio between RGB channels to maintain hue.
|
||||
/// </summary>
|
||||
/// <param name="color">The original color.</param>
|
||||
/// <param name="percentage">The percentage to darken by (e.g., 0.2 for 20% darker).</param>
|
||||
/// <returns>A ratio-preserved darkened <see cref="Color"/>.</returns>
|
||||
public static Color DarkenRelative(this Color color, float percentage)
|
||||
{
|
||||
var multiplier = Math.Max(0f, Math.Min(1f, 1f - percentage));
|
||||
return new Color(
|
||||
color.r * multiplier,
|
||||
color.g * multiplier,
|
||||
color.b * multiplier,
|
||||
color.a);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Darkens the <see cref="Color32"/> multiplicatively, preserving the ratio between RGB channels.
|
||||
/// </summary>
|
||||
/// <param name="color">The original <see cref="Color32"/>.</param>
|
||||
/// <param name="percentage">The percentage to darken by (e.g., 0.2 for 20% darker).</param>
|
||||
/// <returns>A ratio-preserved darkened <see cref="Color32"/>.</returns>
|
||||
public static Color32 DarkenRelative(this Color32 color, float percentage)
|
||||
{
|
||||
var multiplier = Math.Max(0f, Math.Min(1f, 1f - percentage));
|
||||
return new Color32(
|
||||
(color.r * multiplier).ToByte(),
|
||||
(color.g * multiplier).ToByte(),
|
||||
(color.b * multiplier).ToByte(),
|
||||
color.a);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lightens the <see cref="Color"/> multiplicatively, preserving the ratio between RGB channels.
|
||||
/// </summary>
|
||||
/// <param name="color">The original color.</param>
|
||||
/// <param name="percentage">The percentage to lighten by (e.g., 0.2 for 20% lighter).</param>
|
||||
/// <returns>A ratio-preserved lightened <see cref="Color"/>.</returns>
|
||||
public static Color LightenRelative(this Color color, float percentage)
|
||||
{
|
||||
var multiplier = 1f + percentage;
|
||||
return new Color(
|
||||
Math.Min(1f, color.r * multiplier),
|
||||
Math.Min(1f, color.g * multiplier),
|
||||
Math.Min(1f, color.b * multiplier),
|
||||
color.a);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lightens the <see cref="Color32"/> multiplicatively, preserving the ratio between RGB channels.
|
||||
/// </summary>
|
||||
/// <param name="color">The original <see cref="Color32"/>.</param>
|
||||
/// <param name="percentage">The percentage to lighten by (e.g., 0.2 for 20% lighter).</param>
|
||||
/// <returns>A ratio-preserved lightened <see cref="Color32"/>.</returns>
|
||||
public static Color32 LightenRelative(this Color32 color, float percentage)
|
||||
{
|
||||
var multiplier = 1f + percentage;
|
||||
return new Color32(
|
||||
Math.Min(255f, color.r * 255f * multiplier / 255f).ToByte(),
|
||||
Math.Min(255f, color.g * 255f * multiplier / 255f).ToByte(),
|
||||
Math.Min(255f, color.b * 255f * multiplier / 255f).ToByte(),
|
||||
color.a);
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ using System;
|
||||
using Reactor.Utilities.Attributes;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Utilities;
|
||||
namespace MiraAPI.Utilities.Components;
|
||||
|
||||
/// <summary>
|
||||
/// A spinning ring used to indicate that content is loading.
|
||||
@@ -26,6 +26,8 @@ namespace MiraAPI.Utilities;
|
||||
/// </summary>
|
||||
public static class Extensions
|
||||
{
|
||||
private static readonly Regex DisplayRegex = new(@"([^\^])([A-Z][a-z$])", default, Regex.InfiniteMatchTimeout);
|
||||
|
||||
internal static NetData GetNetData(this ICustomRole role)
|
||||
{
|
||||
var count = role.GetCount();
|
||||
@@ -498,8 +500,7 @@ public static class Extensions
|
||||
/// <returns>A proper string for the provided <see cref="Enum"/>.</returns>
|
||||
public static string ToDisplayString(this Enum @enum)
|
||||
{
|
||||
var regex = new Regex(@"([^\^])([A-Z][a-z$])", default, Regex.InfiniteMatchTimeout);
|
||||
return regex.Replace(@enum.ToString(), m => $"{m.Groups[1].Value} {m.Groups[2].Value}");
|
||||
return DisplayRegex.Replace(@enum.ToString(), m => $"{m.Groups[1].Value} {m.Groups[2].Value}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -511,8 +512,7 @@ public static class Extensions
|
||||
public static string ToDisplayString<T>(this T @enum)
|
||||
where T : struct, Enum
|
||||
{
|
||||
var regex = new Regex(@"([^\^])([A-Z][a-z$])", default, Regex.InfiniteMatchTimeout);
|
||||
return regex.Replace(@enum.ToString(), m => $"{m.Groups[1].Value} {m.Groups[2].Value}");
|
||||
return DisplayRegex.Replace(@enum.ToString(), m => $"{m.Groups[1].Value} {m.Groups[2].Value}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -539,17 +539,46 @@ public static class Extensions
|
||||
/// <returns>The distance between the types.</returns>
|
||||
public static int GetInheritanceDistance(Type from, Type to)
|
||||
{
|
||||
if (!from.IsAssignableFrom(to)) return int.MaxValue;
|
||||
if (from == null || to == null)
|
||||
throw new ArgumentNullException(from == null ? nameof(from) : nameof(to));
|
||||
|
||||
var type = from;
|
||||
var distance = 0;
|
||||
while (type != null && type != to)
|
||||
if (from == to)
|
||||
return 0;
|
||||
|
||||
if (!to.IsGenericTypeDefinition && !to.IsAssignableFrom(from))
|
||||
return int.MaxValue;
|
||||
|
||||
var queue = new Queue<(Type Type, int Distance)>();
|
||||
var visited = new HashSet<Type>();
|
||||
|
||||
queue.Enqueue((from, 0));
|
||||
visited.Add(from);
|
||||
|
||||
while (queue.Count > 0)
|
||||
{
|
||||
type = type.BaseType;
|
||||
distance++;
|
||||
var (current, dist) = queue.Dequeue();
|
||||
|
||||
if (current == to || (current.IsGenericType && current.GetGenericTypeDefinition() == to))
|
||||
return dist;
|
||||
|
||||
if (current.BaseType != null && visited.Add(current.BaseType))
|
||||
queue.Enqueue((current.BaseType, dist + 1));
|
||||
|
||||
var allInterfaces = current.GetInterfaces();
|
||||
var baseInterfaces = current.BaseType?.GetInterfaces() ?? Type.EmptyTypes;
|
||||
|
||||
var directInterfaces = allInterfaces.Except(baseInterfaces).ToList();
|
||||
var inheritedInterfaces = directInterfaces.SelectMany(i => i.GetInterfaces());
|
||||
var immediateInterfaces = directInterfaces.Except(inheritedInterfaces);
|
||||
|
||||
foreach (var iFace in immediateInterfaces)
|
||||
{
|
||||
if (visited.Add(iFace))
|
||||
queue.Enqueue((iFace, dist + 1));
|
||||
}
|
||||
}
|
||||
|
||||
return type == to ? distance : int.MaxValue;
|
||||
return to.IsAssignableFrom(from) ? int.MaxValue - 1 : int.MaxValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -578,14 +607,10 @@ public static class Extensions
|
||||
/// <param name="color">The original <see cref="Color32"/>.</param>
|
||||
/// <param name="darknessAmount">A darkness amount between 0 and 255.</param>
|
||||
/// <returns>The darkened <see cref="Color32"/>.</returns>
|
||||
[Obsolete("Use ColorExtensions.DarkenAbsolute instead. This will be removed in a future version.")]
|
||||
public static Color32 GetShadowColor(this Color32 color, byte darknessAmount)
|
||||
{
|
||||
return
|
||||
new Color32(
|
||||
(byte)Mathf.Clamp(color.r - darknessAmount, 0, 255),
|
||||
(byte)Mathf.Clamp(color.g - darknessAmount, 0, 255),
|
||||
(byte)Mathf.Clamp(color.b - darknessAmount, 0, 255),
|
||||
color.a);
|
||||
return color.DarkenAbsolute(darknessAmount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -594,14 +619,10 @@ public static class Extensions
|
||||
/// <param name="color">The original <see cref="Color"/>.</param>
|
||||
/// <param name="darknessAmount">A darkness amount between 0 and 1.</param>
|
||||
/// <returns>The darkened <see cref="Color"/>.</returns>
|
||||
[Obsolete("Use ColorExtensions.DarkenAbsolute instead. This will be removed in a future version.")]
|
||||
public static Color GetShadowColor(this Color color, float darknessAmount)
|
||||
{
|
||||
return
|
||||
new Color(
|
||||
MathUtilities.Clamp01(color.r - darknessAmount),
|
||||
MathUtilities.Clamp01(color.g - darknessAmount),
|
||||
MathUtilities.Clamp01(color.b - darknessAmount),
|
||||
color.a);
|
||||
return color.DarkenAbsolute(darknessAmount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -690,9 +711,10 @@ public static class Extensions
|
||||
/// <param name="color">The original <see cref="Color"/>.</param>
|
||||
/// <param name="amount">A <see langword="float"/> amount between 0 and 1.</param>
|
||||
/// <returns>The darkened <see cref="Color"/>.</returns>
|
||||
[Obsolete("Use ColorExtensions.DarkenAbsolute instead. This will be removed in a future version.")]
|
||||
public static Color DarkenColor(this Color color, float amount = 0.45f)
|
||||
{
|
||||
return new Color(color.r - amount, color.g - amount, color.b - amount);
|
||||
return color.DarkenAbsolute(amount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -701,9 +723,10 @@ public static class Extensions
|
||||
/// <param name="color">The original <see cref="Color"/>.</param>
|
||||
/// <param name="amount">A <see langword="float"/> amount between 0.0 and 1.0.</param>
|
||||
/// <returns>The lightened <see cref="Color"/>.</returns>
|
||||
[Obsolete("Use ColorExtensions.LightenAbsolute instead. This will be removed in a future version.")]
|
||||
public static Color LightenColor(this Color color, float amount = 0.45f)
|
||||
{
|
||||
return new Color(color.r + amount, color.g + amount, color.b + amount);
|
||||
return color.LightenAbsolute(amount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -8,7 +8,6 @@ using MiraAPI.GameOptions.OptionTypes;
|
||||
using MiraAPI.PluginLoading;
|
||||
using MiraAPI.Translation;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using Reactor.Utilities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Utilities;
|
||||
@@ -103,40 +102,42 @@ internal static class GuideOptionPages
|
||||
}
|
||||
}
|
||||
|
||||
if (entries.Count > 0)
|
||||
if (entries.Count <= 0)
|
||||
{
|
||||
var pageKey = item.Group.MatchInfoGuidePageKey?.Trim();
|
||||
var combinedKey = string.IsNullOrEmpty(pageKey) ? null : $"{item.Plugin.PluginId}\0{pageKey}";
|
||||
if (combinedKey != null && combinedPages.TryGetValue(combinedKey, out var combinedPage))
|
||||
continue;
|
||||
}
|
||||
|
||||
var pageKey = item.Group.MatchInfoGuidePageKey?.Trim();
|
||||
var combinedKey = string.IsNullOrEmpty(pageKey) ? null : $"{item.Plugin.PluginId}\0{pageKey}";
|
||||
if (combinedKey != null && combinedPages.TryGetValue(combinedKey, out var combinedPage))
|
||||
{
|
||||
combinedPage.Groups.Add(item.Group);
|
||||
combinedPage.Entries.AddRange(entries);
|
||||
combinedPage.ModSearchTerms.UnionWith(modSearchTerms);
|
||||
combinedPage.Order = Math.Min(combinedPage.Order, item.Group.MatchInfoGuidePageOrder);
|
||||
combinedPage.MenuOrder = Math.Min(
|
||||
combinedPage.MenuOrder,
|
||||
GetMenuOrder(item.Group.ParentMenu));
|
||||
combinedPage.GroupPriority = Math.Min(
|
||||
combinedPage.GroupPriority,
|
||||
item.Group.GroupPriority);
|
||||
}
|
||||
else
|
||||
{
|
||||
var page = new GuideOptionPage(
|
||||
item.Group,
|
||||
item.Group.MatchInfoGuidePageTitle,
|
||||
item.Group.MatchInfoGuidePageOrder,
|
||||
GetMenuOrder(item.Group.ParentMenu),
|
||||
item.Group.GroupPriority,
|
||||
Mathf.Clamp(item.Group.MatchInfoGuidePageColumns, 1, 6),
|
||||
item.ModAbbreviation,
|
||||
modSearchTerms,
|
||||
entries);
|
||||
pages.Add(page);
|
||||
if (combinedKey != null)
|
||||
{
|
||||
combinedPage.Groups.Add(item.Group);
|
||||
combinedPage.Entries.AddRange(entries);
|
||||
combinedPage.ModSearchTerms.UnionWith(modSearchTerms);
|
||||
combinedPage.Order = Math.Min(combinedPage.Order, item.Group.MatchInfoGuidePageOrder);
|
||||
combinedPage.MenuOrder = Math.Min(
|
||||
combinedPage.MenuOrder,
|
||||
GetMenuOrder(item.Group.ParentMenu));
|
||||
combinedPage.GroupPriority = Math.Min(
|
||||
combinedPage.GroupPriority,
|
||||
item.Group.GroupPriority);
|
||||
}
|
||||
else
|
||||
{
|
||||
var page = new GuideOptionPage(
|
||||
item.Group,
|
||||
item.Group.MatchInfoGuidePageTitle,
|
||||
item.Group.MatchInfoGuidePageOrder,
|
||||
GetMenuOrder(item.Group.ParentMenu),
|
||||
item.Group.GroupPriority,
|
||||
Mathf.Clamp(item.Group.MatchInfoGuidePageColumns, 1, 6),
|
||||
item.ModAbbreviation,
|
||||
modSearchTerms,
|
||||
entries);
|
||||
pages.Add(page);
|
||||
if (combinedKey != null)
|
||||
{
|
||||
combinedPages.Add(combinedKey, page);
|
||||
}
|
||||
combinedPages.Add(combinedKey, page);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -269,7 +270,7 @@ internal static class GuideOptionPages
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Warning($"GroupVisible threw for {group.GetType().Name}: {e.Message}");
|
||||
Warning($"GroupVisible threw for {group.GetType().Name}: {e.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -282,7 +283,7 @@ internal static class GuideOptionPages
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Warning($"Visible threw for option '{option.Title}': {e.Message}");
|
||||
Warning($"Visible threw for option '{option.Title}': {e.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -303,7 +304,7 @@ internal static class GuideOptionPages
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Warning($"Could not format option '{option.Title}' for the guide: {e.Message}");
|
||||
Warning($"Could not format option '{option.Title}' for the guide: {e.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user