···11-// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
22-// See the LICENCE file in the repository root for full licence text.
33-44-using System;
55-using NUnit.Framework;
66-using osu.Framework.Extensions;
77-using osu.Framework.Localisation;
88-99-namespace osu.Framework.Tests.Localisation
1010-{
1111- [TestFixture]
1212- public class LocalisableEnumAttributeTest
1313- {
1414- [TestCase(EnumA.Item1, "Item1")]
1515- [TestCase(EnumA.Item2, "B")]
1616- public void TestNonLocalisableEnumReturnsDescriptionOrToString(EnumA value, string expected)
1717- {
1818- Assert.That(value.GetLocalisableDescription().ToString(), Is.EqualTo(expected));
1919- }
2020-2121- [TestCase(EnumB.Item1, "A")]
2222- [TestCase(EnumB.Item2, "B")]
2323- public void TestLocalisableEnumReturnsMappedValue(EnumB value, string expected)
2424- {
2525- Assert.That(value.GetLocalisableDescription().ToString(), Is.EqualTo(expected));
2626- }
2727-2828- [Test]
2929- public void TestLocalisableEnumWithInvalidBaseTypeThrows()
3030- {
3131- // ReSharper disable once ReturnValueOfPureMethodIsNotUsed
3232- Assert.Throws<ArgumentException>(() => EnumC.Item1.GetLocalisableDescription().ToString());
3333- }
3434-3535- [Test]
3636- public void TestLocalisableEnumWithInvalidGenericTypeThrows()
3737- {
3838- // ReSharper disable once ReturnValueOfPureMethodIsNotUsed
3939- Assert.Throws<InvalidOperationException>(() => EnumD.Item1.GetLocalisableDescription().ToString());
4040- }
4141-4242- public enum EnumA
4343- {
4444- Item1,
4545-4646- [System.ComponentModel.Description("B")]
4747- Item2
4848- }
4949-5050- [LocalisableEnum(typeof(EnumBEnumLocalisationMapper))]
5151- public enum EnumB
5252- {
5353- Item1,
5454- Item2
5555- }
5656-5757- private class EnumBEnumLocalisationMapper : EnumLocalisationMapper<EnumB>
5858- {
5959- public override LocalisableString Map(EnumB value)
6060- {
6161- switch (value)
6262- {
6363- case EnumB.Item1:
6464- return "A";
6565-6666- case EnumB.Item2:
6767- return "B";
6868-6969- default:
7070- throw new ArgumentOutOfRangeException(nameof(value), value, null);
7171- }
7272- }
7373- }
7474-7575- [LocalisableEnum(typeof(EnumCEnumLocalisationMapper))]
7676- public enum EnumC
7777- {
7878- Item1,
7979- }
8080-8181- private class EnumCEnumLocalisationMapper
8282- {
8383- }
8484-8585- [LocalisableEnum(typeof(EnumDEnumLocalisationMapper))]
8686- public enum EnumD
8787- {
8888- Item1,
8989- }
9090-9191- private class EnumDEnumLocalisationMapper : EnumLocalisationMapper<EnumA>
9292- {
9393- public override LocalisableString Map(EnumA value) => "A";
9494- }
9595- }
9696-}
+25-24
osu.Framework/Extensions/ExtensionMethods.cs
···44using System;
55using System.Collections.Generic;
66using System.ComponentModel;
77-using System.Diagnostics;
87using System.Drawing;
98using System.IO;
109using System.Linq;
1110using System.Reflection;
1211using System.Security.Cryptography;
1312using System.Text;
1414-using osu.Framework.Extensions.TypeExtensions;
1313+using osu.Framework.Extensions.ObjectExtensions;
1514using osu.Framework.Localisation;
1615using osu.Framework.Platform;
1716using osuTK;
···178177 }
179178180179 /// <summary>
181181- /// Returns the description of a given enum value, via (in order):
180180+ /// Returns the localisable description of a given object, via (in order):
182181 /// <list type="number">
183182 /// <item>
184184- /// <description>Any <see cref="LocalisableEnumAttribute"/> attached to the enum type.</description>
183183+ /// <description>Any attached <see cref="LocalisableDescriptionAttribute"/>.</description>
185184 /// </item>
186185 /// <item>
187186 /// <description><see cref="GetDescription"/></description>
188187 /// </item>
189188 /// </list>
190189 /// </summary>
191191- /// <exception cref="InvalidOperationException">When the enum type has an attached <see cref="LocalisableEnumAttribute"/>
192192- /// and the <see cref="EnumLocalisationMapper{T}"/> could not be instantiated.</exception>
193193- /// <exception cref="InvalidOperationException">When the enum type has an attached <see cref="LocalisableEnumAttribute"/>
194194- /// and the type handled by the <see cref="EnumLocalisationMapper{T}"/> is not <typeparamref name="T"/>.</exception>
190190+ /// <exception cref="InvalidOperationException">
191191+ /// When the <see cref="LocalisableDescriptionAttribute.Name"/> specified in the <see cref="LocalisableDescriptionAttribute"/>
192192+ /// does not match any of the existing members in <see cref="LocalisableDescriptionAttribute.DeclaringType"/>.
193193+ /// </exception>
195194 public static LocalisableString GetLocalisableDescription<T>(this T value)
196196- where T : Enum
197195 {
198198- var enumType = value.GetType();
196196+ MemberInfo type;
199197200200- var mapperType = enumType.GetCustomAttribute<LocalisableEnumAttribute>()?.MapperType;
201201- if (mapperType == null)
202202- return GetDescription(value);
198198+ if (value is Enum)
199199+ type = value.GetType().GetField(value.ToString());
200200+ else
201201+ type = value.GetType();
203202204204- var mapperInstance = Activator.CreateInstance(mapperType);
205205- if (mapperInstance == null)
206206- throw new InvalidOperationException($"Could not create the {nameof(EnumLocalisationMapper<T>)} for enum type {enumType.ReadableName()}");
203203+ var attribute = type.GetCustomAttribute<LocalisableDescriptionAttribute>();
204204+ if (attribute == null)
205205+ return GetDescription(value);
207206208208- var mapMethod = mapperType.GetMethod(nameof(EnumLocalisationMapper<T>.Map), BindingFlags.Instance | BindingFlags.Public);
209209- Debug.Assert(mapMethod != null);
207207+ var property = attribute.DeclaringType.GetMember(attribute.Name, BindingFlags.Static | BindingFlags.Public).FirstOrDefault();
210208211211- var expectedMappingType = mapMethod.GetParameters()[0].ParameterType;
212212- if (expectedMappingType != enumType)
213213- throw new InvalidOperationException($"Cannot use {mapperType.ReadableName()} (maps {expectedMappingType.ReadableName()} enum values) to map {enumType.ReadableName()} enum values.");
209209+ switch (property)
210210+ {
211211+ case FieldInfo f:
212212+ return (LocalisableString)f.GetValue(null).AsNonNull();
214213215215- var mappedValue = mapMethod.Invoke(mapperInstance, new object[] { value });
216216- Debug.Assert(mappedValue != null);
214214+ case PropertyInfo p:
215215+ return (LocalisableString)p.GetValue(null).AsNonNull();
217216218218- return (LocalisableString)mappedValue;
217217+ default:
218218+ throw new InvalidOperationException($"Member \"{attribute.Name}\" was not found in type {attribute.DeclaringType} (must be a static field or property)");
219219+ }
219220 }
220221221222 /// <summary>
+2-2
osu.Framework/Graphics/Drawable.cs
···428428 /// <summary>
429429 /// A lock exclusively used for initial acquisition/construction of the <see cref="Scheduler"/>.
430430 /// </summary>
431431- private readonly object schedulerAcquisitionLock = new object();
431431+ private static readonly object scheduler_acquisition_lock = new object();
432432433433 private Scheduler scheduler;
434434···443443 if (scheduler != null)
444444 return scheduler;
445445446446- lock (schedulerAcquisitionLock)
446446+ lock (scheduler_acquisition_lock)
447447 return scheduler ??= new Scheduler(() => ThreadSafety.IsUpdateThread, Clock);
448448 }
449449 }
···11-// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
22-// See the LICENCE file in the repository root for full licence text.
33-44-using System;
55-66-namespace osu.Framework.Localisation
77-{
88- /// <summary>
99- /// Describes the values of an <see cref="Enum"/> type by <see cref="LocalisableString"/>s.
1010- /// </summary>
1111- /// <typeparam name="T">The <see cref="Enum"/> type.</typeparam>
1212- public abstract class EnumLocalisationMapper<T> : IEnumLocalisationMapper
1313- where T : Enum
1414- {
1515- /// <summary>
1616- /// Describes a <typeparamref name="T"/> value by a <see cref="LocalisableString"/>.
1717- /// </summary>
1818- /// <param name="value">The value to map.</param>
1919- /// <returns>The <see cref="LocalisableString"/> describing <paramref name="value"/>.</returns>
2020- public abstract LocalisableString Map(T value);
2121- }
2222-2323- /// <summary>
2424- /// Marker class for <see cref="EnumLocalisationMapper{T}"/>. Do not use.
2525- /// </summary>
2626- internal interface IEnumLocalisationMapper
2727- {
2828- }
2929-}
···11+// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
22+// See the LICENCE file in the repository root for full licence text.
33+44+using System;
55+using osu.Framework.Extensions;
66+77+namespace osu.Framework.Localisation
88+{
99+ /// <summary>
1010+ /// Specifies a <see cref="LocalisableString"/>-based description for the target element.
1111+ /// The description can be retrieved through <see cref="ExtensionMethods.GetLocalisableDescription{T}"/>.
1212+ /// </summary>
1313+ /// <remarks>
1414+ /// The C# language specification
1515+ /// <a href="https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/attributes#attribute-parameter-types">
1616+ /// only permits a limited set of parameter types for attributes,
1717+ /// </a>
1818+ /// and as such <see cref="LocalisableString"/> instances cannot be passed in directly.
1919+ /// Therefore usages must pass both the target type in which the <see cref="LocalisableString"/> description is declared,
2020+ /// as well as the name of the member which contains/returns the <see cref="LocalisableString"/> (using <see langword="nameof"/> for this is strongly encouraged).
2121+ /// </remarks>
2222+ /// <example>
2323+ /// Assuming the following source class from which the <see cref="LocalisableString"/> description should be returned:
2424+ /// <code>
2525+ /// class Strings
2626+ /// {
2727+ /// public static LocalisableString Example => "example string";
2828+ /// }
2929+ /// </code>
3030+ /// the attribute should be used in the following way:
3131+ /// <code>
3232+ /// [LocalisableDescription(typeof(Strings), nameof(Strings.Example))]
3333+ /// </code>
3434+ /// </example>
3535+ [AttributeUsage(AttributeTargets.All)]
3636+ public sealed class LocalisableDescriptionAttribute : Attribute
3737+ {
3838+ /// <summary>
3939+ /// The type declaring the static member providing the localisable description.
4040+ /// </summary>
4141+ public readonly Type DeclaringType;
4242+4343+ /// <summary>
4444+ /// The name of the static member providing the localisable description.
4545+ /// </summary>
4646+ public readonly string Name;
4747+4848+ /// <summary>
4949+ /// Creates a new <see cref="LocalisableDescriptionAttribute"/>.
5050+ /// </summary>
5151+ public LocalisableDescriptionAttribute(Type declaringType, string name)
5252+ {
5353+ DeclaringType = declaringType;
5454+ Name = name;
5555+ }
5656+ }
5757+}
···11-// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
22-// See the LICENCE file in the repository root for full licence text.
33-44-using System;
55-using osu.Framework.Extensions;
66-using osu.Framework.Extensions.TypeExtensions;
77-88-namespace osu.Framework.Localisation
99-{
1010- /// <summary>
1111- /// Indicates that the values of an enum have <see cref="LocalisableString"/> descriptions.
1212- /// The descriptions can be retrieved through <see cref="ExtensionMethods.GetLocalisableDescription{T}"/>.
1313- /// </summary>
1414- [AttributeUsage(AttributeTargets.Enum)]
1515- public sealed class LocalisableEnumAttribute : Attribute
1616- {
1717- /// <summary>
1818- /// The <see cref="EnumLocalisationMapper{T}"/> type that maps enum values to <see cref="LocalisableString"/>s.
1919- /// </summary>
2020- public readonly Type MapperType;
2121-2222- /// <summary>
2323- /// Creates a new <see cref="LocalisableEnumAttribute"/>.
2424- /// </summary>
2525- /// <param name="mapperType">The <see cref="EnumLocalisationMapper{T}"/> type that maps enum values to <see cref="LocalisableString"/>s.</param>
2626- public LocalisableEnumAttribute(Type mapperType)
2727- {
2828- MapperType = mapperType;
2929-3030- if (!typeof(IEnumLocalisationMapper).IsAssignableFrom(mapperType))
3131- throw new ArgumentException($"Type \"{mapperType.ReadableName()}\" must inherit from {nameof(EnumLocalisationMapper<Enum>)}.", nameof(mapperType));
3232- }
3333- }
3434-}
+5-2
osu.Framework/Platform/Windows/Native/Input.cs
···1010 internal static class Input
1111 {
1212 [DllImport("user32.dll")]
1313- public static extern bool RegisterRawInputDevices([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)]
1414- RawInputDevice[] pRawInputDevices, int uiNumDevices, int cbSize);
1313+ public static extern bool RegisterRawInputDevices(
1414+ [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)]
1515+ RawInputDevice[] pRawInputDevices,
1616+ int uiNumDevices,
1717+ int cbSize);
15181619 [DllImport("user32.dll")]
1720 public static extern int GetRawInputData(IntPtr hRawInput, RawInputCommand uiCommand, out RawInputData pData, ref int pcbSize, int cbSizeHeader);