mirror of
https://github.com/SK-la/osu-framework.git
synced 2026-03-15 03:20:30 +00:00
Merge branch 'master' into window-backend-removal
This commit is contained in:
@@ -13,6 +13,18 @@
|
||||
"commands": [
|
||||
"dotnet-format"
|
||||
]
|
||||
},
|
||||
"jetbrains.resharper.globaltools": {
|
||||
"version": "2020.2.4",
|
||||
"commands": [
|
||||
"jb"
|
||||
]
|
||||
},
|
||||
"nvika": {
|
||||
"version": "2.0.0",
|
||||
"commands": [
|
||||
"nvika"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
12
.vscode/tasks.json
vendored
12
.vscode/tasks.json
vendored
@@ -9,7 +9,6 @@
|
||||
"command": "dotnet",
|
||||
"args": [
|
||||
"build",
|
||||
"--no-restore",
|
||||
"-p:GenerateFullPaths=true",
|
||||
"-m",
|
||||
"-verbosity:m",
|
||||
@@ -24,7 +23,6 @@
|
||||
"command": "dotnet",
|
||||
"args": [
|
||||
"build",
|
||||
"--no-restore",
|
||||
"-p:Configuration=Release",
|
||||
"-p:GenerateFullPaths=true",
|
||||
"-m",
|
||||
@@ -33,16 +31,6 @@
|
||||
],
|
||||
"group": "build",
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "Restore",
|
||||
"type": "shell",
|
||||
"command": "dotnet",
|
||||
"args": [
|
||||
"restore",
|
||||
"osu-framework.sln"
|
||||
],
|
||||
"problemMatcher": []
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -28,7 +28,6 @@ This framework is intended to take steps beyond what you would normally expect f
|
||||
Build configurations for the recommended IDEs (listed above) are included. You should use the provided Build/Run functionality of your IDE to get things going. When testing or building new components, it's highly encouraged you use the `VisualTests` project/configuration. More information on this provided [below](#contributing).
|
||||
|
||||
- Visual Studio / Rider users should load the project via one of the platform-specific .slnf files, rather than the main .sln. This will allow access to template run configurations.
|
||||
- Visual Studio Code users must run the `Restore` task before any build attempt.
|
||||
|
||||
### Code analysis
|
||||
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
using System.Threading;
|
||||
#addin "nuget:?package=CodeFileSanity&version=0.0.36"
|
||||
#addin "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2020.1.3"
|
||||
#tool "nuget:?package=NVika.MSBuild&version=1.0.1"
|
||||
#tool "nuget:?package=Python&version=3.7.2"
|
||||
var nVikaToolPath = GetFiles("./tools/NVika.MSBuild.*/tools/NVika.exe").First();
|
||||
var pythonPath = GetFiles("./tools/python.*/tools/python.exe").First();
|
||||
var waitressPath = pythonPath.GetDirectory().CombineWithFilePath("Scripts/waitress-serve.exe");
|
||||
|
||||
@@ -107,22 +104,15 @@ Task("Test")
|
||||
DotNetCoreVSTest(testAssemblies, settings);
|
||||
});
|
||||
|
||||
// windows only because both inspectcore and nvika depend on net45
|
||||
Task("InspectCode")
|
||||
.WithCriteria(IsRunningOnWindows())
|
||||
.IsDependentOn("Compile")
|
||||
.Does(() => {
|
||||
var inspectcodereport = tempDirectory.CombineWithFilePath("inspectcodereport.xml");
|
||||
var cacheDir = tempDirectory.Combine("inspectcode");
|
||||
|
||||
InspectCode(desktopSlnf, new InspectCodeSettings {
|
||||
CachesHome = tempDirectory.Combine("inspectcode"),
|
||||
OutputFile = inspectcodereport,
|
||||
ArgumentCustomization = args => args.Append("--verbosity=WARN")
|
||||
});
|
||||
|
||||
int returnCode = StartProcess(nVikaToolPath, $@"parsereport ""{inspectcodereport}"" --treatwarningsaserrors");
|
||||
if (returnCode != 0)
|
||||
throw new Exception($"inspectcode failed with return code {returnCode}");
|
||||
DotNetCoreTool(rootDirectory.FullPath,
|
||||
"jb", $@"inspectcode ""{desktopSlnf}"" --output=""{inspectcodereport}"" --caches-home=""{cacheDir}"" --verbosity=WARN");
|
||||
DotNetCoreTool(rootDirectory.FullPath, "nvika", $@"parsereport ""{inspectcodereport}"" --treatwarningsaserrors");
|
||||
});
|
||||
|
||||
Task("CodeFileSanity")
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_002Emp3/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_002Epng/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_002Ewav/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_002Eh/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=2A66DD92_002DADB1_002D4994_002D89E2_002DC94E04ACDA0D_002Fd_003AMigrations/@EntryIndexedValue">ExplicitlyExcluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=D9A367C9_002D4C1A_002D489F_002D9B05_002DA0CEA2B53B58/@EntryIndexedValue">ExplicitlyExcluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/AnalysisEnabled/@EntryValue">SOLUTION</s:String>
|
||||
|
||||
@@ -47,12 +47,12 @@ namespace osu.Framework.Tests.Text
|
||||
public TextBuilderTest()
|
||||
{
|
||||
fontStore = new TestStore(
|
||||
(normal_font, new TestGlyph('a', x_offset, y_offset, x_advance, width, height, kerning)),
|
||||
(normal_font, new TestGlyph('b', b_x_offset, b_y_offset, b_x_advance, b_width, b_height, b_kerning)),
|
||||
(normal_font, new TestGlyph('m', m_x_offset, m_y_offset, m_x_advance, m_width, m_height, m_kerning)),
|
||||
(fixed_width_font, new TestGlyph('a', x_offset, y_offset, x_advance, width, height, kerning)),
|
||||
(fixed_width_font, new TestGlyph('b', b_x_offset, b_y_offset, b_x_advance, b_width, b_height, b_kerning)),
|
||||
(fixed_width_font, new TestGlyph('m', m_x_offset, m_y_offset, m_x_advance, m_width, m_height, m_kerning))
|
||||
new GlyphEntry(normal_font, new TestGlyph('a', x_offset, y_offset, x_advance, width, height, kerning)),
|
||||
new GlyphEntry(normal_font, new TestGlyph('b', b_x_offset, b_y_offset, b_x_advance, b_width, b_height, b_kerning)),
|
||||
new GlyphEntry(normal_font, new TestGlyph('m', m_x_offset, m_y_offset, m_x_advance, m_width, m_height, m_kerning)),
|
||||
new GlyphEntry(fixed_width_font, new TestGlyph('a', x_offset, y_offset, x_advance, width, height, kerning)),
|
||||
new GlyphEntry(fixed_width_font, new TestGlyph('b', b_x_offset, b_y_offset, b_x_advance, b_width, b_height, b_kerning)),
|
||||
new GlyphEntry(fixed_width_font, new TestGlyph('m', m_x_offset, m_y_offset, m_x_advance, m_width, m_height, m_kerning))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -326,10 +326,10 @@ namespace osu.Framework.Tests.Text
|
||||
var font = new TestFontUsage("test");
|
||||
var nullFont = new TestFontUsage(null);
|
||||
var builder = new TextBuilder(new TestStore(
|
||||
(font, new TestGlyph('b', 0, 0, 0, 0, 0, 0)),
|
||||
(nullFont, new TestGlyph('a', 0, 0, 0, 0, 0, 0)),
|
||||
(font, new TestGlyph('?', 0, 0, 0, 0, 0, 0)),
|
||||
(nullFont, new TestGlyph('?', 0, 0, 0, 0, 0, 0))
|
||||
new GlyphEntry(font, new TestGlyph('b', 0, 0, 0, 0, 0, 0)),
|
||||
new GlyphEntry(nullFont, new TestGlyph('a', 0, 0, 0, 0, 0, 0)),
|
||||
new GlyphEntry(font, new TestGlyph('?', 0, 0, 0, 0, 0, 0)),
|
||||
new GlyphEntry(nullFont, new TestGlyph('?', 0, 0, 0, 0, 0, 0))
|
||||
), font);
|
||||
|
||||
builder.AddText("a");
|
||||
@@ -346,10 +346,10 @@ namespace osu.Framework.Tests.Text
|
||||
var font = new TestFontUsage("test");
|
||||
var nullFont = new TestFontUsage(null);
|
||||
var builder = new TextBuilder(new TestStore(
|
||||
(font, new TestGlyph('b', 0, 0, 0, 0, 0, 0)),
|
||||
(nullFont, new TestGlyph('b', 0, 0, 0, 0, 0, 0)),
|
||||
(font, new TestGlyph('?', 0, 0, 0, 0, 0, 0)),
|
||||
(nullFont, new TestGlyph('?', 1, 0, 0, 0, 0, 0))
|
||||
new GlyphEntry(font, new TestGlyph('b', 0, 0, 0, 0, 0, 0)),
|
||||
new GlyphEntry(nullFont, new TestGlyph('b', 0, 0, 0, 0, 0, 0)),
|
||||
new GlyphEntry(font, new TestGlyph('?', 0, 0, 0, 0, 0, 0)),
|
||||
new GlyphEntry(nullFont, new TestGlyph('?', 1, 0, 0, 0, 0, 0))
|
||||
), font);
|
||||
|
||||
builder.AddText("a");
|
||||
@@ -367,10 +367,10 @@ namespace osu.Framework.Tests.Text
|
||||
var font = new TestFontUsage("test");
|
||||
var nullFont = new TestFontUsage(null);
|
||||
var builder = new TextBuilder(new TestStore(
|
||||
(font, new TestGlyph('b', 0, 0, 0, 0, 0, 0)),
|
||||
(nullFont, new TestGlyph('b', 0, 0, 0, 0, 0, 0)),
|
||||
(font, new TestGlyph('b', 0, 0, 0, 0, 0, 0)),
|
||||
(nullFont, new TestGlyph('?', 1, 0, 0, 0, 0, 0))
|
||||
new GlyphEntry(font, new TestGlyph('b', 0, 0, 0, 0, 0, 0)),
|
||||
new GlyphEntry(nullFont, new TestGlyph('b', 0, 0, 0, 0, 0, 0)),
|
||||
new GlyphEntry(font, new TestGlyph('b', 0, 0, 0, 0, 0, 0)),
|
||||
new GlyphEntry(nullFont, new TestGlyph('?', 1, 0, 0, 0, 0, 0))
|
||||
), font);
|
||||
|
||||
builder.AddText("a");
|
||||
@@ -414,9 +414,9 @@ namespace osu.Framework.Tests.Text
|
||||
|
||||
private class TestStore : ITexturedGlyphLookupStore
|
||||
{
|
||||
private readonly (FontUsage font, ITexturedCharacterGlyph glyph)[] glyphs;
|
||||
private readonly GlyphEntry[] glyphs;
|
||||
|
||||
public TestStore(params (FontUsage font, ITexturedCharacterGlyph glyph)[] glyphs)
|
||||
public TestStore(params GlyphEntry[] glyphs)
|
||||
{
|
||||
this.glyphs = glyphs;
|
||||
}
|
||||
@@ -424,14 +424,28 @@ namespace osu.Framework.Tests.Text
|
||||
public ITexturedCharacterGlyph Get(string fontName, char character)
|
||||
{
|
||||
if (string.IsNullOrEmpty(fontName))
|
||||
return glyphs.FirstOrDefault(g => g.glyph.Character == character).glyph;
|
||||
{
|
||||
return glyphs.FirstOrDefault(g => g.Glyph.Character == character).Glyph;
|
||||
}
|
||||
|
||||
return glyphs.FirstOrDefault(g => g.font.FontName == fontName && g.glyph.Character == character).glyph;
|
||||
return glyphs.FirstOrDefault(g => g.Font.FontName == fontName && g.Glyph.Character == character).Glyph;
|
||||
}
|
||||
|
||||
public Task<ITexturedCharacterGlyph> GetAsync(string fontName, char character) => throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
private readonly struct GlyphEntry
|
||||
{
|
||||
public readonly FontUsage Font;
|
||||
public readonly ITexturedCharacterGlyph Glyph;
|
||||
|
||||
public GlyphEntry(FontUsage font, ITexturedCharacterGlyph glyph)
|
||||
{
|
||||
Font = font;
|
||||
Glyph = glyph;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly struct TestGlyph : ITexturedCharacterGlyph
|
||||
{
|
||||
public Texture Texture => new Texture(1, 1);
|
||||
|
||||
@@ -90,31 +90,35 @@ namespace osu.Framework.Allocation
|
||||
foreach (var type in typeof(TModel).EnumerateBaseTypes())
|
||||
{
|
||||
foreach (var field in type.GetFields(activator_flags))
|
||||
perform(targetShadowModel, field, lastModel, t => t.shadowProp.UnbindFrom(t.modelProp));
|
||||
{
|
||||
perform(targetShadowModel, field, lastModel, (shadowProp, modelProp) => shadowProp.UnbindFrom(modelProp));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var type in typeof(TModel).EnumerateBaseTypes())
|
||||
{
|
||||
foreach (var field in type.GetFields(activator_flags))
|
||||
perform(targetShadowModel, field, newModel, t => t.shadowProp.BindTo(t.modelProp));
|
||||
{
|
||||
perform(targetShadowModel, field, newModel, (shadowProp, modelProp) => shadowProp.BindTo(modelProp));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform an arbitrary action across a shadow model and model.
|
||||
/// </summary>
|
||||
private void perform(TModel targetShadowModel, MemberInfo member, TModel target, Action<(IBindable shadowProp, IBindable modelProp)> action)
|
||||
private void perform(TModel targetShadowModel, MemberInfo member, TModel target, Action<IBindable, IBindable> action)
|
||||
{
|
||||
if (target == null) return;
|
||||
|
||||
switch (member)
|
||||
{
|
||||
case PropertyInfo pi:
|
||||
action(((IBindable)pi.GetValue(targetShadowModel), (IBindable)pi.GetValue(target)));
|
||||
action((IBindable)pi.GetValue(targetShadowModel), (IBindable)pi.GetValue(target));
|
||||
break;
|
||||
|
||||
case FieldInfo fi:
|
||||
action(((IBindable)fi.GetValue(targetShadowModel), (IBindable)fi.GetValue(target)));
|
||||
action((IBindable)fi.GetValue(targetShadowModel), (IBindable)fi.GetValue(target));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -303,7 +303,7 @@ namespace osu.Framework.Graphics.Containers
|
||||
int[] distributedIndices = Enumerable.Range(0, cellSizes.Length).Where(i => i >= dimensions.Length || dimensions[i].Mode == GridSizeMode.Distributed).ToArray();
|
||||
|
||||
// The dimensions corresponding to all distributed cells
|
||||
IEnumerable<(int i, Dimension dim)> distributedDimensions = distributedIndices.Select(i => (i, i >= dimensions.Length ? new Dimension() : dimensions[i]));
|
||||
IEnumerable<DimensionEntry> distributedDimensions = distributedIndices.Select(i => new DimensionEntry(i, i >= dimensions.Length ? new Dimension() : dimensions[i]));
|
||||
|
||||
// Total number of distributed cells
|
||||
int distributionCount = distributedIndices.Length;
|
||||
@@ -315,20 +315,32 @@ namespace osu.Framework.Graphics.Containers
|
||||
float distributionSize = Math.Max(0, spanLength - requiredSize) / distributionCount;
|
||||
|
||||
// Write the sizes of distributed cells. Ordering is important to maximize excess at every step
|
||||
foreach (var (i, dim) in distributedDimensions.OrderBy(d => d.dim.Range))
|
||||
foreach (var entry in distributedDimensions.OrderBy(d => d.Dimension.Range))
|
||||
{
|
||||
// Cells start off at their minimum size, and the total size should not exceed their maximum size
|
||||
cellSizes[i] = Math.Min(dim.MaxSize, dim.MinSize + distributionSize);
|
||||
cellSizes[entry.Index] = Math.Min(entry.Dimension.MaxSize, entry.Dimension.MinSize + distributionSize);
|
||||
|
||||
// If there's no excess, any further distributions are guaranteed to also have no excess, so this becomes a null-op
|
||||
// If there is an excess, the excess should be re-distributed among all other n-1 distributed cells
|
||||
if (--distributionCount > 0)
|
||||
distributionSize += Math.Max(0, distributionSize - dim.Range) / distributionCount;
|
||||
distributionSize += Math.Max(0, distributionSize - entry.Dimension.Range) / distributionCount;
|
||||
}
|
||||
|
||||
return cellSizes;
|
||||
}
|
||||
|
||||
private readonly struct DimensionEntry
|
||||
{
|
||||
public readonly int Index;
|
||||
public readonly Dimension Dimension;
|
||||
|
||||
public DimensionEntry(int index, Dimension dimension)
|
||||
{
|
||||
Index = index;
|
||||
Dimension = dimension;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents one cell of the <see cref="GridContainer"/>.
|
||||
/// </summary>
|
||||
|
||||
@@ -236,19 +236,36 @@ namespace osu.Framework.Graphics.Transforms
|
||||
AddDelay(delay, recursive);
|
||||
double newTransformDelay = TransformDelay;
|
||||
|
||||
return new ValueInvokeOnDisposal<(Transformable transformable, double delay, bool recursive, double newTransformDelay)>((this, delay, recursive, newTransformDelay), sender =>
|
||||
return new ValueInvokeOnDisposal<DelayedSequenceSender>(new DelayedSequenceSender(this, delay, recursive, newTransformDelay), sender =>
|
||||
{
|
||||
if (!Precision.AlmostEquals(sender.newTransformDelay, sender.transformable.TransformDelay))
|
||||
if (!Precision.AlmostEquals(sender.NewTransformDelay, sender.Transformable.TransformDelay))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"{nameof(sender.transformable.TransformStartTime)} at the end of delayed sequence is not the same as at the beginning, but should be. " +
|
||||
$"(begin={sender.newTransformDelay} end={sender.transformable.TransformDelay})");
|
||||
$"{nameof(sender.Transformable.TransformStartTime)} at the end of delayed sequence is not the same as at the beginning, but should be. " +
|
||||
$"(begin={sender.NewTransformDelay} end={sender.Transformable.TransformDelay})");
|
||||
}
|
||||
|
||||
AddDelay(-sender.delay, sender.recursive);
|
||||
AddDelay(-sender.Delay, sender.Recursive);
|
||||
});
|
||||
}
|
||||
|
||||
/// An ad-hoc struct used as a closure environment in <see cref="BeginDelayedSequence" />.
|
||||
private readonly struct DelayedSequenceSender
|
||||
{
|
||||
public readonly Transformable Transformable;
|
||||
public readonly double Delay;
|
||||
public readonly bool Recursive;
|
||||
public readonly double NewTransformDelay;
|
||||
|
||||
public DelayedSequenceSender(Transformable transformable, double delay, bool recursive, double newTransformDelay)
|
||||
{
|
||||
Transformable = transformable;
|
||||
Delay = delay;
|
||||
Recursive = recursive;
|
||||
NewTransformDelay = newTransformDelay;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start a sequence of <see cref="Transform"/>s from an absolute time value (adjusts <see cref="TransformStartTime"/>).
|
||||
/// </summary>
|
||||
@@ -261,19 +278,34 @@ namespace osu.Framework.Graphics.Transforms
|
||||
double oldTransformDelay = TransformDelay;
|
||||
double newTransformDelay = TransformDelay = newTransformStartTime - (Clock?.CurrentTime ?? 0);
|
||||
|
||||
return new ValueInvokeOnDisposal<(Transformable transformable, double oldTransformDelay, double newTransformDelay)>((this, oldTransformDelay, newTransformDelay), sender =>
|
||||
return new ValueInvokeOnDisposal<AbsoluteSequenceSender>(new AbsoluteSequenceSender(this, oldTransformDelay, newTransformDelay), sender =>
|
||||
{
|
||||
if (!Precision.AlmostEquals(sender.newTransformDelay, sender.transformable.TransformDelay))
|
||||
if (!Precision.AlmostEquals(sender.NewTransformDelay, sender.Transformable.TransformDelay))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"{nameof(sender.transformable.TransformStartTime)} at the end of absolute sequence is not the same as at the beginning, but should be. " +
|
||||
$"(begin={sender.newTransformDelay} end={sender.transformable.TransformDelay})");
|
||||
$"{nameof(sender.Transformable.TransformStartTime)} at the end of absolute sequence is not the same as at the beginning, but should be. " +
|
||||
$"(begin={sender.NewTransformDelay} end={sender.Transformable.TransformDelay})");
|
||||
}
|
||||
|
||||
sender.transformable.TransformDelay = sender.oldTransformDelay;
|
||||
sender.Transformable.TransformDelay = sender.OldTransformDelay;
|
||||
});
|
||||
}
|
||||
|
||||
/// An ad-hoc struct used as a closure environment in <see cref="BeginAbsoluteSequence" />.
|
||||
private readonly struct AbsoluteSequenceSender
|
||||
{
|
||||
public readonly Transformable Transformable;
|
||||
public readonly double OldTransformDelay;
|
||||
public readonly double NewTransformDelay;
|
||||
|
||||
public AbsoluteSequenceSender(Transformable transformable, double oldTransformDelay, double newTransformDelay)
|
||||
{
|
||||
Transformable = transformable;
|
||||
OldTransformDelay = oldTransformDelay;
|
||||
NewTransformDelay = newTransformDelay;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds to this object a <see cref="Transform"/> which was previously populated using this object via
|
||||
/// <see cref="TransformableExtensions.PopulateTransform{TValue, TEasing, TThis}"/>.
|
||||
|
||||
Reference in New Issue
Block a user