Merge branch 'master' into dispose-waveform-stream

This commit is contained in:
Bartłomiej Dach
2024-02-02 14:50:42 +01:00
committed by GitHub
65 changed files with 755 additions and 250 deletions

View File

@@ -9,7 +9,7 @@
]
},
"jetbrains.resharper.globaltools": {
"version": "2022.2.3",
"version": "2023.3.3",
"commands": [
"jb"
]

View File

@@ -1,5 +1,3 @@
is_global = true
# .NET Code Style
# IDE styles reference: https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/

View File

@@ -66,6 +66,7 @@
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=CompareOfFloatsByEqualityOperator/@EntryIndexedValue">HINT</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertClosureToMethodGroup/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertConditionalTernaryExpressionToSwitchExpression/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertConstructorToMemberInitializers/@EntryIndexedValue">HINT</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertIfDoToWhile/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertIfStatementToConditionalTernaryExpression/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertIfStatementToNullCoalescingAssignment/@EntryIndexedValue">WARNING</s:String>
@@ -81,6 +82,7 @@
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertToConstant_002ELocal/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertToLambdaExpression/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertToLocalFunction/@EntryIndexedValue">HINT</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertToPrimaryConstructor/@EntryIndexedValue">HINT</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertToStaticClass/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertToUsingDeclaration/@EntryIndexedValue">HINT</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertTypeCheckPatternToNullCheck/@EntryIndexedValue">DO_NOT_SHOW</s:String>
@@ -165,6 +167,7 @@
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantImmediateDelegateInvocation/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantLambdaSignatureParentheses/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantReadonlyModifier/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantTypeDeclarationBody/@EntryIndexedValue">HINT</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantTypeSpecificationInDefaultExpression/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantLinebreak/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantSpace/@EntryIndexedValue">WARNING</s:String>
@@ -250,6 +253,7 @@
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UnusedType_002EGlobal/@EntryIndexedValue">HINT</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UseAwaitUsing/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UseCollectionCountProperty/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UseCollectionExpression/@EntryIndexedValue">HINT</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UseConfigureAwaitFalseForAsyncDisposable/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UseFormatSpecifierInFormatString/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UseFormatSpecifierInInterpolation/@EntryIndexedValue">WARNING</s:String>
@@ -262,6 +266,7 @@
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UseObjectOrCollectionInitializer/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UsePatternMatching/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UseStringInterpolation/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UseUtf8StringLiteral/@EntryIndexedValue">HINT</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UseWithExpressionToCopyTuple/@EntryIndexedValue">HINT</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=VariableCanBeMadeConst/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=VirtualMemberCallInConstructor/@EntryIndexedValue">HINT</s:String>

View File

@@ -10,6 +10,7 @@ namespace osu.Framework.Benchmarks
public class BenchmarkBindableList
{
private readonly BindableList<int> list = new BindableList<int>();
private IBindableList<int> iList => list;
[GlobalSetup]
public void GlobalSetup()
@@ -31,5 +32,19 @@ namespace osu.Framework.Benchmarks
return result;
}
[Benchmark]
public int EnumerateInterface()
{
int result = 0;
for (int i = 0; i < 100; i++)
{
foreach (int val in iList)
result += val;
}
return result;
}
}
}

View File

@@ -22,28 +22,28 @@ namespace osu.Framework.Benchmarks
[Benchmark]
public void TestSprite()
{
var _ = new Sprite();
_ = new Sprite();
}
[Test]
[Benchmark]
public void TestCompositeDrawable()
{
var _ = new SimpleComposite();
_ = new SimpleComposite();
}
[Test]
[Benchmark]
public void TestContainer()
{
var _ = new Container();
_ = new Container();
}
[Test]
[Benchmark]
public void TestSpriteText()
{
var _ = new SpriteText();
_ = new SpriteText();
}
public partial class SimpleComposite : CompositeDrawable

View File

@@ -0,0 +1,153 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Collections.ObjectModel;
using BenchmarkDotNet.Attributes;
using osu.Framework.Extensions.ListExtensions;
namespace osu.Framework.Benchmarks
{
[MemoryDiagnoser]
public class BenchmarkSlimReadOnlyDictionary
{
private readonly Dictionary<int, int> dictionary = new Dictionary<int, int>();
private ReadOnlyDictionary<int, int> readOnlyDictionary = null!;
[GlobalSetup]
public void GlobalSetup()
{
readOnlyDictionary = new ReadOnlyDictionary<int, int>(dictionary);
int[] values = { 0, 1, 2, 3, 4, 5, 3, 2, 3, 1, 4, 5, -1 };
for (int i = 0; i < values.Length; i++)
dictionary[i] = values[i];
}
[Benchmark]
public int Dictionary()
{
int sum = 0;
for (int i = 0; i < 1000; i++)
{
foreach ((_, int v) in dictionary)
sum += v;
}
return sum;
}
[Benchmark]
public int DictionaryAsReadOnly()
{
int sum = 0;
for (int i = 0; i < 1000; i++)
{
foreach ((_, int v) in readOnlyDictionary)
sum += v;
}
return sum;
}
[Benchmark]
public int DictionaryAsSlimReadOnly()
{
int sum = 0;
for (int i = 0; i < 1000; i++)
{
foreach ((_, int v) in dictionary.AsSlimReadOnly())
sum += v;
}
return sum;
}
[Benchmark]
public int Keys()
{
int sum = 0;
for (int i = 0; i < 1000; i++)
{
foreach (int v in dictionary.Keys)
sum += v;
}
return sum;
}
[Benchmark]
public int KeysAsReadOnly()
{
int sum = 0;
for (int i = 0; i < 1000; i++)
{
foreach (int v in readOnlyDictionary.Keys)
sum += v;
}
return sum;
}
[Benchmark]
public int KeysAsSlimReadOnly()
{
int sum = 0;
for (int i = 0; i < 1000; i++)
{
foreach (int v in dictionary.AsSlimReadOnly().Keys)
sum += v;
}
return sum;
}
[Benchmark]
public int Values()
{
int sum = 0;
for (int i = 0; i < 1000; i++)
{
foreach (int v in dictionary.Values)
sum += v;
}
return sum;
}
[Benchmark]
public int ValuesAsReadOnly()
{
int sum = 0;
for (int i = 0; i < 1000; i++)
{
foreach (int v in readOnlyDictionary.Values)
sum += v;
}
return sum;
}
[Benchmark]
public int ValuesAsSlimReadOnly()
{
int sum = 0;
for (int i = 0; i < 1000; i++)
{
foreach (int v in dictionary.AsSlimReadOnly().Values)
sum += v;
}
return sum;
}
}
}

View File

@@ -8,7 +8,7 @@ using osu.Framework.Extensions.ListExtensions;
namespace osu.Framework.Benchmarks
{
[MemoryDiagnoser]
public class BenchmarkSlimReadOnlyCollection
public class BenchmarkSlimReadOnlyList
{
private readonly List<int> list = new List<int> { 0, 1, 2, 3, 4, 5, 3, 2, 3, 1, 4, 5, -1 };

View File

@@ -90,7 +90,7 @@ namespace osu.Framework.SourceGeneration.Tests
{
var newTree = CSharpSyntaxTree.ParseText(content, path: filename);
if (sources.ContainsKey(filename))
if (!sources.TryAdd(filename, newTree))
{
var oldTree = sources[filename];
sources[filename] = newTree;
@@ -104,7 +104,6 @@ namespace osu.Framework.SourceGeneration.Tests
}
else
{
sources.Add(filename, newTree);
Compilation = Compilation.AddSyntaxTrees(newTree);
}
}

View File

@@ -144,7 +144,7 @@ namespace FlappyDon.Game.Elements
if (GroundY > 0.0f)
groundPlane = GroundY / 2.0f;
else
groundPlane = Parent.DrawHeight - DrawHeight;
groundPlane = Parent!.DrawHeight - DrawHeight;
Y = Math.Min(Y, groundPlane);

View File

@@ -863,7 +863,8 @@ namespace osu.Framework.Tests.Bindables
[Test]
public void TestGetEnumeratorDoesNotReturnNull()
{
Assert.NotNull(bindableStringByteDictionary.GetEnumerator());
using var enumerator = bindableStringByteDictionary.GetEnumerator();
Assert.NotNull(enumerator);
}
[Test]
@@ -873,8 +874,10 @@ namespace osu.Framework.Tests.Bindables
var dict = new BindableDictionary<string, byte>(array);
var enumerator = dict.GetEnumerator();
using var enumerator = dict.GetEnumerator();
// ReSharper disable once NotDisposedResource
// Array enumerator is not disposable
Assert.AreNotEqual(array.GetEnumerator(), enumerator);
}

View File

@@ -1452,7 +1452,8 @@ namespace osu.Framework.Tests.Bindables
[Test]
public void TestGetEnumeratorDoesNotReturnNull()
{
Assert.NotNull(bindableStringList.GetEnumerator());
using var enumerator = bindableStringList.GetEnumerator();
Assert.NotNull(enumerator);
}
[Test]
@@ -1461,8 +1462,10 @@ namespace osu.Framework.Tests.Bindables
string[] array = { "" };
var list = new BindableList<string>(array);
var enumerator = list.GetEnumerator();
using var enumerator = list.GetEnumerator();
// ReSharper disable once NotDisposedResource
// Array enumerator is not disposable
Assert.AreNotEqual(array.GetEnumerator(), enumerator);
}

View File

@@ -29,9 +29,9 @@ namespace osu.Framework.Tests.Containers
{
Assert.Throws<InvalidOperationException>(() =>
{
var unused = new Container
_ = new Container
{
Children = (IReadOnlyList<Drawable>)Activator.CreateInstance(containerType)
Children = (IReadOnlyList<Drawable>)Activator.CreateInstance(containerType)!
};
});
@@ -39,12 +39,12 @@ namespace osu.Framework.Tests.Containers
{
var unused = new Container();
unused.AddRange((IEnumerable<Drawable>)Activator.CreateInstance(containerType));
unused.AddRange((IEnumerable<Drawable>)Activator.CreateInstance(containerType)!);
});
Assert.Throws<InvalidOperationException>(() =>
{
var unused = new AudioContainer
_ = new AudioContainer
{
Children = (IReadOnlyList<Drawable>)Activator.CreateInstance(containerType)!
};

View File

@@ -81,7 +81,7 @@ namespace osu.Framework.Tests.Containers
Child = new Container(),
};
var unused2 = new Container { Child = unused1.Child };
_ = new Container { Child = unused1.Child };
});
}

View File

@@ -355,7 +355,7 @@ namespace osu.Framework.Tests.IO
bool hasThrown = false;
request.Failed += exception => hasThrown = exception != null;
var _ = request.PerformAsync();
_ = request.PerformAsync();
Assert.DoesNotThrow(request.Abort);

View File

@@ -185,7 +185,7 @@ namespace osu.Framework.Tests.Visual.Audio
protected override void OnDrag(DragEvent e)
{
Y = (int)(e.MousePosition.Y / (Parent.DrawHeight / notes));
Y = (int)(e.MousePosition.Y / (Parent!.DrawHeight / notes));
}
public void Reset()

View File

@@ -47,7 +47,7 @@ namespace osu.Framework.Tests.Visual.Drawables
{
AddSliderStep("resize easings", default_size / 2, default_size * 2, default_size, size =>
{
easingsContainer?.Children?.OfType<Visualiser>().ForEach(easing => easing.ResizeTo(new Vector2(size)));
easingsContainer?.Children.OfType<Visualiser>().ForEach(easing => easing.ResizeTo(new Vector2(size)));
});
}

View File

@@ -81,6 +81,27 @@ namespace osu.Framework.Tests.Visual.Drawables
AddStep("zero child size", () => Assert.Throws<ArgumentException>(() => boxes[0].TransformRelativeChildSizeTo(Vector2.Zero)));
}
[Test]
public void TestNestedAbsoluteSequence()
{
AddStep("Animate", () =>
{
setup();
animate();
});
AddStep("start absolute sequence", () =>
{
using (BeginAbsoluteSequence(0))
{
using (boxes[0].BeginAbsoluteSequence(Time.Current))
{
boxes[0].FadeInFromZero(1000);
}
}
});
}
private void setup()
{
finalizeTriggered = false;

View File

@@ -836,6 +836,160 @@ namespace osu.Framework.Tests.Visual.Layout
void assertContentChangeEventWasFired() => AddAssert("Content change event was fired", () => gridContentChangeEventWasFired);
}
[Test]
public void TestPaddingRelativeSingle()
{
FillBox box = null;
AddStep("set content", () =>
{
grid.Content = new[] { new Drawable[] { box = new FillBox() }, };
grid.RowDimensions = grid.ColumnDimensions = new[] { new Dimension(GridSizeMode.Relative, 1f) };
});
checkCorrectBoxSize(new MarginPadding(20));
checkCorrectBoxSize(new MarginPadding { Horizontal = 20 });
checkCorrectBoxSize(new MarginPadding { Vertical = 20 });
checkCorrectBoxSize(new MarginPadding { Left = 20 });
checkCorrectBoxSize(new MarginPadding { Right = 20 });
checkCorrectBoxSize(new MarginPadding { Top = 20 });
checkCorrectBoxSize(new MarginPadding { Bottom = 20 });
return;
void checkCorrectBoxSize(MarginPadding padding)
{
setPadding(padding);
AddAssert("box size is correct", () => Precision.AlmostEquals(grid.DrawSize - padding.Total, box.DrawSize));
}
}
[Test]
public void TestPaddingAutosizeSingle()
{
FillBox box = null;
AddStep("set content", () =>
{
gridParent.RelativeSizeAxes = Axes.None;
gridParent.AutoSizeAxes = Axes.Both;
grid.RelativeSizeAxes = Axes.None;
grid.AutoSizeAxes = Axes.Both;
grid.Content = new[] { new Drawable[] { box = new FillBox { RelativeSizeAxes = Axes.None, Size = new Vector2(100) } }, };
grid.RowDimensions = grid.ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) };
});
checkCorrectGridSize(new MarginPadding(20));
checkCorrectGridSize(new MarginPadding { Horizontal = 20 });
checkCorrectGridSize(new MarginPadding { Vertical = 20 });
checkCorrectGridSize(new MarginPadding { Left = 20 });
checkCorrectGridSize(new MarginPadding { Right = 20 });
checkCorrectGridSize(new MarginPadding { Top = 20 });
checkCorrectGridSize(new MarginPadding { Bottom = 20 });
return;
void checkCorrectGridSize(MarginPadding padding)
{
setPadding(padding);
AddAssert("grid size is correct", () => Precision.AlmostEquals(grid.DrawSize, box.DrawSize + padding.Total));
}
}
[Test]
public void TestPaddingAutosizeMultiple()
{
FillBox[] boxes = new FillBox[3];
const float box_size = 100f;
AddStep("set content", () =>
{
gridParent.RelativeSizeAxes = Axes.None;
gridParent.AutoSizeAxes = Axes.Both;
grid.RelativeSizeAxes = Axes.None;
grid.AutoSizeAxes = Axes.Both;
grid.RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) };
grid.ColumnDimensions = new[]
{
new Dimension(GridSizeMode.AutoSize),
new Dimension(GridSizeMode.AutoSize),
new Dimension(GridSizeMode.AutoSize)
};
grid.Content = new[]
{
new Drawable[]
{
boxes[0] = new FillBox
{
RelativeSizeAxes = Axes.None,
Size = new Vector2(box_size)
},
boxes[1] = new FillBox
{
RelativeSizeAxes = Axes.None,
Size = new Vector2(box_size)
},
boxes[2] = new FillBox
{
RelativeSizeAxes = Axes.None,
Size = new Vector2(box_size)
}
}
};
});
checkCorrectGridSize(new MarginPadding(20));
checkCorrectGridSize(new MarginPadding { Horizontal = 20 });
checkCorrectGridSize(new MarginPadding { Vertical = 20 });
checkCorrectGridSize(new MarginPadding { Left = 20 });
checkCorrectGridSize(new MarginPadding { Right = 20 });
checkCorrectGridSize(new MarginPadding { Top = 20 });
checkCorrectGridSize(new MarginPadding { Bottom = 20 });
return;
void checkCorrectGridSize(MarginPadding padding)
{
setPadding(padding);
AddAssert("grid size is correct", () => Precision.AlmostEquals(grid.DrawSize, new Vector2(box_size * 3, box_size) + padding.Total));
}
}
[TestCase(false)]
[TestCase(true)]
public void TestPaddingRelativeMultiple(bool row)
{
FillBox[] boxes = new FillBox[3];
setSingleDimensionContent(() => new[]
{
new Drawable[] { boxes[0] = new FillBox(), boxes[1] = new FillBox(), boxes[2] = new FillBox() }
}, row: row);
checkSizes(new MarginPadding(20));
checkSizes(new MarginPadding { Horizontal = 20 });
checkSizes(new MarginPadding { Vertical = 20 });
checkSizes(new MarginPadding { Left = 20 });
checkSizes(new MarginPadding { Right = 20 });
checkSizes(new MarginPadding { Top = 20 });
checkSizes(new MarginPadding { Bottom = 20 });
return;
void checkSizes(MarginPadding padding)
{
setPadding(padding);
for (int i = 0; i < 3; i++)
{
int local = i;
if (row)
AddAssert($"box {local} has correct size", () => Precision.AlmostEquals(boxes[local].DrawSize, new Vector2((grid.DrawWidth - padding.TotalHorizontal) / 3f, grid.DrawHeight - padding.TotalVertical)));
else
AddAssert($"box {local} has correct size", () => Precision.AlmostEquals(boxes[local].DrawSize, new Vector2(grid.DrawWidth - padding.TotalHorizontal, (grid.DrawHeight - padding.TotalVertical) / 3f)));
}
}
}
/// <summary>
/// Returns drawable dimension along desired axis.
/// </summary>
@@ -888,6 +1042,11 @@ namespace osu.Framework.Tests.Visual.Layout
grid.ColumnDimensions = dimensions;
});
private void setPadding(MarginPadding padding) => AddStep($"set padding {padding.Left}, {padding.Top}, {padding.Right}, {padding.Bottom}", () =>
{
grid.Padding = padding;
});
private partial class FillBox : Box
{
public FillBox()

View File

@@ -243,7 +243,7 @@ namespace osu.Framework.Tests.Visual.Layout
{
for (int row = 0; row < getGrid().Content.Count; row++)
{
if (!Precision.AlmostEquals(expectedHeight, getGrid().Content[row][0].Parent.DrawHeight))
if (!Precision.AlmostEquals(expectedHeight, getGrid().Content[row][0].Parent!.DrawHeight))
return false;
}

View File

@@ -39,47 +39,43 @@ namespace osu.Framework.Tests.Visual.Sprites
Cell(1, 2).Child = createTest("drawable - fixed size", () => new TestDrawableAnimation(Axes.Both) { Size = new Vector2(100, 50) });
}
private Drawable createTest(string name, Func<Drawable> animationCreationFunc) => new Container
private Drawable createTest(string name, Func<Drawable> animationCreationFunc) => new GridContainer
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding(10),
Child = new GridContainer
Content = new[]
{
RelativeSizeAxes = Axes.Both,
Content = new[]
new Drawable[]
{
new Drawable[]
new SpriteText
{
new SpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Text = name
},
},
new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Masking = true,
BorderColour = Color4.OrangeRed,
BorderThickness = 2,
Children = new[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
AlwaysPresent = true
},
animationCreationFunc()
}
}
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Text = name
},
},
RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }
}
new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Masking = true,
BorderColour = Color4.OrangeRed,
BorderThickness = 2,
Children = new[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
AlwaysPresent = true
},
animationCreationFunc()
}
}
},
},
RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }
};
private partial class TestDrawableAnimation : DrawableAnimation

View File

@@ -64,35 +64,31 @@ namespace osu.Framework.Tests.Visual.Sprites
Origin = Anchor.TopCentre,
Text = $"useManaulMipmaps: {useManualMipmaps}"
},
new Container
new GridContainer
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Top = 20 },
Child = new GridContainer
ColumnDimensions = new[]
{
RelativeSizeAxes = Axes.Both,
ColumnDimensions = new[]
new Dimension(GridSizeMode.Relative, 0.5f),
new Dimension(GridSizeMode.Relative, 0.5f),
},
RowDimensions = new[]
{
new Dimension(GridSizeMode.Relative, 0.5f),
new Dimension(GridSizeMode.Relative, 0.5f),
},
Content = new[]
{
new Drawable[]
{
new Dimension(GridSizeMode.Relative, 0.5f),
new Dimension(GridSizeMode.Relative, 0.5f),
new MipmapSprite(0),
new MipmapSprite(1)
},
RowDimensions = new[]
new Drawable[]
{
new Dimension(GridSizeMode.Relative, 0.5f),
new Dimension(GridSizeMode.Relative, 0.5f),
},
Content = new[]
{
new Drawable[]
{
new MipmapSprite(0),
new MipmapSprite(1)
},
new Drawable[]
{
new MipmapSprite(2),
new MipmapSprite(3)
}
new MipmapSprite(2),
new MipmapSprite(3)
}
}
}

View File

@@ -25,6 +25,21 @@ namespace osu.Framework.Tests.Visual.Sprites
{
private ScheduledDelegate? scheduledDelegate;
[Test]
public void TestPreloadIcon()
{
SpriteIcon? icon = null;
AddStep("create icon", () => icon = new SpriteIcon
{
Size = new Vector2(20),
Icon = FontAwesome.Solid.Anchor
});
AddStep("preload icon", () => LoadComponent(icon));
AddStep("change icon", () => icon!.Icon = FontAwesome.Solid.Ad);
AddStep("add icon", () => Add(icon));
}
[Test]
public void TestOneIconAtATime()
{

View File

@@ -41,47 +41,43 @@ namespace osu.Framework.Tests.Visual.Sprites
});
}
private Drawable createTest(string name, Func<Drawable> animationCreationFunc) => new Container
private Drawable createTest(string name, Func<Drawable> animationCreationFunc) => new GridContainer
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding(10),
Child = new GridContainer
Content = new[]
{
RelativeSizeAxes = Axes.Both,
Content = new[]
new Drawable[]
{
new Drawable[]
new SpriteText
{
new SpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Text = name
},
},
new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Masking = true,
BorderColour = Color4.OrangeRed,
BorderThickness = 2,
Children = new[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
AlwaysPresent = true
},
animationCreationFunc()
}
}
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Text = name
},
},
RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }
}
new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Masking = true,
BorderColour = Color4.OrangeRed,
BorderThickness = 2,
Children = new[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
AlwaysPresent = true
},
animationCreationFunc()
}
}
},
},
RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }
};
}
}

View File

@@ -290,6 +290,43 @@ namespace osu.Framework.Tests.Visual.UserInterface
AddUntilStep("only one item", () => delayedList.ChildrenOfType<BasicRearrangeableListItem<int>>().Count() == 1);
}
[Test]
public void TestDragSynchronisation()
{
TestRearrangeableList another = null!;
addItems(3);
AddStep("add another list", () =>
{
another = new TestRearrangeableList
{
Origin = Anchor.BottomCentre,
Anchor = Anchor.BottomCentre,
Size = new Vector2(300, 200),
};
Add(another);
});
AddStep("bind lists", () =>
{
another.Items.BindTo(list.Items);
});
AddStep("move mouse to first dragger", () => InputManager.MoveMouseTo(getDragger(0)));
AddStep("begin a drag", () => InputManager.PressButton(MouseButton.Left));
AddStep("move the mouse", () => InputManager.MoveMouseTo(getDragger(0), new Vector2(0, 80)));
AddStep("end the drag", () => InputManager.ReleaseButton(MouseButton.Left));
AddUntilStep("0 is the last in original", () => list.Items.Last() == 0);
AddAssert("0 is the last in bound", () => another.Items.Last() == 0);
AddAssert("items flow updated", () =>
{
var item = (BasicRearrangeableListItem<int>)another.ListContainer.FlowingChildren.Last();
return item.Model == 0;
});
}
private void addDragSteps(int from, int to, int[] expectedSequence)
{
AddStep($"move to {from}", () =>

View File

@@ -70,29 +70,29 @@ namespace osu.Framework.Tests.Visual.UserInterface
AddStep("get tooltip instance", () => originalInstance = tooltipContainer.CurrentTooltip);
AddAssert("custom tooltip used", () => originalInstance.GetType() == typeof(CustomTooltip));
assertTooltipText(() => ((CustomContent)customTooltipTextA.TooltipContent).Text);
assertTooltipText(() => ((CustomContent)customTooltipTextA.TooltipContent!).Text);
hoverTooltipProvider(() => customTooltipTextB);
AddAssert("custom tooltip reused", () => tooltipContainer.CurrentTooltip == originalInstance);
assertTooltipText(() => ((CustomContent)customTooltipTextB.TooltipContent).Text);
assertTooltipText(() => ((CustomContent)customTooltipTextB.TooltipContent!).Text);
}
[Test]
public void TestDifferentCustomTooltips()
{
hoverTooltipProvider(() => customTooltipTextA);
assertTooltipText(() => ((CustomContent)customTooltipTextA.TooltipContent).Text);
assertTooltipText(() => ((CustomContent)customTooltipTextA.TooltipContent!).Text);
AddAssert("current tooltip type normal", () => tooltipContainer.CurrentTooltip.GetType() == typeof(CustomTooltip));
hoverTooltipProvider(() => customTooltipTextAlt);
assertTooltipText(() => ((CustomContent)customTooltipTextAlt.TooltipContent).Text);
assertTooltipText(() => ((CustomContent)customTooltipTextAlt.TooltipContent!).Text);
AddAssert("current tooltip type alt", () => tooltipContainer.CurrentTooltip.GetType() == typeof(CustomTooltipAlt));
hoverTooltipProvider(() => customTooltipTextB);
assertTooltipText(() => ((CustomContent)customTooltipTextB.TooltipContent).Text);
assertTooltipText(() => ((CustomContent)customTooltipTextB.TooltipContent!).Text);
AddAssert("current tooltip type normal", () => tooltipContainer.CurrentTooltip.GetType() == typeof(CustomTooltip));
}

View File

@@ -140,7 +140,7 @@ namespace osu.Framework.Allocation
public void Register(Type type, InjectDependencyDelegate injectDel, CacheDependencyDelegate cacheDel)
{
// The DependencyActivator constructor stores itself to a static dictionary.
var _ = new DependencyActivator(
_ = new DependencyActivator(
type,
injectDel ?? ((_, _) => { }),
cacheDel ?? ((_, d, _) => d));

View File

@@ -37,10 +37,7 @@ namespace osu.Framework.Audio.Sample
throw new ObjectDisposedException(ToString(), "Can not get a channel from a disposed sample.");
var channel = CreateChannel();
if (channel != null)
channel.OnPlay = onPlay;
channel.OnPlay = onPlay;
return channel;
}

View File

@@ -6,6 +6,7 @@
using System;
using System.Diagnostics;
using System.Globalization;
using JetBrains.Annotations;
using osu.Framework.Utils;
namespace osu.Framework.Bindables
@@ -13,6 +14,7 @@ namespace osu.Framework.Bindables
public class BindableNumber<T> : RangeConstrainedBindable<T>, IBindableNumber<T>
where T : struct, IComparable<T>, IConvertible, IEquatable<T>
{
[CanBeNull]
public event Action<T> PrecisionChanged;
public BindableNumber(T defaultValue = default)

View File

@@ -37,5 +37,10 @@ namespace osu.Framework.Bindables
/// <inheritdoc cref="IBindable.GetBoundCopy"/>
IBindableList<T> GetBoundCopy();
// We want this enumerator to reduce enumeration overhead, but it causes mono / android crashes for silly reasons.
// See https://sentry.ppy.sh/share/issue/c7cda39d8ab94897a2bb350059fd3652/
// new List<T>.Enumerator GetEnumerator();
}
}

View File

@@ -4,6 +4,7 @@
#nullable disable
using System;
using JetBrains.Annotations;
using osu.Framework.Bindables;
namespace osu.Framework.Configuration.Tracking
@@ -14,6 +15,7 @@ namespace osu.Framework.Configuration.Tracking
/// <typeparam name="TValue">The type of the tracked value.</typeparam>
public abstract class TrackedSetting<TValue> : ITrackedSetting
{
[CanBeNull]
public event Action<SettingDescription> SettingChanged;
private readonly object setting;

View File

@@ -17,5 +17,17 @@ namespace osu.Framework.Extensions.ListExtensions
/// <returns>The read-only view.</returns>
public static SlimReadOnlyListWrapper<T> AsSlimReadOnly<T>(this List<T> list)
=> new SlimReadOnlyListWrapper<T>(list);
/// <summary>
/// Creates a new read-only view into a <see cref="Dictionary{TKey, TValue}"/>.
/// </summary>
/// <remarks>Enumeration does not allocate the enumerator.</remarks>
/// <param name="dictionary">The dictionary to create the view of.</param>
/// <typeparam name="TKey">The type of keys in the dictionary.</typeparam>
/// <typeparam name="TValue">The type of values in the dictionary.</typeparam>
/// <returns>The read-only view.</returns>
public static SlimReadOnlyDictionaryWrapper<TKey, TValue> AsSlimReadOnly<TKey, TValue>(this Dictionary<TKey, TValue> dictionary)
where TKey : notnull
=> new SlimReadOnlyDictionaryWrapper<TKey, TValue>(dictionary);
}
}

View File

@@ -1310,6 +1310,9 @@ namespace osu.Framework.Graphics.Containers
return SchedulerAfterChildren.Add(action);
}
[ThreadStatic]
private static List<AbsoluteSequenceSender> absoluteSequenceActions;
public override IDisposable BeginAbsoluteSequence(double newTransformStartTime, bool recursive = true)
{
EnsureTransformMutationAllowed();
@@ -1317,20 +1320,30 @@ namespace osu.Framework.Graphics.Containers
if (!recursive || internalChildren.Count == 0)
return base.BeginAbsoluteSequence(newTransformStartTime, false);
List<AbsoluteSequenceSender> disposalActions = new List<AbsoluteSequenceSender>(internalChildren.Count + 1);
absoluteSequenceActions ??= new List<AbsoluteSequenceSender>();
absoluteSequenceActions.EnsureCapacity(internalChildren.Count + 1);
base.CollectAbsoluteSequenceActionsFromSubTree(newTransformStartTime, disposalActions);
int startCount = absoluteSequenceActions.Count;
base.CollectAbsoluteSequenceActionsFromSubTree(newTransformStartTime, absoluteSequenceActions);
foreach (var c in internalChildren)
c.CollectAbsoluteSequenceActionsFromSubTree(newTransformStartTime, disposalActions);
c.CollectAbsoluteSequenceActionsFromSubTree(newTransformStartTime, absoluteSequenceActions);
return new ValueInvokeOnDisposal<List<AbsoluteSequenceSender>>(disposalActions, actions =>
int sequenceLength = absoluteSequenceActions.Count - startCount;
return new ValueInvokeOnDisposal<AbsoluteSequenceRange>(new AbsoluteSequenceRange(absoluteSequenceActions, sequenceLength), static range =>
{
foreach (var a in actions)
a.Dispose();
int startIndex = range.Sequence.Count - range.Length;
for (int i = startIndex; i < range.Sequence.Count; i++)
range.Sequence[i].Dispose();
range.Sequence.RemoveRange(startIndex, range.Length);
});
}
private readonly record struct AbsoluteSequenceRange(List<AbsoluteSequenceSender> Sequence, int Length);
internal override void CollectAbsoluteSequenceActionsFromSubTree(double newTransformStartTime, List<AbsoluteSequenceSender> actions)
{
base.CollectAbsoluteSequenceActionsFromSubTree(newTransformStartTime, actions);

View File

@@ -7,6 +7,7 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using JetBrains.Annotations;
using osu.Framework.Extensions.TypeExtensions;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Effects;
@@ -132,7 +133,11 @@ namespace osu.Framework.Graphics.Containers
array[arrayIndex++] = c;
}
bool ICollection<T>.Remove(T item) => Remove(item, true);
bool ICollection<T>.Remove(T item)
{
ArgumentNullException.ThrowIfNull(item);
return Remove(item, true);
}
public Enumerator GetEnumerator() => new Enumerator(this);
@@ -297,7 +302,7 @@ namespace osu.Framework.Graphics.Containers
/// Removes a range of children. This is equivalent to calling <see cref="Remove(T, bool)"/> on
/// each element of the range in order.
/// </summary>
public void RemoveRange(IEnumerable<T> range, bool disposeImmediately)
public void RemoveRange([CanBeNull] IEnumerable<T> range, bool disposeImmediately)
{
if (range == null)
return;

View File

@@ -20,6 +20,16 @@ namespace osu.Framework.Graphics.Containers
/// </summary>
public partial class GridContainer : CompositeDrawable
{
/// <summary>
/// Shrinks the space children may occupy within this <see cref="GridContainer"/>
/// by the specified amount on each side.
/// </summary>
public new MarginPadding Padding
{
get => base.Padding;
set => base.Padding = value;
}
public GridContainer()
{
AddLayout(cellLayout);
@@ -206,8 +216,8 @@ namespace osu.Framework.Graphics.Containers
if (cellLayout.IsValid)
return;
float[] widths = distribute(columnDimensions, DrawWidth, getCellSizesAlongAxis(Axes.X, DrawWidth));
float[] heights = distribute(rowDimensions, DrawHeight, getCellSizesAlongAxis(Axes.Y, DrawHeight));
float[] widths = distribute(columnDimensions, DrawWidth - Padding.TotalHorizontal, getCellSizesAlongAxis(Axes.X, DrawWidth - Padding.TotalHorizontal));
float[] heights = distribute(rowDimensions, DrawHeight - Padding.TotalVertical, getCellSizesAlongAxis(Axes.Y, DrawHeight - Padding.TotalVertical));
for (int col = 0; col < cellColumns; col++)
{

View File

@@ -100,7 +100,7 @@ namespace osu.Framework.Graphics.Containers
loadDrawable(() => CreateDrawable(model) ?? Empty());
}
private void loadDrawable(Func<Drawable>? createDrawableFunc)
private void loadDrawable(Func<Drawable?>? createDrawableFunc)
{
// Remove the previous wrapper if the inner drawable hasn't finished loading.
if (currentWrapper?.DelayedLoadCompleted == false)
@@ -174,7 +174,7 @@ namespace osu.Framework.Graphics.Containers
/// <param name="createContentFunc">A function that creates the wrapped <see cref="Drawable"/>.</param>
/// <param name="timeBeforeLoad">The time before loading should begin.</param>
/// <returns>A <see cref="DelayedLoadWrapper"/> or null if <paramref name="createContentFunc"/> returns null.</returns>
private DelayedLoadWrapper createWrapper(Func<Drawable> createContentFunc, double timeBeforeLoad)
private DelayedLoadWrapper createWrapper(Func<Drawable?> createContentFunc, double timeBeforeLoad)
{
// Note that this only becomes null after the first consumption.
// ie. the `createContentFunc` cannot provide a null.
@@ -185,7 +185,7 @@ namespace osu.Framework.Graphics.Containers
try
{
// optimisation to use already constructed object (used above for null check).
return content ?? createContentFunc();
return content ?? createContentFunc()!;
}
finally
{

View File

@@ -110,6 +110,11 @@ namespace osu.Framework.Graphics.Containers
removeItems(e.OldItems);
addItems(e.NewItems);
break;
case NotifyCollectionChangedAction.Move:
sortItems();
OnItemsChanged();
break;
}
}
@@ -182,7 +187,7 @@ namespace osu.Framework.Graphics.Containers
// If the async load didn't complete, the item wouldn't exist in the container and an exception would be thrown
if (drawable.Parent == ListContainer)
ListContainer.SetLayoutPosition(drawable, i);
ListContainer!.SetLayoutPosition(drawable, i);
}
}

View File

@@ -5,6 +5,7 @@
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
namespace osu.Framework.Graphics.Containers
{
@@ -16,6 +17,8 @@ namespace osu.Framework.Graphics.Containers
public abstract class TextPart : ITextPart
{
public IEnumerable<Drawable> Drawables { get; }
[CanBeNull]
public event Action<IEnumerable<Drawable>> DrawablePartsRecreated;
private readonly List<Drawable> drawables = new List<Drawable>();

View File

@@ -238,7 +238,10 @@ namespace osu.Framework.Graphics
lock (LoadLock)
{
if (!isDirectAsyncContext && IsLongRunning)
throw new InvalidOperationException($"Tried to load long-running drawable type {GetType().ReadableName()} in a non-direct async context. See https://git.io/Je1YF for more details.");
{
throw new InvalidOperationException(
$"Tried to load long-running drawable type {GetType().ReadableName()} in a non-direct async context. See https://git.io/Je1YF for more details.");
}
if (IsDisposed)
throw new ObjectDisposedException(ToString(), "Attempting to load an already disposed drawable.");
@@ -1901,6 +1904,8 @@ namespace osu.Framework.Graphics
/// <returns>The vector in other's coordinates.</returns>
public Vector2 ToSpaceOfOtherDrawable(Vector2 input, IDrawable other)
{
ArgumentNullException.ThrowIfNull(other);
if (other == this)
return input;
@@ -1926,14 +1931,14 @@ namespace osu.Framework.Graphics
/// </summary>
/// <param name="input">A vector in local coordinates.</param>
/// <returns>The vector in Parent's coordinates.</returns>
public Vector2 ToParentSpace(Vector2 input) => ToSpaceOfOtherDrawable(input, Parent);
public Vector2 ToParentSpace(Vector2 input) => ToSpaceOfOtherDrawable(input, Parent!);
/// <summary>
/// Accepts a rectangle in local coordinates and converts it to a quad in Parent's space.
/// </summary>
/// <param name="input">A rectangle in local coordinates.</param>
/// <returns>The quad in Parent's coordinates.</returns>
public Quad ToParentSpace(RectangleF input) => ToSpaceOfOtherDrawable(input, Parent);
public Quad ToParentSpace(RectangleF input) => ToSpaceOfOtherDrawable(input, Parent!);
/// <summary>
/// Accepts a vector in local coordinates and converts it to coordinates in screen space.

View File

@@ -18,6 +18,7 @@ using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Graphics.Pooling;
using osu.Framework.Graphics.Rendering;
using osu.Framework.Platform;
@@ -70,6 +71,7 @@ namespace osu.Framework.Graphics.Performance
private FrameStatisticsMode state;
[CanBeNull]
public event Action<FrameStatisticsMode> StateChanged;
public FrameStatisticsMode State

View File

@@ -29,11 +29,6 @@ namespace osu.Framework.Graphics.Sprites
{
this.store = store;
TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE);
}
protected override void LoadComplete()
{
base.LoadComplete();
updateTexture();
}
@@ -121,7 +116,7 @@ namespace osu.Framework.Graphics.Sprites
if (icon.Equals(value)) return;
icon = value;
if (IsLoaded)
if (LoadState > LoadState.NotLoaded)
updateTexture();
}
}

View File

@@ -68,7 +68,7 @@ namespace osu.Framework.Graphics.Sprites
// Pre-cache the characters in the texture store
foreach (char character in localisedText.Value)
{
var unused = store.Get(font.FontName, character) ?? store.Get(null, character);
_ = store.Get(font.FontName, character) ?? store.Get(null, character);
}
}

View File

@@ -278,7 +278,7 @@ namespace osu.Framework.Graphics.Transforms
EnsureTransformMutationAllowed();
if (delay == 0)
return null;
return new ValueInvokeOnDisposal(() => { });
AddDelay(delay, recursive);
double newTransformDelay = TransformDelay;

View File

@@ -6,6 +6,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Extensions.IEnumerableExtensions;
using osuTK.Graphics;
@@ -27,6 +28,7 @@ namespace osu.Framework.Graphics.UserInterface
/// <summary>
/// Invoked when this <see cref="Menu"/>'s <see cref="State"/> changes.
/// </summary>
[CanBeNull]
public event Action<MenuState> StateChanged;
/// <summary>
@@ -695,6 +697,7 @@ namespace osu.Framework.Graphics.UserInterface
/// <summary>
/// Invoked when this <see cref="DrawableMenuItem"/>'s <see cref="State"/> changes.
/// </summary>
[CanBeNull]
public event Action<MenuItemState> StateChanged;
/// <summary>

View File

@@ -597,7 +597,7 @@ namespace osu.Framework.Graphics.UserInterface
private int getCharacterClosestTo(Vector2 pos)
{
pos = Parent.ToSpaceOfOtherDrawable(pos, TextFlow);
pos = Parent!.ToSpaceOfOtherDrawable(pos, TextFlow);
int i = 0;

View File

@@ -405,7 +405,7 @@ namespace osu.Framework.Graphics.Veldrid
Debug.Assert(device.BackendType == GraphicsBackend.Vulkan);
var info = device.GetVulkanInfo();
var physicalDevice = info.PhysicalDevice;
IntPtr physicalDevice = info.PhysicalDevice;
uint instanceExtensionsCount = 0;
var result = VulkanNative.vkEnumerateInstanceExtensionProperties((byte*)null, ref instanceExtensionsCount, IntPtr.Zero);

View File

@@ -165,10 +165,8 @@ namespace osu.Framework.IO.Stores
return;
// Check if there's already a reload action bound
if (actionList.ContainsKey(name))
if (!actionList.TryAdd(name, onReload))
throw new InvalidOperationException($"A reload delegate is already bound to the resource '{name}'.");
actionList[name] = onReload;
}
/// <summary>

View File

@@ -9,7 +9,7 @@ namespace osu.Framework.Input.Bindings
{
public KeyCombination KeyCombination { get; set; }
public object Action { get; set; }
public object Action { get; set; } = null!;
/// <summary>
/// Construct a new instance.

View File

@@ -444,7 +444,7 @@ namespace osu.Framework.Input
private readonly List<Drawable> highFrequencyDrawables = new List<Drawable>();
private MouseMoveEvent highFrequencyMoveEvent;
private MouseMoveEvent lastMouseMove;
protected override void Update()
{
@@ -458,10 +458,11 @@ namespace osu.Framework.Input
var pendingInputs = GetPendingInputs();
if (pendingInputs.Count > 0)
lastMouseMove = null;
foreach (var result in pendingInputs)
{
result.Apply(CurrentState, this);
}
if (CurrentState.Mouse.IsPositionValid)
{
@@ -477,10 +478,9 @@ namespace osu.Framework.Input
{
// conditional avoid allocs of MouseMoveEvent when state is guaranteed to not have been mutated.
// can be removed if we pool/change UIEvent allocation to be more efficient.
if (highFrequencyMoveEvent == null || pendingInputs.Count > 0)
highFrequencyMoveEvent = new MouseMoveEvent(CurrentState);
lastMouseMove ??= new MouseMoveEvent(CurrentState);
PropagateBlockableEvent(highFrequencyDrawables.AsSlimReadOnly(), highFrequencyMoveEvent);
PropagateBlockableEvent(highFrequencyDrawables.AsSlimReadOnly(), lastMouseMove);
}
highFrequencyDrawables.Clear();
@@ -973,7 +973,7 @@ namespace osu.Framework.Input
manager.HandleButtonStateChange(e.State, e.Kind);
}
private bool handleMouseMove(InputState state, Vector2 lastPosition) => PropagateBlockableEvent(PositionalInputQueue, new MouseMoveEvent(state, lastPosition));
private bool handleMouseMove(InputState state, Vector2 lastPosition) => PropagateBlockableEvent(PositionalInputQueue, lastMouseMove = new MouseMoveEvent(state, lastPosition));
private bool handleScroll(InputState state, Vector2 lastScroll, bool isPrecise) =>
PropagateBlockableEvent(PositionalInputQueue, new ScrollEvent(state, state.Mouse.Scroll - lastScroll, isPrecise));

View File

@@ -19,6 +19,7 @@ namespace osu.Framework.Lists
/// <summary>
/// Invoked when an element of the array is changed via <see cref="this[int]"/>.
/// </summary>
[CanBeNull]
public event Action ArrayElementChanged;
[NotNull]

View File

@@ -0,0 +1,53 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
namespace osu.Framework.Lists
{
public readonly struct SlimReadOnlyDictionaryWrapper<TKey, TValue> : IReadOnlyDictionary<TKey, TValue>
where TKey : notnull
{
private readonly Dictionary<TKey, TValue> dict;
public SlimReadOnlyDictionaryWrapper(Dictionary<TKey, TValue> dict)
{
this.dict = dict;
}
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()
=> dict.GetEnumerator();
public Dictionary<TKey, TValue>.Enumerator GetEnumerator()
=> dict.GetEnumerator();
public Dictionary<TKey, TValue>.KeyCollection Keys
=> dict.Keys;
public Dictionary<TKey, TValue>.ValueCollection Values
=> dict.Values;
public bool ContainsKey(TKey key)
=> dict.ContainsKey(key);
public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value)
=> dict.TryGetValue(key, out value);
public TValue this[TKey key]
=> dict[key];
IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys
=> dict.Keys;
IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values
=> dict.Values;
IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
public int Count
=> dict.Count;
}
}

View File

@@ -26,7 +26,7 @@ namespace osu.Framework.Physics
Origin = Anchor.Centre;
}
public Drawable Simulation { get; set; }
public Drawable Simulation { get; set; } = null!;
/// <summary>
/// Controls how elastic the material is. A value of 1 means perfect elasticity
@@ -96,7 +96,7 @@ namespace osu.Framework.Physics
/// </summary>
protected float ComputeI()
{
Matrix3 mat = DrawInfo.Matrix * Parent.DrawInfo.MatrixInverse;
Matrix3 mat = DrawInfo.Matrix * Parent!.DrawInfo.MatrixInverse;
Vector2 size = DrawSize;
// Inertial moment for a linearly transformed rectangle with a given size around its center.
@@ -256,7 +256,7 @@ namespace osu.Framework.Physics
/// </summary>
public void ReadState()
{
Matrix3 mat = Parent.DrawInfo.Matrix * ScreenToSimulationSpace;
Matrix3 mat = Parent!.DrawInfo.Matrix * ScreenToSimulationSpace;
Centre = Vector2Extensions.Transform(BoundingBox.Centre, mat);
RotationRadians = MathUtils.DegreesToRadians(Rotation); // TODO: Fix rotations
@@ -269,7 +269,7 @@ namespace osu.Framework.Physics
/// </summary>
public virtual void ApplyState()
{
Matrix3 mat = SimulationToScreenSpace * Parent.DrawInfo.MatrixInverse;
Matrix3 mat = SimulationToScreenSpace * Parent!.DrawInfo.MatrixInverse;
Position = Vector2Extensions.Transform(Centre, mat) + (Position - BoundingBox.Centre);
Rotation = MathUtils.RadiansToDegrees(RotationRadians); // TODO: Fix rotations
}

View File

@@ -27,7 +27,7 @@ namespace osu.Framework.Platform.MacOS
return IntPtr.Zero;
var result = generalPasteboard.ReadObjectsForClasses(classArray, null);
var objects = result?.ToArray();
IntPtr[]? objects = result?.ToArray();
return objects?.Length > 0 ? objects[0] : IntPtr.Zero;
}

View File

@@ -34,7 +34,7 @@ namespace osu.Framework.Platform.MacOS
base.Create();
// replace [SDLView scrollWheel:(NSEvent *)] with our own version
var viewClass = Class.Get("SDLView");
IntPtr viewClass = Class.Get("SDLView");
scrollWheelHandler = scrollWheel;
originalScrollWheel = Class.SwizzleMethod(viewClass, "scrollWheel:", "v@:@", scrollWheelHandler);
}

View File

@@ -22,7 +22,7 @@ namespace osu.Framework.Platform.MacOS.Native
public static IntPtr Get(string name)
{
var id = objc_getClass(name);
IntPtr id = objc_getClass(name);
if (id == IntPtr.Zero)
throw new ArgumentException("Unknown class: " + name);
@@ -48,12 +48,12 @@ namespace osu.Framework.Platform.MacOS.Native
/// <returns>A selector for the newly registered method, containing the old implementation.</returns>
public static IntPtr SwizzleMethod(IntPtr classHandle, string selector, string typeString, Delegate action)
{
var targetSelector = Selector.Get(selector);
var targetMethod = class_getInstanceMethod(classHandle, targetSelector);
var newMethodImplementation = Marshal.GetFunctionPointerForDelegate(action);
var newSelector = Selector.Get($"orig_{selector}");
IntPtr targetSelector = Selector.Get(selector);
IntPtr targetMethod = class_getInstanceMethod(classHandle, targetSelector);
IntPtr newMethodImplementation = Marshal.GetFunctionPointerForDelegate(action);
IntPtr newSelector = Selector.Get($"orig_{selector}");
class_replaceMethod(classHandle, newSelector, newMethodImplementation, typeString);
var newMethod = class_getInstanceMethod(classHandle, newSelector);
IntPtr newMethod = class_getInstanceMethod(classHandle, newSelector);
method_exchangeImplementations(targetMethod, newMethod);
return newSelector;
}

View File

@@ -110,7 +110,7 @@ namespace osu.Framework.Platform.MacOS.Native
fixed (char* ptrFirstChar = str)
{
var handle = SendIntPtr(Class.Get("NSString"), Selector.Get("alloc"));
IntPtr handle = SendIntPtr(Class.Get("NSString"), Selector.Get("alloc"));
return SendIntPtr(handle, Selector.Get("initWithCharacters:length:"), (IntPtr)ptrFirstChar, str.Length);
}
}
@@ -125,7 +125,7 @@ namespace osu.Framework.Platform.MacOS.Native
image.Save(stream, TiffFormat.Instance);
byte[] array = stream.ToArray();
var handle = SendIntPtr(Class.Get("NSImage"), Selector.Get("alloc"));
IntPtr handle = SendIntPtr(Class.Get("NSImage"), Selector.Get("alloc"));
return SendIntPtr(handle, Selector.Get("initWithData:"), NSData.FromBytes(array));
}
}

View File

@@ -26,7 +26,7 @@ namespace osu.Framework.Platform.MacOS.Native
internal static NSArray ArrayWithObjects(IntPtr[] objs)
{
var mutableArray = Cocoa.SendIntPtr(mutable_class_pointer, sel_array);
IntPtr mutableArray = Cocoa.SendIntPtr(mutable_class_pointer, sel_array);
foreach (IntPtr obj in objs)
Cocoa.SendVoid(mutableArray, sel_add_object, obj);
return new NSArray(mutableArray);

View File

@@ -22,7 +22,7 @@ namespace osu.Framework.Platform.MacOS.Native
internal byte[] ToBytes()
{
var pointer = Cocoa.SendIntPtr(Handle, sel_bytes);
IntPtr pointer = Cocoa.SendIntPtr(Handle, sel_bytes);
int size = Cocoa.SendInt(Handle, sel_length);
byte[] bytes = new byte[size];
@@ -34,7 +34,7 @@ namespace osu.Framework.Platform.MacOS.Native
{
fixed (byte* ptr = bytes)
{
var handle = Cocoa.SendIntPtr(class_pointer, sel_data_with_bytes, (IntPtr)ptr, (ulong)bytes.LongLength);
IntPtr handle = Cocoa.SendIntPtr(class_pointer, sel_data_with_bytes, (IntPtr)ptr, (ulong)bytes.LongLength);
return new NSData(handle);
}
}

View File

@@ -30,7 +30,7 @@ namespace osu.Framework.Platform.MacOS.Native
internal NSArray? ReadObjectsForClasses(NSArray classArray, NSDictionary? optionDict)
{
var result = Cocoa.SendIntPtr(Handle, sel_read_objects_for_classes, classArray.Handle, optionDict?.Handle ?? IntPtr.Zero);
IntPtr result = Cocoa.SendIntPtr(Handle, sel_read_objects_for_classes, classArray.Handle, optionDict?.Handle ?? IntPtr.Zero);
return result == IntPtr.Zero ? null : new NSArray(result);
}

View File

@@ -1022,7 +1022,7 @@ namespace osu.Framework.Platform.SDL2
/// <returns><c>true</c> if the <paramref name="bytePointer"/> was successfully converted to a string.</returns>
public static unsafe bool TryGetStringFromBytePointer(byte* bytePointer, out string str)
{
var ptr = new IntPtr(bytePointer);
IntPtr ptr = new IntPtr(bytePointer);
if (ptr == IntPtr.Zero)
{

View File

@@ -131,7 +131,7 @@ namespace osu.Framework.Platform.SDL2
var namesInfo = type.GetRuntimeFields().First(x => x.Name == "_EntryPointNamesInstance");
var offsetsInfo = type.GetRuntimeFields().First(x => x.Name == "_EntryPointNameOffsetsInstance");
var entryPointsInstance = (IntPtr[]?)pointsInfo.GetValue(bindings);
IntPtr[]? entryPointsInstance = (IntPtr[]?)pointsInfo.GetValue(bindings);
byte[]? entryPointNamesInstance = (byte[]?)namesInfo.GetValue(bindings);
int[]? entryPointNameOffsetsInstance = (int[]?)offsetsInfo.GetValue(bindings);

View File

@@ -315,9 +315,9 @@ namespace osu.Framework.Platform
if (controllers.ContainsKey(instanceID))
return;
var joystick = SDL.SDL_JoystickOpen(which);
IntPtr joystick = SDL.SDL_JoystickOpen(which);
var controller = IntPtr.Zero;
IntPtr controller = IntPtr.Zero;
if (SDL.SDL_IsGameController(which) == SDL.SDL_bool.SDL_TRUE)
controller = SDL.SDL_GameControllerOpen(which);

View File

@@ -72,7 +72,7 @@ namespace osu.Framework.Platform.Windows
public override void SetText(string text)
{
int bytes = (text.Length + 1) * 2;
var source = Marshal.StringToHGlobalUni(text);
IntPtr source = Marshal.StringToHGlobalUni(text);
setClipboard(source, bytes, cf_unicodetext);
}
@@ -119,11 +119,11 @@ namespace osu.Framework.Platform.Windows
EmptyClipboard();
// IMPORTANT: SetClipboardData requires memory that was acquired with GlobalAlloc using GMEM_MOVABLE.
var hGlobal = GlobalAlloc(ghnd, (UIntPtr)bytes);
IntPtr hGlobal = GlobalAlloc(ghnd, (UIntPtr)bytes);
try
{
var target = GlobalLock(hGlobal);
IntPtr target = GlobalLock(hGlobal);
if (target == IntPtr.Zero)
return false;

View File

@@ -7,6 +7,7 @@ using System;
using System.Diagnostics;
using System.Diagnostics.Tracing;
using System.Reflection;
using System.Runtime.InteropServices;
using osu.Framework.Extensions.TypeExtensions;
using osu.Framework.Logging;
@@ -132,21 +133,20 @@ namespace osu.Framework.Statistics
// ReSharper disable once UnusedParameter.Local
private static unsafe Type getTypeFromHandle(IntPtr handle)
{
// This is super unsafe code which is dependent upon internal CLR structures.
#pragma warning disable CS8500
TypedReferenceAccess tr = new TypedReferenceAccess { Type = handle };
return __reftype(*(TypedReference*)&tr);
return TypedReference.GetTargetType(*(TypedReference*)&tr);
#pragma warning restore CS8500
}
/// <summary>
/// Matches the internal layout of <see cref="TypedReference"/>.
/// See: https://source.dot.net/#System.Private.CoreLib/src/System/TypedReference.cs
/// </summary>
[StructLayout(LayoutKind.Sequential)]
private struct TypedReferenceAccess
{
[JetBrains.Annotations.UsedImplicitly]
public IntPtr Value;
[JetBrains.Annotations.UsedImplicitly]
public readonly IntPtr Value;
public IntPtr Type;
}

View File

@@ -33,82 +33,78 @@ namespace osu.Framework.Testing.Drawables
Colour = FrameworkColour.GreenDark,
},
},
new Container
new GridContainer
{
Padding = new MarginPadding(section_padding),
RelativeSizeAxes = Axes.Both,
Child = new GridContainer
Padding = new MarginPadding(section_padding),
ColumnDimensions = new[]
{
RelativeSizeAxes = Axes.Both,
ColumnDimensions = new[]
new Dimension(GridSizeMode.AutoSize),
new Dimension(),
new Dimension(GridSizeMode.AutoSize),
new Dimension(GridSizeMode.AutoSize),
},
Content = new[]
{
new Drawable[]
{
new Dimension(GridSizeMode.AutoSize),
new Dimension(),
new Dimension(GridSizeMode.AutoSize),
new Dimension(GridSizeMode.AutoSize),
},
Content = new[]
{
new Drawable[]
new ToolbarRunAllStepsSection { RelativeSizeAxes = Axes.Y },
new ToolbarRateSection { RelativeSizeAxes = Axes.Both },
new Container
{
new ToolbarRunAllStepsSection { RelativeSizeAxes = Axes.Y },
new ToolbarRateSection { RelativeSizeAxes = Axes.Both },
new Container
RelativeSizeAxes = Axes.Y,
AutoSizeAxes = Axes.X,
Margin = new MarginPadding { Horizontal = section_padding },
Children = new Drawable[]
{
RelativeSizeAxes = Axes.Y,
AutoSizeAxes = Axes.X,
Margin = new MarginPadding { Horizontal = section_padding },
Children = new Drawable[]
new Container //Backdrop of the record section
{
new Container //Backdrop of the record section
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding(-section_padding),
Child = new Box
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding(-section_padding),
Child = new Box
{
RelativeSizeAxes = Axes.Both,
Colour = FrameworkColour.GreenDarker,
},
Colour = FrameworkColour.GreenDarker,
},
new ToolbarRecordSection { RelativeSizeAxes = Axes.Y },
}
},
new Container
},
new ToolbarRecordSection { RelativeSizeAxes = Axes.Y },
}
},
new Container
{
RelativeSizeAxes = Axes.Y,
AutoSizeAxes = Axes.X,
Margin = new MarginPadding { Left = section_padding },
Children = new Drawable[]
{
RelativeSizeAxes = Axes.Y,
AutoSizeAxes = Axes.X,
Margin = new MarginPadding { Left = section_padding },
Children = new Drawable[]
new Container //Backdrop of the bg section
{
new Container //Backdrop of the bg section
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding(-section_padding),
Child = new Box
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding(-section_padding),
Child = new Box
Colour = FrameworkColour.GreenDarker.Darken(0.5f),
},
},
new BasicButton
{
Text = "bg",
RelativeSizeAxes = Axes.Y,
Width = 40,
Action = () => browser.CurrentTest.ChangeBackgroundColour(
new ColourInfo
{
RelativeSizeAxes = Axes.Both,
Colour = FrameworkColour.GreenDarker.Darken(0.5f),
},
},
new BasicButton
{
Text = "bg",
RelativeSizeAxes = Axes.Y,
Width = 40,
Action = () => browser.CurrentTest.ChangeBackgroundColour(
new ColourInfo
{
TopLeft = new Color4(RNG.NextSingle(1), RNG.NextSingle(1), RNG.NextSingle(1), 1),
TopRight = new Color4(RNG.NextSingle(1), RNG.NextSingle(1), RNG.NextSingle(1), 1),
BottomLeft = new Color4(RNG.NextSingle(1), RNG.NextSingle(1), RNG.NextSingle(1), 1),
BottomRight = new Color4(RNG.NextSingle(1), RNG.NextSingle(1), RNG.NextSingle(1), 1)
}
)
},
}
TopLeft = new Color4(RNG.NextSingle(1), RNG.NextSingle(1), RNG.NextSingle(1), 1),
TopRight = new Color4(RNG.NextSingle(1), RNG.NextSingle(1), RNG.NextSingle(1), 1),
BottomLeft = new Color4(RNG.NextSingle(1), RNG.NextSingle(1), RNG.NextSingle(1), 1),
BottomRight = new Color4(RNG.NextSingle(1), RNG.NextSingle(1), RNG.NextSingle(1), 1)
}
)
},
}
}
},
}
},
}
};

View File

@@ -94,6 +94,8 @@ namespace osu.Framework.Testing
public override void Add(Drawable drawable)
{
ArgumentNullException.ThrowIfNull(drawable);
if (drawable is Game)
throw new InvalidOperationException($"Use {nameof(AddGame)} when testing a game instance.");